brooklyn-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From grk...@apache.org
Subject [01/10] brooklyn-server git commit: Test the Clocker pattern (of LocationOwner and DynamicLocation)
Date Tue, 22 Mar 2016 18:06:43 GMT
Repository: brooklyn-server
Updated Branches:
  refs/heads/master 1e5f3b608 -> b8211ed17


Test the Clocker pattern (of LocationOwner and DynamicLocation)


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

Branch: refs/heads/master
Commit: 292b4cfb8d5b0bbff0f38eba5fc84a1fc9fe4eee
Parents: d059088
Author: Aled Sage <aled.sage@gmail.com>
Authored: Fri Mar 11 19:37:13 2016 +0000
Committer: Aled Sage <aled.sage@gmail.com>
Committed: Fri Mar 18 22:18:21 2016 +0000

----------------------------------------------------------------------
 ...ClockerDynamicLocationPatternRebindTest.java | 113 +++++++++
 .../ClockerDynamicLocationPatternTest.java      |  96 ++++++++
 .../dynamic/clocker/StubAttributes.java         |  34 +++
 .../location/dynamic/clocker/StubContainer.java |  33 +++
 .../dynamic/clocker/StubContainerImpl.java      | 123 ++++++++++
 .../dynamic/clocker/StubContainerLocation.java  |  41 ++++
 .../core/location/dynamic/clocker/StubHost.java |  38 +++
 .../location/dynamic/clocker/StubHostImpl.java  | 127 ++++++++++
 .../dynamic/clocker/StubHostLocation.java       | 109 ++++++++
 .../dynamic/clocker/StubInfrastructure.java     |  92 +++++++
 .../dynamic/clocker/StubInfrastructureImpl.java | 246 +++++++++++++++++++
 .../clocker/StubInfrastructureLocation.java     |  83 +++++++
 .../location/dynamic/clocker/StubResolver.java  | 196 +++++++++++++++
 .../location/dynamic/clocker/StubUtils.java     |  95 +++++++
 14 files changed, 1426 insertions(+)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/292b4cfb/software/base/src/test/java/org/apache/brooklyn/core/location/dynamic/clocker/ClockerDynamicLocationPatternRebindTest.java
