This closes #1179
diff --git a/brooklyn-dist/.gitignore b/brooklyn-dist/.gitignore
index ed439f2..2ef22e4 100644
--- a/brooklyn-dist/.gitignore
+++ b/brooklyn-dist/.gitignore
@@ -28,5 +28,6 @@
 brooklyn*.log.*
 
 *brooklyn-persisted-state/
+*.vagrant/
 
 ignored
diff --git a/brooklyn-dist/pom.xml b/brooklyn-dist/pom.xml
index a2ecb3a..12ebdd9 100644
--- a/brooklyn-dist/pom.xml
+++ b/brooklyn-dist/pom.xml
@@ -76,6 +76,7 @@
         <module>downstream-parent</module>
         <module>all</module>
         <module>dist</module>
+        <module>vagrant</module>
         <module>archetypes/quickstart</module>
     </modules>
 
diff --git a/brooklyn-dist/release/make-release-artifacts.sh b/brooklyn-dist/release/make-release-artifacts.sh
index 476a6e3..90f138e 100755
--- a/brooklyn-dist/release/make-release-artifacts.sh
+++ b/brooklyn-dist/release/make-release-artifacts.sh
@@ -18,7 +18,10 @@
 # under the License.
 #
 
-# creates a source release - this is a .tar.gz file containing all the source code files that are permitted to be released.
+# Creates the following releases with archives (.tar.gz/.zip), signatures and checksums:
+#   binary  (-bin)     - contains the brooklyn dist binary release
+#   source  (-src)     - contains all the source code files that are permitted to be released
+#   vagrant (-vagrant) - contains a Vagrantfile/scripts to start a Brooklyn getting started environment
 
 set -e
 
@@ -179,7 +182,7 @@
 # * release (where this is running, and people who *have* the release don't need to make it)
 # * jars and friends (these are sometimes included for tests, but those are marked as skippable,
 #     and apache convention does not allow them in source builds; see PR #365
-rsync -rtp --exclude .git\* --exclude docs/ --exclude sandbox/ --exclude release/ --exclude '**/*.[ejw]ar' . ${staging_dir}/${release_name}-src
+rsync -rtp --exclude .git\* --exclude brooklyn-docs/ --exclude brooklyn-library/sandbox/ --exclude brooklyn-dist/release/ --exclude '**/*.[ejw]ar' . ${staging_dir}/${release_name}-src
 
 rm -rf ${artifact_dir}
 mkdir -p ${artifact_dir}
@@ -210,16 +213,29 @@
 # Perform the build and deploy to Nexus staging repository
 ( cd ${src_staging_dir} && mvn deploy -Papache-release )
 ## To test the script without a big deploy, use the line below instead of above
-#( cd ${src_staging_dir} && cd usage/dist && mvn clean install )
+#( cd ${src_staging_dir} && mvn clean install )
 
 # Re-pack the archive with the correct names
-tar xzf ${src_staging_dir}/usage/dist/target/brooklyn-dist-${current_version}-dist.tar.gz -C ${bin_staging_dir}
+tar xzf ${src_staging_dir}/brooklyn-dist/dist/target/brooklyn-dist-${current_version}-dist.tar.gz -C ${bin_staging_dir}
 mv ${bin_staging_dir}/brooklyn-dist-${current_version} ${bin_staging_dir}/${release_name}-bin
 
 ( cd ${bin_staging_dir} && tar czf ${artifact_dir}/${artifact_name}-bin.tar.gz ${release_name}-bin )
 ( cd ${bin_staging_dir} && zip -qr ${artifact_dir}/${artifact_name}-bin.zip ${release_name}-bin )
 
 ###############################################################################
+# Vagrant release
+set +x
+echo "Proceeding to rename and repackage vagrant environment release"
+set -x
+
+# Re-pack the archive with the correct names
+tar xzf ${src_staging_dir}/brooklyn-dist/vagrant/target/brooklyn-vagrant-${current_version}-dist.tar.gz -C ${bin_staging_dir}
+mv ${bin_staging_dir}/brooklyn-vagrant-${current_version} ${bin_staging_dir}/${release_name}-vagrant
+
+( cd ${bin_staging_dir} && tar czf ${artifact_dir}/${artifact_name}-vagrant.tar.gz ${release_name}-vagrant )
+( cd ${bin_staging_dir} && zip -qr ${artifact_dir}/${artifact_name}-vagrant.zip ${release_name}-vagrant )
+
+###############################################################################
 # Signatures and checksums
 
 # OSX doesn't have sha256sum, even if MacPorts md5sha1sum package is installed.
