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 <[email protected]> Authored: Fri Mar 11 19:37:13 2016 +0000 Committer: Aled Sage <[email protected]> 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); + } + }; +}