----------------------------------------------------------------------
diff --git a/software/base/src/test/java/org/apache/brooklyn/core/location/dynamic/clocker/ClockerDynamicLocationPatternRebindTest.java b/software/base/src/test/java/org/apache/brooklyn/core/location/dynamic/clocker/ClockerDynamicLocationPatternRebindTest.java
new file mode 100644
index 0000000..361ee95
--- /dev/null
+++ b/software/base/src/test/java/org/apache/brooklyn/core/location/dynamic/clocker/ClockerDynamicLocationPatternRebindTest.java
@@ -0,0 +1,113 @@
+/*
+ * 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.core.location.dynamic.clocker;
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertFalse;
+import static org.testng.Assert.assertNotNull;
+
+import java.io.File;
+import java.util.List;
+import java.util.Map;
+
+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.core.entity.Entities;
+import org.apache.brooklyn.core.location.BasicLocationRegistry;
+import org.apache.brooklyn.core.location.Locations;
+import org.apache.brooklyn.core.mgmt.internal.LocalManagementContext;
+import org.apache.brooklyn.core.mgmt.rebind.RebindTestFixtureWithApp;
+import org.testng.annotations.Test;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+
+public class ClockerDynamicLocationPatternRebindTest extends RebindTestFixtureWithApp {
+
+    @Override
+    protected LocalManagementContext createOrigManagementContext() {
+        LocalManagementContext result = super.createOrigManagementContext();
+        StubResolver stubResolver = new StubResolver();
+        ((BasicLocationRegistry)result.getLocationRegistry()).registerResolver(stubResolver);
+        return result;
+    }
+    
+    @Override
+    protected LocalManagementContext createNewManagementContext(File mementoDir) {
+        LocalManagementContext result = super.createNewManagementContext(mementoDir);
+        StubResolver stubResolver = new StubResolver();
+        ((BasicLocationRegistry)result.getLocationRegistry()).registerResolver(stubResolver);
+        return result;
+    }
+    
+    // To make this fail (with Clocker code as at 2016-03-11) requires several apps - there's a bug
+    // in rebind that only happens when there are several entities, so the order that they rebind
+    // is more interleaved.
+    @Test
+    public void testRebind() throws Exception {
+        final int NUM_INFRAS = 10;
+        final int HOST_CLUSTER_SIZE = 1;
+        Location loc = mgmt().getLocationRegistry().resolve("localhost");
+        
+        // Maps from the infrastructure locSpec to the (potentially many) host locSpecs
+        Map<String, List<String>> locSpecs = Maps.newLinkedHashMap();
+        
+        for (int i = 0; i < NUM_INFRAS; i++) {
+            StubInfrastructure infra = mgmt().getEntityManager().createEntity(EntitySpec.create(StubInfrastructure.class)
+                    .configure(StubInfrastructure.LOCATION_NAME, "myname"+i));
+            infra.start(ImmutableList.of(loc));
+            infra.getStubHostCluster().resize(HOST_CLUSTER_SIZE);
+            assertEquals(infra.getStubHostCluster().getMembers().size(), HOST_CLUSTER_SIZE);
+            
+            String infraLocSpec = infra.sensors().get(StubInfrastructure.LOCATION_SPEC);
+            List<String> hostLocSpecs = Lists.newArrayList();
+            for (Entity host : infra.getStubHostCluster().getMembers()) {
+                hostLocSpecs.add(host.sensors().get(StubInfrastructure.LOCATION_SPEC));
+            }
+    
+            locSpecs.put(infraLocSpec, hostLocSpecs);
+        }
+        assertEquals(locSpecs.size(), NUM_INFRAS); // in case the infrastructures all used the same loc name!
+
+        rebind();
+
+        for (Map.Entry<String, List<String>> entry : locSpecs.entrySet()) {
+            String infraLocSpec = entry.getKey();
+            List<String> hostLocSpecs = entry.getValue();
+            
+            StubInfrastructureLocation newInfraLoc = (StubInfrastructureLocation) mgmt().getLocationRegistry().resolve(infraLocSpec);
+            assertNotNull(newInfraLoc);
+            for (String hostLocSpec: hostLocSpecs) {
+                StubHostLocation newHostLoc = (StubHostLocation) mgmt().getLocationRegistry().resolve(hostLocSpec);
+                assertNotNull(newHostLoc);
+            }
+    
+            // Confirm that it still functions
+            StubContainerLocation containerLoc = (StubContainerLocation) newInfraLoc.obtain(ImmutableMap.of());
+            StubContainer container = containerLoc.getOwner();
+            
+            newInfraLoc.release(containerLoc);
+            assertFalse(Entities.isManaged(container));
+            assertFalse(Locations.isManaged(containerLoc));
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/292b4cfb/software/base/src/test/java/org/apache/brooklyn/core/location/dynamic/clocker/ClockerDynamicLocationPatternTest.java
----------------------------------------------------------------------
diff --git a/software/base/src/test/java/org/apache/brooklyn/core/location/dynamic/clocker/ClockerDynamicLocationPatternTest.java b/software/base/src/test/java/org/apache/brooklyn/core/location/dynamic/clocker/ClockerDynamicLocationPatternTest.java
new file mode 100644
index 0000000..618c552
--- /dev/null
+++ b/software/base/src/test/java/org/apache/brooklyn/core/location/dynamic/clocker/ClockerDynamicLocationPatternTest.java
@@ -0,0 +1,96 @@
+/*
+ * 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.core.location.dynamic.clocker;
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertFalse;
+
+import org.apache.brooklyn.api.entity.EntitySpec;
+import org.apache.brooklyn.api.location.MachineLocation;
+import org.apache.brooklyn.core.entity.Entities;
+import org.apache.brooklyn.core.location.BasicLocationRegistry;
+import org.apache.brooklyn.core.location.Locations;
+import org.apache.brooklyn.core.test.BrooklynAppUnitTestSupport;
+import org.apache.brooklyn.location.localhost.LocalhostMachineProvisioningLocation;
+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.Iterables;
+
+public class ClockerDynamicLocationPatternTest extends BrooklynAppUnitTestSupport {
+
+    private LocalhostMachineProvisioningLocation loc;
+
+    @BeforeMethod(alwaysRun=true)
+    public void setUp() throws Exception {
+        super.setUp();
+        
+        StubResolver stubResolver = new StubResolver();
+        ((BasicLocationRegistry)mgmt.getLocationRegistry()).registerResolver(stubResolver);
+        
+        loc = app.newLocalhostProvisioningLocation();
+    }
+    
+    @Test
+    public void testCreateAndReleaseDirectly() throws Exception {
+        StubInfrastructure infra = mgmt.getEntityManager().createEntity(EntitySpec.create(StubInfrastructure.class));
+        infra.start(ImmutableList.of(loc));
+        
+        StubInfrastructureLocation loc = infra.getDynamicLocation();
+        MachineLocation machine = loc.obtain(ImmutableMap.of());
+        
+        StubHost host = (StubHost) Iterables.getOnlyElement(infra.getStubHostCluster().getMembers());
+        StubHostLocation hostLoc = host.getDynamicLocation();
+        
+        StubContainer container = (StubContainer) Iterables.getOnlyElement(host.getDockerContainerCluster().getMembers());
+        StubContainerLocation containerLoc = container.getDynamicLocation();
+        assertEquals(containerLoc, machine);
+        assertEquals(Iterables.getOnlyElement(hostLoc.getChildren()), machine);
+        
+        loc.release(machine);
+        assertFalse(Entities.isManaged(container));
+        assertFalse(Locations.isManaged(containerLoc));
+    }
+    
+    @Test
+    public void testThroughLocationRegistry() throws Exception {
+        StubInfrastructure infra = mgmt.getEntityManager().createEntity(EntitySpec.create(StubInfrastructure.class));
+        infra.start(ImmutableList.of(loc));
+        
+        String infraLocSpec = infra.sensors().get(StubInfrastructure.LOCATION_SPEC);
+        StubInfrastructureLocation infraLoc = (StubInfrastructureLocation) mgmt.getLocationRegistry().resolve(infraLocSpec);
+
+        MachineLocation machine = infraLoc.obtain(ImmutableMap.of());
+        
+        StubHost host = (StubHost) Iterables.getOnlyElement(infra.getStubHostCluster().getMembers());
+        String hostLocSpec = host.sensors().get(StubInfrastructure.LOCATION_SPEC);
+        StubHostLocation hostLoc = (StubHostLocation) mgmt.getLocationRegistry().resolve(hostLocSpec);
+
+        StubContainer container = (StubContainer) Iterables.getOnlyElement(host.getDockerContainerCluster().getMembers());
+        StubContainerLocation containerLoc = container.getDynamicLocation();
+        assertEquals(containerLoc, machine);
+        assertEquals(Iterables.getOnlyElement(hostLoc.getChildren()), machine);
+        
+        infraLoc.release(machine);
+        assertFalse(Entities.isManaged(container));
+        assertFalse(Locations.isManaged(containerLoc));
+    }
+}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/292b4cfb/software/base/src/test/java/org/apache/brooklyn/core/location/dynamic/clocker/StubAttributes.java
----------------------------------------------------------------------
diff --git a/software/base/src/test/java/org/apache/brooklyn/core/location/dynamic/clocker/StubAttributes.java b/software/base/src/test/java/org/apache/brooklyn/core/location/dynamic/clocker/StubAttributes.java
new file mode 100644
index 0000000..e356801
--- /dev/null
+++ b/software/base/src/test/java/org/apache/brooklyn/core/location/dynamic/clocker/StubAttributes.java
@@ -0,0 +1,34 @@
+/*
+ * 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.core.location.dynamic.clocker;
+
+import org.apache.brooklyn.core.config.ConfigKeys;
+import org.apache.brooklyn.core.sensor.AttributeSensorAndConfigKey;
+
+public class StubAttributes {
+    public static final AttributeSensorAndConfigKey<StubInfrastructure, StubInfrastructure> DOCKER_INFRASTRUCTURE = ConfigKeys.newSensorAndConfigKey(
+            StubInfrastructure.class,
+            "docker.infrastructure", 
+            "The Docker infrastructure");
+    
+    public static final AttributeSensorAndConfigKey<StubHost, StubHost> DOCKER_HOST = ConfigKeys.newSensorAndConfigKey(
+            StubHost.class,
+            "docker.host", 
+            "The Docker Host");
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/292b4cfb/software/base/src/test/java/org/apache/brooklyn/core/location/dynamic/clocker/StubContainer.java
----------------------------------------------------------------------
diff --git a/software/base/src/test/java/org/apache/brooklyn/core/location/dynamic/clocker/StubContainer.java b/software/base/src/test/java/org/apache/brooklyn/core/location/dynamic/clocker/StubContainer.java
new file mode 100644
index 0000000..504159d
--- /dev/null
+++ b/software/base/src/test/java/org/apache/brooklyn/core/location/dynamic/clocker/StubContainer.java
@@ -0,0 +1,33 @@
+/*
+ * 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.core.location.dynamic.clocker;
+
+import org.apache.brooklyn.api.entity.ImplementedBy;
+import org.apache.brooklyn.core.location.dynamic.LocationOwner;
+import org.apache.brooklyn.core.sensor.AttributeSensorAndConfigKey;
+import org.apache.brooklyn.entity.stock.BasicStartable;
+
+@ImplementedBy(StubContainerImpl.class)
+public interface StubContainer extends BasicStartable, LocationOwner<StubContainerLocation, StubContainer> {
+    AttributeSensorAndConfigKey<StubInfrastructure, StubInfrastructure> DOCKER_INFRASTRUCTURE = StubAttributes.DOCKER_INFRASTRUCTURE;
+    AttributeSensorAndConfigKey<StubHost, StubHost> DOCKER_HOST = StubAttributes.DOCKER_HOST;
+    
+    StubInfrastructure getInfrastructure();
+    StubHost getDockerHost();
+}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/292b4cfb/software/base/src/test/java/org/apache/brooklyn/core/location/dynamic/clocker/StubContainerImpl.java
----------------------------------------------------------------------
diff --git a/software/base/src/test/java/org/apache/brooklyn/core/location/dynamic/clocker/StubContainerImpl.java b/software/base/src/test/java/org/apache/brooklyn/core/location/dynamic/clocker/StubContainerImpl.java
new file mode 100644
index 0000000..45cf9d0
--- /dev/null
+++ b/software/base/src/test/java/org/apache/brooklyn/core/location/dynamic/clocker/StubContainerImpl.java
@@ -0,0 +1,123 @@
+/*
+ * 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.core.location.dynamic.clocker;
+
+import java.io.IOException;
+import java.util.Collection;
+import java.util.Map;
+
+import org.apache.brooklyn.api.location.Location;
+import org.apache.brooklyn.api.location.LocationSpec;
+import org.apache.brooklyn.api.mgmt.LocationManager;
+import org.apache.brooklyn.core.entity.lifecycle.Lifecycle;
+import org.apache.brooklyn.core.entity.lifecycle.ServiceStateLogic;
+import org.apache.brooklyn.core.feed.ConfigToAttributes;
+import org.apache.brooklyn.core.location.dynamic.DynamicLocation;
+import org.apache.brooklyn.entity.stock.BasicStartableImpl;
+import org.apache.brooklyn.location.ssh.SshMachineLocation;
+import org.apache.brooklyn.util.collections.MutableMap;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class StubContainerImpl extends BasicStartableImpl implements StubContainer {
+
+    private static final Logger LOG = LoggerFactory.getLogger(StubContainerImpl.class);
+
+    @Override
+    public void init() {
+        super.init();
+
+        ConfigToAttributes.apply(this);
+    }
+
+    @Override
+    public StubInfrastructure getInfrastructure() {
+        return config().get(DOCKER_INFRASTRUCTURE);
+    }
+
+    @Override
+    public StubHost getDockerHost() {
+        return (StubHost) config().get(DOCKER_HOST);
+    }
+
+    @Override
+    public StubContainerLocation getDynamicLocation() {
+        return (StubContainerLocation) sensors().get(DYNAMIC_LOCATION);
+    }
+
+    @Override
+    public void start(Collection<? extends Location> locations) {
+        ServiceStateLogic.setExpectedState(this, Lifecycle.STARTING);
+
+        Map<String, ?> flags = MutableMap.copyOf(config().get(LOCATION_FLAGS));
+        createLocation(flags);
+
+        super.start(locations);
+
+        ServiceStateLogic.setExpectedState(this, Lifecycle.RUNNING);
+    }
+
+    @Override
+    public StubContainerLocation createLocation(Map<String, ?> flags) {
+        StubHost dockerHost = getDockerHost();
+        StubHostLocation host = dockerHost.getDynamicLocation();
+
+        SshMachineLocation containerMachine = getManagementContext().getLocationManager().createLocation(LocationSpec.create(SshMachineLocation.class)
+                .configure("address", "1.2.3.4"));
+
+        // Create our wrapper location around the container
+        LocationSpec<StubContainerLocation> spec = LocationSpec.create(StubContainerLocation.class)
+                .parent(host)
+                .configure(flags)
+                .configure(DynamicLocation.OWNER, this)
+                .configure("machine", containerMachine)
+                .configure(containerMachine.config().getBag().getAllConfig());
+        StubContainerLocation location = getManagementContext().getLocationManager().createLocation(spec);
+
+        sensors().set(DYNAMIC_LOCATION, location);
+        sensors().set(LOCATION_NAME, location.getId());
+        
+        return location;
+    }
+
+    @Override
+    public boolean isLocationAvailable() {
+        return getDynamicLocation() != null;
+    }
+
+    @Override
+    public void deleteLocation() {
+        StubContainerLocation location = getDynamicLocation();
+
+        if (location != null) {
+            try {
+                location.close();
+            } catch (IOException ioe) {
+                LOG.debug("Error closing container location", ioe);
+            }
+            LocationManager mgr = getManagementContext().getLocationManager();
+            if (mgr.isManaged(location)) {
+                mgr.unmanage(location);
+            }
+        }
+
+        sensors().set(DYNAMIC_LOCATION, null);
+        sensors().set(LOCATION_NAME, null);
+    }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/292b4cfb/software/base/src/test/java/org/apache/brooklyn/core/location/dynamic/clocker/StubContainerLocation.java
----------------------------------------------------------------------
diff --git a/software/base/src/test/java/org/apache/brooklyn/core/location/dynamic/clocker/StubContainerLocation.java b/software/base/src/test/java/org/apache/brooklyn/core/location/dynamic/clocker/StubContainerLocation.java
new file mode 100644
index 0000000..f4b0345
--- /dev/null
+++ b/software/base/src/test/java/org/apache/brooklyn/core/location/dynamic/clocker/StubContainerLocation.java
@@ -0,0 +1,41 @@
+/*
+ * 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.core.location.dynamic.clocker;
+
+import org.apache.brooklyn.core.location.dynamic.DynamicLocation;
+import org.apache.brooklyn.location.ssh.SshMachineLocation;
+import org.apache.brooklyn.util.core.flags.SetFromFlag;
+
+public class StubContainerLocation extends SshMachineLocation implements DynamicLocation<StubContainer, StubContainerLocation> {
+
+    @SetFromFlag("machine")
+    private SshMachineLocation machine;
+
+    @SetFromFlag("owner")
+    private StubContainer owner;
+
+    @Override
+    public StubContainer getOwner() {
+        return owner;
+    }
+
+    public SshMachineLocation getMachine() {
+        return machine;
+    }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/292b4cfb/software/base/src/test/java/org/apache/brooklyn/core/location/dynamic/clocker/StubHost.java
----------------------------------------------------------------------
diff --git a/software/base/src/test/java/org/apache/brooklyn/core/location/dynamic/clocker/StubHost.java b/software/base/src/test/java/org/apache/brooklyn/core/location/dynamic/clocker/StubHost.java
new file mode 100644
index 0000000..e5b4005
--- /dev/null
+++ b/software/base/src/test/java/org/apache/brooklyn/core/location/dynamic/clocker/StubHost.java
@@ -0,0 +1,38 @@
+/*
+ * 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.core.location.dynamic.clocker;
+
+import org.apache.brooklyn.api.entity.ImplementedBy;
+import org.apache.brooklyn.api.sensor.AttributeSensor;
+import org.apache.brooklyn.core.location.dynamic.LocationOwner;
+import org.apache.brooklyn.core.sensor.AttributeSensorAndConfigKey;
+import org.apache.brooklyn.core.sensor.Sensors;
+import org.apache.brooklyn.entity.group.DynamicCluster;
+import org.apache.brooklyn.entity.machine.MachineEntity;
+
+@ImplementedBy(StubHostImpl.class)
+public interface StubHost extends MachineEntity, LocationOwner<StubHostLocation, StubHost> {
+    AttributeSensorAndConfigKey<StubInfrastructure, StubInfrastructure> DOCKER_INFRASTRUCTURE = StubAttributes.DOCKER_INFRASTRUCTURE;
+    
+    AttributeSensor<DynamicCluster> DOCKER_CONTAINER_CLUSTER = Sensors.newSensor(DynamicCluster.class,
+            "docker.container.cluster", "The cluster of Docker containers");
+
+    StubInfrastructure getInfrastructure();
+    DynamicCluster getDockerContainerCluster();
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/292b4cfb/software/base/src/test/java/org/apache/brooklyn/core/location/dynamic/clocker/StubHostImpl.java
----------------------------------------------------------------------
diff --git a/software/base/src/test/java/org/apache/brooklyn/core/location/dynamic/clocker/StubHostImpl.java b/software/base/src/test/java/org/apache/brooklyn/core/location/dynamic/clocker/StubHostImpl.java
new file mode 100644
index 0000000..4c2ad36
--- /dev/null
+++ b/software/base/src/test/java/org/apache/brooklyn/core/location/dynamic/clocker/StubHostImpl.java
@@ -0,0 +1,127 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.brooklyn.core.location.dynamic.clocker;
+
+import java.util.Map;
+
+import org.apache.brooklyn.api.entity.EntitySpec;
+import org.apache.brooklyn.api.location.Location;
+import org.apache.brooklyn.api.location.LocationDefinition;
+import org.apache.brooklyn.core.feed.ConfigToAttributes;
+import org.apache.brooklyn.core.location.BasicLocationDefinition;
+import org.apache.brooklyn.core.location.Locations;
+import org.apache.brooklyn.core.location.Machines;
+import org.apache.brooklyn.entity.group.Cluster;
+import org.apache.brooklyn.entity.group.DynamicCluster;
+import org.apache.brooklyn.entity.machine.MachineEntityImpl;
+import org.apache.brooklyn.location.ssh.SshMachineLocation;
+import org.apache.brooklyn.util.collections.MutableMap;
+import org.apache.brooklyn.util.collections.QuorumCheck.QuorumChecks;
+import org.apache.brooklyn.util.guava.Maybe;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class StubHostImpl extends MachineEntityImpl implements StubHost {
+    
+    private static final Logger LOG = LoggerFactory.getLogger(StubHostImpl.class);
+
+    @Override
+    public void init() {
+        super.init();
+
+        ConfigToAttributes.apply(this);
+
+        EntitySpec<?> dockerContainerSpec = EntitySpec.create(StubContainer.class)
+                .configure(StubContainer.DOCKER_HOST, this)
+                .configure(StubContainer.DOCKER_INFRASTRUCTURE, getInfrastructure());
+
+        DynamicCluster containers = addChild(EntitySpec.create(DynamicCluster.class)
+                .configure(Cluster.INITIAL_SIZE, 0)
+                .configure(DynamicCluster.QUARANTINE_FAILED_ENTITIES, false)
+                .configure(DynamicCluster.MEMBER_SPEC, dockerContainerSpec)
+                .configure(DynamicCluster.RUNNING_QUORUM_CHECK, QuorumChecks.atLeastOneUnlessEmpty())
+                .configure(DynamicCluster.UP_QUORUM_CHECK, QuorumChecks.atLeastOneUnlessEmpty())
+                .displayName("Docker Containers"));
+        sensors().set(DOCKER_CONTAINER_CLUSTER, containers);
+    }
+    
+    @Override
+    public StubInfrastructure getInfrastructure() {
+        return config().get(DOCKER_INFRASTRUCTURE);
+    }
+
+    @Override
+    public DynamicCluster getDockerContainerCluster() {
+        return sensors().get(DOCKER_CONTAINER_CLUSTER);
+    }
+
+    @Override
+    public StubHostLocation getDynamicLocation() {
+        return (StubHostLocation) sensors().get(DYNAMIC_LOCATION);
+    }
+
+    @Override
+    public void preStart() {
+        super.preStart();
+        ConfigToAttributes.apply(this);
+        
+        Maybe<SshMachineLocation> found = Machines.findUniqueMachineLocation(getLocations(), SshMachineLocation.class);
+
+        Map<String, ?> flags = MutableMap.<String, Object>builder()
+                .putAll(config().get(LOCATION_FLAGS))
+                .put("machine", found.get())
+                .build();
+
+        createLocation(flags);
+        sensors().get(DOCKER_CONTAINER_CLUSTER).sensors().set(SERVICE_UP, Boolean.TRUE);
+    }
+
+    @Override
+    public StubHostLocation createLocation(Map<String, ?> flags) {
+        StubInfrastructure infrastructure = getInfrastructure();
+        StubInfrastructureLocation docker = infrastructure.getDynamicLocation();
+        String locationName = docker.getId() + "-" + getId();
+
+        String locationSpec = String.format(StubResolver.DOCKER_HOST_MACHINE_SPEC, infrastructure.getId(), getId()) + String.format(":(name=\"%s\")", locationName);
+        sensors().set(LOCATION_SPEC, locationSpec);
+
+        LocationDefinition definition = new BasicLocationDefinition(locationName, locationSpec, flags);
+        Location location = getManagementContext().getLocationRegistry().resolve(definition);
+        sensors().set(DYNAMIC_LOCATION, location);
+        sensors().set(LOCATION_NAME, location.getId());
+
+        LOG.info("New Docker host location {} created", location);
+        return (StubHostLocation) location;
+    }
+
+    @Override
+    public boolean isLocationAvailable() {
+        return sensors().get(DYNAMIC_LOCATION) != null;
+    }
+
+    @Override
+    public void deleteLocation() {
+        StubHostLocation loc = (StubHostLocation) sensors().get(DYNAMIC_LOCATION);
+        if (loc != null) {
+            Locations.unmanage(loc);
+        }
+        sensors().set(DYNAMIC_LOCATION, null);
+        sensors().set(LOCATION_NAME, null);
+    }
+}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/292b4cfb/software/base/src/test/java/org/apache/brooklyn/core/location/dynamic/clocker/StubHostLocation.java
----------------------------------------------------------------------
diff --git a/software/base/src/test/java/org/apache/brooklyn/core/location/dynamic/clocker/StubHostLocation.java b/software/base/src/test/java/org/apache/brooklyn/core/location/dynamic/clocker/StubHostLocation.java
new file mode 100644
index 0000000..a861220
--- /dev/null
+++ b/software/base/src/test/java/org/apache/brooklyn/core/location/dynamic/clocker/StubHostLocation.java
@@ -0,0 +1,109 @@
+/*
+ * 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.core.location.dynamic.clocker;
+
+import java.util.Collection;
+import java.util.Map;
+
+import org.apache.brooklyn.api.entity.Entity;
+import org.apache.brooklyn.api.entity.EntityLocal;
+import org.apache.brooklyn.api.location.MachineProvisioningLocation;
+import org.apache.brooklyn.api.location.NoMachinesAvailableException;
+import org.apache.brooklyn.core.entity.Entities;
+import org.apache.brooklyn.core.entity.trait.Startable;
+import org.apache.brooklyn.core.location.AbstractLocation;
+import org.apache.brooklyn.core.location.LocationConfigKeys;
+import org.apache.brooklyn.core.location.Locations;
+import org.apache.brooklyn.core.location.dynamic.DynamicLocation;
+import org.apache.brooklyn.entity.group.DynamicCluster;
+import org.apache.brooklyn.location.ssh.SshMachineLocation;
+import org.apache.brooklyn.util.collections.MutableMap;
+import org.apache.brooklyn.util.core.flags.SetFromFlag;
+import org.apache.brooklyn.util.exceptions.Exceptions;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+
+public class StubHostLocation extends AbstractLocation implements MachineProvisioningLocation<StubContainerLocation>, DynamicLocation<StubHost, StubHostLocation> {
+
+    private static final Logger LOG = LoggerFactory.getLogger(StubHostLocation.class);
+
+    @SetFromFlag("machine")
+    private SshMachineLocation machine;
+
+    @SetFromFlag("owner")
+    private StubHost dockerHost;
+
+    @Override
+    public StubHost getOwner() {
+        return (StubHost) getConfig(OWNER);
+    }
+
+    @Override
+    public StubContainerLocation obtain(Map<?, ?> flags) throws NoMachinesAvailableException {
+        Entity callerContext = (Entity) flags.get(LocationConfigKeys.CALLER_CONTEXT.getName());
+        if (callerContext == null) callerContext = getOwner();
+        
+        DynamicCluster cluster = dockerHost.getDockerContainerCluster();
+        Entity added = cluster.addNode(machine, flags);
+        if (added == null) {
+            throw new NoMachinesAvailableException(String.format("Failed to create container at %s", dockerHost));
+        } else {
+            Entities.invokeEffector((EntityLocal) callerContext, added, Startable.START,  MutableMap.of("locations", ImmutableList.of(machine))).getUnchecked();
+        }
+        StubContainer container = (StubContainer) added;
+
+        return container.getDynamicLocation();
+    }
+
+    @Override
+    public void release(StubContainerLocation machine) {
+        DynamicCluster cluster = dockerHost.getDockerContainerCluster();
+        StubContainer container = machine.getOwner();
+        if (cluster.removeMember(container)) {
+            LOG.info("Docker Host {}: member {} released", dockerHost, machine);
+        } else {
+            LOG.warn("Docker Host {}: member {} not found for release", dockerHost, machine);
+        }
+
+        // Now close and unmange the container
+        try {
+            container.stop();
+            machine.close();
+        } catch (Exception e) {
+            LOG.warn("Error stopping container: " + container, e);
+            Exceptions.propagateIfFatal(e);
+        } finally {
+            Entities.unmanage(container);
+            Locations.unmanage(machine);
+        }
+    }
+
+    @Override
+    public MachineProvisioningLocation<StubContainerLocation> newSubLocation(Map<?, ?> newFlags) {
+        return this;
+    }
+
+    @Override
+    public Map<String, Object> getProvisioningFlags(Collection<String> tags) {
+        return ImmutableMap.<String, Object>of();
+    }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/292b4cfb/software/base/src/test/java/org/apache/brooklyn/core/location/dynamic/clocker/StubInfrastructure.java
----------------------------------------------------------------------
diff --git a/software/base/src/test/java/org/apache/brooklyn/core/location/dynamic/clocker/StubInfrastructure.java b/software/base/src/test/java/org/apache/brooklyn/core/location/dynamic/clocker/StubInfrastructure.java
new file mode 100644
index 0000000..3170cf2
--- /dev/null
+++ b/software/base/src/test/java/org/apache/brooklyn/core/location/dynamic/clocker/StubInfrastructure.java
@@ -0,0 +1,92 @@
+/*
+ * 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.core.location.dynamic.clocker;
+
+import java.util.List;
+
+import org.apache.brooklyn.api.entity.Application;
+import org.apache.brooklyn.api.entity.Entity;
+import org.apache.brooklyn.api.entity.ImplementedBy;
+import org.apache.brooklyn.api.mgmt.ManagementContext;
+import org.apache.brooklyn.api.sensor.AttributeSensor;
+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.core.location.dynamic.LocationOwner;
+import org.apache.brooklyn.core.sensor.Sensors;
+import org.apache.brooklyn.entity.group.DynamicCluster;
+import org.apache.brooklyn.entity.group.DynamicGroup;
+import org.apache.brooklyn.entity.group.DynamicMultiGroup;
+
+/**
+ * These entities and locations mirror the structure used in Clocker v1 (prepend everything 
+ * with "Docker" instead of "Stub"). The purpose of repeating it here is to ensure that the 
+ * functionality Clocker is relying on will get regularly tested and kept working - or if it 
+ * does change, then Clocker can be updated as appropriate.
+ * 
+ * The Clocker hierarchy has the following structure:
+ * 
+ * <pre>
+ * Infrastructure -----------> InfrastructureLocation
+ *       |                               |
+ *      Host      ----------->      HostLocation
+ *       |                               |
+ *    Container   ----------->   ContainerLocation
+ * <pre>
+ * 
+ * The Infrastructure, Host and Container are all entities that implement {@link LocationOwner}. 
+ * Whenever one of these is started, it creates the corresponding Location object (shown on the
+ * right).
+ * 
+ * The Infrastructure has a cluster of Host instances. A host has a cluster of Container 
+ * instances.
+ * 
+ * When the {@code Infrastructure} is initially provisioned, it registers the newly create 
+ * location in the {@link ManagementContext#getLocationRegistry()}. This makes it a named 
+ * location, which can be used by other apps.
+ * 
+ * When {@code InfrastructureLocation.obtain()) is called, it will create a container somewhere in 
+ * the cluster. The following chain of events happens:
+ * <ol>
+ *   <li>From the {@code Infrastructure} (i.e. the location's "owner"), get the set of 
+ *       {@code Host}s and thus the set of {@code HostLocation}s.
+ *   <li>Choose a {@code HostLocation}
+ *       (if there's not one, consider scaling up the {@code Infrastructure}'s cluster, if permitted)
+ *   <li>Delegate to {@code DockerHostLocation.obtain()}, and return the result.
+ *   <li>The {@code DockerHostLocation} first does some fiddly Docker stuff around images
+ *       (not represented in this stub structure).
+ *   <li>Get the {@code dockerHost.getDockerContainerCluster()}, and use that to create a new 
+ *       {@code Container} entity.
+ *   <li>Invoke {@code start} effector on the {@code Container} entity
+ *   <li>The {code Container.start} calls {@code Container.createLocation}, which instantiates
+ *       the actual Docker container (by using the jclouds provider).
+ *   <li>Return the {@code container.getDynamicLocation()} (i.e. the {@code ContainerLocation}).
+ * <ul>
+ */
+@ImplementedBy(StubInfrastructureImpl.class)
+public interface StubInfrastructure extends Application, Startable, LocationOwner<StubInfrastructureLocation, StubInfrastructure> {
+    ConfigKey<String> LOCATION_NAME = ConfigKeys.newConfigKeyWithDefault(LocationOwner.LOCATION_NAME.getConfigKey(), "my-stub-cloud");
+    
+    AttributeSensor<DynamicCluster> DOCKER_HOST_CLUSTER = Sensors.newSensor(DynamicCluster.class, "docker.hosts", "Docker host cluster");
+    AttributeSensor<DynamicGroup> DOCKER_CONTAINER_FABRIC = Sensors.newSensor(DynamicGroup.class, "docker.fabric", "Docker container fabric");
+    AttributeSensor<DynamicMultiGroup> DOCKER_APPLICATIONS = Sensors.newSensor(DynamicMultiGroup.class, "docker.buckets", "Docker applications");
+    
+    List<Entity> getStubHostList();
+    DynamicCluster getStubHostCluster();
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/292b4cfb/software/base/src/test/java/org/apache/brooklyn/core/location/dynamic/clocker/StubInfrastructureImpl.java
----------------------------------------------------------------------
diff --git a/software/base/src/test/java/org/apache/brooklyn/core/location/dynamic/clocker/StubInfrastructureImpl.java b/software/base/src/test/java/org/apache/brooklyn/core/location/dynamic/clocker/StubInfrastructureImpl.java
new file mode 100644
index 0000000..0d807fa
--- /dev/null
+++ b/software/base/src/test/java/org/apache/brooklyn/core/location/dynamic/clocker/StubInfrastructureImpl.java
@@ -0,0 +1,246 @@
+/*
+ * 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.core.location.dynamic.clocker;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import javax.annotation.Nullable;
+
+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.location.Location;
+import org.apache.brooklyn.api.location.LocationDefinition;
+import org.apache.brooklyn.api.mgmt.LocationManager;
+import org.apache.brooklyn.api.mgmt.ManagementContext;
+import org.apache.brooklyn.core.entity.AbstractApplication;
+import org.apache.brooklyn.core.entity.Attributes;
+import org.apache.brooklyn.core.entity.Entities;
+import org.apache.brooklyn.core.entity.EntityPredicates;
+import org.apache.brooklyn.core.entity.trait.Startable;
+import org.apache.brooklyn.core.feed.ConfigToAttributes;
+import org.apache.brooklyn.core.location.BasicLocationDefinition;
+import org.apache.brooklyn.core.location.dynamic.LocationOwner;
+import org.apache.brooklyn.entity.group.BasicGroup;
+import org.apache.brooklyn.entity.group.Cluster;
+import org.apache.brooklyn.entity.group.DynamicCluster;
+import org.apache.brooklyn.entity.group.DynamicGroup;
+import org.apache.brooklyn.entity.group.DynamicMultiGroup;
+import org.apache.brooklyn.entity.software.base.SoftwareProcess;
+import org.apache.brooklyn.util.collections.MutableMap;
+import org.apache.brooklyn.util.collections.QuorumCheck.QuorumChecks;
+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.Predicates;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Iterables;
+
+public class StubInfrastructureImpl extends AbstractApplication implements StubInfrastructure {
+
+    private static final Logger LOG = LoggerFactory.getLogger(StubInfrastructureImpl.class);
+
+    @Override
+    public void init() {
+        super.init();
+        
+        ConfigToAttributes.apply(this);
+
+        EntitySpec<?> dockerHostSpec = EntitySpec.create(StubHost.class)
+                .configure(StubHost.DOCKER_INFRASTRUCTURE, this)
+                .configure(SoftwareProcess.CHILDREN_STARTABLE_MODE, SoftwareProcess.ChildStartableMode.BACKGROUND_LATE);
+
+        DynamicCluster hosts = addChild(EntitySpec.create(DynamicCluster.class)
+                .configure(Cluster.INITIAL_SIZE, 1)
+                .configure(DynamicCluster.QUARANTINE_FAILED_ENTITIES, true)
+                .configure(DynamicCluster.MEMBER_SPEC, dockerHostSpec)
+                .configure(DynamicCluster.RUNNING_QUORUM_CHECK, QuorumChecks.atLeastOneUnlessEmpty())
+                .configure(DynamicCluster.UP_QUORUM_CHECK, QuorumChecks.atLeastOneUnlessEmpty())
+                .displayName("Docker Hosts"));
+
+        DynamicGroup fabric = addChild(EntitySpec.create(DynamicGroup.class)
+                .configure(DynamicGroup.ENTITY_FILTER, Predicates.and(Predicates.instanceOf(StubContainer.class), EntityPredicates.attributeEqualTo(StubContainer.DOCKER_INFRASTRUCTURE, this)))
+                .configure(DynamicGroup.MEMBER_DELEGATE_CHILDREN, true)
+                .displayName("All Docker Containers"));
+
+        DynamicMultiGroup buckets = addChild(EntitySpec.create(DynamicMultiGroup.class)
+                .configure(DynamicMultiGroup.ENTITY_FILTER, StubUtils.sameInfrastructure(this))
+                .configure(DynamicMultiGroup.RESCAN_INTERVAL, 15L)
+                .configure(DynamicMultiGroup.BUCKET_FUNCTION, new Function<Entity, String>() {
+                    @Override
+                    public String apply(@Nullable Entity input) {
+                        return input.getApplication().getDisplayName() + ":" + input.getApplicationId();
+                    }
+                })
+                .configure(DynamicMultiGroup.BUCKET_SPEC, EntitySpec.create(BasicGroup.class)
+                        .configure(BasicGroup.MEMBER_DELEGATE_CHILDREN, true))
+                .displayName("Docker Applications"));
+
+        sensors().set(DOCKER_HOST_CLUSTER, hosts);
+        sensors().set(DOCKER_CONTAINER_FABRIC, fabric);
+        sensors().set(DOCKER_APPLICATIONS, buckets);
+    }
+    
+    @Override
+    public void rebind() {
+        super.rebind();
+
+        // Reload our location definition on rebind
+        ManagementContext.PropertiesReloadListener listener = sensors().get(Attributes.PROPERTIES_RELOAD_LISTENER);
+        if (listener != null) {
+            listener.reloaded();
+        }
+    }
+
+    @Override
+    public StubInfrastructureLocation getDynamicLocation() {
+        return (StubInfrastructureLocation) sensors().get(DYNAMIC_LOCATION);
+    }
+
+    @Override
+    public void doStart(Collection<? extends Location> locations) {
+        // TODO support multiple locations
+        sensors().set(SERVICE_UP, Boolean.FALSE);
+
+        Location provisioner = Iterables.getOnlyElement(locations);
+        LOG.info("Creating new DockerLocation wrapping {}", provisioner);
+
+        Map<String, ?> flags = MutableMap.<String, Object>builder()
+                .putAll(config().get(LOCATION_FLAGS))
+                .put("provisioner", provisioner)
+                .build();
+        createLocation(flags);
+
+        super.doStart(locations);
+
+        sensors().set(SERVICE_UP, Boolean.TRUE);
+    }
+
+    /**
+     * De-register our {@link DockerLocation} and its children.
+     */
+    @Override
+    public void stop() {
+        sensors().set(SERVICE_UP, Boolean.FALSE);
+
+        // Find all applications and stop, blocking for up to five minutes until ended
+        try {
+            Iterable<Entity> entities = Iterables.filter(getManagementContext().getEntityManager().getEntities(), StubUtils.sameInfrastructure(this));
+            Set<Application> applications = ImmutableSet.copyOf(Iterables.transform(entities, new Function<Entity, Application>() {
+                @Override
+                public Application apply(Entity input) {
+                    return input.getApplication();
+                }
+            }));
+            LOG.debug("Stopping applications: {}", Iterables.toString(applications));
+            Entities.invokeEffectorList(this, applications, Startable.STOP).get(Duration.THIRTY_SECONDS);
+        } catch (Exception e) {
+            LOG.warn("Error stopping applications", e);
+        }
+
+        // Stop all Docker hosts in parallel
+        try {
+            DynamicCluster hosts = sensors().get(DOCKER_HOST_CLUSTER);
+            LOG.debug("Stopping hosts: {}", Iterables.toString(hosts.getMembers()));
+            Entities.invokeEffectorList(this, hosts.getMembers(), Startable.STOP).get(Duration.THIRTY_SECONDS);
+        } catch (Exception e) {
+            LOG.warn("Error stopping hosts", e);
+        }
+
+        // Stop anything else left over
+        super.stop();
+
+        deleteLocation();
+    }
+
+    @Override
+    public StubInfrastructureLocation createLocation(Map<String, ?> flags) {
+        String locationName = config().get(LOCATION_NAME);
+
+        LocationDefinition check = getManagementContext().getLocationRegistry().getDefinedLocationByName(locationName);
+        if (check != null) {
+            throw new IllegalStateException("Location " + locationName + " is already defined: " + check);
+        }
+
+        String locationSpec = String.format(StubResolver.DOCKER_INFRASTRUCTURE_SPEC, getId()) + String.format(":(name=\"%s\")", locationName);
+        sensors().set(LOCATION_SPEC, locationSpec);
+        LocationDefinition definition = new BasicLocationDefinition(locationName, locationSpec, flags);
+        Location location = getManagementContext().getLocationRegistry().resolve(definition);
+        getManagementContext().getLocationRegistry().updateDefinedLocation(definition);
+
+        ManagementContext.PropertiesReloadListener listener = StubUtils.reloadLocationListener(getManagementContext(), definition);
+        getManagementContext().addPropertiesReloadListener(listener);
+        sensors().set(Attributes.PROPERTIES_RELOAD_LISTENER, listener);
+
+        sensors().set(LocationOwner.LOCATION_DEFINITION, definition);
+        sensors().set(LocationOwner.DYNAMIC_LOCATION, location);
+        sensors().set(LocationOwner.LOCATION_NAME, location.getId());
+        
+        return (StubInfrastructureLocation) location;
+    }
+
+    @Override
+    public boolean isLocationAvailable() {
+        return getDynamicLocation() != null;
+    }
+
+    @Override
+    public void deleteLocation() {
+        StubInfrastructureLocation location = getDynamicLocation();
+
+        if (location != null) {
+            LocationManager mgr = getManagementContext().getLocationManager();
+            if (mgr.isManaged(location)) {
+                mgr.unmanage(location);
+            }
+            final LocationDefinition definition = sensors().get(LocationOwner.LOCATION_DEFINITION);
+            if (definition != null) {
+                getManagementContext().getLocationRegistry().removeDefinedLocation(definition.getId());
+            }
+        }
+        ManagementContext.PropertiesReloadListener listener = sensors().get(Attributes.PROPERTIES_RELOAD_LISTENER);
+        if (listener != null) {
+            getManagementContext().removePropertiesReloadListener(listener);
+        }
+
+        sensors().set(LocationOwner.LOCATION_DEFINITION, null);
+        sensors().set(LocationOwner.DYNAMIC_LOCATION, null);
+        sensors().set(LocationOwner.LOCATION_NAME, null);
+    }
+
+    @Override
+    public List<Entity> getStubHostList() {
+        if (getStubHostCluster() == null) {
+            return ImmutableList.of();
+        } else {
+            return ImmutableList.copyOf(getStubHostCluster().getMembers());
+        }
+    }
+
+    @Override
+    public DynamicCluster getStubHostCluster() {
+        return sensors().get(DOCKER_HOST_CLUSTER);
+    }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/292b4cfb/software/base/src/test/java/org/apache/brooklyn/core/location/dynamic/clocker/StubInfrastructureLocation.java
----------------------------------------------------------------------
diff --git a/software/base/src/test/java/org/apache/brooklyn/core/location/dynamic/clocker/StubInfrastructureLocation.java b/software/base/src/test/java/org/apache/brooklyn/core/location/dynamic/clocker/StubInfrastructureLocation.java
new file mode 100644
index 0000000..05587f1
--- /dev/null
+++ b/software/base/src/test/java/org/apache/brooklyn/core/location/dynamic/clocker/StubInfrastructureLocation.java
@@ -0,0 +1,83 @@
+/*
+ * 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.core.location.dynamic.clocker;
+
+import java.util.Collection;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.brooklyn.api.location.MachineLocation;
+import org.apache.brooklyn.api.location.MachineProvisioningLocation;
+import org.apache.brooklyn.api.location.NoMachinesAvailableException;
+import org.apache.brooklyn.core.location.AbstractLocation;
+import org.apache.brooklyn.core.location.dynamic.DynamicLocation;
+import org.apache.brooklyn.util.core.flags.SetFromFlag;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.common.base.Predicates;
+import com.google.common.collect.HashMultimap;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Multimaps;
+import com.google.common.collect.SetMultimap;
+
+public class StubInfrastructureLocation extends AbstractLocation implements MachineProvisioningLocation<MachineLocation>, DynamicLocation<StubInfrastructure, StubInfrastructureLocation> {
+
+    @SuppressWarnings("unused")
+    private static final Logger LOG = LoggerFactory.getLogger(StubInfrastructureLocation.class);
+
+    @SetFromFlag("owner")
+    private StubInfrastructure infrastructure;
+
+    @SetFromFlag("machines")
+    private final SetMultimap<StubHostLocation, String> containers = Multimaps.synchronizedSetMultimap(HashMultimap.<StubHostLocation, String>create());
+
+    @Override
+    public StubInfrastructure getOwner() {
+        return infrastructure;
+    }
+
+    @Override
+    public MachineLocation obtain(Map<?, ?> flags) throws NoMachinesAvailableException {
+        StubHost host = (StubHost) Iterables.get(infrastructure.getStubHostList(), 0);
+        StubHostLocation hostLocation = host.getDynamicLocation();
+        StubContainerLocation containerLocation = hostLocation.obtain(flags);
+        containers.put(hostLocation, containerLocation.getId());
+        return containerLocation;
+    }
+
+    @Override
+    public void release(MachineLocation machine) {
+        Set<StubHostLocation> set = Multimaps.filterValues(containers, Predicates.equalTo(machine.getId())).keySet();
+        StubHostLocation hostLocation = Iterables.getOnlyElement(set);
+        hostLocation.release((StubContainerLocation)machine);
+        containers.remove(hostLocation, machine);
+    }
+
+    @Override
+    public MachineProvisioningLocation<MachineLocation> newSubLocation(Map<?, ?> newFlags) {
+        return this;
+    }
+
+    @Override
+    public Map<String, Object> getProvisioningFlags(Collection<String> tags) {
+        return ImmutableMap.of();
+    }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/292b4cfb/software/base/src/test/java/org/apache/brooklyn/core/location/dynamic/clocker/StubResolver.java
----------------------------------------------------------------------
diff --git a/software/base/src/test/java/org/apache/brooklyn/core/location/dynamic/clocker/StubResolver.java b/software/base/src/test/java/org/apache/brooklyn/core/location/dynamic/clocker/StubResolver.java
new file mode 100644
index 0000000..99fd2b1
--- /dev/null
+++ b/software/base/src/test/java/org/apache/brooklyn/core/location/dynamic/clocker/StubResolver.java
@@ -0,0 +1,196 @@
+/*
+ * 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.core.location.dynamic.clocker;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import java.util.Collections;
+import java.util.Map;
+import java.util.Set;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import org.apache.brooklyn.api.location.Location;
+import org.apache.brooklyn.api.location.LocationRegistry;
+import org.apache.brooklyn.api.location.LocationResolver.EnableableLocationResolver;
+import org.apache.brooklyn.api.location.LocationSpec;
+import org.apache.brooklyn.api.mgmt.ManagementContext;
+import org.apache.brooklyn.core.location.BasicLocationRegistry;
+import org.apache.brooklyn.core.location.LocationPropertiesFromBrooklynProperties;
+import org.apache.brooklyn.core.location.dynamic.DynamicLocation;
+import org.apache.brooklyn.core.location.internal.LocationInternal;
+import org.apache.brooklyn.util.collections.MutableMap;
+import org.apache.brooklyn.util.text.KeyValueParser;
+import org.apache.brooklyn.util.text.Strings;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.common.base.Joiner;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Sets;
+
+/**
+ * Examples of valid specs:
+ *   <ul>
+ *     <li>docker:infrastructureId
+ *     <li>docker:infrastructureId:(name=docker-infrastructure)
+ *     <li>docker:infrastructureId:dockerHostId
+ *     <li>docker:infrastructureId:dockerHostId:(name=dockerHost-brooklyn-1234,user=docker)
+ *   </ul>
+ */
+public class StubResolver implements EnableableLocationResolver {
+
+    private static final Logger LOG = LoggerFactory.getLogger(StubResolver.class);
+
+    public static final String DOCKER = "docker";
+    public static final Pattern PATTERN = Pattern.compile("("+DOCKER+"|"+DOCKER.toUpperCase()+")" + ":([a-zA-Z0-9]+)" +
+            "(:([a-zA-Z0-9]+))?" + "(:\\((.*)\\))?$");
+    public static final Set<String> ACCEPTABLE_ARGS = ImmutableSet.of("name", "displayName");
+
+    public static final String DOCKER_INFRASTRUCTURE_SPEC = "docker:%s";
+    public static final String DOCKER_HOST_MACHINE_SPEC = "docker:%s:%s";
+
+    private ManagementContext managementContext;
+
+    @Override
+    public void init(ManagementContext managementContext) {
+        this.managementContext = checkNotNull(managementContext, "managementContext");
+    }
+
+    @Override
+    public String getPrefix() {
+        return DOCKER;
+    }
+
+    @Override
+    public Location newLocationFromString(Map locationFlags, String spec, LocationRegistry registry) {
+        return newLocationFromString(spec, registry, registry.getProperties(), locationFlags);
+    }
+
+    protected Location newLocationFromString(String spec, LocationRegistry registry, Map properties, Map locationFlags) {
+        if (LOG.isDebugEnabled()) {
+            LOG.debug("Resolving location '" + spec + "' with flags " + Joiner.on(",").withKeyValueSeparator("=").join(locationFlags));
+        }
+        String namedLocation = (String) locationFlags.get(LocationInternal.NAMED_SPEC_NAME.getName());
+
+        Matcher matcher = PATTERN.matcher(spec);
+        if (!matcher.matches()) {
+            throw new IllegalArgumentException("Invalid location '"+spec+"'; must specify something like docker:entityId or docker:entityId:(name=abc)");
+        }
+
+        String argsPart = matcher.group(6);
+        Map<String, String> argsMap = (argsPart != null) ? KeyValueParser.parseMap(argsPart) : Collections.<String,String>emptyMap();
+        String displayNamePart = argsMap.get("displayName");
+        String namePart = argsMap.get("name");
+
+        if (!ACCEPTABLE_ARGS.containsAll(argsMap.keySet())) {
+            Set<String> illegalArgs = Sets.difference(argsMap.keySet(), ACCEPTABLE_ARGS);
+            throw new IllegalArgumentException("Invalid location '"+spec+"'; illegal args "+illegalArgs+"; acceptable args are "+ACCEPTABLE_ARGS);
+        }
+        if (argsMap.containsKey("displayName") && Strings.isEmpty(displayNamePart)) {
+            throw new IllegalArgumentException("Invalid location '"+spec+"'; if displayName supplied then value must be non-empty");
+        }
+        if (argsMap.containsKey("name") && Strings.isEmpty(namePart)) {
+            throw new IllegalArgumentException("Invalid location '"+spec+"'; if name supplied then value must be non-empty");
+        }
+
+        Map<String, Object> filteredProperties = new LocationPropertiesFromBrooklynProperties().getLocationProperties(DOCKER, namedLocation, properties);
+        MutableMap<String, Object> flags = MutableMap.<String, Object>builder().putAll(filteredProperties).putAll(locationFlags).build();
+
+        String infrastructureId = matcher.group(2);
+        if (Strings.isBlank(infrastructureId)) {
+            throw new IllegalArgumentException("Invalid location '"+spec+"'; infrastructure entity id must be non-empty");
+        }
+        String dockerHostId = matcher.group(4);
+
+        // Build the display name
+        StringBuilder name = new StringBuilder();
+        if (displayNamePart != null) {
+            name.append(displayNamePart);
+        } else {
+            name.append("Docker ");
+            if (dockerHostId == null) {
+                name.append("Infrastructure ").append(infrastructureId);
+            } else {
+                name.append("Host ").append(dockerHostId);
+            }
+        }
+        final String displayName =  name.toString();
+
+        // Build the location name
+        name = new StringBuilder();
+        if (namePart != null) {
+            name.append(namePart);
+        } else {
+            name.append("docker-");
+            name.append(infrastructureId);
+            if (dockerHostId != null) {
+                name.append("-").append(dockerHostId);
+            }
+        }
+        final String locationName =  name.toString();
+        StubInfrastructure infrastructure = (StubInfrastructure) managementContext.getEntityManager().getEntity(infrastructureId);
+        Iterable<Location> managedLocations = managementContext.getLocationManager().getLocations();
+
+        if (dockerHostId == null) {
+            for (Location location : managedLocations) {
+                if (location instanceof StubInfrastructureLocation) {
+                    if (((StubInfrastructureLocation) location).getOwner().getId().equals(infrastructureId)) {
+                        return location;
+                    }
+                }
+            }
+            LocationSpec<StubInfrastructureLocation> locationSpec = LocationSpec.create(StubInfrastructureLocation.class)
+                    .configure(flags)
+                    .configure(DynamicLocation.OWNER, infrastructure)
+                    .configure(LocationInternal.NAMED_SPEC_NAME, locationName)
+                    .displayName(displayName);
+            return managementContext.getLocationManager().createLocation(locationSpec);
+        } else {
+            StubHost dockerHost = (StubHost) managementContext.getEntityManager().getEntity(dockerHostId);
+            for (Location location : managedLocations) {
+                if (location instanceof StubHostLocation) {
+                    if (((StubHostLocation) location).getOwner().getId().equals(dockerHostId)) {
+                        return location;
+                    }
+                }
+            }
+
+            LocationSpec<StubHostLocation> locationSpec = LocationSpec.create(StubHostLocation.class)
+                    .parent(infrastructure.getDynamicLocation())
+                    .configure(flags)
+                    .configure(DynamicLocation.OWNER, dockerHost)
+                    .configure(LocationInternal.NAMED_SPEC_NAME, locationName)
+                    .displayName(displayName);
+            return managementContext.getLocationManager().createLocation(locationSpec);
+        }
+    }
+
+    @Override
+    public boolean accepts(String spec, LocationRegistry registry) {
+        return BasicLocationRegistry.isResolverPrefixForSpec(this, spec, true);
+    }
+
+    @Override
+    public boolean isEnabled() {
+        return true;
+//        return Iterables.tryFind(managementContext.getEntityManager().getEntities(), Predicates.instanceOf(StubInfrastructure.class)).isPresent();
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/292b4cfb/software/base/src/test/java/org/apache/brooklyn/core/location/dynamic/clocker/StubUtils.java
----------------------------------------------------------------------
diff --git a/software/base/src/test/java/org/apache/brooklyn/core/location/dynamic/clocker/StubUtils.java b/software/base/src/test/java/org/apache/brooklyn/core/location/dynamic/clocker/StubUtils.java
new file mode 100644
index 0000000..eaea4fc
--- /dev/null
+++ b/software/base/src/test/java/org/apache/brooklyn/core/location/dynamic/clocker/StubUtils.java
@@ -0,0 +1,95 @@
+/*
+ * 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.core.location.dynamic.clocker;
+
+import javax.annotation.Nullable;
+
+import org.apache.brooklyn.api.entity.Entity;
+import org.apache.brooklyn.api.location.Location;
+import org.apache.brooklyn.api.location.LocationDefinition;
+import org.apache.brooklyn.api.mgmt.ManagementContext;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+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.collect.Iterables;
+
+public class StubUtils {
+
+    private static final Logger LOG = LoggerFactory.getLogger(StubUtils.class);
+
+    /** Do not instantiate. */
+    private StubUtils() { }
+
+    /*
+     * Configuration and constants.
+     */
+
+    public static final Predicate<Entity> sameInfrastructure(Entity entity) {
+        Preconditions.checkNotNull(entity, "entity");
+        return new SameInfrastructurePredicate(entity.getId());
+    }
+
+    public static class SameInfrastructurePredicate implements Predicate<Entity> {
+
+        private final String id;
+
+        public SameInfrastructurePredicate(String id) {
+            this.id = Preconditions.checkNotNull(id, "id");
+        }
+
+        @Override
+        public boolean apply(@Nullable Entity input) {
+            // Check if entity is deployed to a DockerContainerLocation
+            Optional<Location> lookup = Iterables.tryFind(input.getLocations(), Predicates.instanceOf(StubContainerLocation.class));
+            if (lookup.isPresent()) {
+                StubContainerLocation container = (StubContainerLocation) lookup.get();
+                // Only containers that are part of this infrastructure
+                return id.equals(container.getOwner().getDockerHost().getInfrastructure().getId());
+            } else {
+                return false;
+            }
+        }
+    };
+
+    public static final ManagementContext.PropertiesReloadListener reloadLocationListener(ManagementContext context, LocationDefinition definition) {
+        return new ReloadDockerLocation(context, definition);
+    }
+
+    public static class ReloadDockerLocation implements ManagementContext.PropertiesReloadListener {
+
+        private final ManagementContext context;
+        private final LocationDefinition definition;
+
+        public ReloadDockerLocation(ManagementContext context, LocationDefinition definition) {
+            this.context = Preconditions.checkNotNull(context, "context");
+            this.definition = Preconditions.checkNotNull(definition, "definition");
+        }
+
+        @Override
+        public void reloaded() {
+            Location resolved = context.getLocationRegistry().resolve(definition);
+            context.getLocationRegistry().updateDefinedLocation(definition);
+            context.getLocationManager().manage(resolved);
+        }
+    };
+}


Mime
View raw message