diff --git a/brooklyn-dist/vagrant/pom.xml b/brooklyn-dist/vagrant/pom.xml
new file mode 100644
index 0000000..fbe6539
--- /dev/null
+++ b/brooklyn-dist/vagrant/pom.xml
@@ -0,0 +1,83 @@
+<?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.
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+    <packaging>pom</packaging>
+
+    <artifactId>brooklyn-vagrant</artifactId>
+
+    <name>Brooklyn Vagrant Getting Started Environment</name>
+    <description>
+        Brooklyn Getting Started Vagrant environment archive, includes all required
+        files to start Brooklyn and sample BYON nodes for use.
+    </description>
+
+    <parent>
+        <groupId>org.apache.brooklyn</groupId>
+        <artifactId>brooklyn-dist-root</artifactId>
+        <version>0.9.0-SNAPSHOT</version>  <!-- BROOKLYN_VERSION -->
+        <relativePath>../pom.xml</relativePath>
+    </parent>
+
+    <build>
+        <plugins>
+            <plugin>
+                <artifactId>maven-assembly-plugin</artifactId>
+                <executions>
+                    <execution>
+                        <id>build-distribution-archive</id>
+                        <phase>package</phase>
+                        <goals>
+                            <goal>single</goal>
+                        </goals>
+                        <configuration>
+                            <appendAssemblyId>true</appendAssemblyId>
+                            <descriptors>
+                                <descriptor>src/main/config/build-distribution.xml</descriptor>
+                            </descriptors>
+                          <!-- finalName affects name in `target/` but we cannot influence name when it is attached/installed,
+                               so `apache-` prefix would be lost there. to keep it consistent this is commented out, 
+                               but would be nice to have if there is a way!
+                            <finalName>apache-brooklyn-${project.version}</finalName>
+                          -->
+                            <formats>
+                                <format>tar.gz</format>
+                                <format>zip</format>
+                            </formats>
+                        </configuration>
+                    </execution>
+                </executions>
+            </plugin>
+        </plugins>
+        <pluginManagement>
+            <plugins>
+                <plugin>
+                    <groupId>org.apache.rat</groupId>
+                    <artifactId>apache-rat-plugin</artifactId>
+                    <configuration>
+                        <excludes combine.children="append">
+                            <exclude>src/main/vagrant/README.md</exclude>
+                        </excludes>
+                    </configuration>
+                </plugin>
+            </plugins>
+        </pluginManagement>
+    </build>
+</project>
diff --git a/brooklyn-dist/vagrant/src/main/config/build-distribution.xml b/brooklyn-dist/vagrant/src/main/config/build-distribution.xml
new file mode 100644
index 0000000..823ad2a
--- /dev/null
+++ b/brooklyn-dist/vagrant/src/main/config/build-distribution.xml
@@ -0,0 +1,33 @@
+<?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.
+-->
+<assembly xmlns="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.2"
+        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+        xsi:schemaLocation="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.2 http://maven.apache.org/xsd/assembly-1.1.2.xsd">
+    <id>dist</id>
+    <formats><!-- empty, intended for caller to specify --></formats>
+    <fileSets>
+        <fileSet>
+            <directory>${project.basedir}/src/main/vagrant</directory>
+            <outputDirectory></outputDirectory>
+            <fileMode>0644</fileMode>
+            <directoryMode>0755</directoryMode>
+        </fileSet>
+    </fileSets>
+</assembly>
diff --git a/brooklyn-dist/vagrant/src/main/vagrant/README.md b/brooklyn-dist/vagrant/src/main/vagrant/README.md
new file mode 100644
index 0000000..2f5573c
--- /dev/null
+++ b/brooklyn-dist/vagrant/src/main/vagrant/README.md
@@ -0,0 +1,41 @@
+
+# [![**Brooklyn**](https://brooklyn.apache.org/style/img/apache-brooklyn-logo-244px-wide.png)](http://brooklyn.apache.org/)
+
+### Using Vagrant with Brooklyn -SNAPSHOT builds
+
+##### Install a community-managed version from Maven
+1. No action is required other than setting the  `BROOKLYN_VERSION:` environment variable in `servers.yaml` to a `-SNAPSHOT` version. For example:
+
+   ```
+   env:
+     BROOKLYN_VERSION: 0.9.0-SNAPSHOT
+   ```
+
+2. You may proceed to use the `Vagrantfile` as normal; `vagrant up`, `vagrant destroy` etc.
+
+##### Install a locally built `-dist.tar.gz`
+
+1. Set the `BROOKLYN_VERSION:` environment variable in `servers.yaml` to your current `-SNAPSHOT` version. For example:
+
+   ```
+   env:
+     BROOKLYN_VERSION: 0.9.0-SNAPSHOT
+   ```
+
+2. Set the `INSTALL_FROM_LOCAL_DIST:` environment variable in `servers.yaml` to `true`. For example:
+
+   ```
+   env:
+     INSTALL_FROM_LOCAL_DIST: true
+   ```
+
+
+3. Copy your locally built `-dist.tar.gz` archive to the same directory as the Vagrantfile (this directory is mounted in the Vagrant VM at `/vagrant/`).
+
+   For example to copy a locally built `0.9.0-SNAPSHOT` dist:
+
+   ```
+   cp ~/.m2/repository/org/apache/brooklyn/brooklyn-dist/0.9.0-SNAPSHOT/brooklyn-dist-0.9.0-SNAPSHOT-dist.tar.gz .
+   ```
+
+4. You may proceed to use the `Vagrantfile` as normal; `vagrant up`, `vagrant destroy` etc.
\ No newline at end of file
diff --git a/brooklyn-dist/vagrant/src/main/vagrant/Vagrantfile b/brooklyn-dist/vagrant/src/main/vagrant/Vagrantfile
new file mode 100644
index 0000000..fb35a15
--- /dev/null
+++ b/brooklyn-dist/vagrant/src/main/vagrant/Vagrantfile
@@ -0,0 +1,76 @@
+# -*- mode: ruby -*-
+# vi: set ft=ruby :
+#
+# 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.
+#
+
+# Specify minimum Vagrant version and Vagrant API version
+Vagrant.require_version ">= 1.8.1"
+VAGRANTFILE_API_VERSION = "2"
+
+# Update OS (Debian/RedHat based only)
+UPDATE_OS_CMD = "(sudo apt-get update && sudo apt-get -y upgrade) || (sudo yum -y update)"
+
+# Require YAML module
+require 'yaml'
+
+# Read YAML file with box details
+yaml_cfg = YAML.load_file(__dir__ + '/servers.yaml')
+
+# Create boxes
+Vagrant.configure(VAGRANTFILE_API_VERSION) do |config|
+
+  # Iterate through server entries in YAML file
+  yaml_cfg["servers"].each do |server|
+    config.vm.define server["name"] do |server_config|
+
+      server_config.vm.box = server["box"]
+
+      server_config.vm.box_check_update = yaml_cfg["default_config"]["check_newer_vagrant_box"]
+
+      if server.has_key?("ip")
+        server_config.vm.network "private_network", ip: server["ip"]
+      end
+
+      if server.has_key?("forwarded_ports")
+        server["forwarded_ports"].each do |ports|
+          server_config.vm.network "forwarded_port", guest: ports["guest"], host: ports["host"], guest_ip: ports["guest_ip"]
+        end
+      end
+
+      server_config.vm.hostname = server["name"]
+      server_config.vm.provider :virtualbox do |vb|
+        vb.name = server["name"]
+        vb.memory = server["ram"]
+        vb.cpus = server["cpus"]
+      end
+
+      if yaml_cfg["default_config"]["run_os_update"]
+        server_config.vm.provision "shell", privileged: false, inline: UPDATE_OS_CMD
+      end
+
+      if server["shell"] && server["shell"]["cmd"]
+        server["shell"]["cmd"].each do |cmd|
+          server_config.vm.provision "shell", privileged: false, inline: cmd, env: server["shell"]["env"]
+        end
+      end
+
+      server_config.vm.post_up_message = server["post_up_message"]
+    end
+  end
+end
\ No newline at end of file
diff --git a/brooklyn-dist/vagrant/src/main/vagrant/files/brooklyn.properties b/brooklyn-dist/vagrant/src/main/vagrant/files/brooklyn.properties
new file mode 100644
index 0000000..0784ff3
--- /dev/null
+++ b/brooklyn-dist/vagrant/src/main/vagrant/files/brooklyn.properties
@@ -0,0 +1,23 @@
+#
+# 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.
+#
+
+# Disabling security on the Vagrant Brooklyn instance for training purposes
+brooklyn.webconsole.security.provider = org.apache.brooklyn.rest.security.provider.AnyoneSecurityProvider
+
+# Note: BYON locations are loaded from the files/vagrant-catalog.bom on startup
\ No newline at end of file
diff --git a/brooklyn-dist/vagrant/src/main/vagrant/files/brooklyn.service b/brooklyn-dist/vagrant/src/main/vagrant/files/brooklyn.service
new file mode 100644
index 0000000..28b0fea
--- /dev/null
+++ b/brooklyn-dist/vagrant/src/main/vagrant/files/brooklyn.service
@@ -0,0 +1,32 @@
+#
+# 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.
+#
+
+[Unit]
+Description=Apache Brooklyn service
+Documentation=http://brooklyn.apache.org/documentation/index.html
+
+[Service]
+ExecStart=/home/vagrant/apache-brooklyn/bin/brooklyn launch --persist auto --persistenceDir /vagrant/brooklyn-persisted-state --catalogAdd /vagrant/files/vagrant-catalog.bom
+WorkingDirectory=/home/vagrant/apache-brooklyn
+Restart=on-abort
+User=vagrant
+Group=vagrant
+
+[Install]
+WantedBy=multi-user.target
diff --git a/brooklyn-dist/vagrant/src/main/vagrant/files/install_brooklyn.sh b/brooklyn-dist/vagrant/src/main/vagrant/files/install_brooklyn.sh
new file mode 100755
index 0000000..9c52017
--- /dev/null
+++ b/brooklyn-dist/vagrant/src/main/vagrant/files/install_brooklyn.sh
@@ -0,0 +1,92 @@
+#!/usr/bin/env bash
+
+#
+# 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.
+#
+
+BROOKLYN_VERSION=""
+INSTALL_FROM_LOCAL_DIST="false"
+TMP_ARCHIVE_NAME=apache-brooklyn.tar.gz
+
+do_help() {
+  echo "./install.sh -v <Brooklyn Version> [-l <install from local file: true|false>]"
+  exit 1
+}
+
+while getopts ":hv:l:" opt; do
+    case "$opt" in
+    v)  BROOKLYN_VERSION=$OPTARG ;;
+        # using a true/false argopt rather than just flag to allow easier integration with servers.yaml config
+    l)  INSTALL_FROM_LOCAL_DIST=$OPTARG ;;
+    h)  do_help;;
+    esac
+done
+
+# Exit if any step fails
+set -e
+
+if [ "x${BROOKLYN_VERSION}" == "x" ]; then
+  echo "Error: you must supply a Brooklyn version [-v]"
+  do_help
+fi
+
+if [ ! "${INSTALL_FROM_LOCAL_DIST}" == "true" ]; then
+  if [ ! -z "${BROOKLYN_VERSION##*-SNAPSHOT}" ] ; then
+    # url for official release versions
+    BROOKLYN_URL="https://www.apache.org/dyn/closer.lua?action=download&filename=brooklyn/apache-brooklyn-${BROOKLYN_VERSION}/apache-brooklyn-${BROOKLYN_VERSION}-bin.tar.gz"
+    BROOKLYN_DIR="apache-brooklyn-${BROOKLYN_VERSION}-bin"
+  else
+    # url for community-managed snapshots
+    BROOKLYN_URL="https://repository.apache.org/service/local/artifact/maven/redirect?r=snapshots&g=org.apache.brooklyn&a=brooklyn-dist&v=${BROOKLYN_VERSION}&c=dist&e=tar.gz"
+    BROOKLYN_DIR="brooklyn-dist-${BROOKLYN_VERSION}"
+  fi
+else
+  echo "Installing from a local -dist archive [ /vagrant/brooklyn-dist-${BROOKLYN_VERSION}-dist.tar.gz]"
+  # url to install from mounted /vagrant dir
+  BROOKLYN_URL="file:///vagrant/brooklyn-dist-${BROOKLYN_VERSION}-dist.tar.gz"
+  BROOKLYN_DIR="brooklyn-dist-${BROOKLYN_VERSION}"
+
+  # ensure local file exists
+  if [ ! -f /vagrant/brooklyn-dist-${BROOKLYN_VERSION}-dist.tar.gz ]; then
+    echo "Error: file not found /vagrant/brooklyn-dist-${BROOKLYN_VERSION}-dist.tar.gz"
+    exit 1
+  fi
+fi
+
+echo "Installing Apache Brooklyn version ${BROOKLYN_VERSION} from [${BROOKLYN_URL}]"
+
+echo "Downloading Brooklyn release archive"
+curl --fail --silent --show-error --location --output ${TMP_ARCHIVE_NAME} "${BROOKLYN_URL}"
+echo "Extracting Brooklyn release archive"
+tar zxf ${TMP_ARCHIVE_NAME}
+
+echo "Creating Brooklyn dirs and symlinks"
+ln -s ${BROOKLYN_DIR} apache-brooklyn
+sudo mkdir -p /var/log/brooklyn
+sudo chown -R vagrant:vagrant /var/log/brooklyn
+mkdir -p /home/vagrant/.brooklyn
+
+echo "Copying default vagrant Brooklyn properties file"
+cp /vagrant/files/brooklyn.properties /home/vagrant/.brooklyn/
+chmod 600 /home/vagrant/.brooklyn/brooklyn.properties
+
+echo "Installing JRE"
+sudo sh -c 'export DEBIAN_FRONTEND=noninteractive; apt-get install --yes openjdk-8-jre-headless'
+
+echo "Copying Brooklyn systemd service unit file"
+sudo cp /vagrant/files/brooklyn.service /etc/systemd/system/brooklyn.service
\ No newline at end of file
diff --git a/brooklyn-dist/vagrant/src/main/vagrant/files/logback.xml b/brooklyn-dist/vagrant/src/main/vagrant/files/logback.xml
new file mode 100644
index 0000000..1560d8b
--- /dev/null
+++ b/brooklyn-dist/vagrant/src/main/vagrant/files/logback.xml
@@ -0,0 +1,32 @@
+<?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.
+-->
+
+<configuration scan="true">
+
+    <!-- to supply custom logging, either change this file, supply your own logback-main.xml
+         (overriding the default provided on the classpath) or any of the files it references;
+         see the Logging section of the Brooklyn web site for more information -->
+
+    <property name="logging.basename" scope="context" value="brooklyn" />
+    <property name="logging.dir" scope="context" value="/var/log/brooklyn/" />
+
+    <include resource="logback-main.xml"/>
+
+</configuration>
\ No newline at end of file
diff --git a/brooklyn-dist/vagrant/src/main/vagrant/files/vagrant-catalog.bom b/brooklyn-dist/vagrant/src/main/vagrant/files/vagrant-catalog.bom
new file mode 100644
index 0000000..d8b8450
--- /dev/null
+++ b/brooklyn-dist/vagrant/src/main/vagrant/files/vagrant-catalog.bom
@@ -0,0 +1,82 @@
+#
+# 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.
+#
+brooklyn.catalog:
+  items:
+  - id: byon1
+    name: Vagrant BYON VM 1
+    version: 0.9.0-SNAPSHOT  # BROOKLYN_VERSION
+    itemType: location
+    item:
+      type: byon
+      brooklyn.config:
+        user: vagrant
+        password: vagrant
+        hosts:
+        - 10.10.10.101
+
+  - id: byon2
+    name: Vagrant BYON VM 2
+    version: 0.9.0-SNAPSHOT  # BROOKLYN_VERSION
+    itemType: location
+    item:
+      type: byon
+      brooklyn.config:
+        user: vagrant
+        password: vagrant
+        hosts:
+        - 10.10.10.102
+
+  - id: byon3
+    name: Vagrant BYON VM 3
+    version: 0.9.0-SNAPSHOT  # BROOKLYN_VERSION
+    itemType: location
+    item:
+      type: byon
+      brooklyn.config:
+        user: vagrant
+        password: vagrant
+        hosts:
+        - 10.10.10.103
+
+  - id: byon4
+    name: Vagrant BYON VM 4
+    version: 0.9.0-SNAPSHOT  # BROOKLYN_VERSION
+    itemType: location
+    item:
+      type: byon
+      brooklyn.config:
+        user: vagrant
+        password: vagrant
+        hosts:
+        - 10.10.10.104
+
+  - id: byon-all
+    name: Vagrant BYON VM 1-4
+    version: 0.9.0-SNAPSHOT  # BROOKLYN_VERSION
+    itemType: location
+    item:
+      type: byon
+      brooklyn.config:
+        user: vagrant
+        password: vagrant
+        hosts:
+        - 10.10.10.101
+        - 10.10.10.102
+        - 10.10.10.103
+        - 10.10.10.104
diff --git a/brooklyn-dist/vagrant/src/main/vagrant/servers.yaml b/brooklyn-dist/vagrant/src/main/vagrant/servers.yaml
new file mode 100644
index 0000000..3959fff
--- /dev/null
+++ b/brooklyn-dist/vagrant/src/main/vagrant/servers.yaml
@@ -0,0 +1,73 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#  http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+#
+#
+# Default Config
+#   check_newer_vagrant_box
+#     enable/disable the vagrant check for an updated box
+#   run_os_update
+#     enable/disable running a yum/apt-get update on box start
+#
+# Brooklyn Server Config
+#   shell:env:BROOKLYN_VERSION
+#     specifies the version of Brooklyn to install, be aware that for SNAPSHOTS you
+#     may wish to download a local -dist.tar.gz for the latest version.
+#   shell:env:INSTALL_FROM_LOCAL_DIST
+#     if set to `true` Vagrant will install from a local -dist.tar.gz stored in /vagrant
+#     on the guest VM (which is mounted from the Vagrantfile directory). You must
+#     ensure that a -dist.tar.gz archive has been copied to this directory on your host.
+
+---
+default_config:
+    check_newer_vagrant_box: true
+    run_os_update: true
+servers:
+  - name: brooklyn
+    box: ubuntu/vivid64
+    ram: 2048
+    cpus: 4
+    ip: 10.10.10.100
+    shell:
+      env:
+        BROOKLYN_VERSION: 0.9.0-SNAPSHOT
+        INSTALL_FROM_LOCAL_DIST: false
+      cmd:
+        - /vagrant/files/install_brooklyn.sh -v ${BROOKLYN_VERSION} -l ${INSTALL_FROM_LOCAL_DIST}
+        - sudo systemctl start brooklyn
+        - sudo systemctl enable brooklyn
+  - name: byon1
+    box: ubuntu/vivid64
+    ram: 512
+    cpus: 2
+    ip: 10.10.10.101
+  - name: byon2
+    box: ubuntu/vivid64
+    ram: 512
+    cpus: 2
+    ip: 10.10.10.102
+  - name: byon3
+    box: ubuntu/vivid64
+    ram: 512
+    cpus: 2
+    ip: 10.10.10.103
+  - name: byon4
+    box: ubuntu/vivid64
+    ram: 512
+    cpus: 2
+    ip: 10.10.10.104
+...
diff --git a/brooklyn-docs/website/developers/committers/release-process/make-release-artifacts.md b/brooklyn-docs/website/developers/committers/release-process/make-release-artifacts.md
index f72e0c3..8b14c7d 100644
--- a/brooklyn-docs/website/developers/committers/release-process/make-release-artifacts.md
+++ b/brooklyn-docs/website/developers/committers/release-process/make-release-artifacts.md
@@ -4,12 +4,12 @@
 navgroup: developers
 ---
 
-A release script is provided in `release/make-release-artifacts.sh`. This script will prepare all the release artifacts.
+A release script is provided in `brooklyn-dist/release/make-release-artifacts.sh`. This script will prepare all the release artifacts.
 It is written to account for several Apache requirements, so you are strongly advised to use it rather than "rolling your own".
 
 The release script will:
 
-- **Create source code and binary distribution artifacts** and place them in a temporary staging directory on your workstation, usually `releases/`.
+- **Create source code and binary distribution artifacts** and place them in a temporary staging directory on your workstation, usually `brooklyn-dist/release/tmp/`.
 - **Create Maven artifacts and upload them to a staging repository** located on the Apache Nexus server.
 
 The script has a single required parameter `-r` which is given the release candidate number - so `-r1` will create
@@ -25,7 +25,7 @@
 To run the script:
 
 {% highlight bash %}
-./release/make-release-artifacts.sh -r$RC_NUMBER
+./brooklyn-dist/release/make-release-artifacts.sh -r$RC_NUMBER
 {% endhighlight %}
 
 It will show you the release information it has deduced, and ask yes-or-no if it can proceed. Please note that the
diff --git a/brooklyn-docs/website/developers/committers/release-process/publish.md b/brooklyn-docs/website/developers/committers/release-process/publish.md
index b10d262..436dbc4 100644
--- a/brooklyn-docs/website/developers/committers/release-process/publish.md
+++ b/brooklyn-docs/website/developers/committers/release-process/publish.md
@@ -83,7 +83,7 @@
 ------------------
 
 *Instructions on uploading to the website are beyond the scope of these instructions. Refer to the 
-[appropriate instructions](https://github.com/apache/incubator-brooklyn/tree/master/docs).*
+[appropriate instructions](https://github.com/apache/incubator-brooklyn/tree/master/brooklyn-docs).*
 
 ### Publish documentation for the new release
 
@@ -105,7 +105,7 @@
 Generate the permalink docs for the release:
 
 {% highlight bash %}
-cd docs
+cd brooklyn-docs
 ./_build/build.sh guide-version --install
 {% endhighlight %}
 
@@ -135,11 +135,11 @@
 git checkout master
 {% endhighlight %}
 
-1. Edit the file `docs/_config.yml` - change `brooklyn-stable-version` to be the newly-release version, and
+1. Edit the file `brooklyn-docs/_config.yml` - change `brooklyn-stable-version` to be the newly-release version, and
    `brooklyn-version` to be the current SNAPSHOT version on the master branch.
-2. Edit the file `docs/website/download/verify.md` to add links to the MD5/SHA1/SHA256 hashes and PGP signatures for the
+2. Edit the file `brooklyn-docs/website/download/verify.md` to add links to the MD5/SHA1/SHA256 hashes and PGP signatures for the
    new version.
-3. Edit the file `docs/website/meta/versions.md` to add the new version.
+3. Edit the file `brooklyn-docs/website/meta/versions.md` to add the new version.
 4. Build the updated site with `./_build/build.sh website-root --install`.
 5. Publish to the public website.
 6. Commit your changes to master, e.g. with a message like "Update latest docs to 0.8.0-incubating"
diff --git a/brooklyn-docs/website/developers/committers/release-process/release-version.md b/brooklyn-docs/website/developers/committers/release-process/release-version.md
index eea6943..a22314b 100644
--- a/brooklyn-docs/website/developers/committers/release-process/release-version.md
+++ b/brooklyn-docs/website/developers/committers/release-process/release-version.md
@@ -32,10 +32,10 @@
 git push -u apache $VERSION_NAME
 {% endhighlight %}
 
-Now change the version numbers in this branch throughout the project using the script `release/change-version.sh` and commit it:
+Now change the version numbers in this branch throughout the project using the script `brooklyn-dist/release/change-version.sh` and commit it:
 
 {% highlight bash %}
-./release/change-version.sh BROOKLYN $OLD_MASTER_VERSION $VERSION_NAME
+./brooklyn-dist/release/change-version.sh BROOKLYN $OLD_MASTER_VERSION $VERSION_NAME
 git add .
 # Now inspect the staged changes and ensure there are no surprises
 git commit -m "Change version to $VERSION_NAME"
@@ -56,12 +56,12 @@
 
 {% highlight bash %}
 git checkout master
-./release/change-version.sh BROOKLYN $OLD_MASTER_VERSION $NEW_MASTER_VERSION
+./brooklyn-dist/release/change-version.sh BROOKLYN $OLD_MASTER_VERSION $NEW_MASTER_VERSION
 git add .
 # Now inspect the staged changes and ensure there are no surprises
 {% endhighlight %}
 
-Open `docs/guide/misc/release-notes.md` and `docs/website/meta/versions.md` in your favourite editor and amend.
+Open `brooklyn-docs/guide/misc/release-notes.md` and `brooklyn-docs/website/meta/versions.md` in your favourite editor and amend.
 For release notes this means bumping the reference to the previous version in the "Backwards Compatibility" section
 and putting some placeholder text elsewhere.
 
diff --git a/brooklyn-docs/website/developers/committers/release-process/vote.md b/brooklyn-docs/website/developers/committers/release-process/vote.md
index d3f6930..e459bdd 100644
--- a/brooklyn-docs/website/developers/committers/release-process/vote.md
+++ b/brooklyn-docs/website/developers/committers/release-process/vote.md
@@ -7,10 +7,10 @@
 Start the vote
 --------------
 
-A script to generate the voting email can be found in `release/print-vote-email.sh`,
+A script to generate the voting email can be found in `brooklyn-dist/release/print-vote-email.sh`,
 taking a single argument being the staging repo link. For example:
 
-    release/print-vote-email.sh orgapachebrooklyn-1234 | pbcopy 
+    brooklyn-dist/release/print-vote-email.sh orgapachebrooklyn-1234 | pbcopy
 
 You should move the subject and put your name at the end, and simply eyeball the rest. This should be sent to **dev@brooklyn.incubator.apache.org**.
 
diff --git a/brooklyn-library/pom.xml b/brooklyn-library/pom.xml
index 71b87f4..2b6ba8d 100644
--- a/brooklyn-library/pom.xml
+++ b/brooklyn-library/pom.xml
@@ -86,7 +86,7 @@
         <activemq.version>5.10.0</activemq.version>
         <rabbitmq-version>2.8.7</rabbitmq-version>
         <kafka.version>0.8.2.1</kafka.version>
-        <storm.version>0.8.2</storm.version>
+        <storm.version>0.9.0.1</storm.version>
         <redis.version>1.5.2</redis.version>
         
     </properties>
diff --git a/brooklyn-library/software/messaging/pom.xml b/brooklyn-library/software/messaging/pom.xml
index 83e7ef0..57b2ff0 100644
--- a/brooklyn-library/software/messaging/pom.xml
+++ b/brooklyn-library/software/messaging/pom.xml
@@ -181,7 +181,7 @@
         <!-- for storm -->
 		<dependency>
 			<groupId>storm</groupId>
-			<artifactId>storm</artifactId>
+			<artifactId>storm-core</artifactId>
 			<version>${storm.version}</version>
 			<!-- keep storm out of the jar-with-dependencies -->
             <scope>test</scope>
@@ -215,6 +215,10 @@
                     <groupId>commons-codec</groupId>
                     <artifactId>commons-codec</artifactId>
                 </exclusion>
+                <exclusion>
+                    <groupId>storm</groupId>
+                    <artifactId>carbonite</artifactId>
+                </exclusion>
             </exclusions>
         </dependency>
 
diff --git a/brooklyn-server/core/src/test/java/org/apache/brooklyn/util/core/task/DynamicSequentialTaskTest.java b/brooklyn-server/core/src/test/java/org/apache/brooklyn/util/core/task/DynamicSequentialTaskTest.java
index d74c7bf..ac5c11c 100644
--- a/brooklyn-server/core/src/test/java/org/apache/brooklyn/util/core/task/DynamicSequentialTaskTest.java
+++ b/brooklyn-server/core/src/test/java/org/apache/brooklyn/util/core/task/DynamicSequentialTaskTest.java
@@ -26,7 +26,6 @@
 import java.util.concurrent.Callable;
 import java.util.concurrent.Semaphore;
 import java.util.concurrent.TimeUnit;
-import java.util.concurrent.TimeoutException;
 
 import org.apache.brooklyn.api.mgmt.HasTaskChildren;
 import org.apache.brooklyn.api.mgmt.Task;
@@ -39,7 +38,6 @@
 import org.apache.brooklyn.util.core.task.TaskInternal.TaskCancellationMode;
 import org.apache.brooklyn.util.exceptions.Exceptions;
 import org.apache.brooklyn.util.math.MathPredicates;
-import org.apache.brooklyn.util.time.CountdownTimer;
 import org.apache.brooklyn.util.time.Duration;
 import org.apache.brooklyn.util.time.Time;
 import org.slf4j.Logger;
@@ -64,7 +62,7 @@
     private static final Logger log = LoggerFactory.getLogger(DynamicSequentialTaskTest.class);
     
     public static final Duration TIMEOUT = Duration.TEN_SECONDS;
-    public static final Duration TINY_TIME = Duration.millis(20);
+    public static final Duration TINY_TIME = Duration.millis(1);
     
     BasicExecutionManager em;
     BasicExecutionContext ec;
@@ -250,6 +248,8 @@
     
     @Test
     public void testCancellationModeAndSubmitted() throws Exception {
+        // seems actually to be the logging which causes this to take ~50ms ?
+        
         doTestCancellationModeAndSubmitted(true, TaskCancellationMode.DO_NOT_INTERRUPT, false, false);
         
         doTestCancellationModeAndSubmitted(true, TaskCancellationMode.INTERRUPT_TASK_AND_ALL_SUBMITTED_TASKS, true, true);
@@ -321,27 +321,14 @@
                 @Override public Number get() { return t1.getEndTimeUtc(); }}, 
                 MathPredicates.<Number>greaterThanOrEqual(0));
         } else {
-            Time.sleep(Duration.millis(5));
+            Time.sleep(TINY_TIME);
             Assert.assertFalse(t1.isCancelled());
             Assert.assertFalse(t1.isDone());
         }
     }
 
     protected void waitForMessages(Predicate<? super List<String>> predicate, Duration timeout) throws Exception {
-        long endtime = System.currentTimeMillis() + timeout.toMilliseconds();
-        synchronized (messages) {
-            while (true) {
-                if (predicate.apply(messages)) {
-                    return;
-                }
-                long waittime = endtime - System.currentTimeMillis();
-                if (waittime > 0) {
-                    messages.wait(waittime);
-                } else {
-                    throw new TimeoutException("Timeout after "+timeout+"; messages="+messages+"; predicate="+predicate);
-                }
-            }
-        }
+        Asserts.eventuallyOnNotify(messages, predicate, timeout);
     }
     
     protected Task<String> monitorableTask(final String id) {
@@ -377,14 +364,7 @@
         monitorableJobSemaphoreMap.get(id).release();
     }
     protected void waitForMessage(final String id) {
-        CountdownTimer timer = CountdownTimer.newInstanceStarted(TIMEOUT);
-        synchronized (messages) {
-            while (!timer.isExpired()) {
-                if (messages.contains(id)) return;
-                timer.waitOnForExpiryUnchecked(messages);
-            }
-        }
-        Assert.fail("Did not see message "+id);
+        Asserts.eventuallyOnNotify(messages, CollectionFunctionals.contains(id), TIMEOUT);
     }
     protected void releaseAndWaitForMonitorableJob(final String id) {
         releaseMonitorableJob(id);
diff --git a/brooklyn-server/software/base/src/main/java/org/apache/brooklyn/entity/software/base/SoftwareProcessImpl.java b/brooklyn-server/software/base/src/main/java/org/apache/brooklyn/entity/software/base/SoftwareProcessImpl.java
index c62cc3d..ace4ebe 100644
--- a/brooklyn-server/software/base/src/main/java/org/apache/brooklyn/entity/software/base/SoftwareProcessImpl.java
+++ b/brooklyn-server/software/base/src/main/java/org/apache/brooklyn/entity/software/base/SoftwareProcessImpl.java
@@ -548,7 +548,7 @@
         boolean isRunningResult = false;
         long delay = 100;
         Exception firstFailure = null;
-        while (!isRunningResult && !timer.isExpired()) {
+        while (!isRunningResult && timer.isNotExpired()) {
             Time.sleep(delay);
             try {
                 isRunningResult = driver.isRunning();
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
index 77175d2..e3dfb12 100644
--- 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
@@ -38,6 +38,7 @@
 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.collections.CollectionFunctionals;
 import org.apache.brooklyn.util.time.Duration;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -75,12 +76,14 @@
         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);
-            }});
+            @Override public void onItemAdded(Entity item) { addToSetAndNotify(entitiesAdded, item); }
+            @Override public void onItemRemoved(Entity item) { addToSetAndNotify(entitiesRemoved, item); }});
+    }
+    private static <T> void addToSetAndNotify(Set<T> items, T item) {
+        synchronized (items) {
+            items.add(item);
+            items.notifyAll();
+        }
     }
 
     @Test
@@ -179,7 +182,7 @@
 
     protected void assertSize(int targetSize, int quarantineSize, final int deletedSize) {
         assertSize(targetSize, quarantineSize);
-        assertEquals(entitiesRemoved.size(), deletedSize, "removed="+entitiesRemoved);
+        Asserts.eventuallyOnNotify(entitiesRemoved, CollectionFunctionals.sizeEquals(deletedSize));
     }
     
     protected void assertSize(int targetSize, int quarantineSize) {
diff --git a/brooklyn-server/software/base/src/test/java/org/apache/brooklyn/entity/system_service/SystemServiceEnricherTest.java b/brooklyn-server/software/base/src/test/java/org/apache/brooklyn/entity/system_service/SystemServiceEnricherTest.java
index 10e2e15..70c0da5 100644
--- a/brooklyn-server/software/base/src/test/java/org/apache/brooklyn/entity/system_service/SystemServiceEnricherTest.java
+++ b/brooklyn-server/software/base/src/test/java/org/apache/brooklyn/entity/system_service/SystemServiceEnricherTest.java
@@ -86,10 +86,10 @@
     }
 
     private void waitFailed(VanillaSoftwareProcess proc) {
-        Asserts.eventually(ImmutableMap.of("timeout", Duration.FIVE_MINUTES), Suppliers.ofInstance(proc), EntityPredicates.attributeEqualTo(Attributes.SERVICE_STATE_ACTUAL, Lifecycle.ON_FIRE));
+        Asserts.eventually(Suppliers.ofInstance(proc), EntityPredicates.attributeEqualTo(Attributes.SERVICE_STATE_ACTUAL, Lifecycle.ON_FIRE), Duration.FIVE_MINUTES);
     }
 
     private void waitHealthy(VanillaSoftwareProcess proc) {
-        Asserts.eventually(ImmutableMap.of("timeout", Duration.FIVE_MINUTES), Suppliers.ofInstance(proc), EntityPredicates.attributeEqualTo(Attributes.SERVICE_STATE_ACTUAL, Lifecycle.RUNNING));
+        Asserts.eventually(Suppliers.ofInstance(proc), EntityPredicates.attributeEqualTo(Attributes.SERVICE_STATE_ACTUAL, Lifecycle.RUNNING), Duration.FIVE_MINUTES);
     }
 }
diff --git a/brooklyn-server/test-support/src/main/java/org/apache/brooklyn/test/WebAppMonitor.java b/brooklyn-server/test-support/src/main/java/org/apache/brooklyn/test/WebAppMonitor.java
index d804312..b957a5b 100644
--- a/brooklyn-server/test-support/src/main/java/org/apache/brooklyn/test/WebAppMonitor.java
+++ b/brooklyn-server/test-support/src/main/java/org/apache/brooklyn/test/WebAppMonitor.java
@@ -184,7 +184,7 @@
         return this;
     }
     public WebAppMonitor waitForAtLeastOneAttempt() {
-        return waitForAtLeastOneAttempt(Asserts.DEFAULT_TIMEOUT);
+        return waitForAtLeastOneAttempt(Asserts.DEFAULT_LONG_TIMEOUT);
     }
     public WebAppMonitor waitForAtLeastOneAttempt(Duration timeout) {
         Asserts.succeedsEventually(MutableMap.of("timeout", timeout), new Runnable() {
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 15aa76e..fac3142 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
@@ -37,7 +37,9 @@
 import org.apache.brooklyn.util.collections.MutableSet;
 import org.apache.brooklyn.util.exceptions.Exceptions;
 import org.apache.brooklyn.util.javalang.JavaClassNames;
+import org.apache.brooklyn.util.repeat.Repeater;
 import org.apache.brooklyn.util.text.StringPredicates;
+import org.apache.brooklyn.util.time.CountdownTimer;
 import org.apache.brooklyn.util.time.Duration;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -46,6 +48,7 @@
 import com.google.common.base.Predicate;
 import com.google.common.base.Predicates;
 import com.google.common.base.Supplier;
+import com.google.common.base.Suppliers;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.Iterables;
@@ -68,11 +71,24 @@
 @Beta
 public class Asserts {
 
-    /**
-     * The default timeout for assertions - 30s.
-     * Alter in individual tests by giving a "timeout" entry in method flags.
+    /** 
+     * Timeout for use when something should happen within several seconds,
+     * but there might be network calls or computation so {@link #DEFAULT_SHORT_TIMEOUT} is not applicable.
      */
-    public static final Duration DEFAULT_TIMEOUT = Duration.THIRTY_SECONDS;
+    public static final Duration DEFAULT_LONG_TIMEOUT = Duration.THIRTY_SECONDS;
+    
+    /** 
+     * Timeout for use when waiting for other threads to finish.
+     * <p>
+     * Long enough for parallel execution to catch up, 
+     * even on overloaded mediocre test boxes most of the time,
+     * but short enough not to irritate you when your test is failing. */
+    public static final Duration DEFAULT_SHORT_TIMEOUT = Duration.ONE_SECOND;
+    
+    /** @deprecated since 0.9.0 use {@link #DEFAULT_LONG_TIMEOUT} */ @Deprecated
+    public static final Duration DEFAULT_TIMEOUT = DEFAULT_LONG_TIMEOUT;
+    
+    private static final Duration DEFAULT_SHORT_PERIOD = Repeater.DEFAULT_REAL_QUICK_PERIOD;
 
     private static final Logger log = LoggerFactory.getLogger(Asserts.class);
 
@@ -730,58 +746,99 @@
 
     // --- new routines
     
+    /**  As {@link #eventually(Supplier, Predicate, Duration, Duration, String)} with defaults. */
     public static <T> void eventually(Supplier<? extends T> supplier, Predicate<T> predicate) {
-        eventually(ImmutableMap.<String,Object>of(), supplier, predicate);
+        eventually(supplier, predicate, null, null, null);
     }
     
+    /** @deprecated since 0.9.0 use {@link #eventually(Supplier, Predicate, Duration, Duration, String)} */ @Deprecated
     public static <T> void eventually(Map<String,?> flags, Supplier<? extends T> supplier, Predicate<T> predicate) {
         eventually(flags, supplier, predicate, (String)null);
     }
-    
+    /** @deprecated since 0.9.0 use {@link #eventually(Supplier, Predicate, Duration, Duration, String)} */ @Deprecated
     public static <T> void eventually(Map<String,?> flags, Supplier<? extends T> supplier, Predicate<T> predicate, String errMsg) {
-        Duration timeout = toDuration(flags.get("timeout"), Duration.ONE_SECOND);
-        Duration period = toDuration(flags.get("period"), Duration.millis(10));
-        long periodMs = period.toMilliseconds();
-        long startTime = System.currentTimeMillis();
-        long expireTime = startTime+timeout.toMilliseconds();
-        
-        boolean first = true;
-        T supplied = supplier.get();
-        while (first || System.currentTimeMillis() <= expireTime) {
-            supplied = supplier.get();
-            if (predicate.apply(supplied)) {
-                return;
-            }
-            first = false;
-            if (periodMs > 0) sleep(periodMs);
-        }
-        fail("supplied="+supplied+"; predicate="+predicate+(errMsg!=null?"; "+errMsg:""));
+        eventually(supplier, predicate, toDuration(flags.get("timeout"), null), toDuration(flags.get("period"), null), errMsg);
     }
     
-    // TODO improve here -- these methods aren't very useful without timeouts
+    /**  As {@link #eventually(Supplier, Predicate, Duration, Duration, String)} with default. */
+    public static <T> void eventually(Supplier<? extends T> supplier, Predicate<T> predicate, Duration timeout) {
+        eventually(supplier, predicate, timeout, null, null);
+    }
+    
+    /** Asserts that eventually the supplier gives a value accepted by the predicate. 
+     * Tests periodically and succeeds as soon as the supplier gives an allowed value.
+     * Other arguments can be null.
+     * 
+     * @param supplier supplies the value to test, such as {@link Suppliers#ofInstance(Object)} for a constant 
+     * @param predicate the {@link Predicate} to apply to each value given by the supplier
+     * @param timeout how long to wait, default {@link #DEFAULT_SHORT_TIMEOUT}
+     * @param period how often to check, default quite often so you won't notice but letting the CPU do work
+     * @param errMsg an error message to display if not satisfied, in addition to the last-tested supplied value and the predicate
+     */
+    public static <T> void eventually(Supplier<? extends T> supplier, Predicate<T> predicate, Duration timeout, Duration period, String errMsg) {
+        if (timeout==null) timeout = DEFAULT_SHORT_TIMEOUT;
+        if (period==null) period = DEFAULT_SHORT_PERIOD;
+        CountdownTimer timeleft = timeout.countdownTimer();
+        
+        T supplied;
+        int count = 0;
+        do {
+            if (count++ > 0) Duration.sleep(period);
+            supplied = supplier.get();
+            if (predicate.apply(supplied)) return;
+        } while (timeleft.isNotExpired());
+
+        fail("Expected: eventually "+predicate+"; got most recently: "+supplied
+            +" (waited "+timeleft.getDurationElapsed()+", checked "+count+")"
+            +(errMsg!=null?"; "+errMsg:""));
+    }
+    
+    /**  As {@link #continually(Supplier, Predicate, Duration, Duration, String)} with defaults. */
     public static <T> void continually(Supplier<? extends T> supplier, Predicate<T> predicate) {
         continually(ImmutableMap.<String,Object>of(), supplier, predicate);
     }
 
+    /** @deprecated since 0.9.0 use {@link #eventually(Supplier, Predicate, Duration, Duration, String)} */ @Deprecated
     public static <T> void continually(Map<String,?> flags, Supplier<? extends T> supplier, Predicate<? super T> predicate) {
-        continually(flags, supplier, predicate, (String)null);
+        continually(flags, supplier, predicate, null);
     }
 
+    /** @deprecated since 0.9.0 use {@link #eventually(Supplier, Predicate, Duration, Duration, String)} */ @Deprecated
     public static <T> void continually(Map<String,?> flags, Supplier<? extends T> supplier, Predicate<T> predicate, String errMsg) {
-        Duration duration = toDuration(flags.get("timeout"), Duration.ONE_SECOND);
-        Duration period = toDuration(flags.get("period"), Duration.millis(10));
-        long periodMs = period.toMilliseconds();
-        long startTime = System.currentTimeMillis();
-        long expireTime = startTime+duration.toMilliseconds();
-        
-        boolean first = true;
-        while (first || System.currentTimeMillis() <= expireTime) {
-            assertTrue(predicate.apply(supplier.get()), "supplied="+supplier.get()+"; predicate="+predicate+(errMsg!=null?"; "+errMsg:""));
-            if (periodMs > 0) sleep(periodMs);
-            first = false;
-        }
+        continually(supplier, predicate, toDuration(flags.get("timeout"), toDuration(flags.get("duration"), null)), 
+            toDuration(flags.get("period"), null), null);
     }
+    /** 
+     * Asserts that continually the supplier gives a value accepted by the predicate. 
+     * Tests periodically and fails if the supplier gives a disallowed value.
+     * Other arguments can be null.
+     * 
+     * @param supplier supplies the value to test, such as {@link Suppliers#ofInstance(Object)} for a constant 
+     * @param predicate the {@link Predicate} to apply to each value given by the supplier
+     * @param duration how long to test for, default {@link #DEFAULT_SHORT_TIMEOUT}
+     * @param period how often to check, default quite often to minimise chance of missing a flashing violation but letting the CPU do work
+     * @param errMsg an error message to display if not satisfied, in addition to the last-tested supplied value and the predicate
+     */
+    public static <T> void continually(Supplier<? extends T> supplier, Predicate<T> predicate, Duration duration, Duration period, String errMsg) {
+        if (duration==null) duration = DEFAULT_SHORT_TIMEOUT;
+        if (period==null) period = DEFAULT_SHORT_PERIOD;
 
+        CountdownTimer timeleft = duration.countdownTimer();
+        
+        T supplied;
+        int count = 0;
+        do {
+            if (count > 0) Duration.sleep(period);
+            supplied = supplier.get();
+            if (!predicate.apply(supplied)) {
+                fail("Expected: continually "+predicate+"; got violation: "+supplied
+                    // tell timing if it worked the first time and then failed
+                    +(count > 0 ? " (after "+timeleft.getDurationElapsed()+", successfully checked "+count+")" : "")
+                    +(errMsg!=null?"; "+errMsg:""));
+            }
+            count++;
+        } while (timeleft.isNotExpired());
+    }
     
     /**
      * @see #succeedsContinually(Map, Callable)
@@ -818,6 +875,9 @@
         }
     }
 
+    // TODO flags are ugly; remove this in favour of something strongly typed,
+    // e.g. extending Repeater and taking the extra semantics.
+    // TODO remove the #succeedsEventually in favour of #eventually (and same for continually)
     /**
      * Convenience method for cases where we need to test until something is true.
      *
@@ -827,7 +887,7 @@
      * <ul>
      * <li>abortOnError (boolean, default true)
      * <li>abortOnException - (boolean, default false)
-     * <li>timeout - (a Duration or an integer in millis, defaults to {@link Asserts#DEFAULT_TIMEOUT})
+     * <li>timeout - (a Duration or an integer in millis, defaults to {@link Asserts#DEFAULT_LONG_TIMEOUT})
      * <li>period - (a Duration or an integer in millis, for fixed retry time; if not set, defaults to exponentially increasing from 1 to 500ms)
      * <li>minPeriod - (a Duration or an integer in millis; only used if period not explicitly set; the minimum period when exponentially increasing; defaults to 1ms)
      * <li>maxPeriod - (a Duration or an integer in millis; only used if period not explicitly set; the maximum period when exponentially increasing; defaults to 500ms)
@@ -849,7 +909,7 @@
         boolean logException = get(flags, "logException", true);
 
         // To speed up tests, default is for the period to start small and increase...
-        Duration duration = toDuration(flags.get("timeout"), DEFAULT_TIMEOUT);
+        Duration duration = toDuration(flags.get("timeout"), DEFAULT_LONG_TIMEOUT);
         Duration fixedPeriod = toDuration(flags.get("period"), null);
         Duration minPeriod = (fixedPeriod != null) ? fixedPeriod : toDuration(flags.get("minPeriod"), Duration.millis(1));
         Duration maxPeriod = (fixedPeriod != null) ? fixedPeriod : toDuration(flags.get("maxPeriod"), Duration.millis(500));
@@ -915,7 +975,8 @@
     public static <T> T succeedsContinually(Callable<T> c) {
         return succeedsContinually(ImmutableMap.<String,Object>of(), c);
     }
-    
+
+    // TODO unify with "continually"; see also eventually, some of those options might be useful
     public static <T> T succeedsContinually(Map<?,?> flags, Callable<T> job) {
         Duration duration = toDuration(flags.get("timeout"), Duration.ONE_SECOND);
         Duration period = toDuration(flags.get("period"), Duration.millis(10));
@@ -1240,4 +1301,50 @@
         throw new RuntimeException(t);
     }
 
+    /** As {@link #eventuallyOnNotify(Object, Supplier, Predicate, Duration, boolean)} with default timeout. */
+    public static <T> void eventuallyOnNotify(Object notifyTarget, Supplier<T> supplier, Predicate<T> predicate) {
+        eventuallyOnNotify(notifyTarget, supplier, predicate, null);
+    }
+    
+    /** as {@link #eventually(Supplier, Predicate)} for cases where an object is notified;
+     * more efficient as it waits on the notify target object. 
+     * See also the simpler {@link #eventuallyOnNotify(Object, Predicate)} when looking at a collection which is getting notified.
+     * Timeout defaults to {@link #DEFAULT_SHORT_TIMEOUT}. 
+     * <p>
+     * This synchronizes on the notify target for the duration of the wait, 
+     * including while getting and checking the value, so as not to miss any notification. */
+    public static <T> void eventuallyOnNotify(Object notifyTarget, Supplier<T> supplier, Predicate<T> predicate, Duration timeout) {
+        T supplied = null;
+        if (timeout==null) timeout = DEFAULT_SHORT_TIMEOUT;
+        CountdownTimer remaining = timeout.countdownTimer();
+        int checks = 0;
+        synchronized (notifyTarget) {
+            do {
+                if (checks>0) {
+                    remaining.waitOnForExpiryUnchecked(notifyTarget);
+                }
+                supplied = supplier.get();
+                if (predicate.apply(supplied)) return;
+                checks++;
+            } while (remaining.isNotExpired());
+        }
+        
+        // should get 2 checks, 1 before and 1 after, if no notifications; if more, tell the user
+        fail("Expected: eventually "+predicate+"; got most recently: "+supplied+
+            " (waited "+remaining.getDurationElapsed()+
+                (checks>2 ? "; notification count "+(checks-2) : "")+
+            ")");
+    }
+
+    /** Convenience for {@link #eventuallyOnNotify(Object, Supplier, Predicate, Duration, boolean)} 
+     * when the notify target and the value under test are the same. */
+    public static <T> void eventuallyOnNotify(T object, Predicate<T> predicate, Duration timeout) {
+        eventuallyOnNotify(object, Suppliers.ofInstance(object), predicate, timeout);
+    }
+
+    /** As {@link #eventuallyOnNotify(Object, Predicate, Duration)} with the default duration of {@link #eventuallyOnNotify(Object, Supplier, Predicate)}. */
+    public static <T> void eventuallyOnNotify(T object, Predicate<T> predicate) {
+        eventuallyOnNotify(object, Suppliers.ofInstance(object), predicate, null);
+    }
+
 }
diff --git a/brooklyn-server/utils/common/src/main/java/org/apache/brooklyn/util/time/CountdownTimer.java b/brooklyn-server/utils/common/src/main/java/org/apache/brooklyn/util/time/CountdownTimer.java
index 508657d..fea8ee6 100644
--- a/brooklyn-server/utils/common/src/main/java/org/apache/brooklyn/util/time/CountdownTimer.java
+++ b/brooklyn-server/utils/common/src/main/java/org/apache/brooklyn/util/time/CountdownTimer.java
@@ -55,7 +55,7 @@
         return limit;
     }
 
-    /** return how long the timer has been running (longer than limit if {@link #isExpired()}) */
+    /** return how long the timer has been running (may be longer than {@link #getLimit()} if {@link #isExpired()}) */
     public Duration getDurationElapsed() {
         return Duration.nanos(stopwatch.elapsed(TimeUnit.NANOSECONDS));
     }
@@ -65,15 +65,28 @@
         return Duration.millis(limit.toMilliseconds() - stopwatch.elapsed(TimeUnit.MILLISECONDS));
     }
 
-    /** true iff the timer has been running for the duration specified at creation time */
+    /** true iff the timer has run for more than the duration specified at creation time */
     public boolean isExpired() {
         return stopwatch.elapsed(TimeUnit.MILLISECONDS) > limit.toMilliseconds();
     }
-    
-    /** true iff timer is running (even if it is expired) */
-    public boolean isRunning() {
+
+    /** true iff {@link #isNotPaused()} and not {@link #isExpired()} */
+    public boolean isLive() {
+        return isNotPaused() && isNotExpired();
+    }
+
+    /** true iff not {@link #isExpired()} */
+    public boolean isNotExpired() {
+        return !isExpired();
+    }
+
+    /** false if started or paused, true otherwise (ie the timer is counting down, even if it is expired) */
+    public boolean isNotPaused() {
         return stopwatch.isRunning();
     }
+
+    /** @deprecated since 0.9.0 use better named {@link #isNotPaused()} */ @Deprecated
+    public boolean isRunning() { return isNotPaused(); }
     
     // --- constructor methods
     
diff --git a/brooklyn-server/utils/common/src/test/java/org/apache/brooklyn/util/time/CountdownTimerTest.java b/brooklyn-server/utils/common/src/test/java/org/apache/brooklyn/util/time/CountdownTimerTest.java
index 5541caa..45995da 100644
--- a/brooklyn-server/utils/common/src/test/java/org/apache/brooklyn/util/time/CountdownTimerTest.java
+++ b/brooklyn-server/utils/common/src/test/java/org/apache/brooklyn/util/time/CountdownTimerTest.java
@@ -50,6 +50,9 @@
         
         CountdownTimer timer = SIMPLE_DURATION.countdownTimer();
         assertFalse(timer.isExpired());
+        assertTrue(timer.isNotExpired());
+        assertTrue(timer.isLive());
+        assertTrue(timer.isNotPaused());
         assertTrue(timer.getDurationElapsed().toMilliseconds() <= OVERHEAD_MS, "elapsed="+timer.getDurationElapsed().toMilliseconds());
         assertTrue(timer.getDurationRemaining().toMilliseconds() >= TOTAL_TIME_MS - OVERHEAD_MS, "remaining="+timer.getDurationElapsed().toMilliseconds());
         
@@ -60,6 +63,10 @@
         
         Time.sleep(Duration.millis(SECOND_SLEEP_TIME_MS));
         assertTrue(timer.isExpired());
+        assertFalse(timer.isNotExpired());
+        assertFalse(timer.isLive());
+        assertTrue(timer.isNotPaused());
+
     }
     
     public void testNotify() throws InterruptedException {