You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@brooklyn.apache.org by he...@apache.org on 2015/08/18 13:05:59 UTC
[01/24] incubator-brooklyn git commit: Test + fix BrooklynAccessUtils
Repository: incubator-brooklyn
Updated Branches:
refs/heads/master 42d9871ec -> f092e183f
Test + fix BrooklynAccessUtils
- Previously there were no unit tests!
- Previously would not look up PortForwardManager unless machine
implemented SupportsPortForwarding (which broke BYON’s tcpPortMappings)
Project: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/commit/ee1a20a0
Tree: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/tree/ee1a20a0
Diff: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/diff/ee1a20a0
Branch: refs/heads/master
Commit: ee1a20a04bacafba6633c3b7ec2e80635490310d
Parents: 6caee58
Author: Aled Sage <al...@gmail.com>
Authored: Mon Aug 17 12:32:38 2015 +0100
Committer: Aled Sage <al...@gmail.com>
Committed: Mon Aug 17 12:32:38 2015 +0100
----------------------------------------------------------------------
.../location/access/BrooklynAccessUtils.java | 52 ++++---
.../access/BrooklynAccessUtilsTest.java | 138 +++++++++++++++++++
2 files changed, 170 insertions(+), 20 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/ee1a20a0/core/src/main/java/org/apache/brooklyn/location/access/BrooklynAccessUtils.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/brooklyn/location/access/BrooklynAccessUtils.java b/core/src/main/java/org/apache/brooklyn/location/access/BrooklynAccessUtils.java
index 9daf1c5..f1f858c 100644
--- a/core/src/main/java/org/apache/brooklyn/location/access/BrooklynAccessUtils.java
+++ b/core/src/main/java/org/apache/brooklyn/location/access/BrooklynAccessUtils.java
@@ -33,6 +33,8 @@ import brooklyn.event.basic.BasicConfigKey;
import org.apache.brooklyn.location.basic.Machines;
import org.apache.brooklyn.location.basic.SshMachineLocation;
import org.apache.brooklyn.location.basic.SupportsPortForwarding;
+import org.python.google.common.base.Predicates;
+import org.python.google.common.collect.Iterables;
import brooklyn.util.exceptions.Exceptions;
import brooklyn.util.guava.Maybe;
@@ -66,27 +68,37 @@ public class BrooklynAccessUtils {
PortForwardManager pfw = entity.getConfig(PORT_FORWARDING_MANAGER);
if (pfw!=null) {
Collection<Location> ll = entity.getLocations();
- Maybe<SupportsPortForwarding> machine = Machines.findUniqueElement(ll, SupportsPortForwarding.class);
- if (machine.isPresent()) {
- synchronized (BrooklynAccessUtils.class) {
- // TODO finer-grained synchronization
-
- HostAndPort hp = pfw.lookup((MachineLocation)machine.get(), port);
- if (hp!=null) return hp;
-
- Location l = (Location) machine.get();
- if (l instanceof SupportsPortForwarding) {
- Cidr source = entity.getConfig(MANAGEMENT_ACCESS_CIDR);
- if (source!=null) {
- log.debug("BrooklynAccessUtils requesting new port-forwarding rule to access "+port+" on "+entity+" (at "+l+", enabled for "+source+")");
- // TODO discuss, is this the best way to do it
- // (will probably _create_ the port forwarding rule!)
- hp = ((SupportsPortForwarding) l).getSocketEndpointFor(source, port);
- if (hp!=null) return hp;
- } else {
- log.warn("No "+MANAGEMENT_ACCESS_CIDR.getName()+" configured for "+entity+", so cannot forward port "+port+" " +
- "even though "+PORT_FORWARDING_MANAGER.getName()+" was supplied");
+
+ synchronized (BrooklynAccessUtils.class) {
+ // TODO finer-grained synchronization
+
+ for (MachineLocation machine : Iterables.filter(ll, MachineLocation.class)) {
+ HostAndPort hp = pfw.lookup(machine, port);
+ if (hp!=null) {
+ log.debug("BrooklynAccessUtils found port-forwarded address {} for entity {}, port {}, using machine {}",
+ new Object[] {hp, entity, port, machine});
+ return hp;
+ }
+ }
+
+ Maybe<SupportsPortForwarding> supportPortForwardingLoc = Machines.findUniqueElement(ll, SupportsPortForwarding.class);
+ if (supportPortForwardingLoc.isPresent()) {
+ Cidr source = entity.getConfig(MANAGEMENT_ACCESS_CIDR);
+ SupportsPortForwarding loc = supportPortForwardingLoc.get();
+ if (source!=null) {
+ log.debug("BrooklynAccessUtils requesting new port-forwarding rule to access "+port+" on "+entity+" (at "+loc+", enabled for "+source+")");
+ // TODO discuss, is this the best way to do it
+ // (will probably _create_ the port forwarding rule!)
+ HostAndPort hp = loc.getSocketEndpointFor(source, port);
+ if (hp!=null) {
+ log.debug("BrooklynAccessUtils created port-forwarded address {} for entity {}, port {}, using {}",
+ new Object[] {hp, entity, port, loc});
+ return hp;
}
+ } else {
+ log.warn("No "+MANAGEMENT_ACCESS_CIDR.getName()+" configured for "+entity+", so cannot forward "
+ +"port "+port+" "+"even though "+PORT_FORWARDING_MANAGER.getName()+" was supplied, and "
+ +"have location supporting port forwarding "+loc);
}
}
}
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/ee1a20a0/core/src/test/java/org/apache/brooklyn/location/access/BrooklynAccessUtilsTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/org/apache/brooklyn/location/access/BrooklynAccessUtilsTest.java b/core/src/test/java/org/apache/brooklyn/location/access/BrooklynAccessUtilsTest.java
new file mode 100644
index 0000000..e5a8574
--- /dev/null
+++ b/core/src/test/java/org/apache/brooklyn/location/access/BrooklynAccessUtilsTest.java
@@ -0,0 +1,138 @@
+/*
+ * 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.location.access;
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertNotNull;
+import static org.testng.Assert.fail;
+
+import java.util.Map;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import org.apache.brooklyn.api.entity.proxying.EntitySpec;
+import org.apache.brooklyn.api.location.LocationSpec;
+import org.apache.brooklyn.location.basic.SshMachineLocation;
+import org.apache.brooklyn.location.basic.SupportsPortForwarding;
+import org.apache.brooklyn.test.entity.TestEntity;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Maps;
+import com.google.common.net.HostAndPort;
+
+import brooklyn.entity.BrooklynAppUnitTestSupport;
+import brooklyn.entity.basic.Attributes;
+import brooklyn.util.net.Cidr;
+
+public class BrooklynAccessUtilsTest extends BrooklynAppUnitTestSupport {
+
+ protected PortForwardManager pfm;
+ private TestEntity entity;
+
+ @BeforeMethod(alwaysRun=true)
+ @Override
+ public void setUp() throws Exception {
+ super.setUp();
+
+ pfm = (PortForwardManager) mgmt.getLocationRegistry().resolve("portForwardManager(scope=global)");
+ }
+
+ @Test
+ public void testBrooklynAccessibleAddressFindsPreexistingMapping() throws Exception {
+ final int privatePort = 8080;
+ final String publicNatIp = "1.2.3.4";
+ final int publicNatPort = 12000;
+
+ SshMachineLocation machine = mgmt.getLocationManager().createLocation(LocationSpec.create(SshMachineLocation.class)
+ .configure(SshMachineLocation.TCP_PORT_MAPPINGS, ImmutableMap.of(privatePort, publicNatIp+":"+publicNatPort)));
+ entity = app.createAndManageChild(EntitySpec.create(TestEntity.class)
+ .configure(BrooklynAccessUtils.PORT_FORWARDING_MANAGER, pfm)
+ .location(machine));
+
+ assertEquals(BrooklynAccessUtils.getBrooklynAccessibleAddress(entity, privatePort), HostAndPort.fromParts(publicNatIp, publicNatPort));
+ }
+
+ @Test
+ public void testBrooklynAccessibleAddressUsesPrivateHostPortIfNoMapping() throws Exception {
+ final String privateIp = "127.1.2.3";
+ final int privatePort = 8080;
+
+ SshMachineLocation machine = mgmt.getLocationManager().createLocation(LocationSpec.create(SshMachineLocation.class));
+ entity = app.createAndManageChild(EntitySpec.create(TestEntity.class)
+ .configure(BrooklynAccessUtils.PORT_FORWARDING_MANAGER, pfm)
+ .location(machine));
+ entity.setAttribute(Attributes.HOSTNAME, privateIp);
+
+ assertEquals(BrooklynAccessUtils.getBrooklynAccessibleAddress(entity, privatePort), HostAndPort.fromParts(privateIp, privatePort));
+ }
+
+ @Test
+ public void testBrooklynAccessibleAddressFailsIfNoMappingAndNoHostname() throws Exception {
+ final int privatePort = 8080;
+
+ SshMachineLocation machine = mgmt.getLocationManager().createLocation(LocationSpec.create(SshMachineLocation.class));
+ entity = app.createAndManageChild(EntitySpec.create(TestEntity.class)
+ .configure(BrooklynAccessUtils.PORT_FORWARDING_MANAGER, pfm)
+ .location(machine));
+
+ try {
+ BrooklynAccessUtils.getBrooklynAccessibleAddress(entity, privatePort);
+ fail();
+ } catch (IllegalStateException e) {
+ if (!e.toString().contains("no host.name")) throw e;
+ // success
+ }
+ }
+
+ @Test
+ public void testBrooklynAccessibleAddressRequestsNewPortForwarding() throws Exception {
+ final int privatePort = 8080;
+
+ RecordingSupportsPortForwarding machine = mgmt.getLocationManager().createLocation(LocationSpec.create(RecordingSupportsPortForwarding.class));
+ entity = app.createAndManageChild(EntitySpec.create(TestEntity.class)
+ .configure(BrooklynAccessUtils.PORT_FORWARDING_MANAGER, pfm)
+ .configure(BrooklynAccessUtils.MANAGEMENT_ACCESS_CIDR, Cidr.UNIVERSAL)
+ .location(machine));
+
+ HostAndPort result = BrooklynAccessUtils.getBrooklynAccessibleAddress(entity, privatePort);
+ HostAndPort portForwarded = machine.mappings.get(privatePort);
+ assertNotNull(portForwarded);
+ assertEquals(result, portForwarded);
+ }
+
+ @SuppressWarnings("serial") // TODO location should not be serializable; will be changed in future release
+ public static class RecordingSupportsPortForwarding extends SshMachineLocation implements SupportsPortForwarding {
+ protected final String publicIp = "1.2.3.4";
+ protected final Map<Integer, HostAndPort> mappings = Maps.newConcurrentMap();
+ private final AtomicInteger nextPort = new AtomicInteger(12000);
+
+
+ @Override
+ public HostAndPort getSocketEndpointFor(Cidr accessor, int privatePort) {
+ HostAndPort result = mappings.get(privatePort);
+ if (result == null) {
+ int publicPort = nextPort.getAndIncrement();
+ result = HostAndPort.fromParts(publicIp, publicPort);
+ mappings.put(privatePort, result);
+ }
+ return result;
+ }
+ }
+}
[11/24] incubator-brooklyn git commit: [BROOKLYN-162] Renaming
package policy
Posted by he...@apache.org.
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/d30ff597/policy/src/test/java/brooklyn/policy/followthesun/FollowTheSunPolicySoakTest.java
----------------------------------------------------------------------
diff --git a/policy/src/test/java/brooklyn/policy/followthesun/FollowTheSunPolicySoakTest.java b/policy/src/test/java/brooklyn/policy/followthesun/FollowTheSunPolicySoakTest.java
deleted file mode 100644
index a67e24d..0000000
--- a/policy/src/test/java/brooklyn/policy/followthesun/FollowTheSunPolicySoakTest.java
+++ /dev/null
@@ -1,274 +0,0 @@
-/*
- * 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 brooklyn.policy.followthesun;
-
-import static org.testng.Assert.assertEquals;
-
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-
-import org.apache.brooklyn.api.entity.Entity;
-import org.apache.brooklyn.api.entity.basic.EntityLocal;
-import org.apache.brooklyn.api.location.Location;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.testng.annotations.Test;
-
-import brooklyn.entity.basic.Entities;
-
-import org.apache.brooklyn.location.basic.SimulatedLocation;
-
-import brooklyn.policy.loadbalancing.BalanceableContainer;
-import brooklyn.policy.loadbalancing.MockContainerEntity;
-import brooklyn.policy.loadbalancing.MockItemEntity;
-import brooklyn.policy.loadbalancing.MockItemEntityImpl;
-import brooklyn.policy.loadbalancing.Movable;
-import brooklyn.test.Asserts;
-import brooklyn.util.collections.MutableMap;
-
-import com.google.common.base.Function;
-import com.google.common.base.Predicates;
-import com.google.common.collect.HashMultimap;
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.Iterables;
-import com.google.common.collect.Maps;
-import com.google.common.collect.Multimap;
-import com.google.common.collect.Sets;
-
-public class FollowTheSunPolicySoakTest extends AbstractFollowTheSunPolicyTest {
-
- private static final Logger LOG = LoggerFactory.getLogger(FollowTheSunPolicySoakTest.class);
-
- private static final long TIMEOUT_MS = 10*1000;
-
- @Test
- public void testFollowTheSunQuickTest() {
- RunConfig config = new RunConfig();
- config.numCycles = 1;
- config.numLocations=3;
- config.numContainersPerLocation = 5;
- config.numLockedItemsPerLocation = 2;
- config.numMovableItems = 10;
-
- runFollowTheSunSoakTest(config);
- }
-
- @Test
- public void testLoadBalancingManyItemsQuickTest() {
- RunConfig config = new RunConfig();
- config.numCycles = 1;
- config.numLocations=2;
- config.numContainersPerLocation = 3;
- config.numLockedItemsPerLocation = 2;
- config.numMovableItems = 10;
- config.numContainerStopsPerCycle = 1;
- config.numItemStopsPerCycle = 1;
-
- runFollowTheSunSoakTest(config);
- }
-
- @Test(groups={"Integration"}) // takes ~2s
- public void testLoadBalancingManyItemsNotTooLongTest() {
- RunConfig config = new RunConfig();
- config.numCycles = 1;
- config.numLocations=3;
- config.numContainersPerLocation = 5;
- config.numLockedItemsPerLocation = 2;
- config.numMovableItems = 500;
- config.numContainerStopsPerCycle = 1;
- config.numItemStopsPerCycle = 1;
-
- runFollowTheSunSoakTest(config);
- }
-
- @Test(groups={"Integration","Acceptance"}) // integration group, because it's slow to run many cycles
- public void testLoadBalancingSoakTest() {
- RunConfig config = new RunConfig();
- config.numCycles = 100;
- config.numLocations=3;
- config.numContainersPerLocation = 5;
- config.numLockedItemsPerLocation = 2;
- config.numMovableItems = 10;
-
- runFollowTheSunSoakTest(config);
- }
-
- @Test(groups={"Integration","Acceptance"}) // integration group, because it's slow to run many cycles
- public void testLoadBalancingManyItemsSoakTest() {
- RunConfig config = new RunConfig();
- config.numCycles = 100;
- config.numLocations=3;
- config.numContainersPerLocation = 5;
- config.numLockedItemsPerLocation = 2;
- config.numMovableItems = 100;
- config.numContainerStopsPerCycle = 3;
- config.numItemStopsPerCycle = 10;
-
- runFollowTheSunSoakTest(config);
- }
-
- @Test(groups={"Integration","Acceptance"}) // integration group, because it's slow to run many cycles
- public void testLoadBalancingManyManyItemsTest() {
- RunConfig config = new RunConfig();
- config.numCycles = 1;
- config.numLocations=10;
- config.numContainersPerLocation = 5;
- config.numLockedItemsPerLocation = 100;
- config.numMovableItems = 1000;
- config.numContainerStopsPerCycle = 0;
- config.numItemStopsPerCycle = 0;
- config.timeout_ms = 30*1000;
- config.verbose = false;
-
- runFollowTheSunSoakTest(config);
- }
-
- private void runFollowTheSunSoakTest(RunConfig config) {
- int numCycles = config.numCycles;
- int numLocations = config.numLocations;
- int numContainersPerLocation = config.numContainersPerLocation;
- int numLockedItemsPerLocation = config.numLockedItemsPerLocation;
- int numMovableItems = config.numMovableItems;
-
- int numContainerStopsPerCycle = config.numContainerStopsPerCycle;
- int numItemStopsPerCycle = config.numItemStopsPerCycle;
- long timeout_ms = config.timeout_ms;
- final boolean verbose = config.verbose;
-
- MockItemEntityImpl.totalMoveCount.set(0);
-
- List<Location> locations = new ArrayList<Location>();
- Multimap<Location,MockContainerEntity> containers = HashMultimap.<Location,MockContainerEntity>create();
- Multimap<Location,MockItemEntity> lockedItems = HashMultimap.<Location,MockItemEntity>create();
- final List<MockItemEntity> movableItems = new ArrayList<MockItemEntity>();
-
- for (int i = 1; i <= numLocations; i++) {
- String locName = "loc"+i;
- Location loc = new SimulatedLocation(MutableMap.of("name",locName));
- locations.add(loc);
-
- for (int j = 1; j <= numContainersPerLocation; j++) {
- MockContainerEntity container = newContainer(app, loc, "container-"+locName+"-"+j);
- containers.put(loc, container);
- }
- for (int j = 1; j <= numLockedItemsPerLocation; j++) {
- MockContainerEntity container = Iterables.get(containers.get(loc), j%numContainersPerLocation);
- MockItemEntity item = newLockedItem(app, container, "item-locked-"+locName+"-"+j);
- lockedItems.put(loc, item);
- }
- }
-
- for (int i = 1; i <= numMovableItems; i++) {
- MockContainerEntity container = Iterables.get(containers.values(), i%containers.size());
- MockItemEntity item = newItem(app, container, "item-movable"+i);
- movableItems.add(item);
- }
-
- for (int i = 1; i <= numCycles; i++) {
- LOG.info("{}: cycle {}", FollowTheSunPolicySoakTest.class.getSimpleName(), i);
-
- // Stop movable items, and start others
- for (int j = 1; j <= numItemStopsPerCycle; j++) {
- int itemIndex = random.nextInt(numMovableItems);
- MockItemEntity itemToStop = movableItems.get(itemIndex);
- itemToStop.stop();
- LOG.debug("Unmanaging item {}", itemToStop);
- Entities.unmanage(itemToStop);
- movableItems.set(itemIndex, newItem(app, Iterables.get(containers.values(), 0), "item-movable"+itemIndex));
- }
-
- // Choose a location to be busiest
- int locIndex = random.nextInt(numLocations);
- final Location busiestLocation = locations.get(locIndex);
-
- // Repartition the load across the items
- for (int j = 0; j < numMovableItems; j++) {
- MockItemEntity item = movableItems.get(j);
- Map<Entity, Double> workrates = Maps.newLinkedHashMap();
-
- for (Map.Entry<Location,MockItemEntity> entry : lockedItems.entries()) {
- Location location = entry.getKey();
- MockItemEntity source = entry.getValue();
- double baseWorkrate = (location == busiestLocation ? 1000 : 100);
- double jitter = 10;
- double jitteredWorkrate = Math.max(0, baseWorkrate + (random.nextDouble()*jitter*2 - jitter));
- workrates.put(source, jitteredWorkrate);
- }
- ((EntityLocal)item).setAttribute(MockItemEntity.ITEM_USAGE_METRIC, workrates);
- }
-
- // Stop containers, and start others
- // This offloads the "immovable" items to other containers in the same location!
- for (int j = 1; j <= numContainerStopsPerCycle; j++) {
- int containerIndex = random.nextInt(containers.size());
- MockContainerEntity containerToStop = Iterables.get(containers.values(), containerIndex);
- Location location = Iterables.get(containerToStop.getLocations(), 0);
- MockContainerEntity otherContainerInLocation = Iterables.find(containers.get(location), Predicates.not(Predicates.equalTo(containerToStop)), null);
- containerToStop.offloadAndStop(otherContainerInLocation);
- LOG.debug("Unmanaging container {}", containerToStop);
- Entities.unmanage(containerToStop);
- containers.remove(location, containerToStop);
-
- MockContainerEntity containerToAdd = newContainer(app, location, "container-"+location.getDisplayName()+"-new."+i+"."+j);
- containers.put(location, containerToAdd);
- }
-
- // Assert that the items all end up in the location with maximum load-generation
- Asserts.succeedsEventually(MutableMap.of("timeout", timeout_ms), new Runnable() {
- public void run() {
- Iterable<Location> itemLocs = Iterables.transform(movableItems, new Function<MockItemEntity, Location>() {
- public Location apply(MockItemEntity input) {
- BalanceableContainer<?> container = input.getAttribute(Movable.CONTAINER);
- Collection<Location> locs = (container != null) ? container.getLocations(): null;
- return (locs != null && locs.size() > 0) ? Iterables.get(locs, 0) : null;
- }});
-
- Iterable<String> itemLocNames = Iterables.transform(itemLocs, new Function<Location, String>() {
- public String apply(Location input) {
- return (input != null) ? input.getDisplayName() : null;
- }});
- String errMsg;
- if (verbose) {
- errMsg = verboseDumpToString()+"; itemLocs="+itemLocNames;
- } else {
- Set<String> locNamesInUse = Sets.newLinkedHashSet(itemLocNames);
- errMsg = "locsInUse="+locNamesInUse+"; totalMoves="+MockItemEntityImpl.totalMoveCount;
- }
-
- assertEquals(ImmutableList.copyOf(itemLocs), Collections.nCopies(movableItems.size(), busiestLocation), errMsg);
- }});
- }
- }
-
- static class RunConfig {
- int numCycles = 1;
- int numLocations = 3;
- int numContainersPerLocation = 5;
- int numLockedItemsPerLocation = 5;
- int numMovableItems = 5;
- int numContainerStopsPerCycle = 0;
- int numItemStopsPerCycle = 0;
- long timeout_ms = TIMEOUT_MS;
- boolean verbose = true;
- }
-}
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/d30ff597/policy/src/test/java/brooklyn/policy/followthesun/FollowTheSunPolicyTest.java
----------------------------------------------------------------------
diff --git a/policy/src/test/java/brooklyn/policy/followthesun/FollowTheSunPolicyTest.java b/policy/src/test/java/brooklyn/policy/followthesun/FollowTheSunPolicyTest.java
deleted file mode 100644
index 1f5e5db..0000000
--- a/policy/src/test/java/brooklyn/policy/followthesun/FollowTheSunPolicyTest.java
+++ /dev/null
@@ -1,307 +0,0 @@
-/*
- * 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 brooklyn.policy.followthesun;
-
-import static org.testng.Assert.assertEquals;
-import static org.testng.Assert.assertTrue;
-import static org.testng.Assert.fail;
-
-import java.util.Collections;
-import java.util.List;
-import java.util.concurrent.Semaphore;
-import java.util.concurrent.TimeUnit;
-
-import org.apache.brooklyn.api.entity.Entity;
-import org.apache.brooklyn.api.entity.basic.EntityLocal;
-import org.apache.brooklyn.api.event.SensorEvent;
-import org.apache.brooklyn.api.event.SensorEventListener;
-import org.apache.brooklyn.api.location.Location;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.testng.annotations.Test;
-
-import brooklyn.entity.basic.Entities;
-
-import org.apache.brooklyn.location.basic.SimulatedLocation;
-
-import brooklyn.policy.loadbalancing.MockContainerEntity;
-import brooklyn.policy.loadbalancing.MockItemEntity;
-import brooklyn.policy.loadbalancing.Movable;
-import brooklyn.test.Asserts;
-import brooklyn.util.collections.MutableMap;
-
-import com.google.common.base.Function;
-import com.google.common.base.Stopwatch;
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.ImmutableMap;
-import com.google.common.collect.ImmutableSet;
-import com.google.common.collect.Lists;
-
-public class FollowTheSunPolicyTest extends AbstractFollowTheSunPolicyTest {
-
- private static final Logger LOG = LoggerFactory.getLogger(FollowTheSunPolicyTest.class);
-
- @Test
- public void testPolicyUpdatesModel() {
- final MockContainerEntity containerA = newContainer(app, loc1, "A");
- final MockItemEntity item1 = newItem(app, containerA, "1");
- final MockItemEntity item2 = newItem(app, containerA, "2");
- ((EntityLocal)item2).setAttribute(MockItemEntity.ITEM_USAGE_METRIC, ImmutableMap.<Entity,Double>of(item1, 11d));
-
- Asserts.succeedsEventually(MutableMap.of("timeout", TIMEOUT_MS), new Runnable() {
- @Override public void run() {
- assertEquals(ImmutableSet.of(item1, item2), model.getItems());
- assertEquals(model.getItemContainer(item1), containerA);
- assertEquals(model.getItemLocation(item1), loc1);
- assertEquals(model.getContainerLocation(containerA), loc1);
- assertEquals(model.getDirectSendsToItemByLocation(), ImmutableMap.of(item2, ImmutableMap.of(loc1, 11d)));
- }});
- }
-
- @Test
- public void testPolicyAcceptsLocationFinder() {
- pool.removePolicy(policy);
-
- Function<Entity, Location> customLocationFinder = new Function<Entity, Location>() {
- @Override public Location apply(Entity input) {
- return new SimulatedLocation(MutableMap.of("name", "custom location for "+input));
- }};
-
- FollowTheSunPolicy customPolicy = new FollowTheSunPolicy(
- MutableMap.of("minPeriodBetweenExecs", 0, "locationFinder", customLocationFinder),
- MockItemEntity.ITEM_USAGE_METRIC,
- model,
- FollowTheSunParameters.newDefault());
-
- pool.addPolicy(customPolicy);
-
- final MockContainerEntity containerA = newContainer(app, loc1, "A");
-
- Asserts.succeedsEventually(MutableMap.of("timeout", TIMEOUT_MS), new Runnable() {
- @Override public void run() {
- assertEquals(model.getContainerLocation(containerA).getDisplayName(), "custom location for "+containerA);
- }});
- }
-
- @Test
- public void testNoopBalancing() throws Exception {
- // Set-up containers and items.
- MockContainerEntity containerA = newContainer(app, loc1, "A");
- MockContainerEntity containerB = newContainer(app, loc2, "B");
- MockItemEntity item1 = newItem(app, containerA, "1", Collections.<Entity, Double>emptyMap());
- MockItemEntity item2 = newItem(app, containerB, "2", Collections.<Entity, Double>emptyMap());
-
- Thread.sleep(SHORT_WAIT_MS);
- assertItemDistributionEventually(ImmutableMap.of(containerA, ImmutableList.of(item1), containerB, ImmutableList.of(item2)));
- }
-
- @Test
- public void testMovesItemToFollowDemand() throws Exception {
- // Set-up containers and items.
- MockContainerEntity containerA = newContainer(app, loc1, "A");
- MockContainerEntity containerB = newContainer(app, loc2, "B");
- MockItemEntity item1 = newItem(app, containerA, "1");
- MockItemEntity item2 = newItem(app, containerB, "2");
-
- ((EntityLocal)item1).setAttribute(MockItemEntity.ITEM_USAGE_METRIC, ImmutableMap.<Entity,Double>of(item2, 100d));
-
- assertItemDistributionEventually(ImmutableMap.of(containerA, ImmutableList.<MockItemEntity>of(), containerB, ImmutableList.of(item1, item2)));
- }
-
- @Test
- public void testNoopIfDemandIsTiny() throws Exception {
- // Set-up containers and items.
- MockContainerEntity containerA = newContainer(app, loc1, "A");
- MockContainerEntity containerB = newContainer(app, loc2, "B");
- MockItemEntity item1 = newItem(app, containerA, "1");
- MockItemEntity item2 = newItem(app, containerB, "2");
-
- ((EntityLocal)item1).setAttribute(MockItemEntity.ITEM_USAGE_METRIC, ImmutableMap.<Entity,Double>of(item2, 0.1d));
-
- Thread.sleep(SHORT_WAIT_MS);
- assertItemDistributionEventually(ImmutableMap.of(containerA, ImmutableList.of(item1), containerB, ImmutableList.of(item2)));
- }
-
- @Test
- public void testNoopIfDemandIsSimilarToCurrentLocation() throws Exception {
- // Set-up containers and items.
- MockContainerEntity containerA = newContainer(app, loc1, "A");
- MockContainerEntity containerB = newContainer(app, loc2, "B");
- MockItemEntity item1 = newItem(app, containerA, "1");
- MockItemEntity item2 = newItem(app, containerA, "2");
- MockItemEntity item3 = newItem(app, containerB, "3");
-
- ((EntityLocal)item1).setAttribute(MockItemEntity.ITEM_USAGE_METRIC, ImmutableMap.<Entity,Double>of(item2, 100d, item3, 100.1d));
-
- Thread.sleep(SHORT_WAIT_MS);
- assertItemDistributionEventually(ImmutableMap.of(containerA, ImmutableList.of(item1, item2), containerB, ImmutableList.of(item3)));
- }
-
- @Test
- public void testMoveDecisionIgnoresDemandFromItself() throws Exception {
- // Set-up containers and items.
- MockContainerEntity containerA = newContainer(app, loc1, "A");
- MockContainerEntity containerB = newContainer(app, loc2, "B");
- MockItemEntity item1 = newItem(app, containerA, "1");
- MockItemEntity item2 = newItem(app, containerB, "2");
-
- ((EntityLocal)item1).setAttribute(MockItemEntity.ITEM_USAGE_METRIC, ImmutableMap.<Entity,Double>of(item1, 100d));
- ((EntityLocal)item2).setAttribute(MockItemEntity.ITEM_USAGE_METRIC, ImmutableMap.<Entity,Double>of(item2, 100d));
-
- Thread.sleep(SHORT_WAIT_MS);
- assertItemDistributionEventually(ImmutableMap.of(containerA, ImmutableList.of(item1), containerB, ImmutableList.of(item2)));
- }
-
- @Test
- public void testItemRemovedCausesRecalculationOfOptimalLocation() {
- // Set-up containers and items.
- MockContainerEntity containerA = newContainer(app, loc1, "A");
- MockContainerEntity containerB = newContainer(app, loc2, "B");
- MockItemEntity item1 = newItem(app, containerA, "1");
- MockItemEntity item2 = newItem(app, containerA, "2");
- MockItemEntity item3 = newItem(app, containerB, "3");
-
- ((EntityLocal)item1).setAttribute(MockItemEntity.ITEM_USAGE_METRIC, ImmutableMap.<Entity,Double>of(item2, 100d, item3, 1000d));
-
- assertItemDistributionEventually(ImmutableMap.of(containerA, ImmutableList.of(item2), containerB, ImmutableList.of(item1, item3)));
-
- item3.stop();
- Entities.unmanage(item3);
-
- assertItemDistributionEventually(ImmutableMap.of(containerA, ImmutableList.of(item1, item2), containerB, ImmutableList.<MockItemEntity>of()));
- }
-
- @Test
- public void testItemMovedCausesRecalculationOfOptimalLocationForOtherItems() {
- // Set-up containers and items.
- MockContainerEntity containerA = newContainer(app, loc1, "A");
- MockContainerEntity containerB = newContainer(app, loc2, "B");
- MockItemEntity item1 = newItem(app, containerA, "1");
- MockItemEntity item2 = newItem(app, containerA, "2");
- MockItemEntity item3 = newItem(app, containerB, "3");
-
- ((EntityLocal)item1).setAttribute(MockItemEntity.ITEM_USAGE_METRIC, ImmutableMap.<Entity,Double>of(item2, 100d, item3, 1000d));
-
- assertItemDistributionEventually(ImmutableMap.of(containerA, ImmutableList.of(item2), containerB, ImmutableList.of(item1, item3)));
-
- item3.move(containerA);
-
- assertItemDistributionEventually(ImmutableMap.of(containerA, ImmutableList.of(item1, item2, item3), containerB, ImmutableList.<MockItemEntity>of()));
- }
-
- @Test
- public void testImmovableItemIsNotMoved() {
- MockContainerEntity containerA = newContainer(app, loc1, "A");
- MockContainerEntity containerB = newContainer(app, loc2, "B");
- MockItemEntity item1 = newLockedItem(app, containerA, "1");
- MockItemEntity item2 = newItem(app, containerB, "2");
-
- ((EntityLocal)item1).setAttribute(MockItemEntity.ITEM_USAGE_METRIC, ImmutableMap.<Entity,Double>of(item2, 100d));
-
- assertItemDistributionEventually(ImmutableMap.of(containerA, ImmutableList.of(item1), containerB, ImmutableList.of(item2)));
- }
-
- @Test
- public void testImmovableItemContributesTowardsLoad() {
- MockContainerEntity containerA = newContainer(app, loc1, "A");
- MockContainerEntity containerB = newContainer(app, loc2, "B");
- MockItemEntity item1 = newLockedItem(app, containerA, "1");
- MockItemEntity item2 = newItem(app, containerA, "2");
-
- ((EntityLocal)item1).setAttribute(MockItemEntity.ITEM_USAGE_METRIC, ImmutableMap.<Entity,Double>of(item1, 100d));
-
- assertItemDistributionEventually(ImmutableMap.of(containerA, ImmutableList.of(item1, item2), containerB, ImmutableList.<MockItemEntity>of()));
- }
-
- // Marked as "Acceptance" due to time-sensitive nature :-(
- @Test(groups={"Integration", "Acceptance"}, invocationCount=20)
- public void testRepeatedRespectsMinPeriodBetweenExecs() throws Exception {
- testRespectsMinPeriodBetweenExecs();
- }
-
- @Test(groups="Integration")
- public void testRespectsMinPeriodBetweenExecs() throws Exception {
- // Failed in jenkins several times, e.g. with event times [2647, 2655] and [1387, 2001].
- // Aled's guess is that there was a delay notifying the listener the first time
- // (which happens async), causing the listener to be notified in rapid
- // succession. The policy execs probably did happen with a 1000ms separation.
- //
- // Therefore try up to three times to see if we get the desired separation. If the
- // minPeriodBetweenExecs wasn't being respected, we'd expect the default 100ms; this
- // test would therefore hardly ever pass.
- final int MAX_ATTEMPTS = 3;
-
- final long minPeriodBetweenExecs = 1000;
- final long timePrecision = 250;
-
- pool.removePolicy(policy);
-
- MockContainerEntity containerA = newContainer(app, loc1, "A");
- MockContainerEntity containerB = newContainer(app, loc2, "B");
- MockItemEntity item1 = newItem(app, containerA, "1");
- MockItemEntity item2 = newItem(app, containerB, "2");
- MockItemEntity item3 = newItem(app, containerA, "3");
-
- FollowTheSunPolicy customPolicy = new FollowTheSunPolicy(
- MutableMap.of("minPeriodBetweenExecs", minPeriodBetweenExecs),
- MockItemEntity.ITEM_USAGE_METRIC,
- model,
- FollowTheSunParameters.newDefault());
-
- pool.addPolicy(customPolicy);
-
- // Record times that things are moved, by lisening to the container sensor being set
- final Stopwatch stopwatch = Stopwatch.createStarted();
-
- final List<Long> eventTimes = Lists.newCopyOnWriteArrayList();
- final Semaphore semaphore = new Semaphore(0);
-
- app.subscribe(item1, Movable.CONTAINER, new SensorEventListener<Entity>() {
- @Override public void onEvent(SensorEvent<Entity> event) {
- long eventTime = stopwatch.elapsed(TimeUnit.MILLISECONDS);
- LOG.info("Received {} at {}", event, eventTime);
- eventTimes.add(eventTime);
- semaphore.release();
- }});
-
- String errmsg = "";
- for (int i = 0; i < MAX_ATTEMPTS; i++) {
- // Set the workrate, causing the policy to move item1 to item2's location, and wait for it to happen
- ((EntityLocal)item1).setAttribute(MockItemEntity.ITEM_USAGE_METRIC, ImmutableMap.<Entity,Double>of(item2, 100d));
- assertTrue(semaphore.tryAcquire(TIMEOUT_MS, TimeUnit.MILLISECONDS));
- assertEquals(item1.getAttribute(Movable.CONTAINER), containerB);
-
- // now cause item1 to be moved to item3's location, and wait for it to happen
- ((EntityLocal)item1).setAttribute(MockItemEntity.ITEM_USAGE_METRIC, ImmutableMap.<Entity,Double>of(item3, 100d));
- assertTrue(semaphore.tryAcquire(TIMEOUT_MS, TimeUnit.MILLISECONDS));
- assertEquals(item1.getAttribute(Movable.CONTAINER), containerA);
-
- LOG.info("testRepeatedRespectsMinPeriodBetweenExecs event times: "+eventTimes);
- assertEquals(eventTimes.size(), 2);
- if (eventTimes.get(1) - eventTimes.get(0) > (minPeriodBetweenExecs-timePrecision)) {
- return; // success
- } else {
- errmsg += eventTimes;
- eventTimes.clear();
- }
- }
-
- fail("Event times never had sufficient gap: "+errmsg);
- }
-}
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/d30ff597/policy/src/test/java/brooklyn/policy/ha/ConnectionFailureDetectorTest.java
----------------------------------------------------------------------
diff --git a/policy/src/test/java/brooklyn/policy/ha/ConnectionFailureDetectorTest.java b/policy/src/test/java/brooklyn/policy/ha/ConnectionFailureDetectorTest.java
deleted file mode 100644
index bb2a2ec..0000000
--- a/policy/src/test/java/brooklyn/policy/ha/ConnectionFailureDetectorTest.java
+++ /dev/null
@@ -1,303 +0,0 @@
-/*
- * 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 brooklyn.policy.ha;
-
-import static org.testng.Assert.assertEquals;
-import static org.testng.Assert.assertNotNull;
-import static org.testng.Assert.assertTrue;
-import static org.testng.Assert.fail;
-
-import java.io.IOException;
-import java.net.ServerSocket;
-import java.util.List;
-import java.util.concurrent.CopyOnWriteArrayList;
-
-import org.apache.brooklyn.api.event.Sensor;
-import org.apache.brooklyn.api.event.SensorEvent;
-import org.apache.brooklyn.api.event.SensorEventListener;
-import org.apache.brooklyn.api.management.ManagementContext;
-import org.apache.brooklyn.api.policy.PolicySpec;
-import org.apache.brooklyn.test.entity.LocalManagementContextForTests;
-import org.apache.brooklyn.test.entity.TestApplication;
-import org.testng.annotations.AfterMethod;
-import org.testng.annotations.BeforeMethod;
-import org.testng.annotations.Test;
-
-import brooklyn.entity.basic.ApplicationBuilder;
-import brooklyn.entity.basic.Entities;
-import brooklyn.policy.ha.HASensors.FailureDescriptor;
-import brooklyn.test.Asserts;
-import brooklyn.util.collections.MutableMap;
-import brooklyn.util.time.Duration;
-
-import com.google.common.base.Predicate;
-import com.google.common.base.Predicates;
-import com.google.common.collect.ImmutableMap;
-import com.google.common.net.HostAndPort;
-
-public class ConnectionFailureDetectorTest {
-
- private static final int TIMEOUT_MS = 30*1000;
- private static final int OVERHEAD = 250;
- private static final int POLL_PERIOD = 100;
-
- private ManagementContext managementContext;
- private TestApplication app;
-
- private List<SensorEvent<FailureDescriptor>> events;
-
- private ServerSocket serverSocket;
- private HostAndPort serverSocketAddress;
-
- @BeforeMethod(alwaysRun=true)
- public void setUp() throws Exception {
- events = new CopyOnWriteArrayList<SensorEvent<FailureDescriptor>>();
-
- managementContext = new LocalManagementContextForTests();
- app = ApplicationBuilder.newManagedApp(TestApplication.class, managementContext);
-
- app.getManagementContext().getSubscriptionManager().subscribe(
- app,
- HASensors.CONNECTION_FAILED,
- new SensorEventListener<FailureDescriptor>() {
- @Override public void onEvent(SensorEvent<FailureDescriptor> event) {
- events.add(event);
- }
- });
- app.getManagementContext().getSubscriptionManager().subscribe(
- app,
- HASensors.CONNECTION_RECOVERED,
- new SensorEventListener<FailureDescriptor>() {
- @Override public void onEvent(SensorEvent<FailureDescriptor> event) {
- events.add(event);
- }
- });
-
- serverSocketAddress = startServerSocket();
- }
-
- @AfterMethod(alwaysRun=true)
- public void tearDown() throws Exception {
- stopServerSocket();
- if (managementContext != null) Entities.destroyAll(managementContext);
- }
-
- private HostAndPort startServerSocket() throws Exception {
- if (serverSocketAddress != null) {
- serverSocket = new ServerSocket(serverSocketAddress.getPort());
- } else {
- for (int i = 40000; i < 40100; i++) {
- try {
- serverSocket = new ServerSocket(i);
- } catch (IOException e) {
- // try next port
- }
- }
- assertNotNull(serverSocket, "Failed to create server socket; no ports free in range!");
- serverSocketAddress = HostAndPort.fromParts(serverSocket.getInetAddress().getHostAddress(), serverSocket.getLocalPort());
- }
- return serverSocketAddress;
- }
-
- private void stopServerSocket() throws Exception {
- if (serverSocket != null) serverSocket.close();
- }
-
- @Test(groups="Integration") // Has a 1 second wait
- public void testNotNotifiedOfFailuresForHealthy() throws Exception {
- // Create members before and after the policy is registered, to test both scenarios
-
- app.addPolicy(PolicySpec.create(ConnectionFailureDetector.class)
- .configure(ConnectionFailureDetector.ENDPOINT, serverSocketAddress));
-
- assertNoEventsContinually();
- }
-
- @Test
- public void testNotifiedOfFailure() throws Exception {
- app.addPolicy(PolicySpec.create(ConnectionFailureDetector.class)
- .configure(ConnectionFailureDetector.ENDPOINT, serverSocketAddress));
-
- stopServerSocket();
-
- assertHasEventEventually(HASensors.CONNECTION_FAILED, Predicates.<Object>equalTo(app), null);
- assertEquals(events.size(), 1, "events="+events);
- }
-
- @Test
- public void testNotifiedOfRecovery() throws Exception {
- app.addPolicy(PolicySpec.create(ConnectionFailureDetector.class)
- .configure(ConnectionFailureDetector.ENDPOINT, serverSocketAddress));
-
- stopServerSocket();
- assertHasEventEventually(HASensors.CONNECTION_FAILED, Predicates.<Object>equalTo(app), null);
-
- // make the connection recover
- startServerSocket();
- assertHasEventEventually(HASensors.CONNECTION_RECOVERED, Predicates.<Object>equalTo(app), null);
- assertEquals(events.size(), 2, "events="+events);
- }
-
- @Test
- public void testReportsFailureWhenAlreadyDownOnRegisteringPolicy() throws Exception {
- stopServerSocket();
-
- app.addPolicy(PolicySpec.create(ConnectionFailureDetector.class)
- .configure(ConnectionFailureDetector.ENDPOINT, serverSocketAddress));
-
- assertHasEventEventually(HASensors.CONNECTION_FAILED, Predicates.<Object>equalTo(app), null);
- }
-
- @Test(groups="Integration") // Because slow
- public void testNotNotifiedOfTemporaryFailuresDuringStabilisationDelay() throws Exception {
- app.addPolicy(PolicySpec.create(ConnectionFailureDetector.class)
- .configure(ConnectionFailureDetector.ENDPOINT, serverSocketAddress)
- .configure(ConnectionFailureDetector.CONNECTION_FAILED_STABILIZATION_DELAY, Duration.ONE_MINUTE));
-
- stopServerSocket();
- Thread.sleep(100);
- startServerSocket();
-
- assertNoEventsContinually();
- }
-
- @Test(groups="Integration") // Because slow
- public void testNotifiedOfFailureAfterStabilisationDelay() throws Exception {
- final int stabilisationDelay = 1000;
-
- app.addPolicy(PolicySpec.create(ConnectionFailureDetector.class)
- .configure(ConnectionFailureDetector.ENDPOINT, serverSocketAddress)
- .configure(ConnectionFailureDetector.CONNECTION_FAILED_STABILIZATION_DELAY, Duration.of(stabilisationDelay)));
-
- stopServerSocket();
-
- assertNoEventsContinually(Duration.of(stabilisationDelay - OVERHEAD));
- assertHasEventEventually(HASensors.CONNECTION_FAILED, Predicates.<Object>equalTo(app), null);
- }
-
- @Test(groups="Integration") // Because slow
- public void testFailuresThenUpDownResetsStabilisationCount() throws Exception {
- final long stabilisationDelay = 1000;
-
- app.addPolicy(PolicySpec.create(ConnectionFailureDetector.class)
- .configure(ConnectionFailureDetector.ENDPOINT, serverSocketAddress)
- .configure(ConnectionFailureDetector.CONNECTION_FAILED_STABILIZATION_DELAY, Duration.of(stabilisationDelay)));
-
- stopServerSocket();
- assertNoEventsContinually(Duration.of(stabilisationDelay - OVERHEAD));
-
- startServerSocket();
- Thread.sleep(POLL_PERIOD+OVERHEAD);
- stopServerSocket();
- assertNoEventsContinually(Duration.of(stabilisationDelay - OVERHEAD));
-
- assertHasEventEventually(HASensors.CONNECTION_FAILED, Predicates.<Object>equalTo(app), null);
- }
-
- @Test(groups="Integration") // Because slow
- public void testNotNotifiedOfTemporaryRecoveryDuringStabilisationDelay() throws Exception {
- final long stabilisationDelay = 1000;
-
- app.addPolicy(PolicySpec.create(ConnectionFailureDetector.class)
- .configure(ConnectionFailureDetector.ENDPOINT, serverSocketAddress)
- .configure(ConnectionFailureDetector.CONNECTION_RECOVERED_STABILIZATION_DELAY, Duration.of(stabilisationDelay)));
-
- stopServerSocket();
- assertHasEventEventually(HASensors.CONNECTION_FAILED, Predicates.<Object>equalTo(app), null);
- events.clear();
-
- startServerSocket();
- Thread.sleep(POLL_PERIOD+OVERHEAD);
- stopServerSocket();
-
- assertNoEventsContinually(Duration.of(stabilisationDelay + OVERHEAD));
- }
-
- @Test(groups="Integration") // Because slow
- public void testNotifiedOfRecoveryAfterStabilisationDelay() throws Exception {
- final int stabilisationDelay = 1000;
-
- app.addPolicy(PolicySpec.create(ConnectionFailureDetector.class)
- .configure(ConnectionFailureDetector.ENDPOINT, serverSocketAddress)
- .configure(ConnectionFailureDetector.CONNECTION_RECOVERED_STABILIZATION_DELAY, Duration.of(stabilisationDelay)));
-
- stopServerSocket();
- assertHasEventEventually(HASensors.CONNECTION_FAILED, Predicates.<Object>equalTo(app), null);
- events.clear();
-
- startServerSocket();
- assertNoEventsContinually(Duration.of(stabilisationDelay - OVERHEAD));
- assertHasEventEventually(HASensors.CONNECTION_RECOVERED, Predicates.<Object>equalTo(app), null);
- }
-
- @Test(groups="Integration") // Because slow
- public void testRecoversThenDownUpResetsStabilisationCount() throws Exception {
- final long stabilisationDelay = 1000;
-
- app.addPolicy(PolicySpec.create(ConnectionFailureDetector.class)
- .configure(ConnectionFailureDetector.ENDPOINT, serverSocketAddress)
- .configure(ConnectionFailureDetector.CONNECTION_RECOVERED_STABILIZATION_DELAY, Duration.of(stabilisationDelay)));
-
- stopServerSocket();
- assertHasEventEventually(HASensors.CONNECTION_FAILED, Predicates.<Object>equalTo(app), null);
- events.clear();
-
- startServerSocket();
- assertNoEventsContinually(Duration.of(stabilisationDelay - OVERHEAD));
-
- stopServerSocket();
- Thread.sleep(POLL_PERIOD+OVERHEAD);
- startServerSocket();
- assertNoEventsContinually(Duration.of(stabilisationDelay - OVERHEAD));
-
- assertHasEventEventually(HASensors.CONNECTION_RECOVERED, Predicates.<Object>equalTo(app), null);
- }
-
- private void assertHasEvent(Sensor<?> sensor, Predicate<Object> componentPredicate, Predicate<? super CharSequence> descriptionPredicate) {
- for (SensorEvent<FailureDescriptor> event : events) {
- if (event.getSensor().equals(sensor) &&
- (componentPredicate == null || componentPredicate.apply(event.getValue().getComponent())) &&
- (descriptionPredicate == null || descriptionPredicate.apply(event.getValue().getDescription()))) {
- return;
- }
- }
- fail("No matching "+sensor+" event found; events="+events);
- }
-
- private void assertHasEventEventually(final Sensor<?> sensor, final Predicate<Object> componentPredicate, final Predicate<? super CharSequence> descriptionPredicate) {
- Asserts.succeedsEventually(MutableMap.of("timeout", TIMEOUT_MS), new Runnable() {
- @Override public void run() {
- assertHasEvent(sensor, componentPredicate, descriptionPredicate);
- }});
- }
-
- private void assertNoEventsContinually(Duration duration) {
- Asserts.succeedsContinually(ImmutableMap.of("timeout", duration), new Runnable() {
- @Override public void run() {
- assertTrue(events.isEmpty(), "events="+events);
- }});
- }
-
- private void assertNoEventsContinually() {
- Asserts.succeedsContinually(new Runnable() {
- @Override public void run() {
- assertTrue(events.isEmpty(), "events="+events);
- }});
- }
-}
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/d30ff597/policy/src/test/java/brooklyn/policy/ha/HaPolicyRebindTest.java
----------------------------------------------------------------------
diff --git a/policy/src/test/java/brooklyn/policy/ha/HaPolicyRebindTest.java b/policy/src/test/java/brooklyn/policy/ha/HaPolicyRebindTest.java
deleted file mode 100644
index e6d01d0..0000000
--- a/policy/src/test/java/brooklyn/policy/ha/HaPolicyRebindTest.java
+++ /dev/null
@@ -1,173 +0,0 @@
-/*
- * 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 brooklyn.policy.ha;
-
-import static org.testng.Assert.assertEquals;
-import static org.testng.Assert.assertFalse;
-import static org.testng.Assert.fail;
-
-import java.util.List;
-import java.util.Set;
-
-import org.apache.brooklyn.api.entity.Entity;
-import org.apache.brooklyn.api.entity.proxying.EntitySpec;
-import org.apache.brooklyn.api.event.Sensor;
-import org.apache.brooklyn.api.event.SensorEvent;
-import org.apache.brooklyn.api.event.SensorEventListener;
-import org.apache.brooklyn.api.location.Location;
-import org.apache.brooklyn.api.location.LocationSpec;
-import org.apache.brooklyn.api.policy.EnricherSpec;
-import org.apache.brooklyn.api.policy.PolicySpec;
-import org.apache.brooklyn.test.entity.TestApplication;
-import org.apache.brooklyn.test.entity.TestEntity;
-import org.testng.annotations.BeforeMethod;
-import org.testng.annotations.Test;
-
-import brooklyn.entity.basic.Entities;
-import brooklyn.entity.basic.Lifecycle;
-import brooklyn.entity.basic.ServiceStateLogic;
-import brooklyn.entity.group.DynamicCluster;
-import brooklyn.entity.rebind.RebindTestFixtureWithApp;
-
-import org.apache.brooklyn.location.basic.SimulatedLocation;
-
-import brooklyn.policy.ha.HASensors.FailureDescriptor;
-import brooklyn.test.Asserts;
-import brooklyn.util.collections.MutableMap;
-
-import com.google.common.base.Predicate;
-import com.google.common.base.Predicates;
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.ImmutableSet;
-import com.google.common.collect.Iterables;
-import com.google.common.collect.Lists;
-import com.google.common.collect.Sets;
-
-public class HaPolicyRebindTest extends RebindTestFixtureWithApp {
-
- private TestEntity origEntity;
- private SensorEventListener<FailureDescriptor> eventListener;
- private List<SensorEvent<FailureDescriptor>> events;
-
- @Override
- @BeforeMethod(alwaysRun=true)
- public void setUp() throws Exception {
- super.setUp();
- origEntity = origApp.createAndManageChild(EntitySpec.create(TestEntity.class));
- events = Lists.newCopyOnWriteArrayList();
- eventListener = new SensorEventListener<FailureDescriptor>() {
- @Override public void onEvent(SensorEvent<FailureDescriptor> event) {
- events.add(event);
- }
- };
- }
-
- @Test
- public void testServiceRestarterWorksAfterRebind() throws Exception {
- origEntity.addPolicy(PolicySpec.create(ServiceRestarter.class)
- .configure(ServiceRestarter.FAILURE_SENSOR_TO_MONITOR, HASensors.ENTITY_FAILED));
-
- TestApplication newApp = rebind();
- final TestEntity newEntity = (TestEntity) Iterables.find(newApp.getChildren(), Predicates.instanceOf(TestEntity.class));
-
- newEntity.emit(HASensors.ENTITY_FAILED, new FailureDescriptor(origEntity, "simulate failure"));
-
- Asserts.succeedsEventually(new Runnable() {
- @Override public void run() {
- assertEquals(newEntity.getCallHistory(), ImmutableList.of("restart"));
- }});
- }
-
- @Test
- public void testServiceReplacerWorksAfterRebind() throws Exception {
- Location origLoc = origManagementContext.getLocationManager().createLocation(LocationSpec.create(SimulatedLocation.class));
- DynamicCluster origCluster = origApp.createAndManageChild(EntitySpec.create(DynamicCluster.class)
- .configure(DynamicCluster.MEMBER_SPEC, EntitySpec.create(TestEntity.class))
- .configure(DynamicCluster.INITIAL_SIZE, 3));
- origApp.start(ImmutableList.<Location>of(origLoc));
-
- origCluster.addPolicy(PolicySpec.create(ServiceReplacer.class)
- .configure(ServiceReplacer.FAILURE_SENSOR_TO_MONITOR, HASensors.ENTITY_FAILED));
-
- // rebind
- TestApplication newApp = rebind();
- final DynamicCluster newCluster = (DynamicCluster) Iterables.find(newApp.getChildren(), Predicates.instanceOf(DynamicCluster.class));
-
- // stimulate the policy
- final Set<Entity> initialMembers = ImmutableSet.copyOf(newCluster.getMembers());
- final TestEntity e1 = (TestEntity) Iterables.get(initialMembers, 1);
-
- newApp.getManagementContext().getSubscriptionManager().subscribe(e1, HASensors.ENTITY_FAILED, eventListener);
- newApp.getManagementContext().getSubscriptionManager().subscribe(e1, HASensors.ENTITY_RECOVERED, eventListener);
-
- e1.emit(HASensors.ENTITY_FAILED, new FailureDescriptor(e1, "simulate failure"));
-
- // Expect e1 to be replaced
- Asserts.succeedsEventually(new Runnable() {
- @Override public void run() {
- Set<Entity> newMembers = Sets.difference(ImmutableSet.copyOf(newCluster.getMembers()), initialMembers);
- Set<Entity> removedMembers = Sets.difference(initialMembers, ImmutableSet.copyOf(newCluster.getMembers()));
- assertEquals(removedMembers, ImmutableSet.of(e1));
- assertEquals(newMembers.size(), 1);
- assertEquals(((TestEntity)Iterables.getOnlyElement(newMembers)).getCallHistory(), ImmutableList.of("start"));
-
- // TODO e1 not reporting "start" after rebind because callHistory is a field rather than an attribute, so was not persisted
- Asserts.assertEqualsIgnoringOrder(e1.getCallHistory(), ImmutableList.of("stop"));
- assertFalse(Entities.isManaged(e1));
- }});
- }
-
- @Test
- public void testServiceFailureDetectorWorksAfterRebind() throws Exception {
- origEntity.addEnricher(EnricherSpec.create(ServiceFailureDetector.class));
-
- // rebind
- TestApplication newApp = rebind();
- final TestEntity newEntity = (TestEntity) Iterables.find(newApp.getChildren(), Predicates.instanceOf(TestEntity.class));
-
- newApp.getManagementContext().getSubscriptionManager().subscribe(newEntity, HASensors.ENTITY_FAILED, eventListener);
-
- newEntity.setAttribute(TestEntity.SERVICE_UP, true);
- ServiceStateLogic.setExpectedState(newEntity, Lifecycle.RUNNING);
-
- // trigger the failure
- newEntity.setAttribute(TestEntity.SERVICE_UP, false);
-
- assertHasEventEventually(HASensors.ENTITY_FAILED, Predicates.<Object>equalTo(newEntity), null);
- assertEquals(events.size(), 1, "events="+events);
- }
-
- private void assertHasEventEventually(final Sensor<?> sensor, final Predicate<Object> componentPredicate, final Predicate<? super CharSequence> descriptionPredicate) {
- Asserts.succeedsEventually(MutableMap.of("timeout", TIMEOUT_MS), new Runnable() {
- @Override public void run() {
- assertHasEvent(sensor, componentPredicate, descriptionPredicate);
- }});
- }
-
- private void assertHasEvent(Sensor<?> sensor, Predicate<Object> componentPredicate, Predicate<? super CharSequence> descriptionPredicate) {
- for (SensorEvent<FailureDescriptor> event : events) {
- if (event.getSensor().equals(sensor) &&
- (componentPredicate == null || componentPredicate.apply(event.getValue().getComponent())) &&
- (descriptionPredicate == null || descriptionPredicate.apply(event.getValue().getDescription()))) {
- return;
- }
- }
- fail("No matching "+sensor+" event found; events="+events);
- }
-}
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/d30ff597/policy/src/test/java/brooklyn/policy/ha/ServiceFailureDetectorStabilizationTest.java
----------------------------------------------------------------------
diff --git a/policy/src/test/java/brooklyn/policy/ha/ServiceFailureDetectorStabilizationTest.java b/policy/src/test/java/brooklyn/policy/ha/ServiceFailureDetectorStabilizationTest.java
deleted file mode 100644
index b6c5c7b..0000000
--- a/policy/src/test/java/brooklyn/policy/ha/ServiceFailureDetectorStabilizationTest.java
+++ /dev/null
@@ -1,234 +0,0 @@
-/*
- * 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 brooklyn.policy.ha;
-
-import static org.testng.Assert.assertTrue;
-import static org.testng.Assert.fail;
-
-import java.util.List;
-import java.util.concurrent.CopyOnWriteArrayList;
-
-import org.apache.brooklyn.api.entity.proxying.EntitySpec;
-import org.apache.brooklyn.api.event.Sensor;
-import org.apache.brooklyn.api.event.SensorEvent;
-import org.apache.brooklyn.api.event.SensorEventListener;
-import org.apache.brooklyn.api.management.ManagementContext;
-import org.apache.brooklyn.api.policy.EnricherSpec;
-import org.apache.brooklyn.test.entity.LocalManagementContextForTests;
-import org.apache.brooklyn.test.entity.TestApplication;
-import org.apache.brooklyn.test.entity.TestEntity;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.testng.annotations.AfterMethod;
-import org.testng.annotations.BeforeMethod;
-import org.testng.annotations.Test;
-
-import brooklyn.entity.basic.ApplicationBuilder;
-import brooklyn.entity.basic.Entities;
-import brooklyn.entity.basic.Lifecycle;
-import brooklyn.entity.basic.ServiceStateLogic;
-import brooklyn.entity.basic.ServiceStateLogicTest;
-import brooklyn.policy.ha.HASensors.FailureDescriptor;
-import brooklyn.test.Asserts;
-import brooklyn.util.collections.MutableMap;
-import brooklyn.util.time.Duration;
-
-import com.google.common.base.Predicate;
-import com.google.common.base.Predicates;
-import com.google.common.collect.ImmutableMap;
-
-/** also see more primitive tests in {@link ServiceStateLogicTest} */
-public class ServiceFailureDetectorStabilizationTest {
-
- private static final Logger LOG = LoggerFactory.getLogger(ServiceFailureDetectorStabilizationTest.class);
-
- private static final int TIMEOUT_MS = 10*1000;
- private static final int OVERHEAD = 250;
-
- private ManagementContext managementContext;
- private TestApplication app;
- private TestEntity e1;
-
- private List<SensorEvent<FailureDescriptor>> events;
-
- @BeforeMethod(alwaysRun=true)
- public void setUp() throws Exception {
- events = new CopyOnWriteArrayList<SensorEvent<FailureDescriptor>>();
-
- managementContext = new LocalManagementContextForTests();
- app = ApplicationBuilder.newManagedApp(TestApplication.class, managementContext);
- e1 = app.createAndManageChild(EntitySpec.create(TestEntity.class));
- e1.setAttribute(TestEntity.SERVICE_UP, true);
- ServiceStateLogic.setExpectedState(e1, Lifecycle.RUNNING);
-
- app.getManagementContext().getSubscriptionManager().subscribe(
- e1,
- HASensors.ENTITY_FAILED,
- new SensorEventListener<FailureDescriptor>() {
- @Override public void onEvent(SensorEvent<FailureDescriptor> event) {
- events.add(event);
- }
- });
- app.getManagementContext().getSubscriptionManager().subscribe(
- e1,
- HASensors.ENTITY_RECOVERED,
- new SensorEventListener<FailureDescriptor>() {
- @Override public void onEvent(SensorEvent<FailureDescriptor> event) {
- events.add(event);
- }
- });
- }
-
- @AfterMethod(alwaysRun=true)
- public void tearDown() throws Exception {
- if (managementContext != null) Entities.destroyAll(managementContext);
- }
-
- @Test(groups="Integration") // Because slow
- public void testNotNotifiedOfTemporaryFailuresDuringStabilisationDelay() throws Exception {
- e1.addEnricher(EnricherSpec.create(ServiceFailureDetector.class)
- .configure(ServiceFailureDetector.ENTITY_FAILED_STABILIZATION_DELAY, Duration.ONE_MINUTE));
-
- e1.setAttribute(TestEntity.SERVICE_UP, false);
- Thread.sleep(100);
- e1.setAttribute(TestEntity.SERVICE_UP, true);
-
- assertNoEventsContinually();
- }
-
- @Test(groups="Integration") // Because slow
- public void testNotifiedOfFailureAfterStabilisationDelay() throws Exception {
- final int stabilisationDelay = 1000;
-
- e1.addEnricher(EnricherSpec.create(ServiceFailureDetector.class)
- .configure(ServiceFailureDetector.ENTITY_FAILED_STABILIZATION_DELAY, Duration.of(stabilisationDelay)));
-
- e1.setAttribute(TestEntity.SERVICE_UP, false);
-
- assertNoEventsContinually(Duration.of(stabilisationDelay - OVERHEAD));
- assertHasEventEventually(HASensors.ENTITY_FAILED, Predicates.<Object>equalTo(e1), null);
- }
-
- @Test(groups="Integration") // Because slow
- public void testFailuresThenUpDownResetsStabilisationCount() throws Exception {
- LOG.debug("Running testFailuresThenUpDownResetsStabilisationCount");
- final long stabilisationDelay = 1000;
-
- e1.addEnricher(EnricherSpec.create(ServiceFailureDetector.class)
- .configure(ServiceFailureDetector.ENTITY_FAILED_STABILIZATION_DELAY, Duration.of(stabilisationDelay)));
-
- e1.setAttribute(TestEntity.SERVICE_UP, false);
- assertNoEventsContinually(Duration.of(stabilisationDelay - OVERHEAD));
-
- e1.setAttribute(TestEntity.SERVICE_UP, true);
- Thread.sleep(OVERHEAD);
- e1.setAttribute(TestEntity.SERVICE_UP, false);
- assertNoEventsContinually(Duration.of(stabilisationDelay - OVERHEAD));
-
- assertHasEventEventually(HASensors.ENTITY_FAILED, Predicates.<Object>equalTo(e1), null);
- }
-
- @Test(groups="Integration") // Because slow
- public void testNotNotifiedOfTemporaryRecoveryDuringStabilisationDelay() throws Exception {
- final long stabilisationDelay = 1000;
-
- e1.addEnricher(EnricherSpec.create(ServiceFailureDetector.class)
- .configure(ServiceFailureDetector.ENTITY_RECOVERED_STABILIZATION_DELAY, Duration.of(stabilisationDelay)));
-
- e1.setAttribute(TestEntity.SERVICE_UP, false);
- assertHasEventEventually(HASensors.ENTITY_FAILED, Predicates.<Object>equalTo(e1), null);
- events.clear();
-
- e1.setAttribute(TestEntity.SERVICE_UP, true);
- Thread.sleep(100);
- e1.setAttribute(TestEntity.SERVICE_UP, false);
-
- assertNoEventsContinually(Duration.of(stabilisationDelay + OVERHEAD));
- }
-
- @Test(groups="Integration") // Because slow
- public void testNotifiedOfRecoveryAfterStabilisationDelay() throws Exception {
- final int stabilisationDelay = 1000;
-
- e1.addEnricher(EnricherSpec.create(ServiceFailureDetector.class)
- .configure(ServiceFailureDetector.ENTITY_RECOVERED_STABILIZATION_DELAY, Duration.of(stabilisationDelay)));
-
- e1.setAttribute(TestEntity.SERVICE_UP, false);
- assertHasEventEventually(HASensors.ENTITY_FAILED, Predicates.<Object>equalTo(e1), null);
- events.clear();
-
- e1.setAttribute(TestEntity.SERVICE_UP, true);
- assertNoEventsContinually(Duration.of(stabilisationDelay - OVERHEAD));
- assertHasEventEventually(HASensors.ENTITY_RECOVERED, Predicates.<Object>equalTo(e1), null);
- }
-
- @Test(groups="Integration") // Because slow
- public void testRecoversThenDownUpResetsStabilisationCount() throws Exception {
- final long stabilisationDelay = 1000;
-
- e1.addEnricher(EnricherSpec.create(ServiceFailureDetector.class)
- .configure(ServiceFailureDetector.ENTITY_RECOVERED_STABILIZATION_DELAY, Duration.of(stabilisationDelay)));
-
- e1.setAttribute(TestEntity.SERVICE_UP, false);
- assertHasEventEventually(HASensors.ENTITY_FAILED, Predicates.<Object>equalTo(e1), null);
- events.clear();
-
- e1.setAttribute(TestEntity.SERVICE_UP, true);
- assertNoEventsContinually(Duration.of(stabilisationDelay - OVERHEAD));
-
- e1.setAttribute(TestEntity.SERVICE_UP, false);
- Thread.sleep(OVERHEAD);
- e1.setAttribute(TestEntity.SERVICE_UP, true);
- assertNoEventsContinually(Duration.of(stabilisationDelay - OVERHEAD));
-
- assertHasEventEventually(HASensors.ENTITY_RECOVERED, Predicates.<Object>equalTo(e1), null);
- }
-
- private void assertHasEvent(Sensor<?> sensor, Predicate<Object> componentPredicate, Predicate<? super CharSequence> descriptionPredicate) {
- for (SensorEvent<FailureDescriptor> event : events) {
- if (event.getSensor().equals(sensor) &&
- (componentPredicate == null || componentPredicate.apply(event.getValue().getComponent())) &&
- (descriptionPredicate == null || descriptionPredicate.apply(event.getValue().getDescription()))) {
- return;
- }
- }
- fail("No matching "+sensor+" event found; events="+events);
- }
-
- private void assertHasEventEventually(final Sensor<?> sensor, final Predicate<Object> componentPredicate, final Predicate<? super CharSequence> descriptionPredicate) {
- Asserts.succeedsEventually(MutableMap.of("timeout", TIMEOUT_MS), new Runnable() {
- @Override public void run() {
- assertHasEvent(sensor, componentPredicate, descriptionPredicate);
- }});
- }
-
- private void assertNoEventsContinually(Duration duration) {
- Asserts.succeedsContinually(ImmutableMap.of("timeout", duration), new Runnable() {
- @Override public void run() {
- assertTrue(events.isEmpty(), "events="+events);
- }});
- }
-
- private void assertNoEventsContinually() {
- Asserts.succeedsContinually(new Runnable() {
- @Override public void run() {
- assertTrue(events.isEmpty(), "events="+events);
- }});
- }
-}
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/d30ff597/policy/src/test/java/brooklyn/policy/ha/ServiceFailureDetectorTest.java
----------------------------------------------------------------------
diff --git a/policy/src/test/java/brooklyn/policy/ha/ServiceFailureDetectorTest.java b/policy/src/test/java/brooklyn/policy/ha/ServiceFailureDetectorTest.java
deleted file mode 100644
index 37355cf..0000000
--- a/policy/src/test/java/brooklyn/policy/ha/ServiceFailureDetectorTest.java
+++ /dev/null
@@ -1,408 +0,0 @@
-/*
- * 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 brooklyn.policy.ha;
-
-import static org.testng.Assert.assertEquals;
-import static org.testng.Assert.assertTrue;
-import static org.testng.Assert.fail;
-
-import java.util.List;
-import java.util.concurrent.CopyOnWriteArrayList;
-import java.util.concurrent.TimeUnit;
-
-import org.apache.brooklyn.api.entity.proxying.EntitySpec;
-import org.apache.brooklyn.api.event.Sensor;
-import org.apache.brooklyn.api.event.SensorEvent;
-import org.apache.brooklyn.api.event.SensorEventListener;
-import org.apache.brooklyn.api.management.ManagementContext;
-import org.apache.brooklyn.api.policy.EnricherSpec;
-import org.apache.brooklyn.test.EntityTestUtils;
-import org.apache.brooklyn.test.entity.LocalManagementContextForTests;
-import org.apache.brooklyn.test.entity.TestApplication;
-import org.apache.brooklyn.test.entity.TestEntity;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.testng.annotations.AfterMethod;
-import org.testng.annotations.BeforeMethod;
-import org.testng.annotations.Test;
-
-import brooklyn.entity.basic.ApplicationBuilder;
-import brooklyn.entity.basic.Attributes;
-import brooklyn.entity.basic.Entities;
-import brooklyn.entity.basic.Lifecycle;
-import brooklyn.entity.basic.ServiceStateLogic;
-import brooklyn.entity.basic.ServiceStateLogic.ServiceProblemsLogic;
-import brooklyn.policy.ha.HASensors.FailureDescriptor;
-import brooklyn.test.Asserts;
-import brooklyn.util.collections.MutableMap;
-import brooklyn.util.time.Duration;
-import brooklyn.util.time.Time;
-
-import com.google.common.base.Predicate;
-import com.google.common.base.Predicates;
-import com.google.common.collect.ImmutableMap;
-
-public class ServiceFailureDetectorTest {
- private static final Logger log = LoggerFactory.getLogger(ServiceFailureDetectorTest.class);
-
- private static final int TIMEOUT_MS = 10*1000;
-
- private ManagementContext managementContext;
- private TestApplication app;
- private TestEntity e1;
-
- private List<SensorEvent<FailureDescriptor>> events;
- private SensorEventListener<FailureDescriptor> eventListener;
-
- @BeforeMethod(alwaysRun=true)
- public void setUp() throws Exception {
- events = new CopyOnWriteArrayList<SensorEvent<FailureDescriptor>>();
- eventListener = new SensorEventListener<FailureDescriptor>() {
- @Override public void onEvent(SensorEvent<FailureDescriptor> event) {
- events.add(event);
- }
- };
-
- managementContext = new LocalManagementContextForTests();
- app = ApplicationBuilder.newManagedApp(TestApplication.class, managementContext);
- e1 = app.createAndManageChild(EntitySpec.create(TestEntity.class));
- e1.addEnricher(ServiceStateLogic.newEnricherForServiceStateFromProblemsAndUp());
-
- app.getManagementContext().getSubscriptionManager().subscribe(e1, HASensors.ENTITY_FAILED, eventListener);
- app.getManagementContext().getSubscriptionManager().subscribe(e1, HASensors.ENTITY_RECOVERED, eventListener);
- }
-
- @AfterMethod(alwaysRun=true)
- public void tearDown() throws Exception {
- if (managementContext != null) Entities.destroyAll(managementContext);
- }
-
- @Test(groups="Integration") // Has a 1 second wait
- public void testNotNotifiedOfFailuresForHealthy() throws Exception {
- // Create members before and after the policy is registered, to test both scenarios
- e1.setAttribute(TestEntity.SERVICE_UP, true);
- ServiceStateLogic.setExpectedState(e1, Lifecycle.RUNNING);
-
- e1.addEnricher(EnricherSpec.create(ServiceFailureDetector.class));
-
- assertNoEventsContinually();
- assertEquals(e1.getAttribute(TestEntity.SERVICE_STATE_ACTUAL), Lifecycle.RUNNING);
- }
-
- @Test
- public void testNotifiedOfFailure() throws Exception {
- e1.addEnricher(EnricherSpec.create(ServiceFailureDetector.class));
-
- e1.setAttribute(TestEntity.SERVICE_UP, true);
- ServiceStateLogic.setExpectedState(e1, Lifecycle.RUNNING);
-
- assertEquals(events.size(), 0, "events="+events);
-
- e1.setAttribute(TestEntity.SERVICE_UP, false);
-
- assertHasEventEventually(HASensors.ENTITY_FAILED, Predicates.<Object>equalTo(e1), null);
- assertEquals(events.size(), 1, "events="+events);
- EntityTestUtils.assertAttributeEqualsEventually(e1, TestEntity.SERVICE_STATE_ACTUAL, Lifecycle.ON_FIRE);
- }
-
- @Test
- public void testNotifiedOfFailureOnProblem() throws Exception {
- e1.addEnricher(EnricherSpec.create(ServiceFailureDetector.class));
-
- e1.setAttribute(TestEntity.SERVICE_UP, true);
- ServiceStateLogic.setExpectedState(e1, Lifecycle.RUNNING);
-
- assertEquals(events.size(), 0, "events="+events);
-
- ServiceProblemsLogic.updateProblemsIndicator(e1, "test", "foo");
-
- assertHasEventEventually(HASensors.ENTITY_FAILED, Predicates.<Object>equalTo(e1), null);
- assertEquals(events.size(), 1, "events="+events);
- EntityTestUtils.assertAttributeEqualsEventually(e1, TestEntity.SERVICE_STATE_ACTUAL, Lifecycle.ON_FIRE);
- }
-
- @Test
- public void testNotifiedOfFailureOnStateOnFire() throws Exception {
- e1.addEnricher(EnricherSpec.create(ServiceFailureDetector.class));
- e1.setAttribute(TestEntity.SERVICE_UP, true);
- ServiceStateLogic.setExpectedState(e1, Lifecycle.ON_FIRE);
-
- assertHasEventEventually(HASensors.ENTITY_FAILED, Predicates.<Object>equalTo(e1), null);
- assertEquals(events.size(), 1, "events="+events);
- EntityTestUtils.assertAttributeEqualsEventually(e1, TestEntity.SERVICE_STATE_ACTUAL, Lifecycle.ON_FIRE);
- }
-
- @Test
- public void testNotifiedOfRecovery() throws Exception {
- e1.addEnricher(EnricherSpec.create(ServiceFailureDetector.class));
-
- e1.setAttribute(TestEntity.SERVICE_UP, true);
- ServiceStateLogic.setExpectedState(e1, Lifecycle.RUNNING);
- // Make the entity fail
- e1.setAttribute(TestEntity.SERVICE_UP, false);
-
- assertHasEventEventually(HASensors.ENTITY_FAILED, Predicates.<Object>equalTo(e1), null);
- EntityTestUtils.assertAttributeEqualsEventually(e1, TestEntity.SERVICE_STATE_ACTUAL, Lifecycle.ON_FIRE);
-
- // And make the entity recover
- e1.setAttribute(TestEntity.SERVICE_UP, true);
- assertHasEventEventually(HASensors.ENTITY_RECOVERED, Predicates.<Object>equalTo(e1), null);
- assertEquals(events.size(), 2, "events="+events);
- EntityTestUtils.assertAttributeEqualsEventually(e1, TestEntity.SERVICE_STATE_ACTUAL, Lifecycle.RUNNING);
- }
-
- @Test
- public void testNotifiedOfRecoveryFromProblems() throws Exception {
- e1.addEnricher(EnricherSpec.create(ServiceFailureDetector.class));
-
- e1.setAttribute(TestEntity.SERVICE_UP, true);
- ServiceStateLogic.setExpectedState(e1, Lifecycle.RUNNING);
- // Make the entity fail
- ServiceProblemsLogic.updateProblemsIndicator(e1, "test", "foo");
-
- assertHasEventEventually(HASensors.ENTITY_FAILED, Predicates.<Object>equalTo(e1), null);
- EntityTestUtils.assertAttributeEqualsEventually(e1, TestEntity.SERVICE_STATE_ACTUAL, Lifecycle.ON_FIRE);
-
- // And make the entity recover
- ServiceProblemsLogic.clearProblemsIndicator(e1, "test");
- assertHasEventEventually(HASensors.ENTITY_RECOVERED, Predicates.<Object>equalTo(e1), null);
- assertEquals(events.size(), 2, "events="+events);
- EntityTestUtils.assertAttributeEqualsEventually(e1, TestEntity.SERVICE_STATE_ACTUAL, Lifecycle.RUNNING);
- }
-
-
- @Test(groups="Integration") // Has a 1 second wait
- public void testEmitsEntityFailureOnlyIfPreviouslyUp() throws Exception {
- e1.addEnricher(EnricherSpec.create(ServiceFailureDetector.class));
-
- // Make the entity fail
- e1.setAttribute(TestEntity.SERVICE_UP, false);
- ServiceStateLogic.setExpectedState(e1, Lifecycle.RUNNING);
-
- EntityTestUtils.assertAttributeEqualsEventually(e1, TestEntity.SERVICE_STATE_ACTUAL, Lifecycle.ON_FIRE);
- assertNoEventsContinually();
- }
-
- @Test
- public void testDisablingPreviouslyUpRequirementForEntityFailed() throws Exception {
- e1.addEnricher(EnricherSpec.create(ServiceFailureDetector.class)
- .configure(ServiceFailureDetector.ENTITY_FAILED_ONLY_IF_PREVIOUSLY_UP, false));
-
- e1.setAttribute(TestEntity.SERVICE_UP, false);
- ServiceStateLogic.setExpectedState(e1, Lifecycle.RUNNING);
-
- EntityTestUtils.assertAttributeEqualsEventually(e1, TestEntity.SERVICE_STATE_ACTUAL, Lifecycle.ON_FIRE);
- assertHasEventEventually(HASensors.ENTITY_FAILED, Predicates.<Object>equalTo(e1), null);
- }
-
- @Test
- public void testDisablingOnFire() throws Exception {
- e1.addEnricher(EnricherSpec.create(ServiceFailureDetector.class)
- .configure(ServiceFailureDetector.SERVICE_ON_FIRE_STABILIZATION_DELAY, Duration.PRACTICALLY_FOREVER));
-
- // Make the entity fail
- e1.setAttribute(TestEntity.SERVICE_UP, true);
- ServiceStateLogic.setExpectedState(e1, Lifecycle.RUNNING);
- EntityTestUtils.assertAttributeEqualsEventually(e1, Attributes.SERVICE_STATE_ACTUAL, Lifecycle.RUNNING);
- e1.setAttribute(TestEntity.SERVICE_UP, false);
-
- assertEquals(e1.getAttribute(TestEntity.SERVICE_STATE_ACTUAL), Lifecycle.RUNNING);
- }
-
- @Test(groups="Integration") // Has a 1 second wait
- public void testOnFireAfterDelay() throws Exception {
- e1.addEnricher(EnricherSpec.create(ServiceFailureDetector.class)
- .configure(ServiceFailureDetector.SERVICE_ON_FIRE_STABILIZATION_DELAY, Duration.ONE_SECOND));
-
- // Make the entity fail
- e1.setAttribute(TestEntity.SERVICE_UP, true);
- ServiceStateLogic.setExpectedState(e1, Lifecycle.RUNNING);
- EntityTestUtils.assertAttributeEqualsEventually(e1, Attributes.SERVICE_STATE_ACTUAL, Lifecycle.RUNNING);
-
- e1.setAttribute(TestEntity.SERVICE_UP, false);
-
- assertEquals(e1.getAttribute(TestEntity.SERVICE_STATE_ACTUAL), Lifecycle.RUNNING);
- Time.sleep(Duration.millis(100));
- assertEquals(e1.getAttribute(TestEntity.SERVICE_STATE_ACTUAL), Lifecycle.RUNNING);
- EntityTestUtils.assertAttributeEqualsEventually(e1, TestEntity.SERVICE_STATE_ACTUAL, Lifecycle.ON_FIRE);
- }
-
- @Test(groups="Integration") // Has a 1 second wait
- public void testOnFailureDelayFromProblemAndRecover() throws Exception {
- e1.addEnricher(EnricherSpec.create(ServiceFailureDetector.class)
- .configure(ServiceFailureDetector.SERVICE_ON_FIRE_STABILIZATION_DELAY, Duration.ONE_SECOND)
- .configure(ServiceFailureDetector.ENTITY_RECOVERED_STABILIZATION_DELAY, Duration.ONE_SECOND));
-
- // Set the entity to healthy
- e1.setAttribute(TestEntity.SERVICE_UP, true);
- ServiceStateLogic.setExpectedState(e1, Lifecycle.RUNNING);
- EntityTestUtils.assertAttributeEqualsEventually(e1, Attributes.SERVICE_STATE_ACTUAL, Lifecycle.RUNNING);
-
- // Make the entity fail; won't set on-fire for 1s but will publish FAILED immediately.
- ServiceStateLogic.ServiceProblemsLogic.updateProblemsIndicator(e1, "test", "foo");
- EntityTestUtils.assertAttributeEqualsContinually(ImmutableMap.of("timeout", 100), e1, TestEntity.SERVICE_STATE_ACTUAL, Lifecycle.RUNNING);
- assertHasEventEventually(HASensors.ENTITY_FAILED, Predicates.<Object>equalTo(e1), null);
- assertEquals(e1.getAttribute(TestEntity.SERVICE_STATE_ACTUAL), Lifecycle.RUNNING);
-
- EntityTestUtils.assertAttributeEqualsEventually(e1, TestEntity.SERVICE_STATE_ACTUAL, Lifecycle.ON_FIRE);
-
- // Now recover: will publish RUNNING immediately, but has 1s stabilisation for RECOVERED
- ServiceStateLogic.ServiceProblemsLogic.clearProblemsIndicator(e1, "test");
- EntityTestUtils.assertAttributeEqualsEventually(e1, TestEntity.SERVICE_STATE_ACTUAL, Lifecycle.RUNNING);
-
- assertEquals(events.size(), 1, "events="+events);
-
- assertHasEventEventually(HASensors.ENTITY_RECOVERED, Predicates.<Object>equalTo(e1), null);
- assertEquals(events.size(), 2, "events="+events);
- }
-
- @Test(groups="Integration") // Has a 1 second wait
- public void testAttendsToServiceState() throws Exception {
- e1.addEnricher(EnricherSpec.create(ServiceFailureDetector.class));
-
- e1.setAttribute(TestEntity.SERVICE_UP, true);
- // not counted as failed because not expected to be running
- e1.setAttribute(TestEntity.SERVICE_UP, false);
-
- assertNoEventsContinually();
- }
-
- @Test(groups="Integration") // Has a 1 second wait
- public void testOnlyReportsFailureIfRunning() throws Exception {
- e1.addEnricher(EnricherSpec.create(ServiceFailureDetector.class));
-
- // Make the entity fail
- ServiceStateLogic.setExpectedState(e1, Lifecycle.STARTING);
- e1.setAttribute(TestEntity.SERVICE_UP, true);
- e1.setAttribute(TestEntity.SERVICE_UP, false);
-
- assertNoEventsContinually();
- }
-
- @Test
- public void testReportsFailureWhenAlreadyDownOnRegisteringPolicy() throws Exception {
- ServiceStateLogic.setExpectedState(e1, Lifecycle.RUNNING);
- e1.setAttribute(TestEntity.SERVICE_UP, false);
-
- e1.addEnricher(EnricherSpec.create(ServiceFailureDetector.class)
- .configure(ServiceFailureDetector.ENTITY_FAILED_ONLY_IF_PREVIOUSLY_UP, false));
-
- assertHasEventEventually(HASensors.ENTITY_FAILED, Predicates.<Object>equalTo(e1), null);
- }
-
- @Test
- public void testReportsFailureWhenAlreadyOnFireOnRegisteringPolicy() throws Exception {
- ServiceStateLogic.setExpectedState(e1, Lifecycle.ON_FIRE);
-
- e1.addEnricher(EnricherSpec.create(ServiceFailureDetector.class)
- .configure(ServiceFailureDetector.ENTITY_FAILED_ONLY_IF_PREVIOUSLY_UP, false));
-
- assertHasEventEventually(HASensors.ENTITY_FAILED, Predicates.<Object>equalTo(e1), null);
- }
-
- @Test(groups="Integration") // Has a 1.5 second wait
- public void testRepublishedFailure() throws Exception {
- Duration republishPeriod = Duration.millis(100);
-
- e1.addEnricher(EnricherSpec.create(ServiceFailureDetector.class)
- .configure(ServiceFailureDetector.ENTITY_FAILED_REPUBLISH_TIME, republishPeriod));
-
- // Set the entity to healthy
- e1.setAttribute(TestEntity.SERVICE_UP, true);
- ServiceStateLogic.setExpectedState(e1, Lifecycle.RUNNING);
- EntityTestUtils.assertAttributeEqualsEventually(e1, Attributes.SERVICE_STATE_ACTUAL, Lifecycle.RUNNING);
-
- // Make the entity fail;
- ServiceStateLogic.ServiceProblemsLogic.updateProblemsIndicator(e1, "test", "foo");
- EntityTestUtils.assertAttributeEqualsEventually(e1, TestEntity.SERVICE_STATE_ACTUAL, Lifecycle.ON_FIRE);
- assertHasEventEventually(HASensors.ENTITY_FAILED, Predicates.<Object>equalTo(e1), null);
-
- //wait for at least 10 republish events (~1 sec)
- assertEventsSizeEventually(10);
-
- // Now recover
- ServiceStateLogic.ServiceProblemsLogic.clearProblemsIndicator(e1, "test");
- EntityTestUtils.assertAttributeEqualsEventually(e1, TestEntity.SERVICE_STATE_ACTUAL, Lifecycle.RUNNING);
- assertHasEventEventually(HASensors.ENTITY_RECOVERED, Predicates.<Object>equalTo(e1), null);
-
- //once recovered check no more failed events emitted periodically
- assertEventsSizeContiniually(events.size());
-
- SensorEvent<FailureDescriptor> prevEvent = null;
- for (SensorEvent<FailureDescriptor> event : events) {
- if (prevEvent != null) {
- long repeatOffset = event.getTimestamp() - prevEvent.getTimestamp();
- long deviation = Math.abs(repeatOffset - republishPeriod.toMilliseconds());
- if (deviation > republishPeriod.toMilliseconds()/10 &&
- //warn only if recovered is too far away from the last failure
- (!event.getSensor().equals(HASensors.ENTITY_RECOVERED) ||
- repeatOffset > republishPeriod.toMilliseconds())) {
- log.error("The time between failure republish (" + repeatOffset + "ms) deviates too much from the expected " + republishPeriod + ". prevEvent=" + prevEvent + ", event=" + event);
- }
- }
- prevEvent = event;
- }
-
- //make sure no republish takes place after recovered
- assertEquals(prevEvent.getSensor(), HASensors.ENTITY_RECOVERED);
- }
-
- private void assertEventsSizeContiniually(final int size) {
- Asserts.succeedsContinually(MutableMap.of("timeout", 500), new Runnable() {
- @Override
- public void run() {
- assertTrue(events.size() == size, "assertEventsSizeContiniually expects " + size + " events but found " + events.size() + ": " + events);
- }
- });
- }
-
- private void assertEventsSizeEventually(final int size) {
- Asserts.succeedsEventually(MutableMap.of("timeout", TIMEOUT_MS), new Runnable() {
- @Override
- public void run() {
- assertTrue(events.size() >= size, "assertEventsSizeContiniually expects at least " + size + " events but found " + events.size() + ": " + events);
- }
- });
- }
-
- private void assertHasEvent(Sensor<?> sensor, Predicate<Object> componentPredicate, Predicate<? super CharSequence> descriptionPredicate) {
- for (SensorEvent<FailureDescriptor> event : events) {
- if (event.getSensor().equals(sensor) &&
- (componentPredicate == null || componentPredicate.apply(event.getValue().getComponent())) &&
- (descriptionPredicate == null || descriptionPredicate.apply(event.getValue().getDescription()))) {
- return;
- }
- }
- fail("No matching "+sensor+" event found; events="+events);
- }
-
- private void assertHasEventEventually(final Sensor<?> sensor, final Predicate<Object> componentPredicate, final Predicate<? super CharSequence> descriptionPredicate) {
- Asserts.succeedsEventually(MutableMap.of("timeout", TIMEOUT_MS), new Runnable() {
- @Override public void run() {
- assertHasEvent(sensor, componentPredicate, descriptionPredicate);
- }});
- }
-
- private void assertNoEventsContinually() {
- Asserts.succeedsContinually(new Runnable() {
- @Override public void run() {
- assertTrue(events.isEmpty(), "events="+events);
- }});
- }
-}
[09/24] incubator-brooklyn git commit: [BROOKLYN-162] Renaming
package policy
Posted by he...@apache.org.
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/d30ff597/policy/src/test/java/brooklyn/policy/loadbalancing/LoadBalancingPolicyTest.java
----------------------------------------------------------------------
diff --git a/policy/src/test/java/brooklyn/policy/loadbalancing/LoadBalancingPolicyTest.java b/policy/src/test/java/brooklyn/policy/loadbalancing/LoadBalancingPolicyTest.java
deleted file mode 100644
index 442e3b3..0000000
--- a/policy/src/test/java/brooklyn/policy/loadbalancing/LoadBalancingPolicyTest.java
+++ /dev/null
@@ -1,397 +0,0 @@
-/*
- * 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 brooklyn.policy.loadbalancing;
-
-import static org.testng.Assert.assertEquals;
-import static org.testng.Assert.assertFalse;
-
-import org.apache.brooklyn.api.entity.basic.EntityLocal;
-import org.testng.annotations.Test;
-
-import brooklyn.entity.basic.Entities;
-import brooklyn.test.Asserts;
-import brooklyn.util.collections.MutableMap;
-
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.ImmutableMap;
-import com.google.common.collect.ImmutableSet;
-
-public class LoadBalancingPolicyTest extends AbstractLoadBalancingPolicyTest {
-
- // Expect no balancing to occur as container A isn't above the high threshold.
- @Test
- public void testNoopWhenWithinThresholds() {
- MockContainerEntity containerA = newContainer(app, "A", 10, 100);
- MockContainerEntity containerB = newContainer(app, "B", 20, 60);
- MockItemEntity item1 = newItem(app, containerA, "1", 10);
- MockItemEntity item2 = newItem(app, containerA, "2", 10);
- MockItemEntity item3 = newItem(app, containerA, "3", 10);
- MockItemEntity item4 = newItem(app, containerA, "4", 10);
-
- assertWorkratesEventually(
- ImmutableList.of(containerA, containerB),
- ImmutableList.of(item1, item2, item3, item4),
- ImmutableList.of(40d, 0d));
- }
-
- @Test
- public void testNoopWhenAlreadyBalanced() {
- MockContainerEntity containerA = newContainer(app, "A", 20, 80);
- MockContainerEntity containerB = newContainer(app, "B", 20, 80);
- MockItemEntity item1 = newItem(app, containerA, "1", 10);
- MockItemEntity item2 = newItem(app, containerA, "2", 30);
- MockItemEntity item3 = newItem(app, containerB, "3", 20);
- MockItemEntity item4 = newItem(app, containerB, "4", 20);
-
- assertWorkratesEventually(
- ImmutableList.of(containerA, containerB),
- ImmutableList.of(item1, item2, item3, item4),
- ImmutableList.of(40d, 40d));
- assertEquals(containerA.getBalanceableItems(), ImmutableSet.of(item1, item2));
- assertEquals(containerB.getBalanceableItems(), ImmutableSet.of(item3, item4));
- }
-
- // Expect 20 units of workload to be migrated from hot container (A) to cold (B).
- @Test
- public void testSimpleBalancing() {
- // Set-up containers and items.
- MockContainerEntity containerA = newContainer(app, "A", 10, 25);
- MockContainerEntity containerB = newContainer(app, "B", 20, 60);
- MockItemEntity item1 = newItem(app, containerA, "1", 10);
- MockItemEntity item2 = newItem(app, containerA, "2", 10);
- MockItemEntity item3 = newItem(app, containerA, "3", 10);
- MockItemEntity item4 = newItem(app, containerA, "4", 10);
-
- assertWorkratesEventually(
- ImmutableList.of(containerA, containerB),
- ImmutableList.of(item1, item2, item3, item4),
- ImmutableList.of(20d, 20d));
- }
-
- @Test
- public void testSimpleBalancing2() {
- MockContainerEntity containerA = newContainer(app, "A", 20, 40);
- MockContainerEntity containerB = newContainer(app, "B", 20, 40);
- MockItemEntity item1 = newItem(app, containerA, "1", 0);
- MockItemEntity item2 = newItem(app, containerB, "2", 40);
- MockItemEntity item3 = newItem(app, containerB, "3", 20);
- MockItemEntity item4 = newItem(app, containerB, "4", 20);
-
- assertWorkratesEventually(
- ImmutableList.of(containerA, containerB),
- ImmutableList.of(item1, item2, item3, item4),
- ImmutableList.of(40d, 40d));
- }
-
-// @Test
-// public void testAdjustedItemNotMoved() {
-// MockBalancingModel pool = new MockBalancingModel(
-// containers(
-// containerA, 20, 50,
-// containerB, 20, 50),
-// items(
-// "item1", containerA, 0,
-// "item2", containerB, -40,
-// "item3", containerB, 20,
-// "item4", containerB, 20)
-// );
-//
-// BalancingStrategy<String, String> policy = new BalancingStrategy<String, String>("Test", pool);
-// policy.rebalance();
-//
-// assertEquals((Object)pool.getItemsForContainer(containerA), ImmutableSet.of("item1", "item3", "item4"), pool.itemDistributionToString());
-// assertEquals((Object)pool.getItemsForContainer(containerB), ImmutableSet.of("item2"), pool.itemDistributionToString());
-// }
-
- @Test
- public void testMultiMoveBalancing() {
- MockContainerEntity containerA = newContainer(app, "A", 20, 50);
- MockContainerEntity containerB = newContainer(app, "B", 20, 50);
- MockItemEntity item1 = newItem(app, containerA, "1", 10);
- MockItemEntity item2 = newItem(app, containerA, "2", 10);
- MockItemEntity item3 = newItem(app, containerA, "3", 10);
- MockItemEntity item4 = newItem(app, containerA, "4", 10);
- MockItemEntity item5 = newItem(app, containerA, "5", 10);
- MockItemEntity item6 = newItem(app, containerA, "6", 10);
- MockItemEntity item7 = newItem(app, containerA, "7", 10);
- MockItemEntity item8 = newItem(app, containerA, "8", 10);
- MockItemEntity item9 = newItem(app, containerA, "9", 10);
- MockItemEntity item10 = newItem(app, containerA, "10", 10);
-
- // non-deterministic which items will be moved; but can assert how many (given they all have same workrate)
- assertWorkratesEventually(
- ImmutableList.of(containerA, containerB),
- ImmutableList.of(item1, item2, item3, item4, item5, item6, item7, item8, item9, item10),
- ImmutableList.of(50d, 50d));
- assertEquals(containerA.getBalanceableItems().size(), 5);
- assertEquals(containerB.getBalanceableItems().size(), 5);
- }
-
- @Test
- public void testRebalanceWhenWorkratesChange() {
- // Set-up containers and items.
- MockContainerEntity containerA = newContainer(app, "A", 10, 50);
- MockContainerEntity containerB = newContainer(app, "B", 10, 50);
- MockItemEntity item1 = newItem(app, containerA, "1", 0);
- MockItemEntity item2 = newItem(app, containerA, "2", 0);
-
- ((EntityLocal)item1).setAttribute(MockItemEntity.TEST_METRIC, 40);
- ((EntityLocal)item2).setAttribute(MockItemEntity.TEST_METRIC, 40);
-
- assertWorkratesEventually(
- ImmutableList.of(containerA, containerB),
- ImmutableList.of(item1, item2),
- ImmutableList.of(40d, 40d));
- }
-
- // Expect no balancing to occur in hot pool (2 containers over-threshold at 40).
- // On addition of new container, expect hot containers to offload 10 each.
- @Test
- public void testAddContainerWhenHot() {
- // Set-up containers and items.
- MockContainerEntity containerA = newContainer(app, "A", 10, 30);
- MockContainerEntity containerB = newContainer(app, "B", 10, 30);
- MockItemEntity item1 = newItem(app, containerA, "1", 10);
- MockItemEntity item2 = newItem(app, containerA, "2", 10);
- MockItemEntity item3 = newItem(app, containerA, "3", 10);
- MockItemEntity item4 = newItem(app, containerA, "4", 10);
- MockItemEntity item5 = newItem(app, containerB, "5", 10);
- MockItemEntity item6 = newItem(app, containerB, "6", 10);
- MockItemEntity item7 = newItem(app, containerB, "7", 10);
- MockItemEntity item8 = newItem(app, containerB, "8", 10);
- // Both containers are over-threshold at this point; should not rebalance.
-
- MockContainerEntity containerC = newAsyncContainer(app, "C", 10, 30, CONTAINER_STARTUP_DELAY_MS);
- // New container allows hot ones to offload work.
-
- assertWorkratesEventually(
- ImmutableList.of(containerA, containerB, containerC),
- ImmutableList.of(item1, item2, item3, item4, item5, item6, item7, item8),
- ImmutableList.of(30d, 30d, 20d));
- }
-
- // On addition of new container, expect no rebalancing to occur as no existing container is hot.
- @Test
- public void testAddContainerWhenCold() {
- // Set-up containers and items.
- MockContainerEntity containerA = newContainer(app, "A", 10, 50);
- MockContainerEntity containerB = newContainer(app, "B", 10, 50);
- MockItemEntity item1 = newItem(app, containerA, "1", 10);
- MockItemEntity item2 = newItem(app, containerA, "2", 10);
- MockItemEntity item3 = newItem(app, containerA, "3", 10);
- MockItemEntity item4 = newItem(app, containerA, "4", 10);
- MockItemEntity item5 = newItem(app, containerB, "5", 10);
- MockItemEntity item6 = newItem(app, containerB, "6", 10);
- MockItemEntity item7 = newItem(app, containerB, "7", 10);
- MockItemEntity item8 = newItem(app, containerB, "8", 10);
-
- assertWorkratesEventually(
- ImmutableList.of(containerA, containerB),
- ImmutableList.of(item1, item2, item3, item4, item5, item6, item7, item8),
- ImmutableList.of(40d, 40d));
-
- MockContainerEntity containerC = newAsyncContainer(app, "C", 10, 50, CONTAINER_STARTUP_DELAY_MS);
-
- assertWorkratesEventually(
- ImmutableList.of(containerA, containerB, containerC),
- ImmutableList.of(item1, item2, item3, item4, item5, item6, item7, item8),
- ImmutableList.of(40d, 40d, 0d));
- }
-
- // Expect no balancing to occur in cool pool (2 containers under-threshold at 30).
- // On addition of new item, expect over-threshold container (A) to offload 20 to B.
- @Test
- public void testAddItem() {
- // Set-up containers and items.
- MockContainerEntity containerA = newContainer(app, "A", 10, 50);
- MockContainerEntity containerB = newContainer(app, "B", 10, 50);
- MockItemEntity item1 = newItem(app, containerA, "1", 10);
- MockItemEntity item2 = newItem(app, containerA, "2", 10);
- MockItemEntity item3 = newItem(app, containerA, "3", 10);
- MockItemEntity item4 = newItem(app, containerB, "4", 10);
- MockItemEntity item5 = newItem(app, containerB, "5", 10);
- MockItemEntity item6 = newItem(app, containerB, "6", 10);
-
- assertWorkratesEventually(
- ImmutableList.of(containerA, containerB),
- ImmutableList.of(item1, item2, item3, item4, item5, item6),
- ImmutableList.of(30d, 30d));
-
- newItem(app, containerA, "7", 40);
-
- assertWorkratesEventually(
- ImmutableList.of(containerA, containerB),
- ImmutableList.of(item1, item2, item3, item4, item5, item6),
- ImmutableList.of(50d, 50d));
- }
-
- // FIXME Failed in build repeatedly (e.g. #1035), but couldn't reproduce locally yet with invocationCount=100
- @Test(groups="WIP")
- public void testRemoveContainerCausesRebalancing() {
- // Set-up containers and items.
- MockContainerEntity containerA = newContainer(app, "A", 10, 30);
- MockContainerEntity containerB = newContainer(app, "B", 10, 30);
- MockContainerEntity containerC = newContainer(app, "C", 10, 30);
- MockItemEntity item1 = newItem(app, containerA, "1", 10);
- MockItemEntity item2 = newItem(app, containerA, "2", 10);
- MockItemEntity item3 = newItem(app, containerB, "3", 10);
- MockItemEntity item4 = newItem(app, containerB, "4", 10);
- MockItemEntity item5 = newItem(app, containerC, "5", 10);
- MockItemEntity item6 = newItem(app, containerC, "6", 10);
-
- Entities.unmanage(containerC);
- item5.move(containerA);
- item6.move(containerA);
-
- assertWorkratesEventually(
- ImmutableList.of(containerA, containerB),
- ImmutableList.of(item1, item2, item3, item4, item5, item6),
- ImmutableList.of(30d, 30d));
- }
-
- @Test
- public void testRemoveItemCausesRebalancing() {
- // Set-up containers and items.
- MockContainerEntity containerA = newContainer(app, "A", 10, 30);
- MockContainerEntity containerB = newContainer(app, "B", 10, 30);
- MockItemEntity item1 = newItem(app, containerA, "1", 30);
- MockItemEntity item2 = newItem(app, containerB, "2", 20);
- MockItemEntity item3 = newItem(app, containerB, "3", 20);
-
- item1.stop();
- Entities.unmanage(item1);
-
- assertWorkratesEventually(
- ImmutableList.of(containerA, containerB),
- ImmutableList.of(item1, item2, item3),
- ImmutableList.of(20d, 20d));
- }
-
- @Test
- public void testRebalancesAfterManualMove() {
- // Set-up containers and items.
- MockContainerEntity containerA = newContainer(app, "A", 10, 50);
- MockContainerEntity containerB = newContainer(app, "B", 10, 50);
- MockItemEntity item1 = newItem(app, containerA, "1", 20);
- MockItemEntity item2 = newItem(app, containerA, "2", 20);
- MockItemEntity item3 = newItem(app, containerB, "3", 20);
- MockItemEntity item4 = newItem(app, containerB, "4", 20);
-
- // Move everything onto containerA, and expect it to be automatically re-balanced
- item3.move(containerA);
- item4.move(containerA);
-
- assertWorkratesEventually(
- ImmutableList.of(containerA, containerB),
- ImmutableList.of(item1, item2, item3, item4),
- ImmutableList.of(40d, 40d));
- }
-
- @SuppressWarnings({ "unchecked", "rawtypes" })
- @Test
- public void testModelIncludesItemsAndContainersStartedBeforePolicyCreated() {
- pool.removePolicy(policy);
- policy.destroy();
-
- // Set-up containers and items.
- final MockContainerEntity containerA = newContainer(app, "A", 10, 100);
- newItem(app, containerA, "1", 10);
-
- policy = new LoadBalancingPolicy(MutableMap.of(), TEST_METRIC, model);
- pool.addPolicy(policy);
-
- Asserts.succeedsEventually(MutableMap.of("timeout", TIMEOUT_MS), new Runnable() {
- public void run() {
- assertEquals(model.getContainerWorkrates(), ImmutableMap.of(containerA, 10d));
- }
- });
- }
-
- @Test
- public void testLockedItemsNotMoved() {
- // Set-up containers and items.
- MockContainerEntity containerA = newContainer(app, "A", 10, 50);
- MockContainerEntity containerB = newContainer(app, "B", 10, 50);
- MockItemEntity item1 = newLockedItem(app, containerA, "1", 40);
- MockItemEntity item2 = newLockedItem(app, containerA, "2", 40);
-
- assertWorkratesContinually(
- ImmutableList.of(containerA, containerB),
- ImmutableList.of(item1, item2),
- ImmutableList.of(80d, 0d));
- }
-
- @Test
- public void testLockedItemsContributeToOverloadedMeasurements() {
- // Set-up containers and items.
- MockContainerEntity containerA = newContainer(app, "A", 10, 50);
- MockContainerEntity containerB = newContainer(app, "B", 10, 50);
- MockItemEntity item1 = newLockedItem(app, containerA, "1", 40);
- MockItemEntity item2 = newItem(app, containerA, "2", 25);
- MockItemEntity item3 = newItem(app, containerA, "3", 25);
-
- assertWorkratesEventually(
- ImmutableList.of(containerA, containerB),
- ImmutableList.of(item1, item2, item3),
- ImmutableList.of(40d, 50d));
- }
-
- @Test
- public void testOverloadedLockedItemsPreventMoreWorkEnteringContainer() throws Exception {
- // Set-up containers and items.
- MockContainerEntity containerA = newContainer(app, "A", 10, 50);
- MockContainerEntity containerB = newContainer(app, "B", 10, 50);
- MockItemEntity item1 = newLockedItem(app, containerA, "1", 50);
- Thread.sleep(1); // increase chances of item1's workrate having been received first
- MockItemEntity item2 = newItem(app, containerB, "2", 30);
- MockItemEntity item3 = newItem(app, containerB, "3", 30);
-
- assertWorkratesContinually(
- ImmutableList.of(containerA, containerB),
- ImmutableList.of(item1, item2, item3),
- ImmutableList.of(50d, 60d));
- }
-
- @Test
- public void testPolicyUpdatesModel() {
- final MockContainerEntity containerA = newContainer(app, "A", 10, 20);
- final MockContainerEntity containerB = newContainer(app, "B", 11, 21);
- final MockItemEntity item1 = newItem(app, containerA, "1", 12);
- final MockItemEntity item2 = newItem(app, containerB, "2", 13);
-
- Asserts.succeedsEventually(MutableMap.of("timeout", TIMEOUT_MS), new Runnable() {
- public void run() {
- assertEquals(model.getPoolSize(), 2);
- assertEquals(model.getPoolContents(), ImmutableSet.of(containerA, containerB));
- assertEquals(model.getItemWorkrate(item1), 12d);
- assertEquals(model.getItemWorkrate(item2), 13d);
-
- assertEquals(model.getParentContainer(item1), containerA);
- assertEquals(model.getParentContainer(item2), containerB);
- assertEquals(model.getContainerWorkrates(), ImmutableMap.of(containerA, 12d, containerB, 13d));
-
- assertEquals(model.getPoolLowThreshold(), 10+11d);
- assertEquals(model.getPoolHighThreshold(), 20+21d);
- assertEquals(model.getCurrentPoolWorkrate(), 12+13d);
- assertFalse(model.isHot());
- assertFalse(model.isCold());
- }
- });
- }
-}
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/d30ff597/policy/src/test/java/brooklyn/policy/loadbalancing/MockContainerEntity.java
----------------------------------------------------------------------
diff --git a/policy/src/test/java/brooklyn/policy/loadbalancing/MockContainerEntity.java b/policy/src/test/java/brooklyn/policy/loadbalancing/MockContainerEntity.java
deleted file mode 100644
index 70d4b57..0000000
--- a/policy/src/test/java/brooklyn/policy/loadbalancing/MockContainerEntity.java
+++ /dev/null
@@ -1,61 +0,0 @@
-/*
- * 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 brooklyn.policy.loadbalancing;
-
-import java.util.Map;
-
-import org.apache.brooklyn.api.entity.Effector;
-import org.apache.brooklyn.api.entity.Entity;
-import org.apache.brooklyn.api.entity.proxying.ImplementedBy;
-import org.apache.brooklyn.core.util.flags.SetFromFlag;
-
-import brooklyn.config.ConfigKey;
-import brooklyn.entity.annotation.EffectorParam;
-import brooklyn.entity.basic.AbstractGroup;
-import brooklyn.entity.basic.MethodEffector;
-import brooklyn.entity.trait.Startable;
-import brooklyn.event.basic.BasicConfigKey;
-
-@ImplementedBy(MockContainerEntityImpl.class)
-public interface MockContainerEntity extends AbstractGroup, BalanceableContainer<Movable>, Startable {
-
- @SetFromFlag("membership")
- public static final ConfigKey<String> MOCK_MEMBERSHIP = new BasicConfigKey<String>(
- String.class, "mock.container.membership", "For testing ItemsInContainersGroup");
-
- @SetFromFlag("delay")
- public static final ConfigKey<Long> DELAY = new BasicConfigKey<Long>(
- Long.class, "mock.container.delay", "", 0L);
-
- public static final Effector<Void> OFFLOAD_AND_STOP = new MethodEffector<Void>(MockContainerEntity.class, "offloadAndStop");
-
- public void lock();
-
- public void unlock();
-
- public int getWorkrate();
-
- public Map<Entity, Double> getItemUsage();
-
- public void addItem(Entity item);
-
- public void removeItem(Entity item);
-
- public void offloadAndStop(@EffectorParam(name="otherContianer") MockContainerEntity otherContainer);
-}
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/d30ff597/policy/src/test/java/brooklyn/policy/loadbalancing/MockContainerEntityImpl.java
----------------------------------------------------------------------
diff --git a/policy/src/test/java/brooklyn/policy/loadbalancing/MockContainerEntityImpl.java b/policy/src/test/java/brooklyn/policy/loadbalancing/MockContainerEntityImpl.java
deleted file mode 100644
index 672addb..0000000
--- a/policy/src/test/java/brooklyn/policy/loadbalancing/MockContainerEntityImpl.java
+++ /dev/null
@@ -1,208 +0,0 @@
-/*
- * 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 brooklyn.policy.loadbalancing;
-
-import java.util.Collection;
-import java.util.Collections;
-import java.util.Comparator;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-import java.util.concurrent.locks.ReentrantLock;
-
-import org.apache.brooklyn.api.entity.Entity;
-import org.apache.brooklyn.api.event.AttributeSensor;
-import org.apache.brooklyn.api.location.Location;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import brooklyn.entity.basic.AbstractGroupImpl;
-import brooklyn.entity.basic.Attributes;
-import brooklyn.util.collections.MutableList;
-import brooklyn.util.time.Time;
-
-import com.google.common.base.Predicates;
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.Iterables;
-import com.google.common.collect.Lists;
-import com.google.common.collect.Maps;
-import com.google.common.collect.Sets;
-
-
-public class MockContainerEntityImpl extends AbstractGroupImpl implements MockContainerEntity {
-
- private static final Logger LOG = LoggerFactory.getLogger(MockContainerEntity.class);
-
- volatile boolean offloading;
- volatile boolean running;
-
- ReentrantLock _lock = new ReentrantLock();
-
- @Override
- public <T> T setAttribute(AttributeSensor<T> attribute, T val) {
- if (LOG.isDebugEnabled()) LOG.debug("Mocks: container {} setting {} to {}", new Object[] {this, attribute, val});
- return super.setAttribute(attribute, val);
- }
-
- @Override
- public void lock() {
- _lock.lock();
- if (!running) {
- _lock.unlock();
- throw new IllegalStateException("Container lock "+this+"; it is not running");
- }
- }
-
- @Override
- public void unlock() {
- _lock.unlock();
- }
-
- @Override
- public int getWorkrate() {
- int result = 0;
- for (Entity member : getMembers()) {
- Integer memberMetric = member.getAttribute(MockItemEntity.TEST_METRIC);
- result += ((memberMetric != null) ? memberMetric : 0);
- }
- return result;
- }
-
- @Override
- public Map<Entity, Double> getItemUsage() {
- Map<Entity, Double> result = Maps.newLinkedHashMap();
- for (Entity member : getMembers()) {
- Map<Entity, Double> memberItemUsage = member.getAttribute(MockItemEntity.ITEM_USAGE_METRIC);
- if (memberItemUsage != null) {
- for (Map.Entry<Entity, Double> entry : memberItemUsage.entrySet()) {
- double val = (result.containsKey(entry.getKey()) ? result.get(entry.getKey()) : 0d);
- val += ((entry.getValue() != null) ? entry.getValue() : 0);
- result.put(entry.getKey(), val);
- }
- }
- }
- return result;
- }
-
- @Override
- public void addItem(Entity item) {
- if (LOG.isDebugEnabled()) LOG.debug("Mocks: adding item {} to container {}", item, this);
- if (!running || offloading) throw new IllegalStateException("Container "+getDisplayName()+" is not running; cannot add item "+item);
- addMember(item);
- emit(BalanceableContainer.ITEM_ADDED, item);
- }
-
- @Override
- public void removeItem(Entity item) {
- if (LOG.isDebugEnabled()) LOG.debug("Mocks: removing item {} from container {}", item, this);
- if (!running) throw new IllegalStateException("Container "+getDisplayName()+" is not running; cannot remove item "+item);
- removeMember(item);
- emit(BalanceableContainer.ITEM_REMOVED, item);
- }
-
- @SuppressWarnings({ "unchecked", "rawtypes" })
- @Override
- public Set<Movable> getBalanceableItems() {
- return (Set) Sets.newLinkedHashSet(getMembers());
- }
-
- public String toString() {
- return "MockContainer["+getDisplayName()+"]";
- }
-
- private long getDelay() {
- return getConfig(DELAY);
- }
-
- @Override
- public void start(Collection<? extends Location> locs) {
- if (LOG.isDebugEnabled()) LOG.debug("Mocks: starting container {}", this);
- _lock.lock();
- try {
- Time.sleep(getDelay());
- running = true;
- addLocations(locs);
- emit(Attributes.LOCATION_CHANGED, null);
- setAttribute(SERVICE_UP, true);
- } finally {
- _lock.unlock();
- }
- }
-
- @Override
- public void stop() {
- if (LOG.isDebugEnabled()) LOG.debug("Mocks: stopping container {}", this);
- _lock.lock();
- try {
- running = false;
- Time.sleep(getDelay());
- setAttribute(SERVICE_UP, false);
- } finally {
- _lock.unlock();
- }
- }
-
- private void stopWithoutLock() {
- running = false;
- Time.sleep(getDelay());
- setAttribute(SERVICE_UP, false);
- }
-
- public void offloadAndStop(final MockContainerEntity otherContainer) {
- if (LOG.isDebugEnabled()) LOG.debug("Mocks: offloading container {} to {} (items {})", new Object[] {this, otherContainer, getBalanceableItems()});
- runWithLock(ImmutableList.of(this, otherContainer), new Runnable() {
- public void run() {
- offloading = false;
- for (Movable item : getBalanceableItems()) {
- ((MockItemEntity)item).moveNonEffector(otherContainer);
- }
- if (LOG.isDebugEnabled()) LOG.debug("Mocks: stopping offloaded container {}", this);
- stopWithoutLock();
- }});
- }
-
- @Override
- public void restart() {
- if (LOG.isDebugEnabled()) LOG.debug("Mocks: restarting {}", this);
- throw new UnsupportedOperationException();
- }
-
- public static void runWithLock(List<MockContainerEntity> entitiesToLock, Runnable r) {
- List<MockContainerEntity> entitiesToLockCopy = MutableList.copyOf(Iterables.filter(entitiesToLock, Predicates.notNull()));
- List<MockContainerEntity> entitiesLocked = Lists.newArrayList();
- Collections.sort(entitiesToLockCopy, new Comparator<MockContainerEntity>() {
- public int compare(MockContainerEntity o1, MockContainerEntity o2) {
- return o1.getId().compareTo(o2.getId());
- }});
-
- try {
- for (MockContainerEntity it : entitiesToLockCopy) {
- it.lock();
- entitiesLocked.add(it);
- }
-
- r.run();
-
- } finally {
- for (MockContainerEntity it : entitiesLocked) {
- it.unlock();
- }
- }
- }
-}
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/d30ff597/policy/src/test/java/brooklyn/policy/loadbalancing/MockItemEntity.java
----------------------------------------------------------------------
diff --git a/policy/src/test/java/brooklyn/policy/loadbalancing/MockItemEntity.java b/policy/src/test/java/brooklyn/policy/loadbalancing/MockItemEntity.java
deleted file mode 100644
index 9286072..0000000
--- a/policy/src/test/java/brooklyn/policy/loadbalancing/MockItemEntity.java
+++ /dev/null
@@ -1,46 +0,0 @@
-/*
- * 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 brooklyn.policy.loadbalancing;
-
-import java.util.Map;
-
-import org.apache.brooklyn.api.entity.Entity;
-import org.apache.brooklyn.api.entity.proxying.ImplementedBy;
-import org.apache.brooklyn.api.event.AttributeSensor;
-
-import brooklyn.event.basic.Sensors;
-
-import com.google.common.reflect.TypeToken;
-
-@ImplementedBy(MockItemEntityImpl.class)
-public interface MockItemEntity extends Entity, Movable {
-
- public static final AttributeSensor<Integer> TEST_METRIC = Sensors.newIntegerSensor(
- "test.metric", "Dummy workrate for test entities");
-
- @SuppressWarnings("serial")
- public static final AttributeSensor<Map<Entity, Double>> ITEM_USAGE_METRIC = Sensors.newSensor(
- new TypeToken<Map<Entity, Double>>() {}, "test.itemUsage.metric", "Dummy item usage for test entities");
-
- public boolean isStopped();
-
- public void moveNonEffector(Entity rawDestination);
-
- public void stop();
-}
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/d30ff597/policy/src/test/java/brooklyn/policy/loadbalancing/MockItemEntityImpl.java
----------------------------------------------------------------------
diff --git a/policy/src/test/java/brooklyn/policy/loadbalancing/MockItemEntityImpl.java b/policy/src/test/java/brooklyn/policy/loadbalancing/MockItemEntityImpl.java
deleted file mode 100644
index ecc725e..0000000
--- a/policy/src/test/java/brooklyn/policy/loadbalancing/MockItemEntityImpl.java
+++ /dev/null
@@ -1,113 +0,0 @@
-/*
- * 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 brooklyn.policy.loadbalancing;
-
-import static com.google.common.base.Preconditions.checkNotNull;
-
-import java.util.concurrent.atomic.AtomicInteger;
-import java.util.concurrent.atomic.AtomicLong;
-import java.util.concurrent.locks.ReentrantLock;
-
-import org.apache.brooklyn.api.entity.Entity;
-import org.apache.brooklyn.api.event.AttributeSensor;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import brooklyn.entity.basic.AbstractEntity;
-import brooklyn.util.collections.MutableList;
-
-
-public class MockItemEntityImpl extends AbstractEntity implements MockItemEntity {
-
- private static final Logger LOG = LoggerFactory.getLogger(MockItemEntityImpl.class);
-
- public static AtomicInteger totalMoveCount = new AtomicInteger(0);
-
- public static AtomicLong lastMoveTime = new AtomicLong(-1);
-
- private volatile boolean stopped;
- private volatile MockContainerEntity currentContainer;
-
- private final ReentrantLock _lock = new ReentrantLock();
-
- @Override
- public String getContainerId() {
- return (currentContainer == null) ? null : currentContainer.getId();
- }
-
- @Override
- public boolean isStopped() {
- return stopped;
- }
-
- @Override
- public <T> T setAttribute(AttributeSensor<T> attribute, T val) {
- if (LOG.isDebugEnabled()) LOG.debug("Mocks: item {} setting {} to {}", new Object[] {this, attribute, val});
- return super.setAttribute(attribute, val);
- }
-
- @Override
- public void move(Entity destination) {
- totalMoveCount.incrementAndGet();
- lastMoveTime.set(System.currentTimeMillis());
- moveNonEffector(destination);
- }
-
- // only moves if the containers will accept us (otherwise we'd lose the item!)
- @Override
- public void moveNonEffector(Entity rawDestination) {
- if (LOG.isDebugEnabled()) LOG.debug("Mocks: moving item {} from {} to {}", new Object[] {this, currentContainer, rawDestination});
- checkNotNull(rawDestination);
- final MockContainerEntity previousContainer = currentContainer;
- final MockContainerEntity destination = (MockContainerEntity) rawDestination;
-
- MockContainerEntityImpl.runWithLock(MutableList.of(previousContainer, destination), new Runnable() {
- @Override public void run() {
- _lock.lock();
- try {
- if (stopped) throw new IllegalStateException("Item "+this+" is stopped; cannot move to "+destination);
- if (currentContainer != null) currentContainer.removeItem(MockItemEntityImpl.this);
- currentContainer = destination;
- destination.addItem(MockItemEntityImpl.this);
- setAttribute(CONTAINER, currentContainer);
- } finally {
- _lock.unlock();
- }
- }});
- }
-
- @Override
- public void stop() {
- // FIXME How best to indicate this has been entirely stopped, rather than just in-transit?
- if (LOG.isDebugEnabled()) LOG.debug("Mocks: stopping item {} (was in container {})", this, currentContainer);
- _lock.lock();
- try {
- if (currentContainer != null) currentContainer.removeItem(this);
- currentContainer = null;
- stopped = true;
- } finally {
- _lock.unlock();
- }
- }
-
- @Override
- public String toString() {
- return "MockItem["+getDisplayName()+"]";
- }
-}
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/d30ff597/policy/src/test/java/org/apache/brooklyn/policy/autoscaling/AutoScalerPolicyMetricTest.java
----------------------------------------------------------------------
diff --git a/policy/src/test/java/org/apache/brooklyn/policy/autoscaling/AutoScalerPolicyMetricTest.java b/policy/src/test/java/org/apache/brooklyn/policy/autoscaling/AutoScalerPolicyMetricTest.java
new file mode 100644
index 0000000..83f7b33
--- /dev/null
+++ b/policy/src/test/java/org/apache/brooklyn/policy/autoscaling/AutoScalerPolicyMetricTest.java
@@ -0,0 +1,274 @@
+/*
+ * 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.policy.autoscaling;
+
+import static org.apache.brooklyn.policy.autoscaling.AutoScalerPolicyTest.currentSizeAsserter;
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertFalse;
+import static org.testng.Assert.assertTrue;
+
+import java.util.List;
+
+import org.apache.brooklyn.api.entity.proxying.EntitySpec;
+import org.apache.brooklyn.api.event.AttributeSensor;
+import org.apache.brooklyn.api.event.SensorEvent;
+import org.apache.brooklyn.api.event.SensorEventListener;
+import org.apache.brooklyn.test.entity.TestApplication;
+import org.apache.brooklyn.test.entity.TestCluster;
+import org.apache.brooklyn.test.entity.TestEntity;
+import org.testng.annotations.AfterMethod;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import brooklyn.entity.basic.Entities;
+import brooklyn.event.basic.BasicNotificationSensor;
+import brooklyn.event.basic.Sensors;
+import brooklyn.test.Asserts;
+
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Lists;
+
+public class AutoScalerPolicyMetricTest {
+
+ private static long TIMEOUT_MS = 10000;
+ private static long SHORT_WAIT_MS = 50;
+
+ private static final AttributeSensor<Integer> MY_ATTRIBUTE = Sensors.newIntegerSensor("autoscaler.test.intAttrib");
+ TestApplication app;
+ TestCluster tc;
+
+ @BeforeMethod(alwaysRun=true)
+ public void before() {
+ app = TestApplication.Factory.newManagedInstanceForTests();
+ tc = app.createAndManageChild(EntitySpec.create(TestCluster.class)
+ .configure("initialSize", 1));
+ }
+
+ @AfterMethod(alwaysRun=true)
+ public void tearDown() throws Exception {
+ if (app != null) Entities.destroyAll(app.getManagementContext());
+ }
+
+ @Test
+ public void testIncrementsSizeIffUpperBoundExceeded() {
+ tc.resize(1);
+
+ AutoScalerPolicy policy = new AutoScalerPolicy.Builder().metric(MY_ATTRIBUTE).metricLowerBound(50).metricUpperBound(100).build();
+ tc.addPolicy(policy);
+
+ tc.setAttribute(MY_ATTRIBUTE, 100);
+ Asserts.succeedsContinually(ImmutableMap.of("timeout", SHORT_WAIT_MS), currentSizeAsserter(tc, 1));
+
+ tc.setAttribute(MY_ATTRIBUTE, 101);
+ Asserts.succeedsEventually(ImmutableMap.of("timeout", TIMEOUT_MS), currentSizeAsserter(tc, 2));
+ }
+
+ @Test
+ public void testDecrementsSizeIffLowerBoundExceeded() {
+ tc.resize(2);
+
+ AutoScalerPolicy policy = new AutoScalerPolicy.Builder().metric(MY_ATTRIBUTE).metricLowerBound(50).metricUpperBound(100).build();
+ tc.addPolicy(policy);
+
+ tc.setAttribute(MY_ATTRIBUTE, 50);
+ Asserts.succeedsContinually(ImmutableMap.of("timeout", SHORT_WAIT_MS), currentSizeAsserter(tc, 2));
+
+ tc.setAttribute(MY_ATTRIBUTE, 49);
+ Asserts.succeedsEventually(ImmutableMap.of("timeout", TIMEOUT_MS), currentSizeAsserter(tc, 1));
+ }
+
+ @Test(groups="Integration")
+ public void testIncrementsSizeInProportionToMetric() {
+ tc.resize(5);
+
+ AutoScalerPolicy policy = new AutoScalerPolicy.Builder().metric(MY_ATTRIBUTE).metricLowerBound(50).metricUpperBound(100).build();
+ tc.addPolicy(policy);
+
+ // workload 200 so requires doubling size to 10 to handle: (200*5)/100 = 10
+ tc.setAttribute(MY_ATTRIBUTE, 200);
+ Asserts.succeedsEventually(ImmutableMap.of("timeout", TIMEOUT_MS), currentSizeAsserter(tc, 10));
+
+ // workload 5, requires 1 entity: (10*110)/100 = 11
+ tc.setAttribute(MY_ATTRIBUTE, 110);
+ Asserts.succeedsEventually(ImmutableMap.of("timeout", TIMEOUT_MS), currentSizeAsserter(tc, 11));
+ }
+
+ @Test(groups="Integration")
+ public void testDecrementsSizeInProportionToMetric() {
+ tc.resize(5);
+
+ AutoScalerPolicy policy = new AutoScalerPolicy.Builder().metric(MY_ATTRIBUTE).metricLowerBound(50).metricUpperBound(100).build();
+ tc.addPolicy(policy);
+
+ // workload can be handled by 4 servers, within its valid range: (49*5)/50 = 4.9
+ tc.setAttribute(MY_ATTRIBUTE, 49);
+ Asserts.succeedsEventually(ImmutableMap.of("timeout", TIMEOUT_MS), currentSizeAsserter(tc, 4));
+
+ // workload can be handled by 4 servers, within its valid range: (25*4)/50 = 2
+ tc.setAttribute(MY_ATTRIBUTE, 25);
+ Asserts.succeedsEventually(ImmutableMap.of("timeout", TIMEOUT_MS), currentSizeAsserter(tc, 2));
+
+ tc.setAttribute(MY_ATTRIBUTE, 0);
+ Asserts.succeedsEventually(ImmutableMap.of("timeout", TIMEOUT_MS), currentSizeAsserter(tc, 1));
+ }
+
+ @Test(groups="Integration")
+ public void testObeysMinAndMaxSize() {
+ tc.resize(4);
+
+ AutoScalerPolicy policy = new AutoScalerPolicy.Builder().metric(MY_ATTRIBUTE)
+ .metricLowerBound(50).metricUpperBound(100)
+ .minPoolSize(2).maxPoolSize(6)
+ .build();
+ tc.addPolicy(policy);
+
+ // Decreases to min-size only
+ tc.setAttribute(MY_ATTRIBUTE, 0);
+ Asserts.succeedsEventually(ImmutableMap.of("timeout", TIMEOUT_MS), currentSizeAsserter(tc, 2));
+
+ // Increases to max-size only
+ tc.setAttribute(MY_ATTRIBUTE, 100000);
+ Asserts.succeedsEventually(ImmutableMap.of("timeout", TIMEOUT_MS), currentSizeAsserter(tc, 6));
+ }
+
+ @Test(groups="Integration",invocationCount=20)
+ public void testWarnsWhenMaxCapReached() {
+ final List<MaxPoolSizeReachedEvent> maxReachedEvents = Lists.newCopyOnWriteArrayList();
+ tc.resize(1);
+
+ BasicNotificationSensor<MaxPoolSizeReachedEvent> maxSizeReachedSensor = AutoScalerPolicy.DEFAULT_MAX_SIZE_REACHED_SENSOR;
+
+ app.subscribe(tc, maxSizeReachedSensor, new SensorEventListener<MaxPoolSizeReachedEvent>() {
+ @Override public void onEvent(SensorEvent<MaxPoolSizeReachedEvent> event) {
+ maxReachedEvents.add(event.getValue());
+ }});
+
+ AutoScalerPolicy policy = new AutoScalerPolicy.Builder().metric(MY_ATTRIBUTE)
+ .metricLowerBound(50).metricUpperBound(100)
+ .maxPoolSize(6)
+ .maxSizeReachedSensor(maxSizeReachedSensor)
+ .build();
+ tc.addPolicy(policy);
+
+ // workload can be handled by 6 servers, so no need to notify: 6 <= (100*6)/50
+ tc.setAttribute(MY_ATTRIBUTE, 600);
+ Asserts.succeedsEventually(ImmutableMap.of("timeout", TIMEOUT_MS), currentSizeAsserter(tc, 6));
+ assertTrue(maxReachedEvents.isEmpty());
+
+ // Increases to above max capacity: would require (100000*6)/100 = 6000
+ tc.setAttribute(MY_ATTRIBUTE, 100000);
+
+ // Assert our listener gets notified (once)
+ Asserts.succeedsEventually(ImmutableMap.of("timeout", TIMEOUT_MS), new Runnable() {
+ public void run() {
+ assertEquals(maxReachedEvents.size(), 1);
+ assertEquals(maxReachedEvents.get(0).getMaxAllowed(), 6);
+ assertEquals(maxReachedEvents.get(0).getCurrentPoolSize(), 6);
+ assertEquals(maxReachedEvents.get(0).getCurrentUnbounded(), 6000);
+ assertEquals(maxReachedEvents.get(0).getMaxUnbounded(), 6000);
+ assertEquals(maxReachedEvents.get(0).getTimeWindow(), 0);
+ }});
+ Asserts.succeedsContinually(new Runnable() {
+ @Override public void run() {
+ assertEquals(maxReachedEvents.size(), 1);
+ }});
+ currentSizeAsserter(tc, 6).run();
+ }
+
+ @Test
+ public void testDestructionState() {
+ tc.resize(1);
+
+ AutoScalerPolicy policy = new AutoScalerPolicy.Builder().metric(MY_ATTRIBUTE).metricLowerBound(50).metricUpperBound(100).build();
+ tc.addPolicy(policy);
+
+ policy.destroy();
+ assertTrue(policy.isDestroyed());
+ assertFalse(policy.isRunning());
+
+ tc.setAttribute(MY_ATTRIBUTE, 100000);
+ Asserts.succeedsContinually(ImmutableMap.of("timeout", SHORT_WAIT_MS), currentSizeAsserter(tc, 1));
+
+ // TODO Could assert all subscriptions have been de-registered as well,
+ // but that requires exposing more things just for testing...
+ }
+
+ @Test
+ public void testSuspendState() {
+ AutoScalerPolicy policy = new AutoScalerPolicy.Builder().metric(MY_ATTRIBUTE).metricLowerBound(50).metricUpperBound(100).build();
+ tc.addPolicy(policy);
+
+ policy.suspend();
+ assertFalse(policy.isRunning());
+ assertFalse(policy.isDestroyed());
+
+ policy.resume();
+ assertTrue(policy.isRunning());
+ assertFalse(policy.isDestroyed());
+ }
+
+ @Test
+ public void testPostSuspendActions() {
+ tc.resize(1);
+
+ AutoScalerPolicy policy = new AutoScalerPolicy.Builder().metric(MY_ATTRIBUTE).metricLowerBound(50).metricUpperBound(100).build();
+ tc.addPolicy(policy);
+
+ policy.suspend();
+
+ tc.setAttribute(MY_ATTRIBUTE, 100000);
+ Asserts.succeedsContinually(ImmutableMap.of("timeout", SHORT_WAIT_MS), currentSizeAsserter(tc, 1));
+ }
+
+ @Test
+ public void testPostResumeActions() {
+ tc.resize(1);
+
+ AutoScalerPolicy policy = new AutoScalerPolicy.Builder().metric(MY_ATTRIBUTE).metricLowerBound(50).metricUpperBound(100).build();
+ tc.addPolicy(policy);
+
+ policy.suspend();
+ policy.resume();
+ tc.setAttribute(MY_ATTRIBUTE, 101);
+ Asserts.succeedsEventually(ImmutableMap.of("timeout", TIMEOUT_MS), currentSizeAsserter(tc, 2));
+ }
+
+ @Test
+ public void testSubscribesToMetricOnSpecifiedEntity() {
+ TestEntity entityWithMetric = app.createAndManageChild(EntitySpec.create(TestEntity.class));
+
+ tc.resize(1);
+
+ AutoScalerPolicy policy = new AutoScalerPolicy.Builder()
+ .metric(TestEntity.SEQUENCE)
+ .entityWithMetric(entityWithMetric)
+ .metricLowerBound(50)
+ .metricUpperBound(100)
+ .build();
+ tc.addPolicy(policy);
+
+ // First confirm that tc is not being listened to for this entity
+ tc.setAttribute(TestEntity.SEQUENCE, 101);
+ Asserts.succeedsContinually(ImmutableMap.of("timeout", SHORT_WAIT_MS), currentSizeAsserter(tc, 1));
+
+ // Then confirm we listen to the correct "entityWithMetric"
+ entityWithMetric.setAttribute(TestEntity.SEQUENCE, 101);
+ Asserts.succeedsEventually(ImmutableMap.of("timeout", TIMEOUT_MS), currentSizeAsserter(tc, 2));
+ }
+}
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/d30ff597/policy/src/test/java/org/apache/brooklyn/policy/autoscaling/AutoScalerPolicyRebindTest.java
----------------------------------------------------------------------
diff --git a/policy/src/test/java/org/apache/brooklyn/policy/autoscaling/AutoScalerPolicyRebindTest.java b/policy/src/test/java/org/apache/brooklyn/policy/autoscaling/AutoScalerPolicyRebindTest.java
new file mode 100644
index 0000000..f8f200b
--- /dev/null
+++ b/policy/src/test/java/org/apache/brooklyn/policy/autoscaling/AutoScalerPolicyRebindTest.java
@@ -0,0 +1,137 @@
+/*
+ * 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.policy.autoscaling;
+
+import static org.testng.Assert.assertEquals;
+
+import java.util.Map;
+import java.util.concurrent.TimeUnit;
+
+import org.apache.brooklyn.api.entity.proxying.EntitySpec;
+import org.apache.brooklyn.api.event.AttributeSensor;
+import org.apache.brooklyn.api.location.LocationSpec;
+import org.apache.brooklyn.test.EntityTestUtils;
+import org.apache.brooklyn.test.entity.TestApplication;
+import org.apache.brooklyn.test.entity.TestEntity;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import brooklyn.entity.basic.EntityInternal;
+import brooklyn.entity.group.DynamicCluster;
+import brooklyn.entity.rebind.RebindTestFixtureWithApp;
+import brooklyn.event.basic.BasicNotificationSensor;
+import brooklyn.event.basic.Sensors;
+
+import org.apache.brooklyn.location.basic.SimulatedLocation;
+
+import brooklyn.util.time.Duration;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Iterables;
+
+public class AutoScalerPolicyRebindTest extends RebindTestFixtureWithApp {
+
+ public static BasicNotificationSensor<Map> POOL_HOT_SENSOR = new BasicNotificationSensor<Map>(
+ Map.class, "AutoScalerPolicyRebindTest.resizablepool.hot", "Pool is over-utilized; it has insufficient resource for current workload");
+ public static BasicNotificationSensor<Map> POOL_COLD_SENSOR = new BasicNotificationSensor<Map>(
+ Map.class, "AutoScalerPolicyRebindTest.resizablepool.cold", "Pool is under-utilized; it has too much resource for current workload");
+ public static BasicNotificationSensor<Map> POOL_OK_SENSOR = new BasicNotificationSensor<Map>(
+ Map.class, "AutoScalerPolicyRebindTest.resizablepool.cold", "Pool utilization is ok; the available resources are fine for the current workload");
+ public static BasicNotificationSensor<MaxPoolSizeReachedEvent> MAX_SIZE_REACHED_SENSOR = new BasicNotificationSensor<MaxPoolSizeReachedEvent>(
+ MaxPoolSizeReachedEvent.class, "AutoScalerPolicyRebindTest.maxSizeReached");
+ public static AttributeSensor<Integer> METRIC_SENSOR = Sensors.newIntegerSensor("AutoScalerPolicyRebindTest.metric");
+
+ private DynamicCluster origCluster;
+ private SimulatedLocation origLoc;
+
+ @BeforeMethod(alwaysRun=true)
+ @Override
+ public void setUp() throws Exception {
+ super.setUp();
+ origLoc = origManagementContext.getLocationManager().createLocation(LocationSpec.create(SimulatedLocation.class));
+ origCluster = origApp.createAndManageChild(EntitySpec.create(DynamicCluster.class)
+ .configure("memberSpec", EntitySpec.create(TestEntity.class)));
+ }
+
+ @Test
+ public void testRestoresAutoScalerConfig() throws Exception {
+ origCluster.addPolicy(AutoScalerPolicy.builder()
+ .name("myname")
+ .metric(METRIC_SENSOR)
+ .entityWithMetric(origCluster)
+ .metricUpperBound(1)
+ .metricLowerBound(2)
+ .minPoolSize(0)
+ .maxPoolSize(3)
+ .minPeriodBetweenExecs(Duration.of(4, TimeUnit.MILLISECONDS))
+ .resizeUpStabilizationDelay(Duration.of(5, TimeUnit.MILLISECONDS))
+ .resizeDownStabilizationDelay(Duration.of(6, TimeUnit.MILLISECONDS))
+ .poolHotSensor(POOL_HOT_SENSOR)
+ .poolColdSensor(POOL_COLD_SENSOR)
+ .poolOkSensor(POOL_OK_SENSOR)
+ .maxSizeReachedSensor(MAX_SIZE_REACHED_SENSOR)
+ .maxReachedNotificationDelay(Duration.of(7, TimeUnit.MILLISECONDS))
+ .buildSpec());
+
+ TestApplication newApp = rebind();
+ DynamicCluster newCluster = (DynamicCluster) Iterables.getOnlyElement(newApp.getChildren());
+ AutoScalerPolicy newPolicy = (AutoScalerPolicy) Iterables.getOnlyElement(newCluster.getPolicies());
+
+ assertEquals(newPolicy.getDisplayName(), "myname");
+ assertEquals(newPolicy.getConfig(AutoScalerPolicy.METRIC), METRIC_SENSOR);
+ assertEquals(newPolicy.getConfig(AutoScalerPolicy.ENTITY_WITH_METRIC), newCluster);
+ assertEquals(newPolicy.getConfig(AutoScalerPolicy.METRIC_UPPER_BOUND), 1);
+ assertEquals(newPolicy.getConfig(AutoScalerPolicy.METRIC_LOWER_BOUND), 2);
+ assertEquals(newPolicy.getConfig(AutoScalerPolicy.MIN_POOL_SIZE), (Integer)0);
+ assertEquals(newPolicy.getConfig(AutoScalerPolicy.MAX_POOL_SIZE), (Integer)3);
+ assertEquals(newPolicy.getConfig(AutoScalerPolicy.MIN_PERIOD_BETWEEN_EXECS), Duration.of(4, TimeUnit.MILLISECONDS));
+ assertEquals(newPolicy.getConfig(AutoScalerPolicy.RESIZE_UP_STABILIZATION_DELAY), Duration.of(5, TimeUnit.MILLISECONDS));
+ assertEquals(newPolicy.getConfig(AutoScalerPolicy.RESIZE_DOWN_STABILIZATION_DELAY), Duration.of(6, TimeUnit.MILLISECONDS));
+ assertEquals(newPolicy.getConfig(AutoScalerPolicy.POOL_HOT_SENSOR), POOL_HOT_SENSOR);
+ assertEquals(newPolicy.getConfig(AutoScalerPolicy.POOL_COLD_SENSOR), POOL_COLD_SENSOR);
+ assertEquals(newPolicy.getConfig(AutoScalerPolicy.POOL_OK_SENSOR), POOL_OK_SENSOR);
+ assertEquals(newPolicy.getConfig(AutoScalerPolicy.MAX_SIZE_REACHED_SENSOR), MAX_SIZE_REACHED_SENSOR);
+ assertEquals(newPolicy.getConfig(AutoScalerPolicy.MAX_REACHED_NOTIFICATION_DELAY), Duration.of(7, TimeUnit.MILLISECONDS));
+ }
+
+ @Test
+ public void testAutoScalerResizesAfterRebind() throws Exception {
+ origCluster.start(ImmutableList.of(origLoc));
+ origCluster.addPolicy(AutoScalerPolicy.builder()
+ .name("myname")
+ .metric(METRIC_SENSOR)
+ .entityWithMetric(origCluster)
+ .metricUpperBound(10)
+ .metricLowerBound(100)
+ .minPoolSize(1)
+ .maxPoolSize(3)
+ .buildSpec());
+
+ TestApplication newApp = rebind();
+ DynamicCluster newCluster = (DynamicCluster) Iterables.getOnlyElement(newApp.getChildren());
+
+ assertEquals(newCluster.getCurrentSize(), (Integer)1);
+
+ ((EntityInternal)newCluster).setAttribute(METRIC_SENSOR, 1000);
+ EntityTestUtils.assertGroupSizeEqualsEventually(newCluster, 3);
+
+ ((EntityInternal)newCluster).setAttribute(METRIC_SENSOR, 1);
+ EntityTestUtils.assertGroupSizeEqualsEventually(newCluster, 1);
+ }
+}
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/d30ff597/policy/src/test/java/org/apache/brooklyn/policy/autoscaling/AutoScalerPolicyReconfigurationTest.java
----------------------------------------------------------------------
diff --git a/policy/src/test/java/org/apache/brooklyn/policy/autoscaling/AutoScalerPolicyReconfigurationTest.java b/policy/src/test/java/org/apache/brooklyn/policy/autoscaling/AutoScalerPolicyReconfigurationTest.java
new file mode 100644
index 0000000..f91adee
--- /dev/null
+++ b/policy/src/test/java/org/apache/brooklyn/policy/autoscaling/AutoScalerPolicyReconfigurationTest.java
@@ -0,0 +1,190 @@
+/*
+ * 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.policy.autoscaling;
+
+import static org.apache.brooklyn.policy.autoscaling.AutoScalerPolicyTest.currentSizeAsserter;
+
+import org.apache.brooklyn.api.entity.proxying.EntitySpec;
+import org.apache.brooklyn.api.event.AttributeSensor;
+import org.apache.brooklyn.test.entity.TestApplication;
+import org.apache.brooklyn.test.entity.TestCluster;
+import org.testng.annotations.AfterMethod;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import brooklyn.entity.basic.Entities;
+import brooklyn.event.basic.Sensors;
+import brooklyn.test.Asserts;
+import brooklyn.util.time.Duration;
+
+import com.google.common.collect.ImmutableMap;
+
+public class AutoScalerPolicyReconfigurationTest {
+
+ private static long TIMEOUT_MS = 10000;
+
+ private static final AttributeSensor<Integer> MY_ATTRIBUTE = Sensors.newIntegerSensor("autoscaler.test.intAttrib");
+ TestApplication app;
+ TestCluster tc;
+
+ @BeforeMethod(alwaysRun=true)
+ public void before() throws Exception {
+ app = TestApplication.Factory.newManagedInstanceForTests();
+ tc = app.createAndManageChild(EntitySpec.create(TestCluster.class)
+ .configure("initialSize", 1));
+ }
+
+ @AfterMethod(alwaysRun=true)
+ public void tearDown() throws Exception {
+ if (app != null) Entities.destroyAll(app.getManagementContext());
+ }
+
+ @Test
+ public void testIncreaseMinPoolSizeCausesImmediateGrowth() {
+ tc.resize(2);
+
+ AutoScalerPolicy policy = new AutoScalerPolicy.Builder().metric(MY_ATTRIBUTE)
+ .metricLowerBound(50).metricUpperBound(100)
+ .minPoolSize(2)
+ .build();
+ tc.addPolicy(policy);
+
+ policy.config().set(AutoScalerPolicy.MIN_POOL_SIZE, 3);
+
+ Asserts.succeedsEventually(ImmutableMap.of("timeout", TIMEOUT_MS), currentSizeAsserter(tc, 3));
+ }
+
+ @Test
+ public void testDecreaseMinPoolSizeAllowsSubsequentShrink() {
+ tc.resize(4);
+
+ AutoScalerPolicy policy = new AutoScalerPolicy.Builder().metric(MY_ATTRIBUTE)
+ .metricLowerBound(50).metricUpperBound(100)
+ .minPoolSize(2)
+ .build();
+ tc.addPolicy(policy);
+
+ // 25*4 = 100 -> 2 nodes at 50 each
+ tc.setAttribute(MY_ATTRIBUTE, 25);
+ Asserts.succeedsEventually(ImmutableMap.of("timeout", TIMEOUT_MS), currentSizeAsserter(tc, 2));
+
+ // Decreases to new min-size
+ policy.config().set(AutoScalerPolicy.MIN_POOL_SIZE, 1);
+ tc.setAttribute(MY_ATTRIBUTE, 0);
+ Asserts.succeedsEventually(ImmutableMap.of("timeout", TIMEOUT_MS), currentSizeAsserter(tc, 1));
+ }
+
+ @Test
+ public void testDecreaseMaxPoolSizeCausesImmediateShrink() {
+ tc.resize(6);
+
+ AutoScalerPolicy policy = new AutoScalerPolicy.Builder().metric(MY_ATTRIBUTE)
+ .metricLowerBound(50).metricUpperBound(100)
+ .maxPoolSize(6)
+ .build();
+ tc.addPolicy(policy);
+
+ policy.config().set(AutoScalerPolicy.MAX_POOL_SIZE, 4);
+
+ Asserts.succeedsEventually(ImmutableMap.of("timeout", TIMEOUT_MS), currentSizeAsserter(tc, 4));
+ }
+
+ @Test
+ public void testIncreaseMaxPoolSizeAllowsSubsequentGrowth() {
+ tc.resize(3);
+
+ AutoScalerPolicy policy = new AutoScalerPolicy.Builder().metric(MY_ATTRIBUTE)
+ .metricLowerBound(50).metricUpperBound(100)
+ .maxPoolSize(6)
+ .build();
+ tc.addPolicy(policy);
+
+ // 200*3 = 600 -> 6 nodes at 100 each
+ tc.setAttribute(MY_ATTRIBUTE, 200);
+ Asserts.succeedsEventually(ImmutableMap.of("timeout", TIMEOUT_MS), currentSizeAsserter(tc, 6));
+
+ policy.config().set(AutoScalerPolicy.MAX_POOL_SIZE, 8);
+
+ // Increases to max-size only
+ tc.setAttribute(MY_ATTRIBUTE, 100000);
+ Asserts.succeedsEventually(ImmutableMap.of("timeout", TIMEOUT_MS), currentSizeAsserter(tc, 8));
+ }
+
+ @Test
+ public void testReconfigureMetricLowerBound() {
+ tc.resize(2);
+
+ AutoScalerPolicy policy = new AutoScalerPolicy.Builder().metric(MY_ATTRIBUTE)
+ .metricLowerBound(50).metricUpperBound(100)
+ .build();
+ tc.addPolicy(policy);
+
+ policy.config().set(AutoScalerPolicy.METRIC_LOWER_BOUND, 51);
+
+ tc.setAttribute(MY_ATTRIBUTE, 50);
+ Asserts.succeedsEventually(ImmutableMap.of("timeout", TIMEOUT_MS), currentSizeAsserter(tc, 1));
+ }
+
+ @Test
+ public void testReconfigureMetricUpperBound() {
+ tc.resize(1);
+
+ AutoScalerPolicy policy = new AutoScalerPolicy.Builder().metric(MY_ATTRIBUTE)
+ .metricLowerBound(50).metricUpperBound(100)
+ .build();
+ tc.addPolicy(policy);
+
+ policy.config().set(AutoScalerPolicy.METRIC_UPPER_BOUND, 99);
+
+ tc.setAttribute(MY_ATTRIBUTE, 100);
+ Asserts.succeedsEventually(ImmutableMap.of("timeout", TIMEOUT_MS), currentSizeAsserter(tc, 2));
+ }
+
+ @Test
+ public void testReconfigureResizeUpStabilizationDelay() {
+ tc.resize(1);
+
+ AutoScalerPolicy policy = new AutoScalerPolicy.Builder().metric(MY_ATTRIBUTE)
+ .metricLowerBound(50).metricUpperBound(100)
+ .resizeUpStabilizationDelay(Duration.TWO_MINUTES)
+ .build();
+ tc.addPolicy(policy);
+
+ policy.config().set(AutoScalerPolicy.RESIZE_UP_STABILIZATION_DELAY, Duration.ZERO);
+
+ tc.setAttribute(MY_ATTRIBUTE, 101);
+ Asserts.succeedsEventually(ImmutableMap.of("timeout", TIMEOUT_MS), currentSizeAsserter(tc, 2));
+ }
+
+ @Test
+ public void testReconfigureResizeDownStabilizationDelay() {
+ tc.resize(2);
+
+ AutoScalerPolicy policy = new AutoScalerPolicy.Builder().metric(MY_ATTRIBUTE)
+ .metricLowerBound(50).metricUpperBound(100)
+ .resizeDownStabilizationDelay(Duration.TWO_MINUTES)
+ .build();
+ tc.addPolicy(policy);
+
+ policy.config().set(AutoScalerPolicy.RESIZE_DOWN_STABILIZATION_DELAY, Duration.ZERO);
+
+ tc.setAttribute(MY_ATTRIBUTE, 1);
+ Asserts.succeedsEventually(ImmutableMap.of("timeout", TIMEOUT_MS), currentSizeAsserter(tc, 1));
+ }
+}
[03/24] incubator-brooklyn git commit: escape user input to sql
Posted by he...@apache.org.
escape user input to sql
Project: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/commit/ac82d23e
Tree: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/tree/ac82d23e
Diff: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/diff/ac82d23e
Branch: refs/heads/master
Commit: ac82d23e49cdeb0ecbb3e77872bb26c0322c02d9
Parents: 9458e15
Author: Robert Moss <ro...@gmail.com>
Authored: Mon Aug 17 14:54:43 2015 +0100
Committer: Robert Moss <ro...@gmail.com>
Committed: Mon Aug 17 14:54:43 2015 +0100
----------------------------------------------------------------------
.../entity/database/postgresql/PostgreSqlSshDriver.java | 8 +++++---
1 file changed, 5 insertions(+), 3 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/ac82d23e/software/database/src/main/java/brooklyn/entity/database/postgresql/PostgreSqlSshDriver.java
----------------------------------------------------------------------
diff --git a/software/database/src/main/java/brooklyn/entity/database/postgresql/PostgreSqlSshDriver.java b/software/database/src/main/java/brooklyn/entity/database/postgresql/PostgreSqlSshDriver.java
index 18dc9a4..0242af1 100644
--- a/software/database/src/main/java/brooklyn/entity/database/postgresql/PostgreSqlSshDriver.java
+++ b/software/database/src/main/java/brooklyn/entity/database/postgresql/PostgreSqlSshDriver.java
@@ -62,6 +62,7 @@ import brooklyn.util.task.ssh.SshTasks;
import brooklyn.util.task.ssh.SshTasks.OnFailingTask;
import brooklyn.util.task.system.ProcessTaskWrapper;
import brooklyn.util.text.Identifiers;
+import brooklyn.util.text.StringEscapes;
import brooklyn.util.text.StringFunctions;
import brooklyn.util.text.Strings;
@@ -297,12 +298,13 @@ public class PostgreSqlSshDriver extends AbstractSoftwareProcessSshDriver implem
DynamicTasks.waitForLast();
String createUserCommand = String.format(
"\"CREATE USER %s WITH PASSWORD '%s'; \"",
- entity.getConfig(PostgreSqlNode.USERNAME), getUserPassword()
+ StringEscapes.escapeSql(entity.getConfig(PostgreSqlNode.USERNAME)),
+ StringEscapes.escapeSql(getUserPassword())
);
String createDatabaseCommand = String.format(
"\"CREATE DATABASE %s OWNER %s\"",
- entity.getConfig(PostgreSqlNode.DATABASE),
- entity.getConfig(PostgreSqlNode.USERNAME));
+ StringEscapes.escapeSql(entity.getConfig(PostgreSqlNode.DATABASE)),
+ StringEscapes.escapeSql(entity.getConfig(PostgreSqlNode.USERNAME)));
newScript("initializing user and database")
.body.append(
"cd " + getInstallDir(),
[14/24] incubator-brooklyn git commit: [BROOKLYN-162] Renaming
package policy
Posted by he...@apache.org.
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/d30ff597/policy/src/main/java/org/apache/brooklyn/policy/ha/ServiceFailureDetector.java
----------------------------------------------------------------------
diff --git a/policy/src/main/java/org/apache/brooklyn/policy/ha/ServiceFailureDetector.java b/policy/src/main/java/org/apache/brooklyn/policy/ha/ServiceFailureDetector.java
new file mode 100644
index 0000000..80feb4c
--- /dev/null
+++ b/policy/src/main/java/org/apache/brooklyn/policy/ha/ServiceFailureDetector.java
@@ -0,0 +1,340 @@
+/*
+ * 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.policy.ha;
+
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import org.apache.brooklyn.api.event.SensorEvent;
+import org.apache.brooklyn.core.util.config.ConfigBag;
+import org.apache.brooklyn.core.util.flags.SetFromFlag;
+import org.apache.brooklyn.core.util.task.BasicTask;
+import org.apache.brooklyn.core.util.task.ScheduledTask;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import brooklyn.config.ConfigKey;
+import brooklyn.entity.basic.Attributes;
+import brooklyn.entity.basic.ConfigKeys;
+import brooklyn.entity.basic.EntityInternal;
+import brooklyn.entity.basic.Lifecycle;
+import brooklyn.entity.basic.ServiceStateLogic;
+import brooklyn.entity.basic.ServiceStateLogic.ComputeServiceState;
+import brooklyn.event.basic.BasicConfigKey;
+import brooklyn.event.basic.BasicNotificationSensor;
+import org.apache.brooklyn.policy.ha.HASensors.FailureDescriptor;
+import brooklyn.util.collections.MutableMap;
+import brooklyn.util.exceptions.Exceptions;
+import brooklyn.util.time.Duration;
+import brooklyn.util.time.Time;
+
+/**
+ * Emits {@link HASensors#ENTITY_FAILED} whenever the parent's default logic ({@link ComputeServiceState}) would detect a problem,
+ * and similarly {@link HASensors#ENTITY_RECOVERED} when recovered.
+ * <p>
+ * gives more control over suppressing {@link Lifecycle#ON_FIRE},
+ * for some period of time
+ * (or until another process manually sets {@link Attributes#SERVICE_STATE_ACTUAL} to {@value Lifecycle#ON_FIRE},
+ * which this enricher will not clear until all problems have gone away)
+ */
+//@Catalog(name="Service Failure Detector", description="HA policy for deteting failure of a service")
+public class ServiceFailureDetector extends ServiceStateLogic.ComputeServiceState {
+
+ // TODO Remove duplication between this and MemberFailureDetectionPolicy.
+ // The latter could be re-written to use this. Or could even be deprecated
+ // in favour of this.
+
+ public enum LastPublished {
+ NONE,
+ FAILED,
+ RECOVERED;
+ }
+
+ private static final Logger LOG = LoggerFactory.getLogger(ServiceFailureDetector.class);
+
+ private static final long MIN_PERIOD_BETWEEN_EXECS_MILLIS = 100;
+
+ public static final BasicNotificationSensor<FailureDescriptor> ENTITY_FAILED = HASensors.ENTITY_FAILED;
+
+ @SetFromFlag("onlyReportIfPreviouslyUp")
+ public static final ConfigKey<Boolean> ENTITY_FAILED_ONLY_IF_PREVIOUSLY_UP = ConfigKeys.newBooleanConfigKey("onlyReportIfPreviouslyUp",
+ "Prevents the policy from emitting ENTITY_FAILED if the entity fails on startup (ie has never been up)", true);
+
+ public static final ConfigKey<Boolean> MONITOR_SERVICE_PROBLEMS = ConfigKeys.newBooleanConfigKey("monitorServiceProblems",
+ "Whether to monitor service problems, and emit on failures there (if set to false, this monitors only service up)", true);
+
+ @SetFromFlag("serviceOnFireStabilizationDelay")
+ public static final ConfigKey<Duration> SERVICE_ON_FIRE_STABILIZATION_DELAY = BasicConfigKey.builder(Duration.class)
+ .name("serviceOnFire.stabilizationDelay")
+ .description("Time period for which the service must be consistently down for (e.g. doesn't report down-up-down) before concluding ON_FIRE")
+ .defaultValue(Duration.ZERO)
+ .build();
+
+ @SetFromFlag("entityFailedStabilizationDelay")
+ public static final ConfigKey<Duration> ENTITY_FAILED_STABILIZATION_DELAY = BasicConfigKey.builder(Duration.class)
+ .name("entityFailed.stabilizationDelay")
+ .description("Time period for which the service must be consistently down for (e.g. doesn't report down-up-down) before emitting ENTITY_FAILED")
+ .defaultValue(Duration.ZERO)
+ .build();
+
+ @SetFromFlag("entityRecoveredStabilizationDelay")
+ public static final ConfigKey<Duration> ENTITY_RECOVERED_STABILIZATION_DELAY = BasicConfigKey.builder(Duration.class)
+ .name("entityRecovered.stabilizationDelay")
+ .description("For a failed entity, time period for which the service must be consistently up for (e.g. doesn't report up-down-up) before emitting ENTITY_RECOVERED")
+ .defaultValue(Duration.ZERO)
+ .build();
+
+ @SetFromFlag("entityFailedRepublishTime")
+ public static final ConfigKey<Duration> ENTITY_FAILED_REPUBLISH_TIME = BasicConfigKey.builder(Duration.class)
+ .name("entityFailed.republishTime")
+ .description("Publish failed state periodically at the specified intervals, null to disable.")
+ .build();
+
+ protected Long firstUpTime;
+
+ protected Long currentFailureStartTime = null;
+ protected Long currentRecoveryStartTime = null;
+
+ protected Long publishEntityFailedTime = null;
+ protected Long publishEntityRecoveredTime = null;
+ protected Long setEntityOnFireTime = null;
+
+ protected LastPublished lastPublished = LastPublished.NONE;
+
+ private final AtomicBoolean executorQueued = new AtomicBoolean(false);
+ private volatile long executorTime = 0;
+
+ /**
+ * TODO Really don't want this mutex!
+ * ServiceStateLogic.setExpectedState() will call into `onEvent(null)`, so could get concurrent calls.
+ * How to handle that? I don't think `ServiceStateLogic.setExpectedState` should be making the call, but
+ * presumably that is their to remove a race condition so it is set before method returns. Caller shouldn't
+ * rely on that though.
+ * e.g. see `ServiceFailureDetectorTest.testNotifiedOfFailureOnStateOnFire`, where we get two notifications.
+ */
+ private final Object mutex = new Object();
+
+ public ServiceFailureDetector() {
+ this(new ConfigBag());
+ }
+
+ public ServiceFailureDetector(Map<String,?> flags) {
+ this(new ConfigBag().putAll(flags));
+ }
+
+ public ServiceFailureDetector(ConfigBag configBag) {
+ // TODO hierarchy should use ConfigBag, and not change flags
+ super(configBag.getAllConfigMutable());
+ }
+
+ @Override
+ public void onEvent(SensorEvent<Object> event) {
+ if (firstUpTime==null) {
+ if (event!=null && Attributes.SERVICE_UP.equals(event.getSensor()) && Boolean.TRUE.equals(event.getValue())) {
+ firstUpTime = event.getTimestamp();
+ } else if (event == null && Boolean.TRUE.equals(entity.getAttribute(Attributes.SERVICE_UP))) {
+ // If this enricher is registered after the entity is up, then we'll get a "synthetic" onEvent(null)
+ firstUpTime = System.currentTimeMillis();
+ }
+ }
+
+ super.onEvent(event);
+ }
+
+ @Override
+ protected void setActualState(Lifecycle state) {
+ long now = System.currentTimeMillis();
+
+ synchronized (mutex) {
+ if (state==Lifecycle.ON_FIRE) {
+ if (lastPublished == LastPublished.FAILED) {
+ if (currentRecoveryStartTime != null) {
+ if (LOG.isDebugEnabled()) LOG.debug("{} health-check for {}, component was recovering, now failing: {}", new Object[] {this, entity, getExplanation(state)});
+ currentRecoveryStartTime = null;
+ publishEntityRecoveredTime = null;
+ } else {
+ if (LOG.isTraceEnabled()) LOG.trace("{} health-check for {}, component still failed: {}", new Object[] {this, entity, getExplanation(state)});
+ }
+ } else {
+ if (firstUpTime == null && getConfig(ENTITY_FAILED_ONLY_IF_PREVIOUSLY_UP)) {
+ // suppress; won't publish
+ } else if (currentFailureStartTime == null) {
+ if (LOG.isDebugEnabled()) LOG.debug("{} health-check for {}, component now failing: {}", new Object[] {this, entity, getExplanation(state)});
+ currentFailureStartTime = now;
+ publishEntityFailedTime = currentFailureStartTime + getConfig(ENTITY_FAILED_STABILIZATION_DELAY).toMilliseconds();
+ } else {
+ if (LOG.isTraceEnabled()) LOG.trace("{} health-check for {}, component continuing failing: {}", new Object[] {this, entity, getExplanation(state)});
+ }
+ }
+ if (setEntityOnFireTime == null) {
+ setEntityOnFireTime = now + getConfig(SERVICE_ON_FIRE_STABILIZATION_DELAY).toMilliseconds();
+ }
+ currentRecoveryStartTime = null;
+ publishEntityRecoveredTime = null;
+
+ } else if (state == Lifecycle.RUNNING) {
+ if (lastPublished == LastPublished.FAILED) {
+ if (currentRecoveryStartTime == null) {
+ if (LOG.isDebugEnabled()) LOG.debug("{} health-check for {}, component now recovering: {}", new Object[] {this, entity, getExplanation(state)});
+ currentRecoveryStartTime = now;
+ publishEntityRecoveredTime = currentRecoveryStartTime + getConfig(ENTITY_RECOVERED_STABILIZATION_DELAY).toMilliseconds();
+ } else {
+ if (LOG.isTraceEnabled()) LOG.trace("{} health-check for {}, component continuing recovering: {}", new Object[] {this, entity, getExplanation(state)});
+ }
+ } else {
+ if (currentFailureStartTime != null) {
+ if (LOG.isDebugEnabled()) LOG.debug("{} health-check for {}, component was failing, now healthy: {}", new Object[] {this, entity, getExplanation(state)});
+ } else {
+ if (LOG.isTraceEnabled()) LOG.trace("{} health-check for {}, component still healthy: {}", new Object[] {this, entity, getExplanation(state)});
+ }
+ }
+ currentFailureStartTime = null;
+ publishEntityFailedTime = null;
+ setEntityOnFireTime = null;
+
+ } else {
+ if (LOG.isTraceEnabled()) LOG.trace("{} health-check for {}, in unconfirmed sate: {}", new Object[] {this, entity, getExplanation(state)});
+ }
+
+ long recomputeIn = Long.MAX_VALUE; // For whether to call recomputeAfterDelay
+
+ if (publishEntityFailedTime != null) {
+ long delayBeforeCheck = publishEntityFailedTime - now;
+ if (delayBeforeCheck<=0) {
+ if (LOG.isDebugEnabled()) LOG.debug("{} publishing failed (state={}; currentFailureStartTime={}; now={}",
+ new Object[] {this, state, Time.makeDateString(currentFailureStartTime), Time.makeDateString(now)});
+ Duration republishDelay = getConfig(ENTITY_FAILED_REPUBLISH_TIME);
+ if (republishDelay == null) {
+ publishEntityFailedTime = null;
+ } else {
+ publishEntityFailedTime = now + republishDelay.toMilliseconds();
+ recomputeIn = Math.min(recomputeIn, republishDelay.toMilliseconds());
+ }
+ lastPublished = LastPublished.FAILED;
+ entity.emit(HASensors.ENTITY_FAILED, new HASensors.FailureDescriptor(entity, getFailureDescription(now)));
+ } else {
+ recomputeIn = Math.min(recomputeIn, delayBeforeCheck);
+ }
+ } else if (publishEntityRecoveredTime != null) {
+ long delayBeforeCheck = publishEntityRecoveredTime - now;
+ if (delayBeforeCheck<=0) {
+ if (LOG.isDebugEnabled()) LOG.debug("{} publishing recovered (state={}; currentRecoveryStartTime={}; now={}",
+ new Object[] {this, state, Time.makeDateString(currentRecoveryStartTime), Time.makeDateString(now)});
+ publishEntityRecoveredTime = null;
+ lastPublished = LastPublished.RECOVERED;
+ entity.emit(HASensors.ENTITY_RECOVERED, new HASensors.FailureDescriptor(entity, null));
+ } else {
+ recomputeIn = Math.min(recomputeIn, delayBeforeCheck);
+ }
+ }
+
+ if (setEntityOnFireTime != null) {
+ long delayBeforeCheck = setEntityOnFireTime - now;
+ if (delayBeforeCheck<=0) {
+ if (LOG.isDebugEnabled()) LOG.debug("{} setting on-fire, now that deferred period has passed (state={})",
+ new Object[] {this, state});
+ setEntityOnFireTime = null;
+ super.setActualState(state);
+ } else {
+ recomputeIn = Math.min(recomputeIn, delayBeforeCheck);
+ }
+ } else {
+ super.setActualState(state);
+ }
+
+ if (recomputeIn < Long.MAX_VALUE) {
+ recomputeAfterDelay(recomputeIn);
+ }
+ }
+ }
+
+ protected String getExplanation(Lifecycle state) {
+ Duration serviceFailedStabilizationDelay = getConfig(ENTITY_FAILED_STABILIZATION_DELAY);
+ Duration serviceRecoveredStabilizationDelay = getConfig(ENTITY_RECOVERED_STABILIZATION_DELAY);
+
+ return String.format("location=%s; status=%s; lastPublished=%s; timeNow=%s; "+
+ "currentFailurePeriod=%s; currentRecoveryPeriod=%s",
+ entity.getLocations(),
+ (state != null ? state : "<unreported>"),
+ lastPublished,
+ Time.makeDateString(System.currentTimeMillis()),
+ (currentFailureStartTime != null ? getTimeStringSince(currentFailureStartTime) : "<none>") + " (stabilization "+Time.makeTimeStringRounded(serviceFailedStabilizationDelay) + ")",
+ (currentRecoveryStartTime != null ? getTimeStringSince(currentRecoveryStartTime) : "<none>") + " (stabilization "+Time.makeTimeStringRounded(serviceRecoveredStabilizationDelay) + ")");
+ }
+
+ private String getFailureDescription(long now) {
+ String description = null;
+ Map<String, Object> serviceProblems = entity.getAttribute(Attributes.SERVICE_PROBLEMS);
+ if (serviceProblems!=null && !serviceProblems.isEmpty()) {
+ Entry<String, Object> problem = serviceProblems.entrySet().iterator().next();
+ description = problem.getKey()+": "+problem.getValue();
+ if (serviceProblems.size()>1) {
+ description = serviceProblems.size()+" service problems, including "+description;
+ } else {
+ description = "service problem: "+description;
+ }
+ } else if (Boolean.FALSE.equals(entity.getAttribute(Attributes.SERVICE_UP))) {
+ description = "service not up";
+ } else {
+ description = "service failure detected";
+ }
+ if (publishEntityFailedTime!=null && currentFailureStartTime!=null && publishEntityFailedTime > currentFailureStartTime)
+ description = " (stabilized for "+Duration.of(now - currentFailureStartTime, TimeUnit.MILLISECONDS)+")";
+ return description;
+ }
+
+ @SuppressWarnings({ "unchecked", "rawtypes" })
+ protected void recomputeAfterDelay(long delay) {
+ if (isRunning() && executorQueued.compareAndSet(false, true)) {
+ long now = System.currentTimeMillis();
+ delay = Math.max(0, Math.max(delay, (executorTime + MIN_PERIOD_BETWEEN_EXECS_MILLIS) - now));
+ if (LOG.isTraceEnabled()) LOG.trace("{} scheduling publish in {}ms", this, delay);
+
+ Runnable job = new Runnable() {
+ @Override public void run() {
+ try {
+ executorTime = System.currentTimeMillis();
+ executorQueued.set(false);
+
+ onEvent(null);
+
+ } catch (Exception e) {
+ if (isRunning()) {
+ LOG.error("Error in enricher "+this+": "+e, e);
+ } else {
+ if (LOG.isDebugEnabled()) LOG.debug("Error in enricher "+this+" (but no longer running): "+e, e);
+ }
+ } catch (Throwable t) {
+ LOG.error("Error in enricher "+this+": "+t, t);
+ throw Exceptions.propagate(t);
+ }
+ }
+ };
+
+ ScheduledTask task = new ScheduledTask(MutableMap.of("delay", Duration.of(delay, TimeUnit.MILLISECONDS)), new BasicTask(job));
+ ((EntityInternal)entity).getExecutionContext().submit(task);
+ }
+ }
+
+ private String getTimeStringSince(Long time) {
+ return time == null ? null : Time.makeTimeStringRounded(System.currentTimeMillis() - time);
+ }
+}
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/d30ff597/policy/src/main/java/org/apache/brooklyn/policy/ha/ServiceReplacer.java
----------------------------------------------------------------------
diff --git a/policy/src/main/java/org/apache/brooklyn/policy/ha/ServiceReplacer.java b/policy/src/main/java/org/apache/brooklyn/policy/ha/ServiceReplacer.java
new file mode 100644
index 0000000..d6989d9
--- /dev/null
+++ b/policy/src/main/java/org/apache/brooklyn/policy/ha/ServiceReplacer.java
@@ -0,0 +1,216 @@
+/*
+ * 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.policy.ha;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.TimeUnit;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.apache.brooklyn.api.catalog.Catalog;
+import org.apache.brooklyn.api.entity.Entity;
+import org.apache.brooklyn.api.entity.Group;
+import org.apache.brooklyn.api.entity.basic.EntityLocal;
+import org.apache.brooklyn.api.event.Sensor;
+import org.apache.brooklyn.api.event.SensorEvent;
+import org.apache.brooklyn.api.event.SensorEventListener;
+import org.apache.brooklyn.core.policy.basic.AbstractPolicy;
+import org.apache.brooklyn.core.util.config.ConfigBag;
+import org.apache.brooklyn.core.util.flags.SetFromFlag;
+
+import brooklyn.config.ConfigKey;
+import brooklyn.entity.basic.ConfigKeys;
+import brooklyn.entity.basic.Entities;
+import brooklyn.entity.basic.EntityInternal;
+import brooklyn.entity.basic.ServiceStateLogic.ServiceProblemsLogic;
+import brooklyn.entity.group.StopFailedRuntimeException;
+import brooklyn.entity.trait.MemberReplaceable;
+import brooklyn.event.basic.BasicConfigKey;
+import brooklyn.event.basic.BasicNotificationSensor;
+
+import org.apache.brooklyn.policy.ha.HASensors.FailureDescriptor;
+
+import brooklyn.util.collections.MutableMap;
+import brooklyn.util.exceptions.Exceptions;
+
+import com.google.common.base.Ticker;
+import com.google.common.collect.Lists;
+
+/** attaches to a DynamicCluster and replaces a failed member in response to HASensors.ENTITY_FAILED or other sensor;
+ * if this fails, it sets the Cluster state to on-fire */
+@Catalog(name="Service Replacer", description="HA policy for replacing a failed member of a group")
+public class ServiceReplacer extends AbstractPolicy {
+
+ private static final Logger LOG = LoggerFactory.getLogger(ServiceReplacer.class);
+
+ // TODO if there are multiple failures perhaps we should abort quickly
+
+ public static final BasicNotificationSensor<FailureDescriptor> ENTITY_REPLACEMENT_FAILED = new BasicNotificationSensor<FailureDescriptor>(
+ FailureDescriptor.class, "ha.entityFailed.replacement", "Indicates that an entity replacement attempt has failed");
+
+ @SetFromFlag("setOnFireOnFailure")
+ public static final ConfigKey<Boolean> SET_ON_FIRE_ON_FAILURE = ConfigKeys.newBooleanConfigKey("setOnFireOnFailure", "", true);
+
+ /** monitors this sensor, by default ENTITY_RESTART_FAILED */
+ @SetFromFlag("failureSensorToMonitor")
+ @SuppressWarnings("rawtypes")
+ public static final ConfigKey<Sensor> FAILURE_SENSOR_TO_MONITOR = new BasicConfigKey<Sensor>(Sensor.class, "failureSensorToMonitor", "", ServiceRestarter.ENTITY_RESTART_FAILED);
+
+ /** skips replace if replacement has failed this many times failure re-occurs within this time interval */
+ @SetFromFlag("failOnRecurringFailuresInThisDuration")
+ public static final ConfigKey<Long> FAIL_ON_RECURRING_FAILURES_IN_THIS_DURATION = ConfigKeys.newLongConfigKey(
+ "failOnRecurringFailuresInThisDuration",
+ "abandon replace if replacement has failed many times within this time interval",
+ 5*60*1000L);
+
+ /** skips replace if replacement has failed this many times failure re-occurs within this time interval */
+ @SetFromFlag("failOnNumRecurringFailures")
+ public static final ConfigKey<Integer> FAIL_ON_NUM_RECURRING_FAILURES = ConfigKeys.newIntegerConfigKey(
+ "failOnNumRecurringFailures",
+ "abandon replace if replacement has failed this many times (100% of attempts) within the time interval",
+ 5);
+
+ @SetFromFlag("ticker")
+ public static final ConfigKey<Ticker> TICKER = ConfigKeys.newConfigKey(Ticker.class,
+ "ticker",
+ "A time source (defaults to system-clock, which is almost certainly what's wanted, except in tests)",
+ null);
+
+ protected final List<Long> consecutiveReplacementFailureTimes = Lists.newCopyOnWriteArrayList();
+
+ public ServiceReplacer() {
+ this(new ConfigBag());
+ }
+
+ public ServiceReplacer(Map<String,?> flags) {
+ this(new ConfigBag().putAll(flags));
+ }
+
+ public ServiceReplacer(ConfigBag configBag) {
+ // TODO hierarchy should use ConfigBag, and not change flags
+ super(configBag.getAllConfigMutable());
+ }
+
+ public ServiceReplacer(Sensor<?> failureSensorToMonitor) {
+ this(new ConfigBag().configure(FAILURE_SENSOR_TO_MONITOR, failureSensorToMonitor));
+ }
+
+ @Override
+ public void setEntity(final EntityLocal entity) {
+ checkArgument(entity instanceof MemberReplaceable, "ServiceReplacer must take a MemberReplaceable, not %s", entity);
+ Sensor<?> failureSensorToMonitor = checkNotNull(getConfig(FAILURE_SENSOR_TO_MONITOR), "failureSensorToMonitor");
+
+ super.setEntity(entity);
+
+ subscribeToMembers((Group)entity, failureSensorToMonitor, new SensorEventListener<Object>() {
+ @Override public void onEvent(final SensorEvent<Object> event) {
+ // Must execute in another thread - if we called entity.replaceMember in the event-listener's thread
+ // then we'd block all other events being delivered to this entity's other subscribers.
+ // Relies on synchronization of `onDetectedFailure`.
+ // See same pattern used in ServiceRestarter.
+
+ // TODO Could use BasicExecutionManager.setTaskSchedulerForTag to prevent race of two
+ // events being received in rapid succession, and onDetectedFailure being executed out-of-order
+ // for them; or could write events to a blocking queue and have onDetectedFailure read from that.
+
+ if (isRunning()) {
+ LOG.warn("ServiceReplacer notified; dispatching job for "+entity+" ("+event.getValue()+")");
+ ((EntityInternal)entity).getExecutionContext().submit(MutableMap.of(), new Runnable() {
+ @Override public void run() {
+ onDetectedFailure(event);
+ }});
+ } else {
+ LOG.warn("ServiceReplacer not running, so not acting on failure detected at "+entity+" ("+event.getValue()+", child of "+entity+")");
+ }
+ }
+ });
+ }
+
+ // TODO semaphores would be better to allow at-most-one-blocking behaviour
+ protected synchronized void onDetectedFailure(SensorEvent<Object> event) {
+ final Entity failedEntity = event.getSource();
+ final Object reason = event.getValue();
+
+ if (isSuspended()) {
+ LOG.warn("ServiceReplacer suspended, so not acting on failure detected at "+failedEntity+" ("+reason+", child of "+entity+")");
+ return;
+ }
+
+ if (isRepeatedlyFailingTooMuch()) {
+ LOG.error("ServiceReplacer not acting on failure detected at "+failedEntity+" ("+reason+", child of "+entity+"), because too many recent replacement failures");
+ return;
+ }
+
+ LOG.warn("ServiceReplacer acting on failure detected at "+failedEntity+" ("+reason+", child of "+entity+")");
+ ((EntityInternal)entity).getManagementSupport().getExecutionContext().submit(MutableMap.of(), new Runnable() {
+
+ @Override
+ public void run() {
+ try {
+ Entities.invokeEffectorWithArgs(entity, entity, MemberReplaceable.REPLACE_MEMBER, failedEntity.getId()).get();
+ consecutiveReplacementFailureTimes.clear();
+ } catch (Exception e) {
+ if (Exceptions.getFirstThrowableOfType(e, StopFailedRuntimeException.class) != null) {
+ LOG.info("ServiceReplacer: ignoring error reported from stopping failed node "+failedEntity);
+ return;
+ }
+ onReplacementFailed("Replace failure ("+Exceptions.collapseText(e)+") at "+entity+": "+reason);
+ }
+ }
+ });
+ }
+
+ private boolean isRepeatedlyFailingTooMuch() {
+ Integer failOnNumRecurringFailures = getConfig(FAIL_ON_NUM_RECURRING_FAILURES);
+ long failOnRecurringFailuresInThisDuration = getConfig(FAIL_ON_RECURRING_FAILURES_IN_THIS_DURATION);
+ long oldestPermitted = currentTimeMillis() - failOnRecurringFailuresInThisDuration;
+
+ // trim old ones
+ for (Iterator<Long> iter = consecutiveReplacementFailureTimes.iterator(); iter.hasNext();) {
+ Long timestamp = iter.next();
+ if (timestamp < oldestPermitted) {
+ iter.remove();
+ } else {
+ break;
+ }
+ }
+
+ return (consecutiveReplacementFailureTimes.size() >= failOnNumRecurringFailures);
+ }
+
+ protected long currentTimeMillis() {
+ Ticker ticker = getConfig(TICKER);
+ return (ticker == null) ? System.currentTimeMillis() : TimeUnit.NANOSECONDS.toMillis(ticker.read());
+ }
+
+ protected void onReplacementFailed(String msg) {
+ LOG.warn("ServiceReplacer failed for "+entity+": "+msg);
+ consecutiveReplacementFailureTimes.add(currentTimeMillis());
+
+ if (getConfig(SET_ON_FIRE_ON_FAILURE)) {
+ ServiceProblemsLogic.updateProblemsIndicator(entity, "ServiceReplacer", "replacement failed: "+msg);
+ }
+ entity.emit(ENTITY_REPLACEMENT_FAILED, new FailureDescriptor(entity, msg));
+ }
+}
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/d30ff597/policy/src/main/java/org/apache/brooklyn/policy/ha/ServiceRestarter.java
----------------------------------------------------------------------
diff --git a/policy/src/main/java/org/apache/brooklyn/policy/ha/ServiceRestarter.java b/policy/src/main/java/org/apache/brooklyn/policy/ha/ServiceRestarter.java
new file mode 100644
index 0000000..82f8664
--- /dev/null
+++ b/policy/src/main/java/org/apache/brooklyn/policy/ha/ServiceRestarter.java
@@ -0,0 +1,165 @@
+/*
+ * 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.policy.ha;
+
+import java.util.Map;
+import java.util.concurrent.atomic.AtomicReference;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.apache.brooklyn.api.catalog.Catalog;
+import org.apache.brooklyn.api.entity.basic.EntityLocal;
+import org.apache.brooklyn.api.event.Sensor;
+import org.apache.brooklyn.api.event.SensorEvent;
+import org.apache.brooklyn.api.event.SensorEventListener;
+import org.apache.brooklyn.core.policy.basic.AbstractPolicy;
+import org.apache.brooklyn.core.util.config.ConfigBag;
+import org.apache.brooklyn.core.util.flags.SetFromFlag;
+
+import brooklyn.config.ConfigKey;
+import brooklyn.entity.basic.ConfigKeys;
+import brooklyn.entity.basic.Entities;
+import brooklyn.entity.basic.EntityInternal;
+import brooklyn.entity.basic.Lifecycle;
+import brooklyn.entity.basic.ServiceStateLogic;
+import brooklyn.entity.trait.Startable;
+import brooklyn.event.basic.BasicNotificationSensor;
+
+import org.apache.brooklyn.policy.ha.HASensors.FailureDescriptor;
+
+import brooklyn.util.collections.MutableMap;
+import brooklyn.util.javalang.JavaClassNames;
+import brooklyn.util.time.Duration;
+import brooklyn.util.time.Time;
+
+import com.google.common.base.Preconditions;
+
+/** attaches to a SoftwareProcess (or anything Startable, emitting ENTITY_FAILED or other configurable sensor),
+ * and invokes restart on failure;
+ * if there is a subsequent failure within a configurable time interval, or if the restart fails,
+ * this gives up and emits {@link #ENTITY_RESTART_FAILED}
+ */
+@Catalog(name="Service Restarter", description="HA policy for restarting a service automatically, "
+ + "and for emitting an events if the service repeatedly fails")
+public class ServiceRestarter extends AbstractPolicy {
+
+ private static final Logger LOG = LoggerFactory.getLogger(ServiceRestarter.class);
+
+ public static final BasicNotificationSensor<FailureDescriptor> ENTITY_RESTART_FAILED = new BasicNotificationSensor<FailureDescriptor>(
+ FailureDescriptor.class, "ha.entityFailed.restart", "Indicates that an entity restart attempt has failed");
+
+ /** skips retry if a failure re-occurs within this time interval */
+ @SetFromFlag("failOnRecurringFailuresInThisDuration")
+ public static final ConfigKey<Duration> FAIL_ON_RECURRING_FAILURES_IN_THIS_DURATION = ConfigKeys.newConfigKey(
+ Duration.class,
+ "failOnRecurringFailuresInThisDuration",
+ "Reports entity as failed if it fails two or more times in this time window",
+ Duration.minutes(3));
+
+ @SetFromFlag("setOnFireOnFailure")
+ public static final ConfigKey<Boolean> SET_ON_FIRE_ON_FAILURE = ConfigKeys.newBooleanConfigKey("setOnFireOnFailure", "", true);
+
+ /** monitors this sensor, by default ENTITY_FAILED */
+ @SetFromFlag("failureSensorToMonitor")
+ @SuppressWarnings({ "rawtypes", "unchecked" })
+ public static final ConfigKey<Sensor<?>> FAILURE_SENSOR_TO_MONITOR = (ConfigKey) ConfigKeys.newConfigKey(Sensor.class, "failureSensorToMonitor", "", HASensors.ENTITY_FAILED);
+
+ protected final AtomicReference<Long> lastFailureTime = new AtomicReference<Long>();
+
+ public ServiceRestarter() {
+ this(new ConfigBag());
+ }
+
+ public ServiceRestarter(Map<String,?> flags) {
+ this(new ConfigBag().putAll(flags));
+ }
+
+ public ServiceRestarter(ConfigBag configBag) {
+ // TODO hierarchy should use ConfigBag, and not change flags
+ super(configBag.getAllConfigMutable());
+ uniqueTag = JavaClassNames.simpleClassName(getClass())+":"+getConfig(FAILURE_SENSOR_TO_MONITOR).getName();
+ }
+
+ public ServiceRestarter(Sensor<?> failureSensorToMonitor) {
+ this(new ConfigBag().configure(FAILURE_SENSOR_TO_MONITOR, failureSensorToMonitor));
+ }
+
+ @Override
+ public void setEntity(final EntityLocal entity) {
+ Preconditions.checkArgument(entity instanceof Startable, "Restarter must take a Startable, not "+entity);
+
+ super.setEntity(entity);
+
+ subscribe(entity, getConfig(FAILURE_SENSOR_TO_MONITOR), new SensorEventListener<Object>() {
+ @Override public void onEvent(final SensorEvent<Object> event) {
+ // Must execute in another thread - if we called entity.restart in the event-listener's thread
+ // then we'd block all other events being delivered to this entity's other subscribers.
+ // Relies on synchronization of `onDetectedFailure`.
+ // See same pattern used in ServiceReplacer.
+
+ // TODO Could use BasicExecutionManager.setTaskSchedulerForTag to prevent race of two
+ // events being received in rapid succession, and onDetectedFailure being executed out-of-order
+ // for them; or could write events to a blocking queue and have onDetectedFailure read from that.
+
+ if (isRunning()) {
+ LOG.info("ServiceRestarter notified; dispatching job for "+entity+" ("+event.getValue()+")");
+ ((EntityInternal)entity).getExecutionContext().submit(MutableMap.of(), new Runnable() {
+ @Override public void run() {
+ onDetectedFailure(event);
+ }});
+ } else {
+ LOG.warn("ServiceRestarter not running, so not acting on failure detected at "+entity+" ("+event.getValue()+")");
+ }
+ }
+ });
+ }
+
+ // TODO semaphores would be better to allow at-most-one-blocking behaviour
+ // FIXME as this is called in message-dispatch (single threaded) we should do most of this in a new submitted task
+ // (as has been done in ServiceReplacer)
+ protected synchronized void onDetectedFailure(SensorEvent<Object> event) {
+ if (isSuspended()) {
+ LOG.warn("ServiceRestarter suspended, so not acting on failure detected at "+entity+" ("+event.getValue()+")");
+ return;
+ }
+
+ LOG.warn("ServiceRestarter acting on failure detected at "+entity+" ("+event.getValue()+")");
+ long current = System.currentTimeMillis();
+ Long last = lastFailureTime.getAndSet(current);
+ long elapsed = last==null ? -1 : current-last;
+ if (elapsed>=0 && elapsed <= getConfig(FAIL_ON_RECURRING_FAILURES_IN_THIS_DURATION).toMilliseconds()) {
+ onRestartFailed("Restart failure (failed again after "+Time.makeTimeStringRounded(elapsed)+") at "+entity+": "+event.getValue());
+ return;
+ }
+ try {
+ ServiceStateLogic.setExpectedState(entity, Lifecycle.STARTING);
+ Entities.invokeEffector(entity, entity, Startable.RESTART).get();
+ } catch (Exception e) {
+ onRestartFailed("Restart failure (error "+e+") at "+entity+": "+event.getValue());
+ }
+ }
+
+ protected void onRestartFailed(String msg) {
+ LOG.warn("ServiceRestarter failed for "+entity+": "+msg);
+ if (getConfig(SET_ON_FIRE_ON_FAILURE)) {
+ ServiceStateLogic.setExpectedState(entity, Lifecycle.ON_FIRE);
+ }
+ entity.emit(ENTITY_RESTART_FAILED, new FailureDescriptor(entity, msg));
+ }
+}
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/d30ff597/policy/src/main/java/org/apache/brooklyn/policy/ha/SshMachineFailureDetector.java
----------------------------------------------------------------------
diff --git a/policy/src/main/java/org/apache/brooklyn/policy/ha/SshMachineFailureDetector.java b/policy/src/main/java/org/apache/brooklyn/policy/ha/SshMachineFailureDetector.java
new file mode 100644
index 0000000..e679309
--- /dev/null
+++ b/policy/src/main/java/org/apache/brooklyn/policy/ha/SshMachineFailureDetector.java
@@ -0,0 +1,103 @@
+/*
+ * 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.policy.ha;
+
+import java.util.Map;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.apache.brooklyn.api.catalog.Catalog;
+import org.apache.brooklyn.core.util.internal.ssh.SshTool;
+
+import brooklyn.config.ConfigKey;
+import brooklyn.entity.basic.ConfigKeys;
+import brooklyn.event.basic.BasicNotificationSensor;
+
+import org.apache.brooklyn.location.basic.Machines;
+import org.apache.brooklyn.location.basic.SshMachineLocation;
+
+import org.apache.brooklyn.policy.ha.HASensors.FailureDescriptor;
+
+import brooklyn.util.exceptions.Exceptions;
+import brooklyn.util.guava.Maybe;
+import brooklyn.util.time.Duration;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+
+@Catalog(name="Ssh Connectivity Failure Detector", description="HA policy for monitoring an SshMachine, "
+ + "emitting an event if the connection is lost/restored")
+public class SshMachineFailureDetector extends AbstractFailureDetector {
+ private static final Logger LOG = LoggerFactory.getLogger(SshMachineFailureDetector.class);
+ public static final String DEFAULT_UNIQUE_TAG = "failureDetector.sshMachine.tag";
+
+ public static final BasicNotificationSensor<FailureDescriptor> CONNECTION_FAILED = HASensors.CONNECTION_FAILED;
+
+ public static final BasicNotificationSensor<FailureDescriptor> CONNECTION_RECOVERED = HASensors.CONNECTION_RECOVERED;
+
+ public static final ConfigKey<Duration> CONNECT_TIMEOUT = ConfigKeys.newDurationConfigKey(
+ "ha.sshConnection.timeout", "How long to wait for conneciton before declaring failure", Duration.TEN_SECONDS);
+
+ @Override
+ public void init() {
+ super.init();
+ if (config().getRaw(SENSOR_FAILED).isAbsent()) {
+ config().set(SENSOR_FAILED, CONNECTION_FAILED);
+ }
+ if (config().getRaw(SENSOR_RECOVERED).isAbsent()) {
+ config().set(SENSOR_RECOVERED, CONNECTION_RECOVERED);
+ }
+ if (config().getRaw(POLL_PERIOD).isAbsent()) {
+ config().set(POLL_PERIOD, Duration.ONE_MINUTE);
+ }
+ uniqueTag = DEFAULT_UNIQUE_TAG;
+ }
+
+ @Override
+ protected CalculatedStatus calculateStatus() {
+ Maybe<SshMachineLocation> sshMachineOption = Machines.findUniqueSshMachineLocation(entity.getLocations());
+ if (sshMachineOption.isPresent()) {
+ SshMachineLocation sshMachine = sshMachineOption.get();
+ try {
+ Duration timeout = config().get(CONNECT_TIMEOUT);
+ Map<String, ?> flags = ImmutableMap.of(
+ SshTool.PROP_CONNECT_TIMEOUT.getName(), timeout.toMilliseconds(),
+ SshTool.PROP_SESSION_TIMEOUT.getName(), timeout.toMilliseconds(),
+ SshTool.PROP_SSH_TRIES.getName(), 1);
+ int exitCode = sshMachine.execCommands(flags, SshMachineFailureDetector.class.getName(), ImmutableList.of("exit"));
+ return new BasicCalculatedStatus(exitCode == 0, sshMachine.toString());
+ } catch (Exception e) {
+ Exceptions.propagateIfFatal(e);
+ boolean isFirstFailure = lastPublished != LastPublished.FAILED && currentFailureStartTime == null;
+ if (isFirstFailure) {
+ if (LOG.isDebugEnabled()) {
+ LOG.debug("Failed connecting to machine " + sshMachine, e);
+ }
+ } else {
+ if (LOG.isTraceEnabled()) {
+ LOG.trace("Failed connecting to machine " + sshMachine, e);
+ }
+ }
+ return new BasicCalculatedStatus(false, e.getMessage());
+ }
+ } else {
+ return new BasicCalculatedStatus(true, "no machine started, not complaining");
+ }
+ }
+}
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/d30ff597/policy/src/main/java/org/apache/brooklyn/policy/loadbalancing/BalanceableContainer.java
----------------------------------------------------------------------
diff --git a/policy/src/main/java/org/apache/brooklyn/policy/loadbalancing/BalanceableContainer.java b/policy/src/main/java/org/apache/brooklyn/policy/loadbalancing/BalanceableContainer.java
new file mode 100644
index 0000000..451d150
--- /dev/null
+++ b/policy/src/main/java/org/apache/brooklyn/policy/loadbalancing/BalanceableContainer.java
@@ -0,0 +1,51 @@
+/*
+ * 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.policy.loadbalancing;
+
+import java.util.Set;
+
+import org.apache.brooklyn.api.entity.Entity;
+
+import brooklyn.config.ConfigKey;
+import brooklyn.entity.basic.AbstractGroup;
+import brooklyn.entity.basic.ConfigKeys;
+import brooklyn.event.basic.BasicNotificationSensor;
+import brooklyn.util.collections.QuorumCheck;
+import brooklyn.util.collections.QuorumCheck.QuorumChecks;
+
+/**
+ * Contains worker items that can be moved between this container and others to effect load balancing.
+ * Membership of a balanceable container does not imply a parent-child relationship in the Brooklyn
+ * management sense.
+ */
+public interface BalanceableContainer<ItemType extends Movable> extends Entity, AbstractGroup {
+
+ public static BasicNotificationSensor<Entity> ITEM_ADDED = new BasicNotificationSensor<Entity>(
+ Entity.class, "balanceablecontainer.item.added", "Movable item added to balanceable container");
+ public static BasicNotificationSensor<Entity> ITEM_REMOVED = new BasicNotificationSensor<Entity>(
+ Entity.class, "balanceablecontainer.item.removed", "Movable item removed from balanceable container");
+
+ public static final ConfigKey<QuorumCheck> UP_QUORUM_CHECK = ConfigKeys.newConfigKeyWithDefault(AbstractGroup.UP_QUORUM_CHECK,
+ "Up check from members; default one for container overrides usual check to always return true, "
+ + "i.e. not block service up simply because the container is empty or something in the container has failed",
+ QuorumChecks.alwaysTrue());
+
+ public Set<ItemType> getBalanceableItems();
+
+}
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/d30ff597/policy/src/main/java/org/apache/brooklyn/policy/loadbalancing/BalanceablePoolModel.java
----------------------------------------------------------------------
diff --git a/policy/src/main/java/org/apache/brooklyn/policy/loadbalancing/BalanceablePoolModel.java b/policy/src/main/java/org/apache/brooklyn/policy/loadbalancing/BalanceablePoolModel.java
new file mode 100644
index 0000000..72793b1
--- /dev/null
+++ b/policy/src/main/java/org/apache/brooklyn/policy/loadbalancing/BalanceablePoolModel.java
@@ -0,0 +1,64 @@
+/*
+ * 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.policy.loadbalancing;
+
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.brooklyn.api.location.Location;
+
+/**
+ * Captures the state of a balanceable cluster of containers and all their constituent items, including workrates,
+ * for consumption by a {@link BalancingStrategy}.
+ */
+public interface BalanceablePoolModel<ContainerType, ItemType> {
+
+ // Attributes of the pool.
+ public String getName();
+ public int getPoolSize();
+ public Set<ContainerType> getPoolContents();
+ public double getPoolLowThreshold();
+ public double getPoolHighThreshold();
+ public double getCurrentPoolWorkrate();
+ public boolean isHot();
+ public boolean isCold();
+
+
+ // Attributes of containers and items.
+ public String getName(ContainerType container);
+ public Location getLocation(ContainerType container);
+ public double getLowThreshold(ContainerType container); // -1 for not known / invalid
+ public double getHighThreshold(ContainerType container); // -1 for not known / invalid
+ public double getTotalWorkrate(ContainerType container); // -1 for not known / invalid
+ public Map<ContainerType, Double> getContainerWorkrates(); // contains -1 for items which are unknown
+ /** contains -1 instead of actual item workrate, for items which cannot be moved */
+ // @Nullable("null if the node is prevented from reporting and/or being adjusted, or has no data yet")
+ public Map<ItemType, Double> getItemWorkrates(ContainerType container);
+ public boolean isItemMoveable(ItemType item);
+ public boolean isItemAllowedIn(ItemType item, Location location);
+
+ // Mutators for keeping the model in-sync with the observed world
+ public void onContainerAdded(ContainerType newContainer, double lowThreshold, double highThreshold);
+ public void onContainerRemoved(ContainerType oldContainer);
+ public void onItemAdded(ItemType item, ContainerType parentContainer);
+ public void onItemAdded(ItemType item, ContainerType parentContainer, boolean immovable);
+ public void onItemRemoved(ItemType item);
+ public void onItemWorkrateUpdated(ItemType item, double newValue);
+ public void onItemMoved(ItemType item, ContainerType targetContainer);
+}
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/d30ff597/policy/src/main/java/org/apache/brooklyn/policy/loadbalancing/BalanceableWorkerPool.java
----------------------------------------------------------------------
diff --git a/policy/src/main/java/org/apache/brooklyn/policy/loadbalancing/BalanceableWorkerPool.java b/policy/src/main/java/org/apache/brooklyn/policy/loadbalancing/BalanceableWorkerPool.java
new file mode 100644
index 0000000..d600bc4
--- /dev/null
+++ b/policy/src/main/java/org/apache/brooklyn/policy/loadbalancing/BalanceableWorkerPool.java
@@ -0,0 +1,84 @@
+/*
+ * 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.policy.loadbalancing;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import java.io.Serializable;
+
+import org.apache.brooklyn.api.entity.Entity;
+import org.apache.brooklyn.api.entity.Group;
+import org.apache.brooklyn.api.entity.proxying.ImplementedBy;
+
+import brooklyn.entity.trait.Resizable;
+import brooklyn.event.basic.BasicNotificationSensor;
+
+/**
+ * Represents an elastic group of "container" entities, each of which is capable of hosting "item" entities that perform
+ * work and consume the container's available resources (e.g. CPU or bandwidth). Auto-scaling and load-balancing policies can
+ * be attached to this pool to provide dynamic elasticity based on workrates reported by the individual item entities.
+ * <p>
+ * The containers must be "up" in order to receive work, thus they must NOT follow the default enricher pattern
+ * for groups which says that the group must be up to receive work.
+ */
+@ImplementedBy(BalanceableWorkerPoolImpl.class)
+public interface BalanceableWorkerPool extends Entity, Resizable {
+
+ // FIXME Asymmetry between loadbalancing and followTheSun: ITEM_ADDED and ITEM_REMOVED in loadbalancing
+ // are of type ContainerItemPair, but in followTheSun it is just the `Entity item`.
+
+ /** Encapsulates an item and a container; emitted for {@code ITEM_ADDED}, {@code ITEM_REMOVED} and
+ * {@code ITEM_MOVED} sensors.
+ */
+ public static class ContainerItemPair implements Serializable {
+ private static final long serialVersionUID = 1L;
+ public final BalanceableContainer<?> container;
+ public final Entity item;
+
+ public ContainerItemPair(BalanceableContainer<?> container, Entity item) {
+ this.container = container;
+ this.item = checkNotNull(item);
+ }
+
+ @Override
+ public String toString() {
+ return ""+item+" @ "+container;
+ }
+ }
+
+ // Pool constituent notifications.
+ public static BasicNotificationSensor<Entity> CONTAINER_ADDED = new BasicNotificationSensor<Entity>(
+ Entity.class, "balanceablepool.container.added", "Container added to balanceable pool");
+ public static BasicNotificationSensor<Entity> CONTAINER_REMOVED = new BasicNotificationSensor<Entity>(
+ Entity.class, "balanceablepool.container.removed", "Container removed from balanceable pool");
+ public static BasicNotificationSensor<ContainerItemPair> ITEM_ADDED = new BasicNotificationSensor<ContainerItemPair>(
+ ContainerItemPair.class, "balanceablepool.item.added", "Item added to balanceable pool");
+ public static BasicNotificationSensor<ContainerItemPair> ITEM_REMOVED = new BasicNotificationSensor<ContainerItemPair>(
+ ContainerItemPair.class, "balanceablepool.item.removed", "Item removed from balanceable pool");
+ public static BasicNotificationSensor<ContainerItemPair> ITEM_MOVED = new BasicNotificationSensor<ContainerItemPair>(
+ ContainerItemPair.class, "balanceablepool.item.moved", "Item moved in balanceable pool to the given container");
+
+ public void setResizable(Resizable resizable);
+
+ public void setContents(Group containerGroup, Group itemGroup);
+
+ public Group getContainerGroup();
+
+ public Group getItemGroup();
+}
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/d30ff597/policy/src/main/java/org/apache/brooklyn/policy/loadbalancing/BalanceableWorkerPoolImpl.java
----------------------------------------------------------------------
diff --git a/policy/src/main/java/org/apache/brooklyn/policy/loadbalancing/BalanceableWorkerPoolImpl.java b/policy/src/main/java/org/apache/brooklyn/policy/loadbalancing/BalanceableWorkerPoolImpl.java
new file mode 100644
index 0000000..888b957
--- /dev/null
+++ b/policy/src/main/java/org/apache/brooklyn/policy/loadbalancing/BalanceableWorkerPoolImpl.java
@@ -0,0 +1,185 @@
+/*
+ * 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.policy.loadbalancing;
+
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
+
+import org.apache.brooklyn.api.entity.Entity;
+import org.apache.brooklyn.api.entity.Group;
+import org.apache.brooklyn.api.event.Sensor;
+import org.apache.brooklyn.api.event.SensorEvent;
+import org.apache.brooklyn.api.event.SensorEventListener;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import brooklyn.entity.basic.AbstractEntity;
+import brooklyn.entity.basic.AbstractGroup;
+import brooklyn.entity.trait.Resizable;
+import brooklyn.entity.trait.Startable;
+
+/**
+ * @see BalanceableWorkerPool
+ */
+public class BalanceableWorkerPoolImpl extends AbstractEntity implements BalanceableWorkerPool {
+
+ // FIXME Asymmetry between loadbalancing and followTheSun: ITEM_ADDED and ITEM_REMOVED in loadbalancing
+ // are of type ContainerItemPair, but in followTheSun it is just the `Entity item`.
+
+ private static final Logger LOG = LoggerFactory.getLogger(BalanceableWorkerPool.class);
+
+ private Group containerGroup;
+ private Group itemGroup;
+ private Resizable resizable;
+
+ private final Set<Entity> containers = Collections.synchronizedSet(new HashSet<Entity>());
+ private final Set<Entity> items = Collections.synchronizedSet(new HashSet<Entity>());
+
+ private final SensorEventListener<Object> eventHandler = new SensorEventListener<Object>() {
+ @Override
+ public void onEvent(SensorEvent<Object> event) {
+ if (LOG.isTraceEnabled()) LOG.trace("{} received event {}", BalanceableWorkerPoolImpl.this, event);
+ Entity source = event.getSource();
+ Object value = event.getValue();
+ Sensor<?> sensor = event.getSensor();
+
+ if (sensor.equals(AbstractGroup.MEMBER_ADDED)) {
+ if (source.equals(containerGroup)) {
+ onContainerAdded((BalanceableContainer<?>) value);
+ } else if (source.equals(itemGroup)) {
+ onItemAdded((Entity)value);
+ } else {
+ throw new IllegalStateException("unexpected event source="+source);
+ }
+ } else if (sensor.equals(AbstractGroup.MEMBER_REMOVED)) {
+ if (source.equals(containerGroup)) {
+ onContainerRemoved((BalanceableContainer<?>) value);
+ } else if (source.equals(itemGroup)) {
+ onItemRemoved((Entity) value);
+ } else {
+ throw new IllegalStateException("unexpected event source="+source);
+ }
+ } else if (sensor.equals(Startable.SERVICE_UP)) {
+ // TODO What if start has failed? Is there a sensor to indicate that?
+ if ((Boolean)value) {
+ onContainerUp((BalanceableContainer<?>) source);
+ } else {
+ onContainerDown((BalanceableContainer<?>) source);
+ }
+ } else if (sensor.equals(Movable.CONTAINER)) {
+ onItemMoved(source, (BalanceableContainer<?>) value);
+ } else {
+ throw new IllegalStateException("Unhandled event type "+sensor+": "+event);
+ }
+ }
+ };
+
+ public BalanceableWorkerPoolImpl() {
+ }
+
+ @Override
+ public void setResizable(Resizable resizable) {
+ this.resizable = resizable;
+ }
+
+ @Override
+ public void setContents(Group containerGroup, Group itemGroup) {
+ this.containerGroup = containerGroup;
+ this.itemGroup = itemGroup;
+ if (resizable == null && containerGroup instanceof Resizable) resizable = (Resizable) containerGroup;
+
+ subscribe(containerGroup, AbstractGroup.MEMBER_ADDED, eventHandler);
+ subscribe(containerGroup, AbstractGroup.MEMBER_REMOVED, eventHandler);
+ subscribe(itemGroup, AbstractGroup.MEMBER_ADDED, eventHandler);
+ subscribe(itemGroup, AbstractGroup.MEMBER_REMOVED, eventHandler);
+
+ // Process extant containers and items
+ for (Entity existingContainer : containerGroup.getMembers()) {
+ onContainerAdded((BalanceableContainer<?>)existingContainer);
+ }
+ for (Entity existingItem : itemGroup.getMembers()) {
+ onItemAdded(existingItem);
+ }
+ }
+
+ @Override
+ public Group getContainerGroup() {
+ return containerGroup;
+ }
+
+ @Override
+ public Group getItemGroup() {
+ return itemGroup;
+ }
+
+ @Override
+ public Integer getCurrentSize() {
+ return containerGroup.getCurrentSize();
+ }
+
+ @Override
+ public Integer resize(Integer desiredSize) {
+ if (resizable != null) return resizable.resize(desiredSize);
+
+ throw new UnsupportedOperationException("Container group is not resizable, and no resizable supplied: "+containerGroup+" of type "+(containerGroup != null ? containerGroup.getClass().getCanonicalName() : null));
+ }
+
+ private void onContainerAdded(BalanceableContainer<?> newContainer) {
+ subscribe(newContainer, Startable.SERVICE_UP, eventHandler);
+ if (!(newContainer instanceof Startable) || Boolean.TRUE.equals(newContainer.getAttribute(Startable.SERVICE_UP))) {
+ onContainerUp(newContainer);
+ }
+ }
+
+ private void onContainerUp(BalanceableContainer<?> newContainer) {
+ if (containers.add(newContainer)) {
+ emit(CONTAINER_ADDED, newContainer);
+ }
+ }
+
+ private void onContainerDown(BalanceableContainer<?> oldContainer) {
+ if (containers.remove(oldContainer)) {
+ emit(CONTAINER_REMOVED, oldContainer);
+ }
+ }
+
+ private void onContainerRemoved(BalanceableContainer<?> oldContainer) {
+ unsubscribe(oldContainer);
+ onContainerDown(oldContainer);
+ }
+
+ private void onItemAdded(Entity item) {
+ if (items.add(item)) {
+ subscribe(item, Movable.CONTAINER, eventHandler);
+ emit(ITEM_ADDED, new ContainerItemPair(item.getAttribute(Movable.CONTAINER), item));
+ }
+ }
+
+ private void onItemRemoved(Entity item) {
+ if (items.remove(item)) {
+ unsubscribe(item);
+ emit(ITEM_REMOVED, new ContainerItemPair(null, item));
+ }
+ }
+
+ private void onItemMoved(Entity item, BalanceableContainer<?> container) {
+ emit(ITEM_MOVED, new ContainerItemPair(container, item));
+ }
+}
[05/24] incubator-brooklyn git commit: [BROOKLYN-162] Renaming
package policy
Posted by he...@apache.org.
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/d30ff597/policy/src/test/java/org/apache/brooklyn/policy/loadbalancing/MockItemEntityImpl.java
----------------------------------------------------------------------
diff --git a/policy/src/test/java/org/apache/brooklyn/policy/loadbalancing/MockItemEntityImpl.java b/policy/src/test/java/org/apache/brooklyn/policy/loadbalancing/MockItemEntityImpl.java
new file mode 100644
index 0000000..00b665d
--- /dev/null
+++ b/policy/src/test/java/org/apache/brooklyn/policy/loadbalancing/MockItemEntityImpl.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.policy.loadbalancing;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicLong;
+import java.util.concurrent.locks.ReentrantLock;
+
+import org.apache.brooklyn.api.entity.Entity;
+import org.apache.brooklyn.api.event.AttributeSensor;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import brooklyn.entity.basic.AbstractEntity;
+import brooklyn.util.collections.MutableList;
+
+
+public class MockItemEntityImpl extends AbstractEntity implements MockItemEntity {
+
+ private static final Logger LOG = LoggerFactory.getLogger(MockItemEntityImpl.class);
+
+ public static AtomicInteger totalMoveCount = new AtomicInteger(0);
+
+ public static AtomicLong lastMoveTime = new AtomicLong(-1);
+
+ private volatile boolean stopped;
+ private volatile MockContainerEntity currentContainer;
+
+ private final ReentrantLock _lock = new ReentrantLock();
+
+ @Override
+ public String getContainerId() {
+ return (currentContainer == null) ? null : currentContainer.getId();
+ }
+
+ @Override
+ public boolean isStopped() {
+ return stopped;
+ }
+
+ @Override
+ public <T> T setAttribute(AttributeSensor<T> attribute, T val) {
+ if (LOG.isDebugEnabled()) LOG.debug("Mocks: item {} setting {} to {}", new Object[] {this, attribute, val});
+ return super.setAttribute(attribute, val);
+ }
+
+ @Override
+ public void move(Entity destination) {
+ totalMoveCount.incrementAndGet();
+ lastMoveTime.set(System.currentTimeMillis());
+ moveNonEffector(destination);
+ }
+
+ // only moves if the containers will accept us (otherwise we'd lose the item!)
+ @Override
+ public void moveNonEffector(Entity rawDestination) {
+ if (LOG.isDebugEnabled()) LOG.debug("Mocks: moving item {} from {} to {}", new Object[] {this, currentContainer, rawDestination});
+ checkNotNull(rawDestination);
+ final MockContainerEntity previousContainer = currentContainer;
+ final MockContainerEntity destination = (MockContainerEntity) rawDestination;
+
+ MockContainerEntityImpl.runWithLock(MutableList.of(previousContainer, destination), new Runnable() {
+ @Override public void run() {
+ _lock.lock();
+ try {
+ if (stopped) throw new IllegalStateException("Item "+this+" is stopped; cannot move to "+destination);
+ if (currentContainer != null) currentContainer.removeItem(MockItemEntityImpl.this);
+ currentContainer = destination;
+ destination.addItem(MockItemEntityImpl.this);
+ setAttribute(CONTAINER, currentContainer);
+ } finally {
+ _lock.unlock();
+ }
+ }});
+ }
+
+ @Override
+ public void stop() {
+ // FIXME How best to indicate this has been entirely stopped, rather than just in-transit?
+ if (LOG.isDebugEnabled()) LOG.debug("Mocks: stopping item {} (was in container {})", this, currentContainer);
+ _lock.lock();
+ try {
+ if (currentContainer != null) currentContainer.removeItem(this);
+ currentContainer = null;
+ stopped = true;
+ } finally {
+ _lock.unlock();
+ }
+ }
+
+ @Override
+ public String toString() {
+ return "MockItem["+getDisplayName()+"]";
+ }
+}
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/d30ff597/software/webapp/src/test/java/org/apache/brooklyn/entity/webapp/TomcatAutoScalerPolicyTest.java
----------------------------------------------------------------------
diff --git a/software/webapp/src/test/java/org/apache/brooklyn/entity/webapp/TomcatAutoScalerPolicyTest.java b/software/webapp/src/test/java/org/apache/brooklyn/entity/webapp/TomcatAutoScalerPolicyTest.java
index acf6195..9bdd2c6 100644
--- a/software/webapp/src/test/java/org/apache/brooklyn/entity/webapp/TomcatAutoScalerPolicyTest.java
+++ b/software/webapp/src/test/java/org/apache/brooklyn/entity/webapp/TomcatAutoScalerPolicyTest.java
@@ -25,7 +25,6 @@ import static org.testng.Assert.assertTrue;
import org.apache.brooklyn.api.entity.proxying.EntitySpec;
import org.apache.brooklyn.api.location.LocationSpec;
import org.apache.brooklyn.api.management.ManagementContext;
-import org.apache.brooklyn.entity.webapp.DynamicWebAppCluster;
import org.apache.brooklyn.entity.webapp.tomcat.TomcatServer;
import org.apache.brooklyn.entity.webapp.tomcat.TomcatServerImpl;
import org.apache.brooklyn.test.entity.TestApplication;
@@ -39,7 +38,7 @@ import brooklyn.entity.basic.Entities;
import org.apache.brooklyn.location.basic.LocalhostMachineProvisioningLocation;
-import brooklyn.policy.autoscaling.AutoScalerPolicy;
+import org.apache.brooklyn.policy.autoscaling.AutoScalerPolicy;
import brooklyn.test.Asserts;
import brooklyn.util.collections.MutableMap;
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/d30ff597/usage/archetypes/quickstart/src/brooklyn-sample/src/main/java/com/acme/sample/brooklyn/sample/app/ClusterWebServerDatabaseSample.java
----------------------------------------------------------------------
diff --git a/usage/archetypes/quickstart/src/brooklyn-sample/src/main/java/com/acme/sample/brooklyn/sample/app/ClusterWebServerDatabaseSample.java b/usage/archetypes/quickstart/src/brooklyn-sample/src/main/java/com/acme/sample/brooklyn/sample/app/ClusterWebServerDatabaseSample.java
index c9b73e0..9b101a8 100644
--- a/usage/archetypes/quickstart/src/brooklyn-sample/src/main/java/com/acme/sample/brooklyn/sample/app/ClusterWebServerDatabaseSample.java
+++ b/usage/archetypes/quickstart/src/brooklyn-sample/src/main/java/com/acme/sample/brooklyn/sample/app/ClusterWebServerDatabaseSample.java
@@ -30,7 +30,7 @@ import org.apache.brooklyn.entity.webapp.WebAppServiceConstants;
import org.apache.brooklyn.api.event.AttributeSensor;
import brooklyn.event.basic.Sensors;
import org.apache.brooklyn.location.basic.PortRanges;
-import brooklyn.policy.autoscaling.AutoScalerPolicy;
+import org.apache.brooklyn.policy.autoscaling.AutoScalerPolicy;
import brooklyn.util.maven.MavenArtifact;
import brooklyn.util.maven.MavenRetriever;
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/d30ff597/usage/camp/src/test/java/org/apache/brooklyn/camp/brooklyn/BrooklynYamlTypeInstantiatorTest.java
----------------------------------------------------------------------
diff --git a/usage/camp/src/test/java/org/apache/brooklyn/camp/brooklyn/BrooklynYamlTypeInstantiatorTest.java b/usage/camp/src/test/java/org/apache/brooklyn/camp/brooklyn/BrooklynYamlTypeInstantiatorTest.java
index 107b0b9..7999721 100644
--- a/usage/camp/src/test/java/org/apache/brooklyn/camp/brooklyn/BrooklynYamlTypeInstantiatorTest.java
+++ b/usage/camp/src/test/java/org/apache/brooklyn/camp/brooklyn/BrooklynYamlTypeInstantiatorTest.java
@@ -27,7 +27,7 @@ import org.apache.brooklyn.core.management.classloading.JavaBrooklynClassLoading
import org.testng.Assert;
import org.testng.annotations.Test;
-import brooklyn.policy.ha.ServiceRestarter;
+import org.apache.brooklyn.policy.ha.ServiceRestarter;
import brooklyn.util.collections.MutableMap;
import brooklyn.util.javalang.JavaClassNames;
import brooklyn.util.time.Duration;
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/d30ff597/usage/camp/src/test/java/org/apache/brooklyn/camp/brooklyn/JavaWebAppsIntegrationTest.java
----------------------------------------------------------------------
diff --git a/usage/camp/src/test/java/org/apache/brooklyn/camp/brooklyn/JavaWebAppsIntegrationTest.java b/usage/camp/src/test/java/org/apache/brooklyn/camp/brooklyn/JavaWebAppsIntegrationTest.java
index 7314e66..5a6d7d8 100644
--- a/usage/camp/src/test/java/org/apache/brooklyn/camp/brooklyn/JavaWebAppsIntegrationTest.java
+++ b/usage/camp/src/test/java/org/apache/brooklyn/camp/brooklyn/JavaWebAppsIntegrationTest.java
@@ -36,9 +36,11 @@ import org.apache.brooklyn.api.entity.Entity;
import org.apache.brooklyn.api.management.ManagementContext;
import org.apache.brooklyn.api.management.Task;
import org.apache.brooklyn.api.policy.Policy;
+
import org.apache.brooklyn.camp.brooklyn.BrooklynCampPlatform;
import org.apache.brooklyn.camp.brooklyn.BrooklynCampPlatformLauncherNoServer;
import org.apache.brooklyn.core.util.ResourceUtils;
+
import org.apache.brooklyn.entity.webapp.DynamicWebAppCluster;
import org.apache.brooklyn.entity.webapp.JavaWebAppService;
import org.apache.brooklyn.entity.webapp.WebAppService;
@@ -54,7 +56,7 @@ import brooklyn.entity.basic.Attributes;
import brooklyn.entity.basic.BrooklynTaskTags;
import brooklyn.entity.basic.Entities;
import brooklyn.entity.basic.Lifecycle;
-import brooklyn.policy.autoscaling.AutoScalerPolicy;
+import org.apache.brooklyn.policy.autoscaling.AutoScalerPolicy;
import brooklyn.test.Asserts;
import brooklyn.util.collections.MutableMap;
import brooklyn.util.exceptions.Exceptions;
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/d30ff597/usage/camp/src/test/java/org/apache/brooklyn/camp/brooklyn/catalog/CatalogYamlCombiTest.java
----------------------------------------------------------------------
diff --git a/usage/camp/src/test/java/org/apache/brooklyn/camp/brooklyn/catalog/CatalogYamlCombiTest.java b/usage/camp/src/test/java/org/apache/brooklyn/camp/brooklyn/catalog/CatalogYamlCombiTest.java
index 73bb59b..2baf194 100644
--- a/usage/camp/src/test/java/org/apache/brooklyn/camp/brooklyn/catalog/CatalogYamlCombiTest.java
+++ b/usage/camp/src/test/java/org/apache/brooklyn/camp/brooklyn/catalog/CatalogYamlCombiTest.java
@@ -31,7 +31,7 @@ import brooklyn.entity.basic.BasicEntity;
import brooklyn.entity.basic.BasicStartable;
import brooklyn.entity.basic.ConfigKeys;
import brooklyn.entity.basic.Entities;
-import brooklyn.policy.ha.ServiceRestarter;
+import org.apache.brooklyn.policy.ha.ServiceRestarter;
import brooklyn.util.exceptions.Exceptions;
import com.google.common.collect.Iterables;
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/d30ff597/usage/camp/src/test/resources/java-web-app-and-db-with-policy.yaml
----------------------------------------------------------------------
diff --git a/usage/camp/src/test/resources/java-web-app-and-db-with-policy.yaml b/usage/camp/src/test/resources/java-web-app-and-db-with-policy.yaml
index 671f64a..c2152b5 100644
--- a/usage/camp/src/test/resources/java-web-app-and-db-with-policy.yaml
+++ b/usage/camp/src/test/resources/java-web-app-and-db-with-policy.yaml
@@ -29,7 +29,7 @@ services:
brooklyn.example.db.url: $brooklyn:formatString("jdbc:%s%s?user=%s&password=%s",
component("db").attributeWhenReady("datastore.url"), "visitors", "brooklyn", "br00k11n")
brooklyn.policies:
- - policyType: brooklyn.policy.autoscaling.AutoScalerPolicy
+ - policyType: org.apache.brooklyn.policy.autoscaling.AutoScalerPolicy
brooklyn.config:
metric: $brooklyn:sensor("org.apache.brooklyn.entity.webapp.DynamicWebAppCluster", "webapp.reqs.perSec.windowed.perNode")
metricLowerBound: 10
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/d30ff597/usage/camp/src/test/resources/vanilla-bash-netcat-w-client.yaml
----------------------------------------------------------------------
diff --git a/usage/camp/src/test/resources/vanilla-bash-netcat-w-client.yaml b/usage/camp/src/test/resources/vanilla-bash-netcat-w-client.yaml
index d1bc4d4..3279dc7 100644
--- a/usage/camp/src/test/resources/vanilla-bash-netcat-w-client.yaml
+++ b/usage/camp/src/test/resources/vanilla-bash-netcat-w-client.yaml
@@ -34,13 +34,13 @@ services:
# a failure detector and a service restarter work together
brooklyn.enrichers:
- - type: brooklyn.policy.ha.ServiceFailureDetector
+ - type: org.apache.brooklyn.policy.ha.ServiceFailureDetector
brooklyn.config:
# wait 15s after service fails before propagating failure
serviceFailedStabilizationDelay: 15s
brooklyn.policies:
- - policyType: brooklyn.policy.ha.ServiceRestarter
+ - policyType: org.apache.brooklyn.policy.ha.ServiceRestarter
brooklyn.config:
# repeated failures in a time window can cause the restarter to abort,
# propagating the failure; a time window of 0 will mean it always restarts!
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/d30ff597/usage/cli/src/main/resources/brooklyn/default.catalog.bom
----------------------------------------------------------------------
diff --git a/usage/cli/src/main/resources/brooklyn/default.catalog.bom b/usage/cli/src/main/resources/brooklyn/default.catalog.bom
index 4cd5f6b..9255913 100644
--- a/usage/cli/src/main/resources/brooklyn/default.catalog.bom
+++ b/usage/cli/src/main/resources/brooklyn/default.catalog.bom
@@ -188,7 +188,7 @@ brooklyn.catalog:
initialSize: 3
# and add a policy to scale based on ops per minute
brooklyn.policies:
- - type: brooklyn.policy.autoscaling.AutoScalerPolicy
+ - type: org.apache.brooklyn.policy.autoscaling.AutoScalerPolicy
brooklyn.config:
metric: riak.node.ops.1m.perNode
# more than 100 ops per second (6k/min) scales out, less than 50 scales back
@@ -274,13 +274,13 @@ brooklyn.catalog:
enricher.window.duration: 30s
# emit failure sensor if a failure connecting to the service is sustained for 30s
- - type: brooklyn.policy.ha.ServiceFailureDetector
+ - type: org.apache.brooklyn.policy.ha.ServiceFailureDetector
brooklyn.config:
entityFailed.stabilizationDelay: 30s
brooklyn.policies:
# restart if a failure is detected (with a max of one restart in 2m, sensor will propagate otherwise)
- - type: brooklyn.policy.ha.ServiceRestarter
+ - type: org.apache.brooklyn.policy.ha.ServiceRestarter
brooklyn.config:
failOnRecurringFailuresInThisDuration: 2m
@@ -305,10 +305,10 @@ brooklyn.catalog:
brooklyn.policies:
# resilience: if a per-node restart policy fails,
# just throw that node away and create a new one
- - type: brooklyn.policy.ha.ServiceReplacer
+ - type: org.apache.brooklyn.policy.ha.ServiceReplacer
# and scale based on reqs/sec
- - type: brooklyn.policy.autoscaling.AutoScalerPolicy
+ - type: org.apache.brooklyn.policy.autoscaling.AutoScalerPolicy
brooklyn.config:
# scale based on reqs/sec (though in a real-world situation,
# reqs.per_sec.windowed_30s.per_node might be a better choice)
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/d30ff597/usage/launcher/src/test/resources/couchbase-w-loadgen.yaml
----------------------------------------------------------------------
diff --git a/usage/launcher/src/test/resources/couchbase-w-loadgen.yaml b/usage/launcher/src/test/resources/couchbase-w-loadgen.yaml
index 7dc4995..7803a8c 100644
--- a/usage/launcher/src/test/resources/couchbase-w-loadgen.yaml
+++ b/usage/launcher/src/test/resources/couchbase-w-loadgen.yaml
@@ -33,7 +33,7 @@ services:
minRam: 16g
minCores: 4
brooklyn.policies:
- - type: brooklyn.policy.autoscaling.AutoScalerPolicy
+ - type: org.apache.brooklyn.policy.autoscaling.AutoScalerPolicy
brooklyn.config:
metric: $brooklyn:sensor("org.apache.brooklyn.entity.nosql.couchbase.CouchbaseCluster",
"couchbase.stats.cluster.per.node.ops")
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/d30ff597/usage/qa/src/main/java/org/apache/brooklyn/qa/load/SimulatedTheeTierApp.java
----------------------------------------------------------------------
diff --git a/usage/qa/src/main/java/org/apache/brooklyn/qa/load/SimulatedTheeTierApp.java b/usage/qa/src/main/java/org/apache/brooklyn/qa/load/SimulatedTheeTierApp.java
index bc7b2c3..2e70a2b 100644
--- a/usage/qa/src/main/java/org/apache/brooklyn/qa/load/SimulatedTheeTierApp.java
+++ b/usage/qa/src/main/java/org/apache/brooklyn/qa/load/SimulatedTheeTierApp.java
@@ -48,7 +48,7 @@ import org.apache.brooklyn.entity.webapp.jboss.JBoss7Server;
import org.apache.brooklyn.launcher.BrooklynLauncher;
import org.apache.brooklyn.location.basic.PortRanges;
-import brooklyn.policy.autoscaling.AutoScalerPolicy;
+import org.apache.brooklyn.policy.autoscaling.AutoScalerPolicy;
import brooklyn.util.CommandLineUtil;
import brooklyn.util.collections.MutableSet;
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/d30ff597/usage/qa/src/test/java/org/apache/brooklyn/qa/longevity/webcluster/WebClusterApp.java
----------------------------------------------------------------------
diff --git a/usage/qa/src/test/java/org/apache/brooklyn/qa/longevity/webcluster/WebClusterApp.java b/usage/qa/src/test/java/org/apache/brooklyn/qa/longevity/webcluster/WebClusterApp.java
index 8b635f1..399f847 100644
--- a/usage/qa/src/test/java/org/apache/brooklyn/qa/longevity/webcluster/WebClusterApp.java
+++ b/usage/qa/src/test/java/org/apache/brooklyn/qa/longevity/webcluster/WebClusterApp.java
@@ -35,7 +35,7 @@ import org.apache.brooklyn.entity.webapp.ControlledDynamicWebAppCluster;
import org.apache.brooklyn.entity.webapp.jboss.JBoss7Server;
import org.apache.brooklyn.launcher.BrooklynLauncher;
-import brooklyn.policy.autoscaling.AutoScalerPolicy;
+import org.apache.brooklyn.policy.autoscaling.AutoScalerPolicy;
import brooklyn.util.CommandLineUtil;
import com.google.common.collect.Lists;
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/d30ff597/usage/rest-api/src/main/java/org/apache/brooklyn/rest/domain/ConfigSummary.java
----------------------------------------------------------------------
diff --git a/usage/rest-api/src/main/java/org/apache/brooklyn/rest/domain/ConfigSummary.java b/usage/rest-api/src/main/java/org/apache/brooklyn/rest/domain/ConfigSummary.java
index 5f79e4d..12d5a9d 100644
--- a/usage/rest-api/src/main/java/org/apache/brooklyn/rest/domain/ConfigSummary.java
+++ b/usage/rest-api/src/main/java/org/apache/brooklyn/rest/domain/ConfigSummary.java
@@ -86,7 +86,7 @@ public abstract class ConfigSummary implements HasName, Serializable {
this.reconfigurable = config.isReconfigurable();
/* Use String, to guarantee it is serializable; otherwise get:
- * No serializer found for class brooklyn.policy.autoscaling.AutoScalerPolicy$3 and no properties discovered to create BeanSerializer (to avoid exception, disable SerializationConfig.Feature.FAIL_ON_EMPTY_BEANS) ) (through reference chain: java.util.ArrayList[9]->org.apache.brooklyn.rest.domain.PolicyConfigSummary["defaultValue"])
+ * No serializer found for class org.apache.brooklyn.policy.autoscaling.AutoScalerPolicy$3 and no properties discovered to create BeanSerializer (to avoid exception, disable SerializationConfig.Feature.FAIL_ON_EMPTY_BEANS) ) (through reference chain: java.util.ArrayList[9]->org.apache.brooklyn.rest.domain.PolicyConfigSummary["defaultValue"])
* at org.codehaus.jackson.map.ser.impl.UnknownSerializer.failForEmpty(UnknownSerializer.java:52)
*/
this.label = label;
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/d30ff597/usage/rest-server/src/test/java/org/apache/brooklyn/rest/resources/CatalogResourceTest.java
----------------------------------------------------------------------
diff --git a/usage/rest-server/src/test/java/org/apache/brooklyn/rest/resources/CatalogResourceTest.java b/usage/rest-server/src/test/java/org/apache/brooklyn/rest/resources/CatalogResourceTest.java
index 83690d8..68c7082 100644
--- a/usage/rest-server/src/test/java/org/apache/brooklyn/rest/resources/CatalogResourceTest.java
+++ b/usage/rest-server/src/test/java/org/apache/brooklyn/rest/resources/CatalogResourceTest.java
@@ -45,7 +45,7 @@ import org.apache.brooklyn.api.catalog.CatalogItem.CatalogBundle;
import org.apache.brooklyn.core.catalog.internal.CatalogUtils;
import org.apache.brooklyn.core.management.osgi.OsgiStandaloneTest;
-import brooklyn.policy.autoscaling.AutoScalerPolicy;
+import org.apache.brooklyn.policy.autoscaling.AutoScalerPolicy;
import org.apache.brooklyn.rest.domain.CatalogEntitySummary;
import org.apache.brooklyn.rest.domain.CatalogItemSummary;
[24/24] incubator-brooklyn git commit: This closes #832
Posted by he...@apache.org.
This closes #832
Project: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/commit/f092e183
Tree: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/tree/f092e183
Diff: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/diff/f092e183
Branch: refs/heads/master
Commit: f092e183f9fc0ebc060d615a0efdea78357d9f87
Parents: bf2cfcd 3fff641
Author: Alex Heneveld <al...@cloudsoftcorp.com>
Authored: Tue Aug 18 12:04:55 2015 +0100
Committer: Alex Heneveld <al...@cloudsoftcorp.com>
Committed: Tue Aug 18 12:04:55 2015 +0100
----------------------------------------------------------------------
.../database/postgresql/PostgreSqlNode.java | 23 ++++++-
.../postgresql/PostgreSqlSshDriver.java | 64 +++++++++++++++++---
2 files changed, 77 insertions(+), 10 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/f092e183/software/database/src/main/java/org/apache/brooklyn/entity/database/postgresql/PostgreSqlNode.java
----------------------------------------------------------------------
diff --cc software/database/src/main/java/org/apache/brooklyn/entity/database/postgresql/PostgreSqlNode.java
index 7d195f7,0000000..70ac0c7
mode 100644,000000..100644
--- a/software/database/src/main/java/org/apache/brooklyn/entity/database/postgresql/PostgreSqlNode.java
+++ b/software/database/src/main/java/org/apache/brooklyn/entity/database/postgresql/PostgreSqlNode.java
@@@ -1,95 -1,0 +1,116 @@@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.brooklyn.entity.database.postgresql;
+
+import org.apache.brooklyn.api.catalog.Catalog;
+import org.apache.brooklyn.api.entity.Effector;
+import org.apache.brooklyn.api.entity.proxying.ImplementedBy;
+import org.apache.brooklyn.api.entity.trait.HasShortName;
+import org.apache.brooklyn.core.util.flags.SetFromFlag;
+
+import brooklyn.config.ConfigKey;
+import brooklyn.entity.basic.ConfigKeys;
+import brooklyn.entity.basic.SoftwareProcess;
+import org.apache.brooklyn.entity.database.DatabaseNode;
+import org.apache.brooklyn.entity.database.DatastoreMixins;
+import org.apache.brooklyn.entity.database.DatastoreMixins.DatastoreCommon;
+import brooklyn.entity.effector.Effectors;
++import brooklyn.event.basic.BasicAttributeSensorAndConfigKey;
+import brooklyn.event.basic.PortAttributeSensorAndConfigKey;
-
+import org.apache.brooklyn.location.basic.PortRanges;
+
+/**
+ * PostgreSQL database node entity.
+ * <p>
+ * <ul>
+ * <li>You may need to increase shared memory settings in the kernel depending on the setting of
+ * the {@link #SHARED_MEMORY_BUFFER} key. The minimumm value is <em>128kB</em>. See the PostgreSQL
+ * <a href="http://www.postgresql.org/docs/9.1/static/kernel-resources.html">documentation</a>.
+ * <li>You will also need to enable passwordless sudo.
+ * </ul>
+ */
+@Catalog(name="PostgreSQL Node", description="PostgreSQL is an object-relational database management system (ORDBMS)", iconUrl="classpath:///postgresql-logo-200px.png")
+@ImplementedBy(PostgreSqlNodeImpl.class)
+public interface PostgreSqlNode extends SoftwareProcess, HasShortName, DatastoreCommon, DatabaseNode {
+
+ @SetFromFlag("version")
+ ConfigKey<String> SUGGESTED_VERSION = ConfigKeys.newConfigKeyWithDefault(SoftwareProcess.SUGGESTED_VERSION, "9.3-1");//"9.1-4");
+
+ @SetFromFlag("configFileUrl")
+ ConfigKey<String> CONFIGURATION_FILE_URL = ConfigKeys.newStringConfigKey(
+ "postgresql.config.file.url", "URL where PostgreSQL configuration file can be found; "
+ + "if not supplied the blueprint uses the default and customises it");
+
+ @SetFromFlag("authConfigFileUrl")
+ ConfigKey<String> AUTHENTICATION_CONFIGURATION_FILE_URL = ConfigKeys.newStringConfigKey(
+ "postgresql.authConfig.file.url", "URL where PostgreSQL host-based authentication configuration file can be found; "
+ + "if not supplied the blueprint uses the default and customises it");
+
+ @SetFromFlag("port")
+ PortAttributeSensorAndConfigKey POSTGRESQL_PORT = new PortAttributeSensorAndConfigKey(
+ "postgresql.port", "PostgreSQL port", PortRanges.fromString("5432+"));
+
+ @SetFromFlag("sharedMemory")
+ ConfigKey<String> SHARED_MEMORY = ConfigKeys.newStringConfigKey(
+ "postgresql.sharedMemory", "Size of shared memory buffer (must specify as kB, MB or GB, minimum 128kB)", "4MB");
+
+ @SetFromFlag("maxConnections")
+ ConfigKey<Integer> MAX_CONNECTIONS = ConfigKeys.newIntegerConfigKey(
+ "postgresql.maxConnections", "Maximum number of connections to the database", 100);
+
+ @SetFromFlag("disconnectOnStop")
+ ConfigKey<Boolean> DISCONNECT_ON_STOP = ConfigKeys.newBooleanConfigKey(
+ "postgresql.disconnect.on.stop", "If true, PostgreSQL will immediately disconnet (pg_ctl -m immediate stop) all current connections when the node is stopped", true);
+
+ @SetFromFlag("pollPeriod")
+ ConfigKey<Long> POLL_PERIOD = ConfigKeys.newLongConfigKey(
+ "postgresql.sensorpoll", "Poll period (in milliseconds)", 1000L);
++
++ @SetFromFlag("initializeDB")
++ ConfigKey<Boolean> INITIALIZE_DB = ConfigKeys.newBooleanConfigKey(
++ "postgresql.initialize", "If true, PostgreSQL will create a new user and database", false);
++
++ @SetFromFlag("username")
++ BasicAttributeSensorAndConfigKey<String> USERNAME = new BasicAttributeSensorAndConfigKey<>(
++ String.class, "postgresql.username", "Username of the database user");
++
++ String DEFAULT_USERNAME = "postgresqluser";
++
++ @SetFromFlag("password")
++ BasicAttributeSensorAndConfigKey<String> PASSWORD = new BasicAttributeSensorAndConfigKey<>(
++ String.class, "postgresql.password",
++ "Password for the database user, auto-generated if not set");
++
++ @SetFromFlag("database")
++ BasicAttributeSensorAndConfigKey<String> DATABASE = new BasicAttributeSensorAndConfigKey<>(
++ String.class, "postgresql.database", "Database to be used");
++
++ String DEFAULT_DB_NAME = "db";
+
+ Effector<String> EXECUTE_SCRIPT = Effectors.effector(DatastoreMixins.EXECUTE_SCRIPT)
+ .description("Executes the given script contents using psql")
+ .buildAbstract();
+
+ Integer getPostgreSqlPort();
+ String getSharedMemory();
+ Integer getMaxConnections();
+
+ String executeScript(String commands);
+
+}
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/f092e183/software/database/src/main/java/org/apache/brooklyn/entity/database/postgresql/PostgreSqlSshDriver.java
----------------------------------------------------------------------
diff --cc software/database/src/main/java/org/apache/brooklyn/entity/database/postgresql/PostgreSqlSshDriver.java
index d66ed76,0000000..54b88a2
mode 100644,000000..100644
--- a/software/database/src/main/java/org/apache/brooklyn/entity/database/postgresql/PostgreSqlSshDriver.java
+++ b/software/database/src/main/java/org/apache/brooklyn/entity/database/postgresql/PostgreSqlSshDriver.java
@@@ -1,425 -1,0 +1,471 @@@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.brooklyn.entity.database.postgresql;
+
+import static brooklyn.util.ssh.BashCommands.INSTALL_WGET;
+import static brooklyn.util.ssh.BashCommands.alternativesGroup;
+import static brooklyn.util.ssh.BashCommands.chainGroup;
+import static brooklyn.util.ssh.BashCommands.dontRequireTtyForSudo;
+import static brooklyn.util.ssh.BashCommands.executeCommandThenAsUserTeeOutputToFile;
+import static brooklyn.util.ssh.BashCommands.fail;
+import static brooklyn.util.ssh.BashCommands.ifExecutableElse0;
+import static brooklyn.util.ssh.BashCommands.ifExecutableElse1;
+import static brooklyn.util.ssh.BashCommands.installPackage;
+import static brooklyn.util.ssh.BashCommands.sudo;
+import static brooklyn.util.ssh.BashCommands.sudoAsUser;
+import static brooklyn.util.ssh.BashCommands.warn;
+import static java.lang.String.format;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+
+import javax.annotation.Nullable;
+
++import org.apache.brooklyn.api.location.OsDetails;
++import org.apache.brooklyn.core.util.task.DynamicTasks;
++import org.apache.brooklyn.core.util.task.ssh.SshTasks;
++import org.apache.brooklyn.core.util.task.ssh.SshTasks.OnFailingTask;
++import org.apache.brooklyn.core.util.task.system.ProcessTaskWrapper;
++import org.apache.brooklyn.location.basic.SshMachineLocation;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import brooklyn.entity.basic.AbstractSoftwareProcessSshDriver;
+import brooklyn.entity.basic.Attributes;
+import brooklyn.entity.basic.SoftwareProcess;
+import org.apache.brooklyn.entity.database.DatastoreMixins;
+import brooklyn.entity.software.SshEffectorTasks;
-
- import org.apache.brooklyn.api.location.OsDetails;
- import org.apache.brooklyn.core.util.task.DynamicTasks;
- import org.apache.brooklyn.core.util.task.ssh.SshTasks;
- import org.apache.brooklyn.core.util.task.ssh.SshTasks.OnFailingTask;
- import org.apache.brooklyn.core.util.task.system.ProcessTaskWrapper;
- import org.apache.brooklyn.location.basic.SshMachineLocation;
-
++import brooklyn.event.basic.BasicAttributeSensorAndConfigKey;
+import brooklyn.util.collections.MutableList;
+import brooklyn.util.collections.MutableMap;
+import brooklyn.util.exceptions.Exceptions;
+import brooklyn.util.net.Urls;
+import brooklyn.util.os.Os;
+import brooklyn.util.stream.Streams;
+import brooklyn.util.text.Identifiers;
++import brooklyn.util.text.StringEscapes;
+import brooklyn.util.text.StringFunctions;
+import brooklyn.util.text.Strings;
+
+import com.google.common.base.Charsets;
+import com.google.common.base.Function;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Iterables;
+import com.google.common.io.Files;
+
+/**
+ * The SSH implementation of the {@link PostgreSqlDriver}.
+ */
+public class PostgreSqlSshDriver extends AbstractSoftwareProcessSshDriver implements PostgreSqlDriver {
+
+ public static final Logger log = LoggerFactory.getLogger(PostgreSqlSshDriver.class);
+
+ public PostgreSqlSshDriver(PostgreSqlNodeImpl entity, SshMachineLocation machine) {
+ super(entity, machine);
+
+ entity.setAttribute(Attributes.LOG_FILE_LOCATION, getLogFile());
+ }
+
+ /*
+ * TODO this is much messier than we would like because postgres runs as user postgres,
+ * meaning the dirs must be RW by that user, and accessible (thus all parent paths),
+ * which may rule out putting it in a location used by the default user.
+ * Two irritating things:
+ * * currently we sometimes make up a different onbox base dir;
+ * * currently we put files to /tmp for staging
+ * Could investigate if it really needs to run as user postgres;
+ * could also see whether default user can be added to group postgres,
+ * and the run dir (and all parents) made accessible to group postgres.
+ */
+ @Override
+ public void install() {
+ String version = getEntity().getConfig(SoftwareProcess.SUGGESTED_VERSION);
+ String majorMinorVersion = version.substring(0, version.lastIndexOf("-"));
+ String shortVersion = majorMinorVersion.replace(".", "");
+
+ String altTarget = "/opt/brooklyn/postgres/";
+ String altInstallDir = Urls.mergePaths(altTarget, "install/"+majorMinorVersion);
+
+ Iterable<String> pgctlLocations = ImmutableList.of(
+ altInstallDir+"/bin",
+ "/usr/lib/postgresql/"+majorMinorVersion+"/bin/",
+ "/opt/local/lib/postgresql"+shortVersion+"/bin/",
+ "/usr/pgsql-"+majorMinorVersion+"/bin",
+ "/usr/local/bin/",
+ "/usr/bin/",
+ "/bin/");
+
+ DynamicTasks.queueIfPossible(SshTasks.dontRequireTtyForSudo(getMachine(),
+ // sudo is absolutely required here, in customize we set user to postgres
+ OnFailingTask.FAIL)).orSubmitAndBlock();
+ DynamicTasks.waitForLast();
+
+ // Check whether we can find a usable pg_ctl, and if not install one
+ MutableList<String> findOrInstall = MutableList.<String>of()
+ .append("which pg_ctl")
+ .appendAll(Iterables.transform(pgctlLocations, StringFunctions.formatter("test -x %s/pg_ctl")))
+ .append(installPackage(ImmutableMap.of(
+ "yum", "postgresql"+shortVersion+" postgresql"+shortVersion+"-server",
+ "apt", "postgresql-"+majorMinorVersion,
+ "port", "postgresql"+shortVersion+" postgresql"+shortVersion+"-server"
+ ), null))
+ // due to impl of installPackage, it will not come to the line below I don't think
+ .append(warn(format("WARNING: failed to find or install postgresql %s binaries", majorMinorVersion)));
+
+ // Link to correct binaries folder (different versions of pg_ctl and psql don't always play well together)
+ MutableList<String> linkFromHere = MutableList.<String>of()
+ .append(ifExecutableElse1("pg_ctl", chainGroup(
+ "PG_EXECUTABLE=`which pg_ctl`",
+ "PG_DIR=`dirname $PG_EXECUTABLE`",
+ "echo 'found pg_ctl in '$PG_DIR' on path so linking PG bin/ to that dir'",
+ "ln -s $PG_DIR bin")))
+ .appendAll(Iterables.transform(pgctlLocations, givenDirIfFileExistsInItLinkToDir("pg_ctl", "bin")))
+ .append(fail(format("WARNING: failed to find postgresql %s binaries for pg_ctl, may already have another version installed; aborting", majorMinorVersion), 9));
+
+ newScript(INSTALLING)
+ .body.append(
+ dontRequireTtyForSudo(),
+ ifExecutableElse0("yum", getYumRepository(version, majorMinorVersion, shortVersion)),
+ ifExecutableElse0("apt-get", getAptRepository()),
+ "rm -f bin", // if left over from previous incomplete/failed install (not sure why that keeps happening!)
+ alternativesGroup(findOrInstall),
+ alternativesGroup(linkFromHere))
+ .failOnNonZeroResultCode()
+ .queue();
+
+ // check that the proposed install dir is one that user postgres can access
+ if (DynamicTasks.queue(SshEffectorTasks.ssh(sudoAsUser("postgres", "ls "+getInstallDir())).allowingNonZeroExitCode()
+ .summary("check postgres user can access install dir")).asTask().getUnchecked()!=0) {
+ log.info("Postgres install dir "+getInstallDir()+" for "+getEntity()+" is not accessible to user 'postgres'; " + "using "+altInstallDir+" instead");
+ String newRunDir = Urls.mergePaths(altTarget, "apps", getEntity().getApplication().getId(), getEntity().getId());
+ if (DynamicTasks.queue(SshEffectorTasks.ssh("ls "+altInstallDir+"/pg_ctl").allowingNonZeroExitCode()
+ .summary("check whether "+altInstallDir+" is set up")).asTask().getUnchecked()==0) {
+ // alt target already exists with binary; nothing to do for install
+ } else {
+ DynamicTasks.queue(SshEffectorTasks.ssh(
+ "mkdir -p "+altInstallDir,
+ "rm -rf '"+altInstallDir+"'",
+ "mv "+getInstallDir()+" "+altInstallDir,
+ "rm -rf '"+getInstallDir()+"'",
+ "ln -s "+altInstallDir+" "+getInstallDir(),
+ "mkdir -p " + newRunDir,
+ "chown -R postgres:postgres "+altTarget).runAsRoot().requiringExitCodeZero()
+ .summary("move install dir from user to postgres owned space"));
+ }
+ DynamicTasks.waitForLast();
+ setInstallDir(altInstallDir);
+ setRunDir(newRunDir);
+ }
+ }
+
+ private String getYumRepository(String version, String majorMinorVersion, String shortVersion) {
+ // postgres becomes available if you add the repos using an RPM such as
+ // http://yum.postgresql.org/9.3/redhat/rhel-6-i386/pgdg-centos93-9.3-1.noarch.rpm
+ // fedora, rhel, sl, and centos supported for RPM's
+
+ OsDetails osDetails = getMachine().getMachineDetails().getOsDetails();
+ String arch = osDetails.getArch();
+ String osMajorVersion = osDetails.getVersion();
+ String osName = osDetails.getName();
+
+ log.debug("postgres detecting yum information for "+getEntity()+" at "+getMachine()+": "+osName+", "+osMajorVersion+", "+arch);
+
+ if (osName==null) osName = ""; else osName = osName.toLowerCase();
+
+ if (osName.equals("ubuntu")) return "echo skipping yum repo setup as this is not an rpm environment";
+
+ if (osName.equals("rhel")) osName = "redhat";
+ else if (osName.equals("centos")) osName = "centos";
+ else if (osName.equals("sl") || osName.startsWith("scientific")) osName = "sl";
+ else if (osName.equals("fedora")) osName = "fedora";
+ else {
+ log.debug("insufficient OS family information '"+osName+"' for "+getMachine()+" when installing "+getEntity()+" (yum repos); treating as centos");
+ osName = "centos";
+ }
+
+ if (Strings.isBlank(arch)) {
+ log.warn("Insuffient architecture information '"+arch+"' for "+getMachine()+"when installing "+getEntity()+"; treating as x86_64");
+ arch = "x86_64";
+ }
+
+ if (Strings.isBlank(osMajorVersion)) {
+ if (osName.equals("fedora")) osMajorVersion = "20";
+ else osMajorVersion = "6";
+ log.warn("Insuffient OS version information '"+getMachine().getOsDetails().getVersion()+"' for "+getMachine()+"when installing "+getEntity()+" (yum repos); treating as "+osMajorVersion);
+ } else {
+ if (osMajorVersion.indexOf(".")>0)
+ osMajorVersion = osMajorVersion.substring(0, osMajorVersion.indexOf('.'));
+ }
+
+ return chainGroup(
+ INSTALL_WGET,
+ sudo(format("wget http://yum.postgresql.org/%s/redhat/rhel-%s-%s/pgdg-%s%s-%s.noarch.rpm", majorMinorVersion, osMajorVersion, arch, osName, shortVersion, version)),
+ sudo(format("rpm -Uvh pgdg-%s%s-%s.noarch.rpm", osName, shortVersion, version))
+ );
+ }
+
+ private String getAptRepository() {
+ return chainGroup(
+ INSTALL_WGET,
+ "wget --quiet -O - http://apt.postgresql.org/pub/repos/apt/ACCC4CF8.asc | sudo tee -a apt-key add -",
+ "echo \"deb http://apt.postgresql.org/pub/repos/apt/ $(sudo lsb_release --codename --short)-pgdg main\" | sudo tee -a /etc/apt/sources.list.d/postgresql.list"
+ );
+ }
+
+ private static Function<String, String> givenDirIfFileExistsInItLinkToDir(final String filename, final String linkToMake) {
+ return new Function<String, String>() {
+ public String apply(@Nullable String dir) {
+ return ifExecutableElse1(Urls.mergePaths(dir, filename),
+ chainGroup("echo 'found "+filename+" in "+dir+" so linking to it in "+linkToMake+"'", "ln -s "+dir+" "+linkToMake));
+ }
+ };
+ }
+
+ @Override
+ public void customize() {
+ // Some OSes start postgres during package installation
+ DynamicTasks.queue(SshEffectorTasks.ssh(sudoAsUser("postgres", "/etc/init.d/postgresql stop")).allowingNonZeroExitCode()).get();
+
+ newScript(CUSTOMIZING)
+ .body.append(
+ sudo("mkdir -p " + getDataDir()),
+ sudo("chown postgres:postgres " + getDataDir()),
+ sudo("chmod 700 " + getDataDir()),
+ sudo("touch " + getLogFile()),
+ sudo("chown postgres:postgres " + getLogFile()),
+ sudo("touch " + getPidFile()),
+ sudo("chown postgres:postgres " + getPidFile()),
+ alternativesGroup(
+ chainGroup(format("test -e %s", getInstallDir() + "/bin/initdb"),
+ sudoAsUser("postgres", getInstallDir() + "/bin/initdb -D " + getDataDir())),
+ callPgctl("initdb", true)))
+ .failOnNonZeroResultCode()
+ .execute();
+
+ String configUrl = getEntity().getConfig(PostgreSqlNode.CONFIGURATION_FILE_URL);
+ if (Strings.isBlank(configUrl)) {
+ // http://wiki.postgresql.org/wiki/Tuning_Your_PostgreSQL_Server
+ // If the same setting is listed multiple times, the last one wins.
+ DynamicTasks.queue(SshEffectorTasks.ssh(
+ executeCommandThenAsUserTeeOutputToFile(
+ chainGroup(
+ "echo \"listen_addresses = '*'\"",
+ "echo \"port = " + getEntity().getPostgreSqlPort() + "\"",
+ "echo \"max_connections = " + getEntity().getMaxConnections() + "\"",
+ "echo \"shared_buffers = " + getEntity().getSharedMemory() + "\"",
+ "echo \"external_pid_file = '" + getPidFile() + "'\""),
+ "postgres", getDataDir() + "/postgresql.conf")));
+ } else {
+ String contents = processTemplate(configUrl);
+ DynamicTasks.queue(
+ SshEffectorTasks.put("/tmp/postgresql.conf").contents(contents),
+ SshEffectorTasks.ssh(sudoAsUser("postgres", "cp /tmp/postgresql.conf " + getDataDir() + "/postgresql.conf")));
+ }
+
+ String authConfigUrl = getEntity().getConfig(PostgreSqlNode.AUTHENTICATION_CONFIGURATION_FILE_URL);
+ if (Strings.isBlank(authConfigUrl)) {
+ DynamicTasks.queue(SshEffectorTasks.ssh(
+ // TODO give users control which hosts can connect and the authentication mechanism
+ executeCommandThenAsUserTeeOutputToFile("echo \"host all all 0.0.0.0/0 md5\"", "postgres", getDataDir() + "/pg_hba.conf")));
+ } else {
+ String contents = processTemplate(authConfigUrl);
+ DynamicTasks.queue(
+ SshEffectorTasks.put("/tmp/pg_hba.conf").contents(contents),
+ SshEffectorTasks.ssh(sudoAsUser("postgres", "cp /tmp/pg_hba.conf " + getDataDir() + "/pg_hba.conf")));
+ }
+
+ // Wait for commands to complete before running the creation script
+ DynamicTasks.waitForLast();
-
++ if(entity.getConfig(PostgreSqlNode.INITIALIZE_DB)){
++ initializeNewDatabase();
++ }
+ // Capture log file contents if there is an error configuring the database
+ try {
+ executeDatabaseCreationScript();
+ } catch (RuntimeException r) {
+ logTailOfPostgresLog();
+ throw Exceptions.propagate(r);
+ }
+
+ // Try establishing an external connection. If you get a "Connection refused...accepting TCP/IP connections
+ // on port 5432?" error then the port is probably closed. Check that the firewall allows external TCP/IP
+ // connections (netstat -nap). You can open a port with lokkit or by configuring the iptables.
+ }
+
++ private void initializeNewDatabase() {
++ String createUserCommand = String.format(
++ "\"CREATE USER %s WITH PASSWORD '%s'; \"",
++ StringEscapes.escapeSql(getUsername()),
++ StringEscapes.escapeSql(getUserPassword())
++ );
++ String createDatabaseCommand = String.format(
++ "\"CREATE DATABASE %s OWNER %s\"",
++ StringEscapes.escapeSql(getDatabaseName()),
++ StringEscapes.escapeSql(getUsername()));
++ newScript("initializing user and database")
++ .body.append(
++ "cd " + getInstallDir(),
++ callPgctl("start", true),
++ sudoAsUser("postgres", getInstallDir() + "/bin/psql -p " + entity.getAttribute(PostgreSqlNode.POSTGRESQL_PORT) +
++ " --command="+ createUserCommand),
++ sudoAsUser("postgres", getInstallDir() + "/bin/psql -p " + entity.getAttribute(PostgreSqlNode.POSTGRESQL_PORT) +
++ " --command="+ createDatabaseCommand),
++ callPgctl("stop", true))
++ .failOnNonZeroResultCode().execute();
++ }
++
++ private String getConfigOrDefault(BasicAttributeSensorAndConfigKey<String> key, String def) {
++ String config = entity.getConfig(key);
++ if(Strings.isEmpty(config)) {
++ config = def;
++ log.debug(entity + " has no config specified for " + key + "; using default `" + def + "`");
++ entity.setAttribute(key, config);
++ }
++ return config;
++ }
++
++ protected String getDatabaseName() {
++ return getConfigOrDefault(PostgreSqlNode.DATABASE, PostgreSqlNode.DEFAULT_DB_NAME);
++ }
++
++ protected String getUsername(){
++ return getConfigOrDefault(PostgreSqlNode.USERNAME, PostgreSqlNode.DEFAULT_USERNAME);
++ }
++
++ protected String getUserPassword() {
++ return getConfigOrDefault(PostgreSqlNode.PASSWORD, Strings.makeRandomId(8));
++ }
++
+ protected void executeDatabaseCreationScript() {
+ if (copyDatabaseCreationScript()) {
+ newScript("running postgres creation script")
+ .body.append(
+ "cd " + getInstallDir(),
+ callPgctl("start", true),
+ sudoAsUser("postgres", getInstallDir() + "/bin/psql -p " + entity.getAttribute(PostgreSqlNode.POSTGRESQL_PORT) + " --file " + getRunDir() + "/creation-script.sql"),
+ callPgctl("stop", true))
+ .failOnNonZeroResultCode()
+ .execute();
+ }
+ }
+
+ private boolean installFile(InputStream contents, String destName) {
+ String uid = Identifiers.makeRandomId(8);
+ // TODO currently put in /tmp for staging, since run dir may not be accessible to ssh user
+ getMachine().copyTo(contents, "/tmp/"+destName+"_"+uid);
+ DynamicTasks.queueIfPossible(SshEffectorTasks.ssh(
+ "cd "+getRunDir(),
+ "mv /tmp/"+destName+"_"+uid+" "+destName,
+ "chown postgres:postgres "+destName,
+ "chmod 644 "+destName)
+ .runAsRoot().requiringExitCodeZero())
+ .orSubmitAndBlock(getEntity()).andWaitForSuccess();
+ return true;
+ }
+ private boolean copyDatabaseCreationScript() {
+ InputStream creationScript = DatastoreMixins.getDatabaseCreationScript(entity);
+ if (creationScript==null)
+ return false;
+ return installFile(creationScript, "creation-script.sql");
+ }
+
+ public String getDataDir() {
+ return getRunDir() + "/data";
+ }
+
+ public String getLogFile() {
+ return getRunDir() + "/postgresql.log";
+ }
+
+ public String getPidFile() {
+ return getRunDir() + "/postgresql.pid";
+ }
+
+ /** @deprecated since 0.7.0 renamed {@link #logTailOfPostgresLog()} */
+ @Deprecated
+ public void copyLogFileContents() { logTailOfPostgresLog(); }
+ public void logTailOfPostgresLog() {
+ try {
+ File file = Os.newTempFile("postgresql-"+getEntity().getId(), "log");
+ int result = getMachine().copyFrom(getLogFile(), file.getAbsolutePath());
+ if (result != 0) throw new IllegalStateException("Could not access log file " + getLogFile());
+ log.info("Saving {} contents as {}", getLogFile(), file);
+ Streams.logStreamTail(log, "postgresql.log", Streams.byteArrayOfString(Files.toString(file, Charsets.UTF_8)), 1024);
+ file.delete();
+ } catch (IOException ioe) {
+ log.debug("Error reading copied log file: {}", ioe);
+ }
+ }
+
+ protected String callPgctl(String command, boolean waitForIt) {
+ return sudoAsUser("postgres", getInstallDir() + "/bin/pg_ctl -D " + getDataDir() +
+ " -l " + getLogFile() + (waitForIt ? " -w " : " ") + command);
+ }
+
+ @Override
+ public void launch() {
+ log.info(String.format("Starting entity %s at %s", this, getLocation()));
+ newScript(MutableMap.of("usePidFile", false), LAUNCHING)
+ .body.append(callPgctl("start", false))
+ .execute();
+ }
+
+ @Override
+ public boolean isRunning() {
+ return newScript(MutableMap.of("usePidFile", getPidFile()), CHECK_RUNNING)
+ .body.append(getStatusCmd())
+ .execute() == 0;
+ }
+
+ @Override
+ public void stop() {
+ newScript(MutableMap.of("usePidFile", false), STOPPING)
+ .body.append(callPgctl((entity.getConfig(PostgreSqlNode.DISCONNECT_ON_STOP) ? "-m immediate " : "") + "stop", false))
+ .failOnNonZeroResultCode()
+ .execute();
+ newScript(MutableMap.of("usePidFile", getPidFile(), "processOwner", "postgres"), STOPPING).execute();
+ }
+
+ @Override
+ public PostgreSqlNodeImpl getEntity() {
+ return (PostgreSqlNodeImpl) super.getEntity();
+ }
+
+ @Override
+ public String getStatusCmd() {
+ return callPgctl("status", false);
+ }
+
+ public ProcessTaskWrapper<Integer> executeScriptAsync(String commands) {
+ String filename = "postgresql-commands-"+Identifiers.makeRandomId(8);
+ installFile(Streams.newInputStreamWithContents(commands), filename);
+ return executeScriptFromInstalledFileAsync(filename);
+ }
+
+ public ProcessTaskWrapper<Integer> executeScriptFromInstalledFileAsync(String filenameAlreadyInstalledAtServer) {
+ return DynamicTasks.queue(
+ SshEffectorTasks.ssh(
+ "cd "+getRunDir(),
+ sudoAsUser("postgres", getInstallDir() + "/bin/psql -p " + entity.getAttribute(PostgreSqlNode.POSTGRESQL_PORT) + " --file " + filenameAlreadyInstalledAtServer))
+ .summary("executing datastore script "+filenameAlreadyInstalledAtServer));
+ }
+
+}
[07/24] incubator-brooklyn git commit: [BROOKLYN-162] Renaming
package policy
Posted by he...@apache.org.
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/d30ff597/policy/src/test/java/org/apache/brooklyn/policy/ha/ConnectionFailureDetectorTest.java
----------------------------------------------------------------------
diff --git a/policy/src/test/java/org/apache/brooklyn/policy/ha/ConnectionFailureDetectorTest.java b/policy/src/test/java/org/apache/brooklyn/policy/ha/ConnectionFailureDetectorTest.java
new file mode 100644
index 0000000..e2d6998
--- /dev/null
+++ b/policy/src/test/java/org/apache/brooklyn/policy/ha/ConnectionFailureDetectorTest.java
@@ -0,0 +1,303 @@
+/*
+ * 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.policy.ha;
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertNotNull;
+import static org.testng.Assert.assertTrue;
+import static org.testng.Assert.fail;
+
+import java.io.IOException;
+import java.net.ServerSocket;
+import java.util.List;
+import java.util.concurrent.CopyOnWriteArrayList;
+
+import org.apache.brooklyn.api.event.Sensor;
+import org.apache.brooklyn.api.event.SensorEvent;
+import org.apache.brooklyn.api.event.SensorEventListener;
+import org.apache.brooklyn.api.management.ManagementContext;
+import org.apache.brooklyn.api.policy.PolicySpec;
+import org.apache.brooklyn.test.entity.LocalManagementContextForTests;
+import org.apache.brooklyn.test.entity.TestApplication;
+import org.testng.annotations.AfterMethod;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import brooklyn.entity.basic.ApplicationBuilder;
+import brooklyn.entity.basic.Entities;
+import org.apache.brooklyn.policy.ha.HASensors.FailureDescriptor;
+import brooklyn.test.Asserts;
+import brooklyn.util.collections.MutableMap;
+import brooklyn.util.time.Duration;
+
+import com.google.common.base.Predicate;
+import com.google.common.base.Predicates;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.net.HostAndPort;
+
+public class ConnectionFailureDetectorTest {
+
+ private static final int TIMEOUT_MS = 30*1000;
+ private static final int OVERHEAD = 250;
+ private static final int POLL_PERIOD = 100;
+
+ private ManagementContext managementContext;
+ private TestApplication app;
+
+ private List<SensorEvent<FailureDescriptor>> events;
+
+ private ServerSocket serverSocket;
+ private HostAndPort serverSocketAddress;
+
+ @BeforeMethod(alwaysRun=true)
+ public void setUp() throws Exception {
+ events = new CopyOnWriteArrayList<SensorEvent<FailureDescriptor>>();
+
+ managementContext = new LocalManagementContextForTests();
+ app = ApplicationBuilder.newManagedApp(TestApplication.class, managementContext);
+
+ app.getManagementContext().getSubscriptionManager().subscribe(
+ app,
+ HASensors.CONNECTION_FAILED,
+ new SensorEventListener<FailureDescriptor>() {
+ @Override public void onEvent(SensorEvent<FailureDescriptor> event) {
+ events.add(event);
+ }
+ });
+ app.getManagementContext().getSubscriptionManager().subscribe(
+ app,
+ HASensors.CONNECTION_RECOVERED,
+ new SensorEventListener<FailureDescriptor>() {
+ @Override public void onEvent(SensorEvent<FailureDescriptor> event) {
+ events.add(event);
+ }
+ });
+
+ serverSocketAddress = startServerSocket();
+ }
+
+ @AfterMethod(alwaysRun=true)
+ public void tearDown() throws Exception {
+ stopServerSocket();
+ if (managementContext != null) Entities.destroyAll(managementContext);
+ }
+
+ private HostAndPort startServerSocket() throws Exception {
+ if (serverSocketAddress != null) {
+ serverSocket = new ServerSocket(serverSocketAddress.getPort());
+ } else {
+ for (int i = 40000; i < 40100; i++) {
+ try {
+ serverSocket = new ServerSocket(i);
+ } catch (IOException e) {
+ // try next port
+ }
+ }
+ assertNotNull(serverSocket, "Failed to create server socket; no ports free in range!");
+ serverSocketAddress = HostAndPort.fromParts(serverSocket.getInetAddress().getHostAddress(), serverSocket.getLocalPort());
+ }
+ return serverSocketAddress;
+ }
+
+ private void stopServerSocket() throws Exception {
+ if (serverSocket != null) serverSocket.close();
+ }
+
+ @Test(groups="Integration") // Has a 1 second wait
+ public void testNotNotifiedOfFailuresForHealthy() throws Exception {
+ // Create members before and after the policy is registered, to test both scenarios
+
+ app.addPolicy(PolicySpec.create(ConnectionFailureDetector.class)
+ .configure(ConnectionFailureDetector.ENDPOINT, serverSocketAddress));
+
+ assertNoEventsContinually();
+ }
+
+ @Test
+ public void testNotifiedOfFailure() throws Exception {
+ app.addPolicy(PolicySpec.create(ConnectionFailureDetector.class)
+ .configure(ConnectionFailureDetector.ENDPOINT, serverSocketAddress));
+
+ stopServerSocket();
+
+ assertHasEventEventually(HASensors.CONNECTION_FAILED, Predicates.<Object>equalTo(app), null);
+ assertEquals(events.size(), 1, "events="+events);
+ }
+
+ @Test
+ public void testNotifiedOfRecovery() throws Exception {
+ app.addPolicy(PolicySpec.create(ConnectionFailureDetector.class)
+ .configure(ConnectionFailureDetector.ENDPOINT, serverSocketAddress));
+
+ stopServerSocket();
+ assertHasEventEventually(HASensors.CONNECTION_FAILED, Predicates.<Object>equalTo(app), null);
+
+ // make the connection recover
+ startServerSocket();
+ assertHasEventEventually(HASensors.CONNECTION_RECOVERED, Predicates.<Object>equalTo(app), null);
+ assertEquals(events.size(), 2, "events="+events);
+ }
+
+ @Test
+ public void testReportsFailureWhenAlreadyDownOnRegisteringPolicy() throws Exception {
+ stopServerSocket();
+
+ app.addPolicy(PolicySpec.create(ConnectionFailureDetector.class)
+ .configure(ConnectionFailureDetector.ENDPOINT, serverSocketAddress));
+
+ assertHasEventEventually(HASensors.CONNECTION_FAILED, Predicates.<Object>equalTo(app), null);
+ }
+
+ @Test(groups="Integration") // Because slow
+ public void testNotNotifiedOfTemporaryFailuresDuringStabilisationDelay() throws Exception {
+ app.addPolicy(PolicySpec.create(ConnectionFailureDetector.class)
+ .configure(ConnectionFailureDetector.ENDPOINT, serverSocketAddress)
+ .configure(ConnectionFailureDetector.CONNECTION_FAILED_STABILIZATION_DELAY, Duration.ONE_MINUTE));
+
+ stopServerSocket();
+ Thread.sleep(100);
+ startServerSocket();
+
+ assertNoEventsContinually();
+ }
+
+ @Test(groups="Integration") // Because slow
+ public void testNotifiedOfFailureAfterStabilisationDelay() throws Exception {
+ final int stabilisationDelay = 1000;
+
+ app.addPolicy(PolicySpec.create(ConnectionFailureDetector.class)
+ .configure(ConnectionFailureDetector.ENDPOINT, serverSocketAddress)
+ .configure(ConnectionFailureDetector.CONNECTION_FAILED_STABILIZATION_DELAY, Duration.of(stabilisationDelay)));
+
+ stopServerSocket();
+
+ assertNoEventsContinually(Duration.of(stabilisationDelay - OVERHEAD));
+ assertHasEventEventually(HASensors.CONNECTION_FAILED, Predicates.<Object>equalTo(app), null);
+ }
+
+ @Test(groups="Integration") // Because slow
+ public void testFailuresThenUpDownResetsStabilisationCount() throws Exception {
+ final long stabilisationDelay = 1000;
+
+ app.addPolicy(PolicySpec.create(ConnectionFailureDetector.class)
+ .configure(ConnectionFailureDetector.ENDPOINT, serverSocketAddress)
+ .configure(ConnectionFailureDetector.CONNECTION_FAILED_STABILIZATION_DELAY, Duration.of(stabilisationDelay)));
+
+ stopServerSocket();
+ assertNoEventsContinually(Duration.of(stabilisationDelay - OVERHEAD));
+
+ startServerSocket();
+ Thread.sleep(POLL_PERIOD+OVERHEAD);
+ stopServerSocket();
+ assertNoEventsContinually(Duration.of(stabilisationDelay - OVERHEAD));
+
+ assertHasEventEventually(HASensors.CONNECTION_FAILED, Predicates.<Object>equalTo(app), null);
+ }
+
+ @Test(groups="Integration") // Because slow
+ public void testNotNotifiedOfTemporaryRecoveryDuringStabilisationDelay() throws Exception {
+ final long stabilisationDelay = 1000;
+
+ app.addPolicy(PolicySpec.create(ConnectionFailureDetector.class)
+ .configure(ConnectionFailureDetector.ENDPOINT, serverSocketAddress)
+ .configure(ConnectionFailureDetector.CONNECTION_RECOVERED_STABILIZATION_DELAY, Duration.of(stabilisationDelay)));
+
+ stopServerSocket();
+ assertHasEventEventually(HASensors.CONNECTION_FAILED, Predicates.<Object>equalTo(app), null);
+ events.clear();
+
+ startServerSocket();
+ Thread.sleep(POLL_PERIOD+OVERHEAD);
+ stopServerSocket();
+
+ assertNoEventsContinually(Duration.of(stabilisationDelay + OVERHEAD));
+ }
+
+ @Test(groups="Integration") // Because slow
+ public void testNotifiedOfRecoveryAfterStabilisationDelay() throws Exception {
+ final int stabilisationDelay = 1000;
+
+ app.addPolicy(PolicySpec.create(ConnectionFailureDetector.class)
+ .configure(ConnectionFailureDetector.ENDPOINT, serverSocketAddress)
+ .configure(ConnectionFailureDetector.CONNECTION_RECOVERED_STABILIZATION_DELAY, Duration.of(stabilisationDelay)));
+
+ stopServerSocket();
+ assertHasEventEventually(HASensors.CONNECTION_FAILED, Predicates.<Object>equalTo(app), null);
+ events.clear();
+
+ startServerSocket();
+ assertNoEventsContinually(Duration.of(stabilisationDelay - OVERHEAD));
+ assertHasEventEventually(HASensors.CONNECTION_RECOVERED, Predicates.<Object>equalTo(app), null);
+ }
+
+ @Test(groups="Integration") // Because slow
+ public void testRecoversThenDownUpResetsStabilisationCount() throws Exception {
+ final long stabilisationDelay = 1000;
+
+ app.addPolicy(PolicySpec.create(ConnectionFailureDetector.class)
+ .configure(ConnectionFailureDetector.ENDPOINT, serverSocketAddress)
+ .configure(ConnectionFailureDetector.CONNECTION_RECOVERED_STABILIZATION_DELAY, Duration.of(stabilisationDelay)));
+
+ stopServerSocket();
+ assertHasEventEventually(HASensors.CONNECTION_FAILED, Predicates.<Object>equalTo(app), null);
+ events.clear();
+
+ startServerSocket();
+ assertNoEventsContinually(Duration.of(stabilisationDelay - OVERHEAD));
+
+ stopServerSocket();
+ Thread.sleep(POLL_PERIOD+OVERHEAD);
+ startServerSocket();
+ assertNoEventsContinually(Duration.of(stabilisationDelay - OVERHEAD));
+
+ assertHasEventEventually(HASensors.CONNECTION_RECOVERED, Predicates.<Object>equalTo(app), null);
+ }
+
+ private void assertHasEvent(Sensor<?> sensor, Predicate<Object> componentPredicate, Predicate<? super CharSequence> descriptionPredicate) {
+ for (SensorEvent<FailureDescriptor> event : events) {
+ if (event.getSensor().equals(sensor) &&
+ (componentPredicate == null || componentPredicate.apply(event.getValue().getComponent())) &&
+ (descriptionPredicate == null || descriptionPredicate.apply(event.getValue().getDescription()))) {
+ return;
+ }
+ }
+ fail("No matching "+sensor+" event found; events="+events);
+ }
+
+ private void assertHasEventEventually(final Sensor<?> sensor, final Predicate<Object> componentPredicate, final Predicate<? super CharSequence> descriptionPredicate) {
+ Asserts.succeedsEventually(MutableMap.of("timeout", TIMEOUT_MS), new Runnable() {
+ @Override public void run() {
+ assertHasEvent(sensor, componentPredicate, descriptionPredicate);
+ }});
+ }
+
+ private void assertNoEventsContinually(Duration duration) {
+ Asserts.succeedsContinually(ImmutableMap.of("timeout", duration), new Runnable() {
+ @Override public void run() {
+ assertTrue(events.isEmpty(), "events="+events);
+ }});
+ }
+
+ private void assertNoEventsContinually() {
+ Asserts.succeedsContinually(new Runnable() {
+ @Override public void run() {
+ assertTrue(events.isEmpty(), "events="+events);
+ }});
+ }
+}
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/d30ff597/policy/src/test/java/org/apache/brooklyn/policy/ha/HaPolicyRebindTest.java
----------------------------------------------------------------------
diff --git a/policy/src/test/java/org/apache/brooklyn/policy/ha/HaPolicyRebindTest.java b/policy/src/test/java/org/apache/brooklyn/policy/ha/HaPolicyRebindTest.java
new file mode 100644
index 0000000..3782ccf
--- /dev/null
+++ b/policy/src/test/java/org/apache/brooklyn/policy/ha/HaPolicyRebindTest.java
@@ -0,0 +1,173 @@
+/*
+ * 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.policy.ha;
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertFalse;
+import static org.testng.Assert.fail;
+
+import java.util.List;
+import java.util.Set;
+
+import org.apache.brooklyn.api.entity.Entity;
+import org.apache.brooklyn.api.entity.proxying.EntitySpec;
+import org.apache.brooklyn.api.event.Sensor;
+import org.apache.brooklyn.api.event.SensorEvent;
+import org.apache.brooklyn.api.event.SensorEventListener;
+import org.apache.brooklyn.api.location.Location;
+import org.apache.brooklyn.api.location.LocationSpec;
+import org.apache.brooklyn.api.policy.EnricherSpec;
+import org.apache.brooklyn.api.policy.PolicySpec;
+import org.apache.brooklyn.test.entity.TestApplication;
+import org.apache.brooklyn.test.entity.TestEntity;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import brooklyn.entity.basic.Entities;
+import brooklyn.entity.basic.Lifecycle;
+import brooklyn.entity.basic.ServiceStateLogic;
+import brooklyn.entity.group.DynamicCluster;
+import brooklyn.entity.rebind.RebindTestFixtureWithApp;
+
+import org.apache.brooklyn.location.basic.SimulatedLocation;
+
+import org.apache.brooklyn.policy.ha.HASensors.FailureDescriptor;
+import brooklyn.test.Asserts;
+import brooklyn.util.collections.MutableMap;
+
+import com.google.common.base.Predicate;
+import com.google.common.base.Predicates;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Sets;
+
+public class HaPolicyRebindTest extends RebindTestFixtureWithApp {
+
+ private TestEntity origEntity;
+ private SensorEventListener<FailureDescriptor> eventListener;
+ private List<SensorEvent<FailureDescriptor>> events;
+
+ @Override
+ @BeforeMethod(alwaysRun=true)
+ public void setUp() throws Exception {
+ super.setUp();
+ origEntity = origApp.createAndManageChild(EntitySpec.create(TestEntity.class));
+ events = Lists.newCopyOnWriteArrayList();
+ eventListener = new SensorEventListener<FailureDescriptor>() {
+ @Override public void onEvent(SensorEvent<FailureDescriptor> event) {
+ events.add(event);
+ }
+ };
+ }
+
+ @Test
+ public void testServiceRestarterWorksAfterRebind() throws Exception {
+ origEntity.addPolicy(PolicySpec.create(ServiceRestarter.class)
+ .configure(ServiceRestarter.FAILURE_SENSOR_TO_MONITOR, HASensors.ENTITY_FAILED));
+
+ TestApplication newApp = rebind();
+ final TestEntity newEntity = (TestEntity) Iterables.find(newApp.getChildren(), Predicates.instanceOf(TestEntity.class));
+
+ newEntity.emit(HASensors.ENTITY_FAILED, new FailureDescriptor(origEntity, "simulate failure"));
+
+ Asserts.succeedsEventually(new Runnable() {
+ @Override public void run() {
+ assertEquals(newEntity.getCallHistory(), ImmutableList.of("restart"));
+ }});
+ }
+
+ @Test
+ public void testServiceReplacerWorksAfterRebind() throws Exception {
+ Location origLoc = origManagementContext.getLocationManager().createLocation(LocationSpec.create(SimulatedLocation.class));
+ DynamicCluster origCluster = origApp.createAndManageChild(EntitySpec.create(DynamicCluster.class)
+ .configure(DynamicCluster.MEMBER_SPEC, EntitySpec.create(TestEntity.class))
+ .configure(DynamicCluster.INITIAL_SIZE, 3));
+ origApp.start(ImmutableList.<Location>of(origLoc));
+
+ origCluster.addPolicy(PolicySpec.create(ServiceReplacer.class)
+ .configure(ServiceReplacer.FAILURE_SENSOR_TO_MONITOR, HASensors.ENTITY_FAILED));
+
+ // rebind
+ TestApplication newApp = rebind();
+ final DynamicCluster newCluster = (DynamicCluster) Iterables.find(newApp.getChildren(), Predicates.instanceOf(DynamicCluster.class));
+
+ // stimulate the policy
+ final Set<Entity> initialMembers = ImmutableSet.copyOf(newCluster.getMembers());
+ final TestEntity e1 = (TestEntity) Iterables.get(initialMembers, 1);
+
+ newApp.getManagementContext().getSubscriptionManager().subscribe(e1, HASensors.ENTITY_FAILED, eventListener);
+ newApp.getManagementContext().getSubscriptionManager().subscribe(e1, HASensors.ENTITY_RECOVERED, eventListener);
+
+ e1.emit(HASensors.ENTITY_FAILED, new FailureDescriptor(e1, "simulate failure"));
+
+ // Expect e1 to be replaced
+ Asserts.succeedsEventually(new Runnable() {
+ @Override public void run() {
+ Set<Entity> newMembers = Sets.difference(ImmutableSet.copyOf(newCluster.getMembers()), initialMembers);
+ Set<Entity> removedMembers = Sets.difference(initialMembers, ImmutableSet.copyOf(newCluster.getMembers()));
+ assertEquals(removedMembers, ImmutableSet.of(e1));
+ assertEquals(newMembers.size(), 1);
+ assertEquals(((TestEntity)Iterables.getOnlyElement(newMembers)).getCallHistory(), ImmutableList.of("start"));
+
+ // TODO e1 not reporting "start" after rebind because callHistory is a field rather than an attribute, so was not persisted
+ Asserts.assertEqualsIgnoringOrder(e1.getCallHistory(), ImmutableList.of("stop"));
+ assertFalse(Entities.isManaged(e1));
+ }});
+ }
+
+ @Test
+ public void testServiceFailureDetectorWorksAfterRebind() throws Exception {
+ origEntity.addEnricher(EnricherSpec.create(ServiceFailureDetector.class));
+
+ // rebind
+ TestApplication newApp = rebind();
+ final TestEntity newEntity = (TestEntity) Iterables.find(newApp.getChildren(), Predicates.instanceOf(TestEntity.class));
+
+ newApp.getManagementContext().getSubscriptionManager().subscribe(newEntity, HASensors.ENTITY_FAILED, eventListener);
+
+ newEntity.setAttribute(TestEntity.SERVICE_UP, true);
+ ServiceStateLogic.setExpectedState(newEntity, Lifecycle.RUNNING);
+
+ // trigger the failure
+ newEntity.setAttribute(TestEntity.SERVICE_UP, false);
+
+ assertHasEventEventually(HASensors.ENTITY_FAILED, Predicates.<Object>equalTo(newEntity), null);
+ assertEquals(events.size(), 1, "events="+events);
+ }
+
+ private void assertHasEventEventually(final Sensor<?> sensor, final Predicate<Object> componentPredicate, final Predicate<? super CharSequence> descriptionPredicate) {
+ Asserts.succeedsEventually(MutableMap.of("timeout", TIMEOUT_MS), new Runnable() {
+ @Override public void run() {
+ assertHasEvent(sensor, componentPredicate, descriptionPredicate);
+ }});
+ }
+
+ private void assertHasEvent(Sensor<?> sensor, Predicate<Object> componentPredicate, Predicate<? super CharSequence> descriptionPredicate) {
+ for (SensorEvent<FailureDescriptor> event : events) {
+ if (event.getSensor().equals(sensor) &&
+ (componentPredicate == null || componentPredicate.apply(event.getValue().getComponent())) &&
+ (descriptionPredicate == null || descriptionPredicate.apply(event.getValue().getDescription()))) {
+ return;
+ }
+ }
+ fail("No matching "+sensor+" event found; events="+events);
+ }
+}
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/d30ff597/policy/src/test/java/org/apache/brooklyn/policy/ha/ServiceFailureDetectorStabilizationTest.java
----------------------------------------------------------------------
diff --git a/policy/src/test/java/org/apache/brooklyn/policy/ha/ServiceFailureDetectorStabilizationTest.java b/policy/src/test/java/org/apache/brooklyn/policy/ha/ServiceFailureDetectorStabilizationTest.java
new file mode 100644
index 0000000..ca84592
--- /dev/null
+++ b/policy/src/test/java/org/apache/brooklyn/policy/ha/ServiceFailureDetectorStabilizationTest.java
@@ -0,0 +1,234 @@
+/*
+ * 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.policy.ha;
+
+import static org.testng.Assert.assertTrue;
+import static org.testng.Assert.fail;
+
+import java.util.List;
+import java.util.concurrent.CopyOnWriteArrayList;
+
+import org.apache.brooklyn.api.entity.proxying.EntitySpec;
+import org.apache.brooklyn.api.event.Sensor;
+import org.apache.brooklyn.api.event.SensorEvent;
+import org.apache.brooklyn.api.event.SensorEventListener;
+import org.apache.brooklyn.api.management.ManagementContext;
+import org.apache.brooklyn.api.policy.EnricherSpec;
+import org.apache.brooklyn.test.entity.LocalManagementContextForTests;
+import org.apache.brooklyn.test.entity.TestApplication;
+import org.apache.brooklyn.test.entity.TestEntity;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.testng.annotations.AfterMethod;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import brooklyn.entity.basic.ApplicationBuilder;
+import brooklyn.entity.basic.Entities;
+import brooklyn.entity.basic.Lifecycle;
+import brooklyn.entity.basic.ServiceStateLogic;
+import brooklyn.entity.basic.ServiceStateLogicTest;
+import org.apache.brooklyn.policy.ha.HASensors.FailureDescriptor;
+import brooklyn.test.Asserts;
+import brooklyn.util.collections.MutableMap;
+import brooklyn.util.time.Duration;
+
+import com.google.common.base.Predicate;
+import com.google.common.base.Predicates;
+import com.google.common.collect.ImmutableMap;
+
+/** also see more primitive tests in {@link ServiceStateLogicTest} */
+public class ServiceFailureDetectorStabilizationTest {
+
+ private static final Logger LOG = LoggerFactory.getLogger(ServiceFailureDetectorStabilizationTest.class);
+
+ private static final int TIMEOUT_MS = 10*1000;
+ private static final int OVERHEAD = 250;
+
+ private ManagementContext managementContext;
+ private TestApplication app;
+ private TestEntity e1;
+
+ private List<SensorEvent<FailureDescriptor>> events;
+
+ @BeforeMethod(alwaysRun=true)
+ public void setUp() throws Exception {
+ events = new CopyOnWriteArrayList<SensorEvent<FailureDescriptor>>();
+
+ managementContext = new LocalManagementContextForTests();
+ app = ApplicationBuilder.newManagedApp(TestApplication.class, managementContext);
+ e1 = app.createAndManageChild(EntitySpec.create(TestEntity.class));
+ e1.setAttribute(TestEntity.SERVICE_UP, true);
+ ServiceStateLogic.setExpectedState(e1, Lifecycle.RUNNING);
+
+ app.getManagementContext().getSubscriptionManager().subscribe(
+ e1,
+ HASensors.ENTITY_FAILED,
+ new SensorEventListener<FailureDescriptor>() {
+ @Override public void onEvent(SensorEvent<FailureDescriptor> event) {
+ events.add(event);
+ }
+ });
+ app.getManagementContext().getSubscriptionManager().subscribe(
+ e1,
+ HASensors.ENTITY_RECOVERED,
+ new SensorEventListener<FailureDescriptor>() {
+ @Override public void onEvent(SensorEvent<FailureDescriptor> event) {
+ events.add(event);
+ }
+ });
+ }
+
+ @AfterMethod(alwaysRun=true)
+ public void tearDown() throws Exception {
+ if (managementContext != null) Entities.destroyAll(managementContext);
+ }
+
+ @Test(groups="Integration") // Because slow
+ public void testNotNotifiedOfTemporaryFailuresDuringStabilisationDelay() throws Exception {
+ e1.addEnricher(EnricherSpec.create(ServiceFailureDetector.class)
+ .configure(ServiceFailureDetector.ENTITY_FAILED_STABILIZATION_DELAY, Duration.ONE_MINUTE));
+
+ e1.setAttribute(TestEntity.SERVICE_UP, false);
+ Thread.sleep(100);
+ e1.setAttribute(TestEntity.SERVICE_UP, true);
+
+ assertNoEventsContinually();
+ }
+
+ @Test(groups="Integration") // Because slow
+ public void testNotifiedOfFailureAfterStabilisationDelay() throws Exception {
+ final int stabilisationDelay = 1000;
+
+ e1.addEnricher(EnricherSpec.create(ServiceFailureDetector.class)
+ .configure(ServiceFailureDetector.ENTITY_FAILED_STABILIZATION_DELAY, Duration.of(stabilisationDelay)));
+
+ e1.setAttribute(TestEntity.SERVICE_UP, false);
+
+ assertNoEventsContinually(Duration.of(stabilisationDelay - OVERHEAD));
+ assertHasEventEventually(HASensors.ENTITY_FAILED, Predicates.<Object>equalTo(e1), null);
+ }
+
+ @Test(groups="Integration") // Because slow
+ public void testFailuresThenUpDownResetsStabilisationCount() throws Exception {
+ LOG.debug("Running testFailuresThenUpDownResetsStabilisationCount");
+ final long stabilisationDelay = 1000;
+
+ e1.addEnricher(EnricherSpec.create(ServiceFailureDetector.class)
+ .configure(ServiceFailureDetector.ENTITY_FAILED_STABILIZATION_DELAY, Duration.of(stabilisationDelay)));
+
+ e1.setAttribute(TestEntity.SERVICE_UP, false);
+ assertNoEventsContinually(Duration.of(stabilisationDelay - OVERHEAD));
+
+ e1.setAttribute(TestEntity.SERVICE_UP, true);
+ Thread.sleep(OVERHEAD);
+ e1.setAttribute(TestEntity.SERVICE_UP, false);
+ assertNoEventsContinually(Duration.of(stabilisationDelay - OVERHEAD));
+
+ assertHasEventEventually(HASensors.ENTITY_FAILED, Predicates.<Object>equalTo(e1), null);
+ }
+
+ @Test(groups="Integration") // Because slow
+ public void testNotNotifiedOfTemporaryRecoveryDuringStabilisationDelay() throws Exception {
+ final long stabilisationDelay = 1000;
+
+ e1.addEnricher(EnricherSpec.create(ServiceFailureDetector.class)
+ .configure(ServiceFailureDetector.ENTITY_RECOVERED_STABILIZATION_DELAY, Duration.of(stabilisationDelay)));
+
+ e1.setAttribute(TestEntity.SERVICE_UP, false);
+ assertHasEventEventually(HASensors.ENTITY_FAILED, Predicates.<Object>equalTo(e1), null);
+ events.clear();
+
+ e1.setAttribute(TestEntity.SERVICE_UP, true);
+ Thread.sleep(100);
+ e1.setAttribute(TestEntity.SERVICE_UP, false);
+
+ assertNoEventsContinually(Duration.of(stabilisationDelay + OVERHEAD));
+ }
+
+ @Test(groups="Integration") // Because slow
+ public void testNotifiedOfRecoveryAfterStabilisationDelay() throws Exception {
+ final int stabilisationDelay = 1000;
+
+ e1.addEnricher(EnricherSpec.create(ServiceFailureDetector.class)
+ .configure(ServiceFailureDetector.ENTITY_RECOVERED_STABILIZATION_DELAY, Duration.of(stabilisationDelay)));
+
+ e1.setAttribute(TestEntity.SERVICE_UP, false);
+ assertHasEventEventually(HASensors.ENTITY_FAILED, Predicates.<Object>equalTo(e1), null);
+ events.clear();
+
+ e1.setAttribute(TestEntity.SERVICE_UP, true);
+ assertNoEventsContinually(Duration.of(stabilisationDelay - OVERHEAD));
+ assertHasEventEventually(HASensors.ENTITY_RECOVERED, Predicates.<Object>equalTo(e1), null);
+ }
+
+ @Test(groups="Integration") // Because slow
+ public void testRecoversThenDownUpResetsStabilisationCount() throws Exception {
+ final long stabilisationDelay = 1000;
+
+ e1.addEnricher(EnricherSpec.create(ServiceFailureDetector.class)
+ .configure(ServiceFailureDetector.ENTITY_RECOVERED_STABILIZATION_DELAY, Duration.of(stabilisationDelay)));
+
+ e1.setAttribute(TestEntity.SERVICE_UP, false);
+ assertHasEventEventually(HASensors.ENTITY_FAILED, Predicates.<Object>equalTo(e1), null);
+ events.clear();
+
+ e1.setAttribute(TestEntity.SERVICE_UP, true);
+ assertNoEventsContinually(Duration.of(stabilisationDelay - OVERHEAD));
+
+ e1.setAttribute(TestEntity.SERVICE_UP, false);
+ Thread.sleep(OVERHEAD);
+ e1.setAttribute(TestEntity.SERVICE_UP, true);
+ assertNoEventsContinually(Duration.of(stabilisationDelay - OVERHEAD));
+
+ assertHasEventEventually(HASensors.ENTITY_RECOVERED, Predicates.<Object>equalTo(e1), null);
+ }
+
+ private void assertHasEvent(Sensor<?> sensor, Predicate<Object> componentPredicate, Predicate<? super CharSequence> descriptionPredicate) {
+ for (SensorEvent<FailureDescriptor> event : events) {
+ if (event.getSensor().equals(sensor) &&
+ (componentPredicate == null || componentPredicate.apply(event.getValue().getComponent())) &&
+ (descriptionPredicate == null || descriptionPredicate.apply(event.getValue().getDescription()))) {
+ return;
+ }
+ }
+ fail("No matching "+sensor+" event found; events="+events);
+ }
+
+ private void assertHasEventEventually(final Sensor<?> sensor, final Predicate<Object> componentPredicate, final Predicate<? super CharSequence> descriptionPredicate) {
+ Asserts.succeedsEventually(MutableMap.of("timeout", TIMEOUT_MS), new Runnable() {
+ @Override public void run() {
+ assertHasEvent(sensor, componentPredicate, descriptionPredicate);
+ }});
+ }
+
+ private void assertNoEventsContinually(Duration duration) {
+ Asserts.succeedsContinually(ImmutableMap.of("timeout", duration), new Runnable() {
+ @Override public void run() {
+ assertTrue(events.isEmpty(), "events="+events);
+ }});
+ }
+
+ private void assertNoEventsContinually() {
+ Asserts.succeedsContinually(new Runnable() {
+ @Override public void run() {
+ assertTrue(events.isEmpty(), "events="+events);
+ }});
+ }
+}
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/d30ff597/policy/src/test/java/org/apache/brooklyn/policy/ha/ServiceFailureDetectorTest.java
----------------------------------------------------------------------
diff --git a/policy/src/test/java/org/apache/brooklyn/policy/ha/ServiceFailureDetectorTest.java b/policy/src/test/java/org/apache/brooklyn/policy/ha/ServiceFailureDetectorTest.java
new file mode 100644
index 0000000..2ad2d79
--- /dev/null
+++ b/policy/src/test/java/org/apache/brooklyn/policy/ha/ServiceFailureDetectorTest.java
@@ -0,0 +1,407 @@
+/*
+ * 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.policy.ha;
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertTrue;
+import static org.testng.Assert.fail;
+
+import java.util.List;
+import java.util.concurrent.CopyOnWriteArrayList;
+
+import org.apache.brooklyn.api.entity.proxying.EntitySpec;
+import org.apache.brooklyn.api.event.Sensor;
+import org.apache.brooklyn.api.event.SensorEvent;
+import org.apache.brooklyn.api.event.SensorEventListener;
+import org.apache.brooklyn.api.management.ManagementContext;
+import org.apache.brooklyn.api.policy.EnricherSpec;
+import org.apache.brooklyn.test.EntityTestUtils;
+import org.apache.brooklyn.test.entity.LocalManagementContextForTests;
+import org.apache.brooklyn.test.entity.TestApplication;
+import org.apache.brooklyn.test.entity.TestEntity;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.testng.annotations.AfterMethod;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import brooklyn.entity.basic.ApplicationBuilder;
+import brooklyn.entity.basic.Attributes;
+import brooklyn.entity.basic.Entities;
+import brooklyn.entity.basic.Lifecycle;
+import brooklyn.entity.basic.ServiceStateLogic;
+import brooklyn.entity.basic.ServiceStateLogic.ServiceProblemsLogic;
+import org.apache.brooklyn.policy.ha.HASensors.FailureDescriptor;
+import brooklyn.test.Asserts;
+import brooklyn.util.collections.MutableMap;
+import brooklyn.util.time.Duration;
+import brooklyn.util.time.Time;
+
+import com.google.common.base.Predicate;
+import com.google.common.base.Predicates;
+import com.google.common.collect.ImmutableMap;
+
+public class ServiceFailureDetectorTest {
+ private static final Logger log = LoggerFactory.getLogger(ServiceFailureDetectorTest.class);
+
+ private static final int TIMEOUT_MS = 10*1000;
+
+ private ManagementContext managementContext;
+ private TestApplication app;
+ private TestEntity e1;
+
+ private List<SensorEvent<FailureDescriptor>> events;
+ private SensorEventListener<FailureDescriptor> eventListener;
+
+ @BeforeMethod(alwaysRun=true)
+ public void setUp() throws Exception {
+ events = new CopyOnWriteArrayList<SensorEvent<FailureDescriptor>>();
+ eventListener = new SensorEventListener<FailureDescriptor>() {
+ @Override public void onEvent(SensorEvent<FailureDescriptor> event) {
+ events.add(event);
+ }
+ };
+
+ managementContext = new LocalManagementContextForTests();
+ app = ApplicationBuilder.newManagedApp(TestApplication.class, managementContext);
+ e1 = app.createAndManageChild(EntitySpec.create(TestEntity.class));
+ e1.addEnricher(ServiceStateLogic.newEnricherForServiceStateFromProblemsAndUp());
+
+ app.getManagementContext().getSubscriptionManager().subscribe(e1, HASensors.ENTITY_FAILED, eventListener);
+ app.getManagementContext().getSubscriptionManager().subscribe(e1, HASensors.ENTITY_RECOVERED, eventListener);
+ }
+
+ @AfterMethod(alwaysRun=true)
+ public void tearDown() throws Exception {
+ if (managementContext != null) Entities.destroyAll(managementContext);
+ }
+
+ @Test(groups="Integration") // Has a 1 second wait
+ public void testNotNotifiedOfFailuresForHealthy() throws Exception {
+ // Create members before and after the policy is registered, to test both scenarios
+ e1.setAttribute(TestEntity.SERVICE_UP, true);
+ ServiceStateLogic.setExpectedState(e1, Lifecycle.RUNNING);
+
+ e1.addEnricher(EnricherSpec.create(ServiceFailureDetector.class));
+
+ assertNoEventsContinually();
+ assertEquals(e1.getAttribute(TestEntity.SERVICE_STATE_ACTUAL), Lifecycle.RUNNING);
+ }
+
+ @Test
+ public void testNotifiedOfFailure() throws Exception {
+ e1.addEnricher(EnricherSpec.create(ServiceFailureDetector.class));
+
+ e1.setAttribute(TestEntity.SERVICE_UP, true);
+ ServiceStateLogic.setExpectedState(e1, Lifecycle.RUNNING);
+
+ assertEquals(events.size(), 0, "events="+events);
+
+ e1.setAttribute(TestEntity.SERVICE_UP, false);
+
+ assertHasEventEventually(HASensors.ENTITY_FAILED, Predicates.<Object>equalTo(e1), null);
+ assertEquals(events.size(), 1, "events="+events);
+ EntityTestUtils.assertAttributeEqualsEventually(e1, TestEntity.SERVICE_STATE_ACTUAL, Lifecycle.ON_FIRE);
+ }
+
+ @Test
+ public void testNotifiedOfFailureOnProblem() throws Exception {
+ e1.addEnricher(EnricherSpec.create(ServiceFailureDetector.class));
+
+ e1.setAttribute(TestEntity.SERVICE_UP, true);
+ ServiceStateLogic.setExpectedState(e1, Lifecycle.RUNNING);
+
+ assertEquals(events.size(), 0, "events="+events);
+
+ ServiceProblemsLogic.updateProblemsIndicator(e1, "test", "foo");
+
+ assertHasEventEventually(HASensors.ENTITY_FAILED, Predicates.<Object>equalTo(e1), null);
+ assertEquals(events.size(), 1, "events="+events);
+ EntityTestUtils.assertAttributeEqualsEventually(e1, TestEntity.SERVICE_STATE_ACTUAL, Lifecycle.ON_FIRE);
+ }
+
+ @Test
+ public void testNotifiedOfFailureOnStateOnFire() throws Exception {
+ e1.addEnricher(EnricherSpec.create(ServiceFailureDetector.class));
+ e1.setAttribute(TestEntity.SERVICE_UP, true);
+ ServiceStateLogic.setExpectedState(e1, Lifecycle.ON_FIRE);
+
+ assertHasEventEventually(HASensors.ENTITY_FAILED, Predicates.<Object>equalTo(e1), null);
+ assertEquals(events.size(), 1, "events="+events);
+ EntityTestUtils.assertAttributeEqualsEventually(e1, TestEntity.SERVICE_STATE_ACTUAL, Lifecycle.ON_FIRE);
+ }
+
+ @Test
+ public void testNotifiedOfRecovery() throws Exception {
+ e1.addEnricher(EnricherSpec.create(ServiceFailureDetector.class));
+
+ e1.setAttribute(TestEntity.SERVICE_UP, true);
+ ServiceStateLogic.setExpectedState(e1, Lifecycle.RUNNING);
+ // Make the entity fail
+ e1.setAttribute(TestEntity.SERVICE_UP, false);
+
+ assertHasEventEventually(HASensors.ENTITY_FAILED, Predicates.<Object>equalTo(e1), null);
+ EntityTestUtils.assertAttributeEqualsEventually(e1, TestEntity.SERVICE_STATE_ACTUAL, Lifecycle.ON_FIRE);
+
+ // And make the entity recover
+ e1.setAttribute(TestEntity.SERVICE_UP, true);
+ assertHasEventEventually(HASensors.ENTITY_RECOVERED, Predicates.<Object>equalTo(e1), null);
+ assertEquals(events.size(), 2, "events="+events);
+ EntityTestUtils.assertAttributeEqualsEventually(e1, TestEntity.SERVICE_STATE_ACTUAL, Lifecycle.RUNNING);
+ }
+
+ @Test
+ public void testNotifiedOfRecoveryFromProblems() throws Exception {
+ e1.addEnricher(EnricherSpec.create(ServiceFailureDetector.class));
+
+ e1.setAttribute(TestEntity.SERVICE_UP, true);
+ ServiceStateLogic.setExpectedState(e1, Lifecycle.RUNNING);
+ // Make the entity fail
+ ServiceProblemsLogic.updateProblemsIndicator(e1, "test", "foo");
+
+ assertHasEventEventually(HASensors.ENTITY_FAILED, Predicates.<Object>equalTo(e1), null);
+ EntityTestUtils.assertAttributeEqualsEventually(e1, TestEntity.SERVICE_STATE_ACTUAL, Lifecycle.ON_FIRE);
+
+ // And make the entity recover
+ ServiceProblemsLogic.clearProblemsIndicator(e1, "test");
+ assertHasEventEventually(HASensors.ENTITY_RECOVERED, Predicates.<Object>equalTo(e1), null);
+ assertEquals(events.size(), 2, "events="+events);
+ EntityTestUtils.assertAttributeEqualsEventually(e1, TestEntity.SERVICE_STATE_ACTUAL, Lifecycle.RUNNING);
+ }
+
+
+ @Test(groups="Integration") // Has a 1 second wait
+ public void testEmitsEntityFailureOnlyIfPreviouslyUp() throws Exception {
+ e1.addEnricher(EnricherSpec.create(ServiceFailureDetector.class));
+
+ // Make the entity fail
+ e1.setAttribute(TestEntity.SERVICE_UP, false);
+ ServiceStateLogic.setExpectedState(e1, Lifecycle.RUNNING);
+
+ EntityTestUtils.assertAttributeEqualsEventually(e1, TestEntity.SERVICE_STATE_ACTUAL, Lifecycle.ON_FIRE);
+ assertNoEventsContinually();
+ }
+
+ @Test
+ public void testDisablingPreviouslyUpRequirementForEntityFailed() throws Exception {
+ e1.addEnricher(EnricherSpec.create(ServiceFailureDetector.class)
+ .configure(ServiceFailureDetector.ENTITY_FAILED_ONLY_IF_PREVIOUSLY_UP, false));
+
+ e1.setAttribute(TestEntity.SERVICE_UP, false);
+ ServiceStateLogic.setExpectedState(e1, Lifecycle.RUNNING);
+
+ EntityTestUtils.assertAttributeEqualsEventually(e1, TestEntity.SERVICE_STATE_ACTUAL, Lifecycle.ON_FIRE);
+ assertHasEventEventually(HASensors.ENTITY_FAILED, Predicates.<Object>equalTo(e1), null);
+ }
+
+ @Test
+ public void testDisablingOnFire() throws Exception {
+ e1.addEnricher(EnricherSpec.create(ServiceFailureDetector.class)
+ .configure(ServiceFailureDetector.SERVICE_ON_FIRE_STABILIZATION_DELAY, Duration.PRACTICALLY_FOREVER));
+
+ // Make the entity fail
+ e1.setAttribute(TestEntity.SERVICE_UP, true);
+ ServiceStateLogic.setExpectedState(e1, Lifecycle.RUNNING);
+ EntityTestUtils.assertAttributeEqualsEventually(e1, Attributes.SERVICE_STATE_ACTUAL, Lifecycle.RUNNING);
+ e1.setAttribute(TestEntity.SERVICE_UP, false);
+
+ assertEquals(e1.getAttribute(TestEntity.SERVICE_STATE_ACTUAL), Lifecycle.RUNNING);
+ }
+
+ @Test(groups="Integration") // Has a 1 second wait
+ public void testOnFireAfterDelay() throws Exception {
+ e1.addEnricher(EnricherSpec.create(ServiceFailureDetector.class)
+ .configure(ServiceFailureDetector.SERVICE_ON_FIRE_STABILIZATION_DELAY, Duration.ONE_SECOND));
+
+ // Make the entity fail
+ e1.setAttribute(TestEntity.SERVICE_UP, true);
+ ServiceStateLogic.setExpectedState(e1, Lifecycle.RUNNING);
+ EntityTestUtils.assertAttributeEqualsEventually(e1, Attributes.SERVICE_STATE_ACTUAL, Lifecycle.RUNNING);
+
+ e1.setAttribute(TestEntity.SERVICE_UP, false);
+
+ assertEquals(e1.getAttribute(TestEntity.SERVICE_STATE_ACTUAL), Lifecycle.RUNNING);
+ Time.sleep(Duration.millis(100));
+ assertEquals(e1.getAttribute(TestEntity.SERVICE_STATE_ACTUAL), Lifecycle.RUNNING);
+ EntityTestUtils.assertAttributeEqualsEventually(e1, TestEntity.SERVICE_STATE_ACTUAL, Lifecycle.ON_FIRE);
+ }
+
+ @Test(groups="Integration") // Has a 1 second wait
+ public void testOnFailureDelayFromProblemAndRecover() throws Exception {
+ e1.addEnricher(EnricherSpec.create(ServiceFailureDetector.class)
+ .configure(ServiceFailureDetector.SERVICE_ON_FIRE_STABILIZATION_DELAY, Duration.ONE_SECOND)
+ .configure(ServiceFailureDetector.ENTITY_RECOVERED_STABILIZATION_DELAY, Duration.ONE_SECOND));
+
+ // Set the entity to healthy
+ e1.setAttribute(TestEntity.SERVICE_UP, true);
+ ServiceStateLogic.setExpectedState(e1, Lifecycle.RUNNING);
+ EntityTestUtils.assertAttributeEqualsEventually(e1, Attributes.SERVICE_STATE_ACTUAL, Lifecycle.RUNNING);
+
+ // Make the entity fail; won't set on-fire for 1s but will publish FAILED immediately.
+ ServiceStateLogic.ServiceProblemsLogic.updateProblemsIndicator(e1, "test", "foo");
+ EntityTestUtils.assertAttributeEqualsContinually(ImmutableMap.of("timeout", 100), e1, TestEntity.SERVICE_STATE_ACTUAL, Lifecycle.RUNNING);
+ assertHasEventEventually(HASensors.ENTITY_FAILED, Predicates.<Object>equalTo(e1), null);
+ assertEquals(e1.getAttribute(TestEntity.SERVICE_STATE_ACTUAL), Lifecycle.RUNNING);
+
+ EntityTestUtils.assertAttributeEqualsEventually(e1, TestEntity.SERVICE_STATE_ACTUAL, Lifecycle.ON_FIRE);
+
+ // Now recover: will publish RUNNING immediately, but has 1s stabilisation for RECOVERED
+ ServiceStateLogic.ServiceProblemsLogic.clearProblemsIndicator(e1, "test");
+ EntityTestUtils.assertAttributeEqualsEventually(e1, TestEntity.SERVICE_STATE_ACTUAL, Lifecycle.RUNNING);
+
+ assertEquals(events.size(), 1, "events="+events);
+
+ assertHasEventEventually(HASensors.ENTITY_RECOVERED, Predicates.<Object>equalTo(e1), null);
+ assertEquals(events.size(), 2, "events="+events);
+ }
+
+ @Test(groups="Integration") // Has a 1 second wait
+ public void testAttendsToServiceState() throws Exception {
+ e1.addEnricher(EnricherSpec.create(ServiceFailureDetector.class));
+
+ e1.setAttribute(TestEntity.SERVICE_UP, true);
+ // not counted as failed because not expected to be running
+ e1.setAttribute(TestEntity.SERVICE_UP, false);
+
+ assertNoEventsContinually();
+ }
+
+ @Test(groups="Integration") // Has a 1 second wait
+ public void testOnlyReportsFailureIfRunning() throws Exception {
+ e1.addEnricher(EnricherSpec.create(ServiceFailureDetector.class));
+
+ // Make the entity fail
+ ServiceStateLogic.setExpectedState(e1, Lifecycle.STARTING);
+ e1.setAttribute(TestEntity.SERVICE_UP, true);
+ e1.setAttribute(TestEntity.SERVICE_UP, false);
+
+ assertNoEventsContinually();
+ }
+
+ @Test
+ public void testReportsFailureWhenAlreadyDownOnRegisteringPolicy() throws Exception {
+ ServiceStateLogic.setExpectedState(e1, Lifecycle.RUNNING);
+ e1.setAttribute(TestEntity.SERVICE_UP, false);
+
+ e1.addEnricher(EnricherSpec.create(ServiceFailureDetector.class)
+ .configure(ServiceFailureDetector.ENTITY_FAILED_ONLY_IF_PREVIOUSLY_UP, false));
+
+ assertHasEventEventually(HASensors.ENTITY_FAILED, Predicates.<Object>equalTo(e1), null);
+ }
+
+ @Test
+ public void testReportsFailureWhenAlreadyOnFireOnRegisteringPolicy() throws Exception {
+ ServiceStateLogic.setExpectedState(e1, Lifecycle.ON_FIRE);
+
+ e1.addEnricher(EnricherSpec.create(ServiceFailureDetector.class)
+ .configure(ServiceFailureDetector.ENTITY_FAILED_ONLY_IF_PREVIOUSLY_UP, false));
+
+ assertHasEventEventually(HASensors.ENTITY_FAILED, Predicates.<Object>equalTo(e1), null);
+ }
+
+ @Test(groups="Integration") // Has a 1.5 second wait
+ public void testRepublishedFailure() throws Exception {
+ Duration republishPeriod = Duration.millis(100);
+
+ e1.addEnricher(EnricherSpec.create(ServiceFailureDetector.class)
+ .configure(ServiceFailureDetector.ENTITY_FAILED_REPUBLISH_TIME, republishPeriod));
+
+ // Set the entity to healthy
+ e1.setAttribute(TestEntity.SERVICE_UP, true);
+ ServiceStateLogic.setExpectedState(e1, Lifecycle.RUNNING);
+ EntityTestUtils.assertAttributeEqualsEventually(e1, Attributes.SERVICE_STATE_ACTUAL, Lifecycle.RUNNING);
+
+ // Make the entity fail;
+ ServiceStateLogic.ServiceProblemsLogic.updateProblemsIndicator(e1, "test", "foo");
+ EntityTestUtils.assertAttributeEqualsEventually(e1, TestEntity.SERVICE_STATE_ACTUAL, Lifecycle.ON_FIRE);
+ assertHasEventEventually(HASensors.ENTITY_FAILED, Predicates.<Object>equalTo(e1), null);
+
+ //wait for at least 10 republish events (~1 sec)
+ assertEventsSizeEventually(10);
+
+ // Now recover
+ ServiceStateLogic.ServiceProblemsLogic.clearProblemsIndicator(e1, "test");
+ EntityTestUtils.assertAttributeEqualsEventually(e1, TestEntity.SERVICE_STATE_ACTUAL, Lifecycle.RUNNING);
+ assertHasEventEventually(HASensors.ENTITY_RECOVERED, Predicates.<Object>equalTo(e1), null);
+
+ //once recovered check no more failed events emitted periodically
+ assertEventsSizeContiniually(events.size());
+
+ SensorEvent<FailureDescriptor> prevEvent = null;
+ for (SensorEvent<FailureDescriptor> event : events) {
+ if (prevEvent != null) {
+ long repeatOffset = event.getTimestamp() - prevEvent.getTimestamp();
+ long deviation = Math.abs(repeatOffset - republishPeriod.toMilliseconds());
+ if (deviation > republishPeriod.toMilliseconds()/10 &&
+ //warn only if recovered is too far away from the last failure
+ (!event.getSensor().equals(HASensors.ENTITY_RECOVERED) ||
+ repeatOffset > republishPeriod.toMilliseconds())) {
+ log.error("The time between failure republish (" + repeatOffset + "ms) deviates too much from the expected " + republishPeriod + ". prevEvent=" + prevEvent + ", event=" + event);
+ }
+ }
+ prevEvent = event;
+ }
+
+ //make sure no republish takes place after recovered
+ assertEquals(prevEvent.getSensor(), HASensors.ENTITY_RECOVERED);
+ }
+
+ private void assertEventsSizeContiniually(final int size) {
+ Asserts.succeedsContinually(MutableMap.of("timeout", 500), new Runnable() {
+ @Override
+ public void run() {
+ assertTrue(events.size() == size, "assertEventsSizeContiniually expects " + size + " events but found " + events.size() + ": " + events);
+ }
+ });
+ }
+
+ private void assertEventsSizeEventually(final int size) {
+ Asserts.succeedsEventually(MutableMap.of("timeout", TIMEOUT_MS), new Runnable() {
+ @Override
+ public void run() {
+ assertTrue(events.size() >= size, "assertEventsSizeContiniually expects at least " + size + " events but found " + events.size() + ": " + events);
+ }
+ });
+ }
+
+ private void assertHasEvent(Sensor<?> sensor, Predicate<Object> componentPredicate, Predicate<? super CharSequence> descriptionPredicate) {
+ for (SensorEvent<FailureDescriptor> event : events) {
+ if (event.getSensor().equals(sensor) &&
+ (componentPredicate == null || componentPredicate.apply(event.getValue().getComponent())) &&
+ (descriptionPredicate == null || descriptionPredicate.apply(event.getValue().getDescription()))) {
+ return;
+ }
+ }
+ fail("No matching "+sensor+" event found; events="+events);
+ }
+
+ private void assertHasEventEventually(final Sensor<?> sensor, final Predicate<Object> componentPredicate, final Predicate<? super CharSequence> descriptionPredicate) {
+ Asserts.succeedsEventually(MutableMap.of("timeout", TIMEOUT_MS), new Runnable() {
+ @Override public void run() {
+ assertHasEvent(sensor, componentPredicate, descriptionPredicate);
+ }});
+ }
+
+ private void assertNoEventsContinually() {
+ Asserts.succeedsContinually(new Runnable() {
+ @Override public void run() {
+ assertTrue(events.isEmpty(), "events="+events);
+ }});
+ }
+}
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/d30ff597/policy/src/test/java/org/apache/brooklyn/policy/ha/ServiceReplacerTest.java
----------------------------------------------------------------------
diff --git a/policy/src/test/java/org/apache/brooklyn/policy/ha/ServiceReplacerTest.java b/policy/src/test/java/org/apache/brooklyn/policy/ha/ServiceReplacerTest.java
new file mode 100644
index 0000000..f92603a
--- /dev/null
+++ b/policy/src/test/java/org/apache/brooklyn/policy/ha/ServiceReplacerTest.java
@@ -0,0 +1,340 @@
+/*
+ * 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.policy.ha;
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertFalse;
+import static org.testng.Assert.assertNotEquals;
+import static org.testng.Assert.assertTrue;
+
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import org.apache.brooklyn.api.entity.Entity;
+import org.apache.brooklyn.api.entity.proxying.EntitySpec;
+import org.apache.brooklyn.api.event.SensorEvent;
+import org.apache.brooklyn.api.event.SensorEventListener;
+import org.apache.brooklyn.api.location.Location;
+import org.apache.brooklyn.api.location.LocationSpec;
+import org.apache.brooklyn.api.management.ManagementContext;
+import org.apache.brooklyn.api.policy.PolicySpec;
+import org.apache.brooklyn.core.util.config.ConfigBag;
+import org.apache.brooklyn.test.EntityTestUtils;
+import org.apache.brooklyn.test.entity.LocalManagementContextForTests;
+import org.apache.brooklyn.test.entity.TestApplication;
+import org.apache.brooklyn.test.entity.TestEntity;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.testng.Assert;
+import org.testng.annotations.AfterMethod;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import brooklyn.entity.basic.ApplicationBuilder;
+import brooklyn.entity.basic.Attributes;
+import brooklyn.entity.basic.Entities;
+import brooklyn.entity.basic.EntityInternal;
+import brooklyn.entity.basic.Lifecycle;
+import brooklyn.entity.basic.QuorumCheck;
+import brooklyn.entity.basic.ServiceStateLogic.ComputeServiceIndicatorsFromChildrenAndMembers;
+import brooklyn.entity.group.DynamicCluster;
+import brooklyn.entity.trait.FailingEntity;
+
+import org.apache.brooklyn.location.basic.SimulatedLocation;
+
+import org.apache.brooklyn.policy.ha.HASensors.FailureDescriptor;
+import brooklyn.test.Asserts;
+import brooklyn.util.javalang.JavaClassNames;
+
+import com.google.common.base.Predicate;
+import com.google.common.base.Predicates;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Sets;
+
+public class ServiceReplacerTest {
+
+ private static final Logger log = LoggerFactory.getLogger(ServiceReplacerTest.class);
+
+ private ManagementContext managementContext;
+ private TestApplication app;
+ private SimulatedLocation loc;
+ private SensorEventListener<Object> eventListener;
+ private List<SensorEvent<?>> events;
+
+ @BeforeMethod(alwaysRun=true)
+ public void setUp() throws Exception {
+ managementContext = new LocalManagementContextForTests();
+ app = ApplicationBuilder.newManagedApp(TestApplication.class, managementContext);
+ loc = managementContext.getLocationManager().createLocation(LocationSpec.create(SimulatedLocation.class));
+ events = Lists.newCopyOnWriteArrayList();
+ eventListener = new SensorEventListener<Object>() {
+ @Override public void onEvent(SensorEvent<Object> event) {
+ events.add(event);
+ }
+ };
+ }
+
+ @AfterMethod(alwaysRun=true)
+ public void tearDown() throws Exception {
+ if (managementContext != null) Entities.destroyAll(managementContext);
+ }
+
+ @Test
+ public void testReplacesFailedMember() throws Exception {
+ final DynamicCluster cluster = app.createAndManageChild(EntitySpec.create(DynamicCluster.class)
+ .configure(DynamicCluster.MEMBER_SPEC, EntitySpec.create(TestEntity.class))
+ .configure(DynamicCluster.INITIAL_SIZE, 3));
+ app.start(ImmutableList.<Location>of(loc));
+
+ ServiceReplacer policy = new ServiceReplacer(new ConfigBag().configure(ServiceReplacer.FAILURE_SENSOR_TO_MONITOR, HASensors.ENTITY_FAILED));
+ cluster.addPolicy(policy);
+
+ final Set<Entity> initialMembers = ImmutableSet.copyOf(cluster.getMembers());
+ final TestEntity e1 = (TestEntity) Iterables.get(initialMembers, 1);
+
+ e1.emit(HASensors.ENTITY_FAILED, new FailureDescriptor(e1, "simulate failure"));
+
+ // Expect e1 to be replaced
+ Asserts.succeedsEventually(new Runnable() {
+ @Override public void run() {
+ Set<Entity> newMembers = Sets.difference(ImmutableSet.copyOf(cluster.getMembers()), initialMembers);
+ Set<Entity> removedMembers = Sets.difference(initialMembers, ImmutableSet.copyOf(cluster.getMembers()));
+ assertEquals(removedMembers, ImmutableSet.of(e1));
+ assertEquals(newMembers.size(), 1);
+ assertEquals(((TestEntity)Iterables.getOnlyElement(newMembers)).getCallHistory(), ImmutableList.of("start"));
+ assertEquals(e1.getCallHistory(), ImmutableList.of("start", "stop"));
+ assertFalse(Entities.isManaged(e1));
+ }});
+ }
+
+ @Test(invocationCount=100)
+ public void testSetsOnFireWhenFailToReplaceMemberManyTimes() throws Exception {
+ testSetsOnFireWhenFailToReplaceMember();
+ }
+
+ // fails the startup of the replacement entity (but not the original).
+ @Test
+ public void testSetsOnFireWhenFailToReplaceMember() throws Exception {
+ app.subscribe(null, ServiceReplacer.ENTITY_REPLACEMENT_FAILED, eventListener);
+
+ final DynamicCluster cluster = app.createAndManageChild(EntitySpec.create(DynamicCluster.class)
+ .configure(DynamicCluster.MEMBER_SPEC, EntitySpec.create(FailingEntity.class)
+ .configure(FailingEntity.FAIL_ON_START_CONDITION, predicateOnlyTrueForCallAtOrAfter(2)))
+ .configure(DynamicCluster.INITIAL_SIZE, 1)
+ .configure(DynamicCluster.QUARANTINE_FAILED_ENTITIES, true)
+ .configure(ComputeServiceIndicatorsFromChildrenAndMembers.UP_QUORUM_CHECK, QuorumCheck.QuorumChecks.alwaysTrue())
+ .configure(ComputeServiceIndicatorsFromChildrenAndMembers.RUNNING_QUORUM_CHECK, QuorumCheck.QuorumChecks.alwaysTrue()));
+ app.start(ImmutableList.<Location>of(loc));
+
+ // should not be on fire
+ Assert.assertNotEquals(cluster.getAttribute(Attributes.SERVICE_STATE_ACTUAL), Lifecycle.ON_FIRE);
+ // and should eventually be running
+ EntityTestUtils.assertAttributeEqualsEventually(cluster, Attributes.SERVICE_STATE_ACTUAL, Lifecycle.RUNNING);
+
+ log.info("started "+app+" for "+JavaClassNames.niceClassAndMethod());
+
+ ServiceReplacer policy = new ServiceReplacer(new ConfigBag().configure(ServiceReplacer.FAILURE_SENSOR_TO_MONITOR, HASensors.ENTITY_FAILED));
+ cluster.addPolicy(policy);
+
+ final Set<Entity> initialMembers = ImmutableSet.copyOf(cluster.getMembers());
+ final TestEntity e1 = (TestEntity) Iterables.get(initialMembers, 0);
+
+ e1.emit(HASensors.ENTITY_FAILED, new FailureDescriptor(e1, "simulate failure"));
+
+ // Expect cluster to go on-fire when fails to start replacement
+ // Note that we've set up-quorum and running-quorum to be "alwaysTrue" so that we don't get a transient onFire
+ // when the failed node fails to start (but before it has been removed from the group to be put in quarantine).
+ EntityTestUtils.assertAttributeEqualsEventually(cluster, Attributes.SERVICE_STATE_ACTUAL, Lifecycle.ON_FIRE);
+
+ // Expect to have the second failed entity still kicking around as proof (in quarantine)
+ // The cluster should NOT go on fire until after the 2nd failure
+ Iterable<Entity> members = Iterables.filter(managementContext.getEntityManager().getEntities(), Predicates.instanceOf(FailingEntity.class));
+ assertEquals(Iterables.size(members), 2);
+
+ // e2 failed to start, so it won't have called stop on e1
+ TestEntity e2 = (TestEntity) Iterables.getOnlyElement(Sets.difference(ImmutableSet.copyOf(members), initialMembers));
+ assertEquals(e1.getCallHistory(), ImmutableList.of("start"), "e1.history="+e1.getCallHistory());
+ assertEquals(e2.getCallHistory(), ImmutableList.of("start"), "e2.history="+e2.getCallHistory());
+
+ // And will have received notification event about it
+ assertEventuallyHasEntityReplacementFailedEvent(cluster);
+ }
+
+ @Test(groups="Integration") // has a 1 second wait
+ public void testDoesNotOnFireWhenFailToReplaceMember() throws Exception {
+ app.subscribe(null, ServiceReplacer.ENTITY_REPLACEMENT_FAILED, eventListener);
+
+ final DynamicCluster cluster = app.createAndManageChild(EntitySpec.create(DynamicCluster.class)
+ .configure(DynamicCluster.MEMBER_SPEC, EntitySpec.create(FailingEntity.class)
+ .configure(FailingEntity.FAIL_ON_START_CONDITION, predicateOnlyTrueForCallAtOrAfter(2)))
+ .configure(DynamicCluster.INITIAL_SIZE, 1)
+ .configure(DynamicCluster.QUARANTINE_FAILED_ENTITIES, true));
+ app.start(ImmutableList.<Location>of(loc));
+
+ ServiceReplacer policy = new ServiceReplacer(new ConfigBag()
+ .configure(ServiceReplacer.FAILURE_SENSOR_TO_MONITOR, HASensors.ENTITY_FAILED)
+ .configure(ServiceReplacer.SET_ON_FIRE_ON_FAILURE, false));
+ cluster.addPolicy(policy);
+
+ final Set<Entity> initialMembers = ImmutableSet.copyOf(cluster.getMembers());
+ final TestEntity e1 = (TestEntity) Iterables.get(initialMembers, 0);
+
+ e1.emit(HASensors.ENTITY_FAILED, new FailureDescriptor(e1, "simulate failure"));
+
+ // Configured to not mark cluster as on fire
+ Asserts.succeedsContinually(new Runnable() {
+ @Override public void run() {
+ assertNotEquals(cluster.getAttribute(Attributes.SERVICE_STATE_ACTUAL), Lifecycle.ON_FIRE);
+ }});
+
+ // And will have received notification event about it
+ assertEventuallyHasEntityReplacementFailedEvent(cluster);
+ }
+
+ @Test(groups="Integration") // 1s wait
+ public void testStopFailureOfOldEntityDoesNotSetClusterOnFire() throws Exception {
+ app.subscribe(null, ServiceReplacer.ENTITY_REPLACEMENT_FAILED, eventListener);
+
+ final DynamicCluster cluster = app.createAndManageChild(EntitySpec.create(DynamicCluster.class)
+ .configure(DynamicCluster.MEMBER_SPEC, EntitySpec.create(FailingEntity.class)
+ .configure(FailingEntity.FAIL_ON_STOP_CONDITION, predicateOnlyTrueForCallAt(1)))
+ .configure(DynamicCluster.INITIAL_SIZE, 2));
+ app.start(ImmutableList.<Location>of(loc));
+
+ cluster.addPolicy(PolicySpec.create(ServiceReplacer.class)
+ .configure(ServiceReplacer.FAILURE_SENSOR_TO_MONITOR, HASensors.ENTITY_FAILED));
+
+ final Set<Entity> initialMembers = ImmutableSet.copyOf(cluster.getMembers());
+ final TestEntity e1 = (TestEntity) Iterables.get(initialMembers, 0);
+
+ e1.emit(HASensors.ENTITY_FAILED, new FailureDescriptor(e1, "simulate failure"));
+
+ // Expect e1 to be replaced
+ Asserts.succeedsEventually(new Runnable() {
+ @Override public void run() {
+ Set<Entity> newMembers = Sets.difference(ImmutableSet.copyOf(cluster.getMembers()), initialMembers);
+ Set<Entity> removedMembers = Sets.difference(initialMembers, ImmutableSet.copyOf(cluster.getMembers()));
+ assertEquals(removedMembers, ImmutableSet.of(e1));
+ assertEquals(newMembers.size(), 1);
+ assertEquals(((TestEntity)Iterables.getOnlyElement(newMembers)).getCallHistory(), ImmutableList.of("start"));
+ assertEquals(e1.getCallHistory(), ImmutableList.of("start", "stop"));
+ assertFalse(Entities.isManaged(e1));
+ }});
+
+ // Failure to stop the failed member should not cause "on-fire" of cluster
+ Asserts.succeedsContinually(new Runnable() {
+ @Override public void run() {
+ assertNotEquals(cluster.getAttribute(Attributes.SERVICE_STATE_ACTUAL), Lifecycle.ON_FIRE);
+ }});
+ }
+
+ /**
+ * If we keep on getting failure reports, never managing to replace the failed node, then don't keep trying
+ * (i.e. avoid infinite loop).
+ *
+ * TODO This code + configuration needs some work; it's not testing quite the scenarios that I
+ * was thinking of!
+ * I saw problem where a node failed, and the replacements failed, and we ended up trying thousands of times.
+ * (describing this scenario is made more complex by me having temporarily disabled the cluster from
+ * removing failed members, for debugging purposes!)
+ * Imagine these two scenarios:
+ * <ol>
+ * <li>Entity fails during call to start().
+ * Here, the cluster removes it as a member (either unmanages it or puts it in quarantine)
+ * So the ENTITY_FAILED is ignored because the entity is not a member at that point.
+ * <li>Entity returns from start(), but quickly goes to service-down.
+ * Here we'll keep trying to replace that entity. Depending how long that takes, we'll either
+ * enter a horrible infinite loop, or we'll just provision a huge number of VMs over a long
+ * time period.
+ * Unfortunately this scenario is not catered for in the code yet.
+ * </ol>
+ */
+ @Test(groups="Integration") // because takes 1.2 seconds
+ public void testAbandonsReplacementAfterNumFailures() throws Exception {
+ app.subscribe(null, ServiceReplacer.ENTITY_REPLACEMENT_FAILED, eventListener);
+
+ final DynamicCluster cluster = app.createAndManageChild(EntitySpec.create(DynamicCluster.class)
+ .configure(DynamicCluster.MEMBER_SPEC, EntitySpec.create(FailingEntity.class)
+ .configure(FailingEntity.FAIL_ON_START_CONDITION, predicateOnlyTrueForCallAtOrAfter(11)))
+ .configure(DynamicCluster.INITIAL_SIZE, 10)
+ .configure(DynamicCluster.QUARANTINE_FAILED_ENTITIES, true));
+ app.start(ImmutableList.<Location>of(loc));
+
+ ServiceReplacer policy = new ServiceReplacer(new ConfigBag()
+ .configure(ServiceReplacer.FAILURE_SENSOR_TO_MONITOR, HASensors.ENTITY_FAILED)
+ .configure(ServiceReplacer.FAIL_ON_NUM_RECURRING_FAILURES, 3));
+ cluster.addPolicy(policy);
+
+ final Set<Entity> initialMembers = ImmutableSet.copyOf(cluster.getMembers());
+ for (int i = 0; i < 5; i++) {
+ final int counter = i+1;
+ EntityInternal entity = (EntityInternal) Iterables.get(initialMembers, i);
+ entity.emit(HASensors.ENTITY_FAILED, new FailureDescriptor(entity, "simulate failure"));
+ if (i <= 3) {
+ Asserts.succeedsEventually(new Runnable() {
+ @Override public void run() {
+ Set<FailingEntity> all = ImmutableSet.copyOf(Iterables.filter(managementContext.getEntityManager().getEntities(), FailingEntity.class));
+ Set<FailingEntity> replacements = Sets.difference(all, initialMembers);
+ Set<?> replacementMembers = Sets.intersection(ImmutableSet.of(cluster.getMembers()), replacements);
+ assertTrue(replacementMembers.isEmpty());
+ assertEquals(replacements.size(), counter);
+ }});
+ } else {
+ Asserts.succeedsContinually(new Runnable() {
+ @Override public void run() {
+ Set<FailingEntity> all = ImmutableSet.copyOf(Iterables.filter(managementContext.getEntityManager().getEntities(), FailingEntity.class));
+ Set<FailingEntity> replacements = Sets.difference(all, initialMembers);
+ assertEquals(replacements.size(), 4);
+ }});
+ }
+ }
+ }
+
+
+ private Predicate<Object> predicateOnlyTrueForCallAt(final int callNumber) {
+ return predicateOnlyTrueForCallRange(callNumber, callNumber);
+ }
+
+ private Predicate<Object> predicateOnlyTrueForCallAtOrAfter(final int callLowerNumber) {
+ return predicateOnlyTrueForCallRange(callLowerNumber, Integer.MAX_VALUE);
+ }
+
+ private Predicate<Object> predicateOnlyTrueForCallRange(final int callLowerNumber, final int callUpperNumber) {
+ return new Predicate<Object>() {
+ private final AtomicInteger counter = new AtomicInteger(0);
+ @Override public boolean apply(Object input) {
+ int num = counter.incrementAndGet();
+ return num >= callLowerNumber && num <= callUpperNumber;
+ }
+ };
+ }
+
+ private void assertEventuallyHasEntityReplacementFailedEvent(final Entity expectedCluster) {
+ Asserts.succeedsEventually(new Runnable() {
+ @Override public void run() {
+ assertEquals(Iterables.getOnlyElement(events).getSensor(), ServiceReplacer.ENTITY_REPLACEMENT_FAILED, "events="+events);
+ assertEquals(Iterables.getOnlyElement(events).getSource(), expectedCluster, "events="+events);
+ assertEquals(((FailureDescriptor)Iterables.getOnlyElement(events).getValue()).getComponent(), expectedCluster, "events="+events);
+ }});
+ }
+}
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/d30ff597/policy/src/test/java/org/apache/brooklyn/policy/ha/ServiceRestarterTest.java
----------------------------------------------------------------------
diff --git a/policy/src/test/java/org/apache/brooklyn/policy/ha/ServiceRestarterTest.java b/policy/src/test/java/org/apache/brooklyn/policy/ha/ServiceRestarterTest.java
new file mode 100644
index 0000000..c93612b
--- /dev/null
+++ b/policy/src/test/java/org/apache/brooklyn/policy/ha/ServiceRestarterTest.java
@@ -0,0 +1,190 @@
+/*
+ * 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.policy.ha;
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertNotEquals;
+import static org.testng.Assert.assertTrue;
+
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+import org.apache.brooklyn.api.entity.proxying.EntitySpec;
+import org.apache.brooklyn.api.event.SensorEvent;
+import org.apache.brooklyn.api.event.SensorEventListener;
+import org.apache.brooklyn.api.management.ManagementContext;
+import org.apache.brooklyn.api.policy.PolicySpec;
+import org.apache.brooklyn.core.util.config.ConfigBag;
+import org.apache.brooklyn.test.entity.LocalManagementContextForTests;
+import org.apache.brooklyn.test.entity.TestApplication;
+import org.apache.brooklyn.test.entity.TestEntity;
+import org.testng.annotations.AfterMethod;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import brooklyn.entity.basic.ApplicationBuilder;
+import brooklyn.entity.basic.Attributes;
+import brooklyn.entity.basic.Entities;
+import brooklyn.entity.basic.Lifecycle;
+import brooklyn.entity.trait.FailingEntity;
+import org.apache.brooklyn.policy.ha.HASensors.FailureDescriptor;
+import brooklyn.test.Asserts;
+import brooklyn.util.exceptions.Exceptions;
+
+import com.google.common.base.Function;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Lists;
+
+public class ServiceRestarterTest {
+
+ private static final int TIMEOUT_MS = 10*1000;
+
+ private ManagementContext managementContext;
+ private TestApplication app;
+ private TestEntity e1;
+ private ServiceRestarter policy;
+ private SensorEventListener<Object> eventListener;
+ private List<SensorEvent<?>> events;
+
+ @BeforeMethod(alwaysRun=true)
+ public void setUp() throws Exception {
+ managementContext = new LocalManagementContextForTests();
+ app = ApplicationBuilder.newManagedApp(TestApplication.class, managementContext);
+ e1 = app.createAndManageChild(EntitySpec.create(TestEntity.class));
+ events = Lists.newCopyOnWriteArrayList();
+ eventListener = new SensorEventListener<Object>() {
+ @Override public void onEvent(SensorEvent<Object> event) {
+ events.add(event);
+ }
+ };
+ }
+
+ @AfterMethod(alwaysRun=true)
+ public void tearDown() throws Exception {
+ if (managementContext != null) Entities.destroyAll(managementContext);
+ }
+
+ @Test
+ public void testRestartsOnFailure() throws Exception {
+ policy = new ServiceRestarter(new ConfigBag().configure(ServiceRestarter.FAILURE_SENSOR_TO_MONITOR, HASensors.ENTITY_FAILED));
+ e1.addPolicy(policy);
+
+ e1.emit(HASensors.ENTITY_FAILED, new FailureDescriptor(e1, "simulate failure"));
+
+ Asserts.succeedsEventually(new Runnable() {
+ @Override public void run() {
+ assertEquals(e1.getCallHistory(), ImmutableList.of("restart"));
+ }});
+ }
+
+ @Test(groups="Integration") // Has a 1 second wait
+ public void testDoesNotRestartsWhenHealthy() throws Exception {
+ policy = new ServiceRestarter(new ConfigBag().configure(ServiceRestarter.FAILURE_SENSOR_TO_MONITOR, HASensors.ENTITY_FAILED));
+ e1.addPolicy(policy);
+
+ e1.emit(HASensors.ENTITY_RECOVERED, new FailureDescriptor(e1, "not a failure"));
+
+ Asserts.succeedsContinually(new Runnable() {
+ @Override public void run() {
+ assertEquals(e1.getCallHistory(), ImmutableList.of());
+ }});
+ }
+
+ @Test
+ public void testEmitsFailureEventWhenRestarterFails() throws Exception {
+ final FailingEntity e2 = app.createAndManageChild(EntitySpec.create(FailingEntity.class)
+ .configure(FailingEntity.FAIL_ON_RESTART, true));
+ app.subscribe(e2, ServiceRestarter.ENTITY_RESTART_FAILED, eventListener);
+
+ policy = new ServiceRestarter(new ConfigBag().configure(ServiceRestarter.FAILURE_SENSOR_TO_MONITOR, HASensors.ENTITY_FAILED));
+ e2.addPolicy(policy);
+
+ e2.emit(HASensors.ENTITY_FAILED, new FailureDescriptor(e2, "simulate failure"));
+
+ Asserts.succeedsEventually(new Runnable() {
+ @Override public void run() {
+ assertEquals(Iterables.getOnlyElement(events).getSensor(), ServiceRestarter.ENTITY_RESTART_FAILED, "events="+events);
+ assertEquals(Iterables.getOnlyElement(events).getSource(), e2, "events="+events);
+ assertEquals(((FailureDescriptor)Iterables.getOnlyElement(events).getValue()).getComponent(), e2, "events="+events);
+ }});
+
+ assertEquals(e2.getAttribute(Attributes.SERVICE_STATE_ACTUAL), Lifecycle.ON_FIRE);
+ }
+
+ @Test
+ public void testDoesNotSetOnFireOnFailure() throws Exception {
+ final FailingEntity e2 = app.createAndManageChild(EntitySpec.create(FailingEntity.class)
+ .configure(FailingEntity.FAIL_ON_RESTART, true));
+ app.subscribe(e2, ServiceRestarter.ENTITY_RESTART_FAILED, eventListener);
+
+ policy = new ServiceRestarter(new ConfigBag()
+ .configure(ServiceRestarter.FAILURE_SENSOR_TO_MONITOR, HASensors.ENTITY_FAILED)
+ .configure(ServiceRestarter.SET_ON_FIRE_ON_FAILURE, false));
+ e2.addPolicy(policy);
+
+ e2.emit(HASensors.ENTITY_FAILED, new FailureDescriptor(e2, "simulate failure"));
+
+ Asserts.succeedsContinually(new Runnable() {
+ @Override public void run() {
+ assertNotEquals(e2.getAttribute(Attributes.SERVICE_STATE_ACTUAL), Lifecycle.ON_FIRE);
+ }});
+ }
+
+ // Previously RestarterPolicy called entity.restart inside the event-listener thread.
+ // That caused all other events for that entity's subscriptions to be queued until that
+ // entity's single event handler thread was free again.
+ @Test
+ public void testRestartDoesNotBlockOtherSubscriptions() throws Exception {
+ final CountDownLatch inRestartLatch = new CountDownLatch(1);
+ final CountDownLatch continueRestartLatch = new CountDownLatch(1);
+
+ final FailingEntity e2 = app.createAndManageChild(EntitySpec.create(FailingEntity.class)
+ .configure(FailingEntity.FAIL_ON_RESTART, true)
+ .configure(FailingEntity.EXEC_ON_FAILURE, new Function<Object, Void>() {
+ @Override public Void apply(Object input) {
+ inRestartLatch.countDown();
+ try {
+ continueRestartLatch.await();
+ } catch (InterruptedException e) {
+ throw Exceptions.propagate(e);
+ }
+ return null;
+ }}));
+
+ e2.addPolicy(PolicySpec.create(ServiceRestarter.class)
+ .configure(ServiceRestarter.FAILURE_SENSOR_TO_MONITOR, HASensors.ENTITY_FAILED));
+ e2.subscribe(e2, TestEntity.SEQUENCE, eventListener);
+
+ // Cause failure, and wait for entity.restart to be blocking
+ e2.emit(HASensors.ENTITY_FAILED, new FailureDescriptor(e1, "simulate failure"));
+ assertTrue(inRestartLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+
+ // Expect other notifications to continue to get through
+ e2.setAttribute(TestEntity.SEQUENCE, 1);
+ Asserts.succeedsEventually(new Runnable() {
+ @Override public void run() {
+ assertEquals(Iterables.getOnlyElement(events).getValue(), 1);
+ }});
+
+ // Allow restart to finish
+ continueRestartLatch.countDown();
+ }
+}
[06/24] incubator-brooklyn git commit: [BROOKLYN-162] Renaming
package policy
Posted by he...@apache.org.
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/d30ff597/policy/src/test/java/org/apache/brooklyn/policy/loadbalancing/AbstractLoadBalancingPolicyTest.java
----------------------------------------------------------------------
diff --git a/policy/src/test/java/org/apache/brooklyn/policy/loadbalancing/AbstractLoadBalancingPolicyTest.java b/policy/src/test/java/org/apache/brooklyn/policy/loadbalancing/AbstractLoadBalancingPolicyTest.java
new file mode 100644
index 0000000..6257bc9
--- /dev/null
+++ b/policy/src/test/java/org/apache/brooklyn/policy/loadbalancing/AbstractLoadBalancingPolicyTest.java
@@ -0,0 +1,253 @@
+/*
+ * 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.policy.loadbalancing;
+
+import static org.testng.Assert.assertEquals;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+import java.util.Random;
+import java.util.Set;
+
+import org.apache.brooklyn.api.entity.Entity;
+import org.apache.brooklyn.api.entity.Group;
+import org.apache.brooklyn.api.entity.basic.EntityLocal;
+import org.apache.brooklyn.api.entity.proxying.EntitySpec;
+import org.apache.brooklyn.api.event.AttributeSensor;
+import org.apache.brooklyn.test.entity.TestApplication;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.testng.annotations.AfterMethod;
+import org.testng.annotations.BeforeMethod;
+
+import brooklyn.config.ConfigKey;
+import brooklyn.entity.basic.DynamicGroup;
+import brooklyn.entity.basic.Entities;
+import brooklyn.event.basic.BasicConfigKey;
+import brooklyn.event.basic.Sensors;
+import org.apache.brooklyn.location.basic.SimulatedLocation;
+import brooklyn.test.Asserts;
+import brooklyn.util.collections.MutableMap;
+import brooklyn.util.time.Time;
+
+import com.google.common.base.Function;
+import com.google.common.base.Preconditions;
+import com.google.common.base.Predicates;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+
+public class AbstractLoadBalancingPolicyTest {
+
+ private static final Logger LOG = LoggerFactory.getLogger(AbstractLoadBalancingPolicyTest.class);
+
+ protected static final long TIMEOUT_MS = 10*1000;
+ protected static final long SHORT_WAIT_MS = 250;
+
+ protected static final long CONTAINER_STARTUP_DELAY_MS = 100;
+
+ public static final AttributeSensor<Integer> TEST_METRIC =
+ Sensors.newIntegerSensor("test.metric", "Dummy workrate for test entities");
+
+ public static final ConfigKey<Double> LOW_THRESHOLD_CONFIG_KEY = new BasicConfigKey<Double>(Double.class, TEST_METRIC.getName()+".threshold.low", "desc", 0.0);
+ public static final ConfigKey<Double> HIGH_THRESHOLD_CONFIG_KEY = new BasicConfigKey<Double>(Double.class, TEST_METRIC.getName()+".threshold.high", "desc", 0.0);
+
+ protected TestApplication app;
+ protected SimulatedLocation loc;
+ protected BalanceableWorkerPool pool;
+ protected DefaultBalanceablePoolModel<Entity, Entity> model;
+ protected LoadBalancingPolicy policy;
+ protected Group containerGroup;
+ protected Group itemGroup;
+ protected Random random = new Random();
+
+ @BeforeMethod(alwaysRun=true)
+ public void before() {
+ LOG.debug("In AbstractLoadBalancingPolicyTest.before()");
+
+ MockItemEntityImpl.totalMoveCount.set(0);
+ MockItemEntityImpl.lastMoveTime.set(0);
+
+ loc = new SimulatedLocation(MutableMap.of("name", "loc"));
+
+ model = new DefaultBalanceablePoolModel<Entity, Entity>("pool-model");
+
+ app = TestApplication.Factory.newManagedInstanceForTests();
+ containerGroup = app.createAndManageChild(EntitySpec.create(DynamicGroup.class)
+ .displayName("containerGroup")
+ .configure(DynamicGroup.ENTITY_FILTER, Predicates.instanceOf(MockContainerEntity.class)));
+ itemGroup = app.createAndManageChild(EntitySpec.create(DynamicGroup.class)
+ .displayName("itemGroup")
+ .configure(DynamicGroup.ENTITY_FILTER, Predicates.instanceOf(MockItemEntity.class)));
+ pool = app.createAndManageChild(EntitySpec.create(BalanceableWorkerPool.class));
+ pool.setContents(containerGroup, itemGroup);
+ policy = new LoadBalancingPolicy(MutableMap.of("minPeriodBetweenExecs", 1), TEST_METRIC, model);
+ pool.addPolicy(policy);
+ app.start(ImmutableList.of(loc));
+ }
+
+ @AfterMethod(alwaysRun=true)
+ public void after() {
+ if (policy != null) policy.destroy();
+ if (app != null) Entities.destroyAll(app.getManagementContext());
+ }
+
+ // Using this utility, as it gives more info about the workrates of all containers rather than just the one that differs
+ protected void assertWorkrates(Collection<MockContainerEntity> containers, Collection<Double> expectedC, double precision) {
+ Iterable<Double> actual = Iterables.transform(containers, new Function<MockContainerEntity, Double>() {
+ public Double apply(MockContainerEntity input) {
+ return getContainerWorkrate(input);
+ }});
+
+ List<Double> expected = Lists.newArrayList(expectedC);
+ String errMsg = "actual="+actual+"; expected="+expected;
+ assertEquals(containers.size(), expected.size(), errMsg);
+ for (int i = 0; i < containers.size(); i++) {
+ assertEquals(Iterables.get(actual, i), expected.get(i), precision, errMsg);
+ }
+ }
+
+ protected void assertWorkratesEventually(Collection<MockContainerEntity> containers, Iterable<? extends Movable> items, Collection<Double> expected) {
+ assertWorkratesEventually(containers, items, expected, 0d);
+ }
+
+ /**
+ * Asserts that the given container have the given expected workrates (by querying the containers directly).
+ * Accepts an accuracy of "precision" for each container's workrate.
+ */
+ protected void assertWorkratesEventually(final Collection<MockContainerEntity> containers, final Iterable<? extends Movable> items, final Collection<Double> expected, final double precision) {
+ try {
+ Asserts.succeedsEventually(MutableMap.of("timeout", TIMEOUT_MS), new Runnable() {
+ public void run() {
+ assertWorkrates(containers, expected, precision);
+ }});
+ } catch (AssertionError e) {
+ String errMsg = e.getMessage()+"; "+verboseDumpToString(containers, items);
+ throw new RuntimeException(errMsg, e);
+ }
+ }
+
+ // Using this utility, as it gives more info about the workrates of all containers rather than just the one that differs
+ protected void assertWorkratesContinually(List<MockContainerEntity> containers, Iterable<? extends Movable> items, List<Double> expected) {
+ assertWorkratesContinually(containers, items, expected, 0d);
+ }
+
+ /**
+ * Asserts that the given containers have the given expected workrates (by querying the containers directly)
+ * continuously for SHORT_WAIT_MS.
+ * Accepts an accuracy of "precision" for each container's workrate.
+ */
+ protected void assertWorkratesContinually(final List<MockContainerEntity> containers, Iterable<? extends Movable> items, final List<Double> expected, final double precision) {
+ try {
+ Asserts.succeedsContinually(MutableMap.of("timeout", SHORT_WAIT_MS), new Runnable() {
+ public void run() {
+ assertWorkrates(containers, expected, precision);
+ }});
+ } catch (AssertionError e) {
+ String errMsg = e.getMessage()+"; "+verboseDumpToString(containers, items);
+ throw new RuntimeException(errMsg, e);
+ }
+ }
+
+ protected String verboseDumpToString(Iterable<MockContainerEntity> containers, Iterable<? extends Movable> items) {
+ Iterable<Double> containerRates = Iterables.transform(containers, new Function<MockContainerEntity, Double>() {
+ @Override public Double apply(MockContainerEntity input) {
+ return (double) input.getWorkrate();
+ }});
+
+ Map<MockContainerEntity, Set<Movable>> itemDistributionByContainer = Maps.newLinkedHashMap();
+ for (MockContainerEntity container : containers) {
+ itemDistributionByContainer.put(container, container.getBalanceableItems());
+ }
+
+ Map<Movable, BalanceableContainer<?>> itemDistributionByItem = Maps.newLinkedHashMap();
+ for (Movable item : items) {
+ itemDistributionByItem.put(item, item.getAttribute(Movable.CONTAINER));
+ }
+
+ String modelItemDistribution = model.itemDistributionToString();
+ return "containers="+containers+"; containerRates="+containerRates
+ +"; itemDistributionByContainer="+itemDistributionByContainer
+ +"; itemDistributionByItem="+itemDistributionByItem
+ +"; model="+modelItemDistribution
+ +"; totalMoves="+MockItemEntityImpl.totalMoveCount
+ +"; lastMoveTime="+Time.makeDateString(MockItemEntityImpl.lastMoveTime.get());
+ }
+
+ protected MockContainerEntity newContainer(TestApplication app, String name, double lowThreshold, double highThreshold) {
+ return newAsyncContainer(app, name, lowThreshold, highThreshold, 0);
+ }
+
+ /**
+ * Creates a new container that will take "delay" millis to complete its start-up.
+ */
+ protected MockContainerEntity newAsyncContainer(TestApplication app, String name, double lowThreshold, double highThreshold, long delay) {
+ MockContainerEntity container = app.createAndManageChild(EntitySpec.create(MockContainerEntity.class)
+ .displayName(name)
+ .configure(MockContainerEntity.DELAY, delay)
+ .configure(LOW_THRESHOLD_CONFIG_KEY, lowThreshold)
+ .configure(HIGH_THRESHOLD_CONFIG_KEY, highThreshold));
+ LOG.debug("Managed new container {}", container);
+ container.start(ImmutableList.of(loc));
+ return container;
+ }
+
+ protected static MockItemEntity newItem(TestApplication app, MockContainerEntity container, String name, double workrate) {
+ MockItemEntity item = app.createAndManageChild(EntitySpec.create(MockItemEntity.class)
+ .displayName(name));
+ LOG.debug("Managing new item {} on container {}", item, container);
+ item.move(container);
+ ((EntityLocal)item).setAttribute(TEST_METRIC, (int)workrate);
+ return item;
+ }
+
+ protected static MockItemEntity newLockedItem(TestApplication app, MockContainerEntity container, String name, double workrate) {
+ MockItemEntity item = app.createAndManageChild(EntitySpec.create(MockItemEntity.class)
+ .displayName(name)
+ .configure(Movable.IMMOVABLE, true));
+ LOG.debug("Managed new item {} on container {}", item, container);
+ item.move(container);
+ ((EntityLocal)item).setAttribute(TEST_METRIC, (int)workrate);
+ return item;
+ }
+
+ /**
+ * Asks the item directly for its workrate.
+ */
+ protected static double getItemWorkrate(MockItemEntity item) {
+ Object result = item.getAttribute(TEST_METRIC);
+ return (result == null ? 0 : ((Number) result).doubleValue());
+ }
+
+ /**
+ * Asks the container for its items, and then each of those items directly for their workrates; returns the total.
+ */
+ protected static double getContainerWorkrate(MockContainerEntity container) {
+ double result = 0.0;
+ Preconditions.checkNotNull(container, "container");
+ for (Movable item : container.getBalanceableItems()) {
+ Preconditions.checkNotNull(item, "item in container");
+ assertEquals(item.getContainerId(), container.getId());
+ result += getItemWorkrate((MockItemEntity)item);
+ }
+ return result;
+ }
+}
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/d30ff597/policy/src/test/java/org/apache/brooklyn/policy/loadbalancing/BalanceableWorkerPoolTest.java
----------------------------------------------------------------------
diff --git a/policy/src/test/java/org/apache/brooklyn/policy/loadbalancing/BalanceableWorkerPoolTest.java b/policy/src/test/java/org/apache/brooklyn/policy/loadbalancing/BalanceableWorkerPoolTest.java
new file mode 100644
index 0000000..18f6847
--- /dev/null
+++ b/policy/src/test/java/org/apache/brooklyn/policy/loadbalancing/BalanceableWorkerPoolTest.java
@@ -0,0 +1,133 @@
+/*
+ * 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.policy.loadbalancing;
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.fail;
+
+import org.apache.brooklyn.api.entity.Group;
+import org.apache.brooklyn.api.entity.proxying.EntitySpec;
+import org.apache.brooklyn.api.entity.proxying.ImplementedBy;
+import org.apache.brooklyn.test.entity.TestApplication;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.testng.annotations.AfterMethod;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import brooklyn.entity.basic.AbstractGroup;
+import brooklyn.entity.basic.AbstractGroupImpl;
+import brooklyn.entity.basic.ApplicationBuilder;
+import brooklyn.entity.basic.DynamicGroup;
+import brooklyn.entity.basic.Entities;
+import brooklyn.entity.trait.Resizable;
+import org.apache.brooklyn.location.basic.SimulatedLocation;
+import brooklyn.util.collections.MutableMap;
+import brooklyn.util.exceptions.Exceptions;
+
+import com.google.common.base.Predicates;
+import com.google.common.collect.ImmutableList;
+
+public class BalanceableWorkerPoolTest {
+
+ private static final Logger LOG = LoggerFactory.getLogger(BalanceableWorkerPoolTest.class);
+
+ protected static final long TIMEOUT_MS = 10*1000;
+ protected static final long SHORT_WAIT_MS = 250;
+
+ protected static final long CONTAINER_STARTUP_DELAY_MS = 100;
+
+ protected TestApplication app;
+ protected SimulatedLocation loc;
+ protected BalanceableWorkerPool pool;
+ protected Group containerGroup;
+ protected Group itemGroup;
+
+ @BeforeMethod(alwaysRun=true)
+ public void before() {
+ loc = new SimulatedLocation(MutableMap.of("name", "loc"));
+
+ app = ApplicationBuilder.newManagedApp(TestApplication.class);
+ containerGroup = app.createAndManageChild(EntitySpec.create(DynamicGroup.class)
+ .displayName("containerGroup")
+ .configure(DynamicGroup.ENTITY_FILTER, Predicates.instanceOf(MockContainerEntity.class)));
+ itemGroup = app.createAndManageChild(EntitySpec.create(DynamicGroup.class)
+ .displayName("itemGroup")
+ .configure(DynamicGroup.ENTITY_FILTER, Predicates.instanceOf(MockItemEntity.class)));
+ pool = app.createAndManageChild(EntitySpec.create(BalanceableWorkerPool.class));
+ pool.setContents(containerGroup, itemGroup);
+
+ app.start(ImmutableList.of(loc));
+ }
+
+ @AfterMethod(alwaysRun=true)
+ public void after() {
+ if (app != null) Entities.destroyAll(app.getManagementContext());
+ }
+
+ @Test
+ public void testDefaultResizeFailsIfContainerGroupNotResizable() throws Exception {
+ try {
+ pool.resize(1);
+ fail();
+ } catch (Exception e) {
+ if (Exceptions.getFirstThrowableOfType(e, UnsupportedOperationException.class) == null) throw e;
+ }
+ }
+
+ @Test
+ public void testDefaultResizeCallsResizeOnContainerGroup() {
+ LocallyResizableGroup resizable = app.createAndManageChild(EntitySpec.create(LocallyResizableGroup.class));
+
+ BalanceableWorkerPool pool2 = app.createAndManageChild(EntitySpec.create(BalanceableWorkerPool.class));
+ pool2.setContents(resizable, itemGroup);
+ Entities.manage(pool2);
+
+ pool2.resize(123);
+ assertEquals(resizable.getCurrentSize(), (Integer) 123);
+ }
+
+ @Test
+ public void testCustomResizableCalledWhenResizing() {
+ LocallyResizableGroup resizable = app.createAndManageChild(EntitySpec.create(LocallyResizableGroup.class));
+
+ pool.setResizable(resizable);
+
+ pool.resize(123);
+ assertEquals(resizable.getCurrentSize(), (Integer)123);
+ }
+
+ @ImplementedBy(LocallyResizableGroupImpl.class)
+ public static interface LocallyResizableGroup extends AbstractGroup, Resizable {
+ }
+
+ public static class LocallyResizableGroupImpl extends AbstractGroupImpl implements LocallyResizableGroup {
+ private int size = 0;
+
+ @Override
+ public Integer resize(Integer newSize) {
+ size = newSize;
+ return size;
+ }
+ @Override
+ public Integer getCurrentSize() {
+ return size;
+ }
+ }
+}
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/d30ff597/policy/src/test/java/org/apache/brooklyn/policy/loadbalancing/ItemsInContainersGroupTest.java
----------------------------------------------------------------------
diff --git a/policy/src/test/java/org/apache/brooklyn/policy/loadbalancing/ItemsInContainersGroupTest.java b/policy/src/test/java/org/apache/brooklyn/policy/loadbalancing/ItemsInContainersGroupTest.java
new file mode 100644
index 0000000..c256334
--- /dev/null
+++ b/policy/src/test/java/org/apache/brooklyn/policy/loadbalancing/ItemsInContainersGroupTest.java
@@ -0,0 +1,189 @@
+/*
+ * 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.policy.loadbalancing;
+
+import static org.testng.Assert.assertEquals;
+
+import org.apache.brooklyn.api.entity.Entity;
+import org.apache.brooklyn.api.entity.Group;
+import org.apache.brooklyn.api.entity.proxying.EntitySpec;
+import org.apache.brooklyn.test.entity.TestApplication;
+import org.testng.annotations.AfterMethod;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import brooklyn.entity.basic.ApplicationBuilder;
+import brooklyn.entity.basic.DynamicGroup;
+import brooklyn.entity.basic.Entities;
+import org.apache.brooklyn.location.basic.SimulatedLocation;
+import brooklyn.test.Asserts;
+import brooklyn.util.collections.MutableMap;
+
+import com.google.common.base.Predicate;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+
+public class ItemsInContainersGroupTest {
+
+ // all tests are 20ms or less, but use a big timeout just in case very slow machine!
+ private static final long TIMEOUT_MS = 15000;
+
+ private TestApplication app;
+ private SimulatedLocation loc;
+ private Group containerGroup;
+ private ItemsInContainersGroup itemGroup;
+
+ @BeforeMethod(alwaysRun=true)
+ public void setUp() throws Exception {
+ loc = new SimulatedLocation(MutableMap.of("name", "loc"));
+
+ app = ApplicationBuilder.newManagedApp(TestApplication.class);
+ containerGroup = app.createAndManageChild(EntitySpec.create(DynamicGroup.class)
+ .displayName("containerGroup")
+ .configure(DynamicGroup.ENTITY_FILTER, new Predicate<Entity>() {
+ public boolean apply(Entity input) {
+ return input instanceof MockContainerEntity &&
+ input.getConfig(MockContainerEntity.MOCK_MEMBERSHIP) == "ingroup";
+ }}));
+ itemGroup = app.createAndManageChild(EntitySpec.create(ItemsInContainersGroup.class)
+ .displayName("itemGroup"));
+ itemGroup.setContainers(containerGroup);
+
+ app.start(ImmutableList.of(loc));
+ }
+
+ @AfterMethod(alwaysRun=true)
+ public void tearDown() throws Exception {
+ if (app != null) Entities.destroyAll(app.getManagementContext());
+ }
+
+ @Test
+ public void testSimpleMembership() throws Exception {
+ MockContainerEntity containerIn = newContainer(app, "A", "ingroup");
+ MockItemEntity item1 = newItem(app, containerIn, "1");
+ MockItemEntity item2 = newItem(app, containerIn, "2");
+
+ assertItemsEventually(item1, item2);
+ }
+
+ @Test
+ public void testFilterIsAppliedToItems() throws Exception {
+ itemGroup.stop();
+ Entities.unmanage(itemGroup);
+
+ itemGroup = app.createAndManageChild(EntitySpec.create(ItemsInContainersGroup.class)
+ .displayName("itemGroupWithDispName2")
+ .configure(ItemsInContainersGroup.ITEM_FILTER, new Predicate<Entity>() {
+ public boolean apply(Entity input) {
+ return "2".equals(input.getDisplayName());
+ }}));
+ itemGroup.setContainers(containerGroup);
+
+ MockContainerEntity containerIn = newContainer(app, "A", "ingroup");
+ MockItemEntity item1 = newItem(app, containerIn, "1");
+ MockItemEntity item2 = newItem(app, containerIn, "2");
+
+ assertItemsEventually(item2); // does not include item1
+ }
+
+ @Test
+ public void testItemsInOtherContainersIgnored() throws Exception {
+ MockContainerEntity containerOut = newContainer(app, "A", "outgroup");
+ MockItemEntity item1 = newItem(app, containerOut, "1");
+
+ assertItemsEventually();
+ }
+
+ @Test
+ public void testItemMovedInIsAdded() throws Exception {
+ MockContainerEntity containerIn = newContainer(app, "A", "ingroup");
+ MockContainerEntity containerOut = newContainer(app, "A", "outgroup");
+ MockItemEntity item1 = newItem(app, containerOut, "1");
+ item1.move(containerIn);
+
+ assertItemsEventually(item1);
+ }
+
+ @Test
+ public void testItemMovedOutIsRemoved() throws Exception {
+ MockContainerEntity containerIn = newContainer(app, "A", "ingroup");
+ MockContainerEntity containerOut = newContainer(app, "A", "outgroup");
+ MockItemEntity item1 = newItem(app, containerIn, "1");
+ assertItemsEventually(item1);
+
+ item1.move(containerOut);
+ assertItemsEventually();
+ }
+
+ /*
+ * Previously could fail if...
+ * ItemsInContainersGroupImpl listener got notified of Movable.CONTAINER after entity was unmanaged
+ * (because being done in concurrent threads).
+ * This called ItemsInContainersGroupImpl.onItemMoved, which called addMember to add it back in again.
+ * In AbstractGroup.addMember, we now check if the entity is still managed, to
+ * ensure there is synchronization for concurrent calls to add/remove member.
+ */
+ @Test
+ public void testItemUnmanagedIsRemoved() throws Exception {
+ MockContainerEntity containerIn = newContainer(app, "A", "ingroup");
+ MockItemEntity item1 = newItem(app, containerIn, "1");
+ assertItemsEventually(item1);
+
+ Entities.unmanage(item1);
+ assertItemsEventually();
+ }
+
+ // TODO How to test this? Will it be used?
+ // Adding a new container then adding items to it is tested in many other methods.
+ @Test(enabled=false)
+ public void testContainerAddedWillAddItsItems() throws Exception {
+ }
+
+ @Test
+ public void testContainerRemovedWillRemoveItsItems() throws Exception {
+ MockContainerEntity containerA = newContainer(app, "A", "ingroup");
+ MockItemEntity item1 = newItem(app, containerA, "1");
+ assertItemsEventually(item1);
+
+ Entities.unmanage(containerA);
+ assertItemsEventually();
+ }
+
+ private void assertItemsEventually(final MockItemEntity... expected) {
+ Asserts.succeedsEventually(MutableMap.of("timeout", TIMEOUT_MS), new Runnable() {
+ public void run() {
+ assertEquals(ImmutableSet.copyOf(itemGroup.getMembers()), ImmutableSet.copyOf(expected));
+ }});
+ }
+
+ private MockContainerEntity newContainer(TestApplication app, String name, String membership) {
+ MockContainerEntity container = app.createAndManageChild(EntitySpec.create(MockContainerEntity.class)
+ .displayName(name)
+ .configure(MockContainerEntity.MOCK_MEMBERSHIP, membership));
+ container.start(ImmutableList.of(loc));
+ return container;
+ }
+
+ private static MockItemEntity newItem(TestApplication app, MockContainerEntity container, String name) {
+ MockItemEntity item = app.createAndManageChild(EntitySpec.create(MockItemEntity.class)
+ .displayName(name));
+ item.move(container);
+ return item;
+ }
+}
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/d30ff597/policy/src/test/java/org/apache/brooklyn/policy/loadbalancing/LoadBalancingModelTest.java
----------------------------------------------------------------------
diff --git a/policy/src/test/java/org/apache/brooklyn/policy/loadbalancing/LoadBalancingModelTest.java b/policy/src/test/java/org/apache/brooklyn/policy/loadbalancing/LoadBalancingModelTest.java
new file mode 100644
index 0000000..d96f509
--- /dev/null
+++ b/policy/src/test/java/org/apache/brooklyn/policy/loadbalancing/LoadBalancingModelTest.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.policy.loadbalancing;
+
+import static org.testng.Assert.assertEquals;
+
+import java.util.Collections;
+
+import org.testng.annotations.AfterMethod;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+
+public class LoadBalancingModelTest {
+
+ private static final double PRECISION = 0.00001;
+
+ private MockContainerEntity container1 = new MockContainerEntityImpl();
+ private MockContainerEntity container2 = new MockContainerEntityImpl();
+ private MockItemEntity item1 = new MockItemEntityImpl();
+ private MockItemEntity item2 = new MockItemEntityImpl();
+ private MockItemEntity item3 = new MockItemEntityImpl();
+
+ private DefaultBalanceablePoolModel<MockContainerEntity, MockItemEntity> model;
+
+ @BeforeMethod(alwaysRun=true)
+ public void setUp() throws Exception {
+ model = new DefaultBalanceablePoolModel<MockContainerEntity, MockItemEntity>("myname");
+ }
+
+ @AfterMethod(alwaysRun=true)
+ public void tearDown() throws Exception {
+ // nothing to tear down; no management context created
+ }
+
+ @Test
+ public void testPoolRatesCorrectlySumContainers() throws Exception {
+ model.onContainerAdded(container1, 10d, 20d);
+ model.onContainerAdded(container2, 11d, 22d);
+
+ assertEquals(model.getPoolLowThreshold(), 10d+11d, PRECISION);
+ assertEquals(model.getPoolHighThreshold(), 20d+22d, PRECISION);
+ }
+
+ @Test
+ public void testPoolRatesCorrectlySumItems() throws Exception {
+ model.onContainerAdded(container1, 10d, 20d);
+ model.onItemAdded(item1, container1, true);
+ model.onItemAdded(item2, container1, true);
+
+ model.onItemWorkrateUpdated(item1, 1d);
+ assertEquals(model.getCurrentPoolWorkrate(), 1d, PRECISION);
+
+ model.onItemWorkrateUpdated(item2, 2d);
+ assertEquals(model.getCurrentPoolWorkrate(), 1d+2d, PRECISION);
+
+ model.onItemWorkrateUpdated(item2, 4d);
+ assertEquals(model.getCurrentPoolWorkrate(), 1d+4d, PRECISION);
+
+ model.onItemRemoved(item1);
+ assertEquals(model.getCurrentPoolWorkrate(), 4d, PRECISION);
+ }
+
+ @Test
+ public void testWorkrateUpdateAfterItemRemovalIsNotRecorded() throws Exception {
+ model.onContainerAdded(container1, 10d, 20d);
+ model.onItemAdded(item1, container1, true);
+ model.onItemRemoved(item1);
+ model.onItemWorkrateUpdated(item1, 123d);
+
+ assertEquals(model.getCurrentPoolWorkrate(), 0d, PRECISION);
+ assertEquals(model.getContainerWorkrates().get(container1), 0d, PRECISION);
+ assertEquals(model.getItemWorkrate(item1), null);
+ }
+
+ @Test
+ public void testItemMovedWillUpdateContainerWorkrates() throws Exception {
+ model.onContainerAdded(container1, 10d, 20d);
+ model.onContainerAdded(container2, 11d, 21d);
+ model.onItemAdded(item1, container1, false);
+ model.onItemWorkrateUpdated(item1, 123d);
+
+ model.onItemMoved(item1, container2);
+
+ assertEquals(model.getItemsForContainer(container1), Collections.emptySet());
+ assertEquals(model.getItemsForContainer(container2), ImmutableSet.of(item1));
+ assertEquals(model.getItemWorkrate(item1), 123d);
+ assertEquals(model.getTotalWorkrate(container1), 0d);
+ assertEquals(model.getTotalWorkrate(container2), 123d);
+ assertEquals(model.getItemWorkrates(container1), Collections.emptyMap());
+ assertEquals(model.getItemWorkrates(container2), ImmutableMap.of(item1, 123d));
+ assertEquals(model.getContainerWorkrates(), ImmutableMap.of(container1, 0d, container2, 123d));
+ assertEquals(model.getCurrentPoolWorkrate(), 123d);
+ }
+}
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/d30ff597/policy/src/test/java/org/apache/brooklyn/policy/loadbalancing/LoadBalancingPolicyConcurrencyTest.java
----------------------------------------------------------------------
diff --git a/policy/src/test/java/org/apache/brooklyn/policy/loadbalancing/LoadBalancingPolicyConcurrencyTest.java b/policy/src/test/java/org/apache/brooklyn/policy/loadbalancing/LoadBalancingPolicyConcurrencyTest.java
new file mode 100644
index 0000000..5a3b328
--- /dev/null
+++ b/policy/src/test/java/org/apache/brooklyn/policy/loadbalancing/LoadBalancingPolicyConcurrencyTest.java
@@ -0,0 +1,211 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.brooklyn.policy.loadbalancing;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Queue;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ConcurrentLinkedQueue;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicReference;
+
+import org.apache.brooklyn.api.entity.basic.EntityLocal;
+import org.apache.brooklyn.test.entity.TestApplication;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.testng.annotations.AfterMethod;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import brooklyn.entity.basic.Entities;
+
+import com.google.common.collect.Lists;
+
+public class LoadBalancingPolicyConcurrencyTest extends AbstractLoadBalancingPolicyTest {
+
+ private static final Logger LOG = LoggerFactory.getLogger(LoadBalancingPolicyConcurrencyTest.class);
+
+ private static final double WORKRATE_JITTER = 2d;
+ private static final int NUM_CONTAINERS = 20;
+ private static final int WORKRATE_UPDATE_PERIOD_MS = 1000;
+
+ private ScheduledExecutorService scheduledExecutor;
+
+ @BeforeMethod(alwaysRun=true)
+ @Override
+ public void before() {
+ scheduledExecutor = Executors.newScheduledThreadPool(10);
+ super.before();
+ }
+
+ @AfterMethod(alwaysRun=true)
+ @Override
+ public void after() {
+ if (scheduledExecutor != null) scheduledExecutor.shutdownNow();
+ super.after();
+ }
+
+ @Test
+ public void testSimplePeriodicWorkrateUpdates() {
+ List<MockItemEntity> items = Lists.newArrayList();
+ List<MockContainerEntity> containers = Lists.newArrayList();
+
+ for (int i = 0; i < NUM_CONTAINERS; i++) {
+ containers.add(newContainer(app, "container"+i, 10, 30));
+ }
+ for (int i = 0; i < NUM_CONTAINERS; i++) {
+ newItemWithPeriodicWorkrates(app, containers.get(0), "item"+i, 20);
+ }
+
+ assertWorkratesEventually(containers, items, Collections.nCopies(NUM_CONTAINERS, 20d), WORKRATE_JITTER);
+ }
+
+ @Test
+ public void testConcurrentlyAddContainers() {
+ final Queue<MockContainerEntity> containers = new ConcurrentLinkedQueue<MockContainerEntity>();
+ final List<MockItemEntity> items = Lists.newArrayList();
+
+ containers.add(newContainer(app, "container-orig", 10, 30));
+
+ for (int i = 0; i < NUM_CONTAINERS; i++) {
+ items.add(newItemWithPeriodicWorkrates(app, containers.iterator().next(), "item"+i, 20));
+ }
+ for (int i = 0; i < NUM_CONTAINERS-1; i++) {
+ final int index = i;
+ scheduledExecutor.submit(new Callable<Void>() {
+ @Override public Void call() {
+ containers.add(newContainer(app, "container"+index, 10, 30));
+ return null;
+ }});
+ }
+
+ assertWorkratesEventually(containers, items, Collections.nCopies(NUM_CONTAINERS, 20d), WORKRATE_JITTER);
+ }
+
+ @Test
+ public void testConcurrentlyAddItems() {
+ final Queue<MockItemEntity> items = new ConcurrentLinkedQueue<MockItemEntity>();
+ final List<MockContainerEntity> containers = Lists.newArrayList();
+
+ for (int i = 0; i < NUM_CONTAINERS; i++) {
+ containers.add(newContainer(app, "container"+i, 10, 30));
+ }
+ for (int i = 0; i < NUM_CONTAINERS; i++) {
+ final int index = i;
+ scheduledExecutor.submit(new Callable<Void>() {
+ @Override public Void call() {
+ items.add(newItemWithPeriodicWorkrates(app, containers.get(0), "item"+index, 20));
+ return null;
+ }});
+ }
+ assertWorkratesEventually(containers, items, Collections.nCopies(NUM_CONTAINERS, 20d), WORKRATE_JITTER);
+ }
+
+ // TODO Got IndexOutOfBoundsException from containers.last()
+ @Test(groups="WIP", invocationCount=100)
+ public void testConcurrentlyRemoveContainers() {
+ List<MockItemEntity> items = Lists.newArrayList();
+ final List<MockContainerEntity> containers = Lists.newArrayList();
+
+ for (int i = 0; i < NUM_CONTAINERS; i++) {
+ containers.add(newContainer(app, "container"+i, 15, 45));
+ }
+ for (int i = 0; i < NUM_CONTAINERS; i++) {
+ items.add(newItemWithPeriodicWorkrates(app, containers.get(i), "item"+i, 20));
+ }
+
+ final List<MockContainerEntity> containersToStop = Lists.newArrayList();
+ for (int i = 0; i < NUM_CONTAINERS/2; i++) {
+ containersToStop.add(containers.remove(0));
+ }
+ for (final MockContainerEntity containerToStop : containersToStop) {
+ scheduledExecutor.submit(new Callable<Void>() {
+ @Override public Void call() {
+ try {
+ containerToStop.offloadAndStop(containers.get(containers.size()-1));
+ Entities.unmanage(containerToStop);
+ } catch (Throwable t) {
+ LOG.error("Error stopping container "+containerToStop, t);
+ }
+ return null;
+ }});
+ }
+
+ assertWorkratesEventually(containers, items, Collections.nCopies((int)(NUM_CONTAINERS/2), 40d), WORKRATE_JITTER*2);
+ }
+
+ @Test(groups="WIP")
+ public void testConcurrentlyRemoveItems() {
+ List<MockItemEntity> items = Lists.newArrayList();
+ List<MockContainerEntity> containers = Lists.newArrayList();
+
+ for (int i = 0; i < NUM_CONTAINERS; i++) {
+ containers.add(newContainer(app, "container"+i, 15, 45));
+ }
+ for (int i = 0; i < NUM_CONTAINERS*2; i++) {
+ items.add(newItemWithPeriodicWorkrates(app, containers.get(i%NUM_CONTAINERS), "item"+i, 20));
+ }
+ // should now have item0 and item{0+NUM_CONTAINERS} on container0, etc
+
+ for (int i = 0; i < NUM_CONTAINERS; i++) {
+ // not removing consecutive items as that would leave it balanced!
+ int indexToStop = (i < NUM_CONTAINERS/2) ? NUM_CONTAINERS : 0;
+ final MockItemEntity itemToStop = items.remove(indexToStop);
+ scheduledExecutor.submit(new Callable<Void>() {
+ @Override public Void call() {
+ try {
+ itemToStop.stop();
+ Entities.unmanage(itemToStop);
+ } catch (Throwable t) {
+ LOG.error("Error stopping item "+itemToStop, t);
+ }
+ return null;
+ }});
+ }
+
+ assertWorkratesEventually(containers, items, Collections.nCopies(NUM_CONTAINERS, 20d), WORKRATE_JITTER);
+ }
+
+ protected MockItemEntity newItemWithPeriodicWorkrates(TestApplication app, MockContainerEntity container, String name, double workrate) {
+ MockItemEntity item = newItem(app, container, name, workrate);
+ scheduleItemWorkrateUpdates(item, workrate, WORKRATE_JITTER);
+ return item;
+ }
+
+ private void scheduleItemWorkrateUpdates(final MockItemEntity item, final double workrate, final double jitter) {
+ final AtomicReference<Future<?>> futureRef = new AtomicReference<Future<?>>();
+ Future<?> future = scheduledExecutor.scheduleAtFixedRate(
+ new Runnable() {
+ @Override public void run() {
+ if (item.isStopped() && futureRef.get() != null) {
+ futureRef.get().cancel(true);
+ return;
+ }
+ double jitteredWorkrate = workrate + (random.nextDouble()*jitter*2 - jitter);
+ ((EntityLocal)item).setAttribute(TEST_METRIC, (int) Math.max(0, jitteredWorkrate));
+ }
+ },
+ 0, WORKRATE_UPDATE_PERIOD_MS, TimeUnit.MILLISECONDS);
+ futureRef.set(future);
+ }
+}
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/d30ff597/policy/src/test/java/org/apache/brooklyn/policy/loadbalancing/LoadBalancingPolicySoakTest.java
----------------------------------------------------------------------
diff --git a/policy/src/test/java/org/apache/brooklyn/policy/loadbalancing/LoadBalancingPolicySoakTest.java b/policy/src/test/java/org/apache/brooklyn/policy/loadbalancing/LoadBalancingPolicySoakTest.java
new file mode 100644
index 0000000..8da494c
--- /dev/null
+++ b/policy/src/test/java/org/apache/brooklyn/policy/loadbalancing/LoadBalancingPolicySoakTest.java
@@ -0,0 +1,273 @@
+/*
+ * 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.policy.loadbalancing;
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertTrue;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import org.apache.brooklyn.api.entity.Entity;
+import org.apache.brooklyn.api.entity.basic.EntityLocal;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.testng.annotations.Test;
+
+import brooklyn.entity.basic.Entities;
+import brooklyn.test.Asserts;
+import brooklyn.util.collections.MutableMap;
+
+import com.google.common.base.Function;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Lists;
+
+public class LoadBalancingPolicySoakTest extends AbstractLoadBalancingPolicyTest {
+
+ private static final Logger LOG = LoggerFactory.getLogger(LoadBalancingPolicySoakTest.class);
+
+ private static final long TIMEOUT_MS = 40*1000;
+
+ @Test
+ public void testLoadBalancingQuickTest() {
+ RunConfig config = new RunConfig();
+ config.numCycles = 1;
+ config.numContainers = 5;
+ config.numItems = 5;
+ config.lowThreshold = 200;
+ config.highThreshold = 300;
+ config.totalRate = (int) (config.numContainers*(0.95*config.highThreshold));
+
+ runLoadBalancingSoakTest(config);
+ }
+
+ @Test
+ public void testLoadBalancingManyItemsQuickTest() {
+ RunConfig config = new RunConfig();
+ config.numCycles = 1;
+ config.numContainers = 5;
+ config.numItems = 30;
+ config.lowThreshold = 200;
+ config.highThreshold = 300;
+ config.numContainerStopsPerCycle = 1;
+ config.numItemStopsPerCycle = 1;
+ config.totalRate = (int) (config.numContainers*(0.95*config.highThreshold));
+
+ runLoadBalancingSoakTest(config);
+ }
+
+ @Test(groups={"Integration","Acceptance"}) // acceptance group, because it's slow to run many cycles
+ public void testLoadBalancingSoakTest() {
+ RunConfig config = new RunConfig();
+ config.numCycles = 100;
+ config.numContainers = 5;
+ config.numItems = 5;
+ config.lowThreshold = 200;
+ config.highThreshold = 300;
+ config.totalRate = (int) (config.numContainers*(0.95*config.highThreshold));
+
+ runLoadBalancingSoakTest(config);
+ }
+
+ @Test(groups={"Integration","Acceptance"}) // acceptance group, because it's slow to run many cycles
+ public void testLoadBalancingManyItemsSoakTest() {
+ RunConfig config = new RunConfig();
+ config.numCycles = 100;
+ config.numContainers = 5;
+ config.numItems = 30;
+ config.lowThreshold = 200;
+ config.highThreshold = 300;
+ config.totalRate = (int) (config.numContainers*(0.95*config.highThreshold));
+ config.numContainerStopsPerCycle = 3;
+ config.numItemStopsPerCycle = 10;
+
+ runLoadBalancingSoakTest(config);
+ }
+
+ @Test(groups={"Integration","Acceptance"})
+ public void testLoadBalancingManyManyItemsTest() {
+ RunConfig config = new RunConfig();
+ config.numCycles = 1;
+ config.numContainers = 5;
+ config.numItems = 1000;
+ config.lowThreshold = 2000;
+ config.highThreshold = 3000;
+ config.numContainerStopsPerCycle = 0;
+ config.numItemStopsPerCycle = 0;
+ config.totalRate = (int) (config.numContainers*(0.95*config.highThreshold));
+ config.verbose = false;
+
+ runLoadBalancingSoakTest(config);
+ }
+
+ private void runLoadBalancingSoakTest(RunConfig config) {
+ final int numCycles = config.numCycles;
+ final int numContainers = config.numContainers;
+ final int numItems = config.numItems;
+ final double lowThreshold = config.lowThreshold;
+ final double highThreshold = config.highThreshold;
+ final int totalRate = config.totalRate;
+ final int numContainerStopsPerCycle = config.numContainerStopsPerCycle;
+ final int numItemStopsPerCycle = config.numItemStopsPerCycle;
+ final boolean verbose = config.verbose;
+
+ MockItemEntityImpl.totalMoveCount.set(0);
+
+ final List<MockContainerEntity> containers = new ArrayList<MockContainerEntity>();
+ final List<MockItemEntity> items = new ArrayList<MockItemEntity>();
+
+ for (int i = 1; i <= numContainers; i++) {
+ MockContainerEntity container = newContainer(app, "container-"+i, lowThreshold, highThreshold);
+ containers.add(container);
+ }
+ for (int i = 1; i <= numItems; i++) {
+ MockItemEntity item = newItem(app, containers.get(0), "item-"+i, 5);
+ items.add(item);
+ }
+
+ for (int i = 1; i <= numCycles; i++) {
+ LOG.info(LoadBalancingPolicySoakTest.class.getSimpleName()+": cycle "+i);
+
+ // Stop items, and start others
+ for (int j = 1; j <= numItemStopsPerCycle; j++) {
+ int itemIndex = random.nextInt(numItems);
+ MockItemEntity itemToStop = items.get(itemIndex);
+ itemToStop.stop();
+ LOG.debug("Unmanaging item {}", itemToStop);
+ Entities.unmanage(itemToStop);
+ items.set(itemIndex, newItem(app, containers.get(0), "item-"+(itemIndex+1)+"."+i+"."+j, 5));
+ }
+
+ // Repartition the load across the items
+ final List<Integer> itemRates = randomlyDivideLoad(numItems, totalRate, 0, (int)highThreshold);
+
+ for (int j = 0; j < numItems; j++) {
+ MockItemEntity item = items.get(j);
+ ((EntityLocal)item).setAttribute(MockItemEntity.TEST_METRIC, itemRates.get(j));
+ }
+
+ // Stop containers, and start others
+ for (int j = 1; j <= numContainerStopsPerCycle; j++) {
+ int containerIndex = random.nextInt(numContainers);
+ MockContainerEntity containerToStop = containers.get(containerIndex);
+ containerToStop.offloadAndStop(containers.get((containerIndex+1)%numContainers));
+ LOG.debug("Unmanaging container {}", containerToStop);
+ Entities.unmanage(containerToStop);
+
+ MockContainerEntity containerToAdd = newContainer(app, "container-"+(containerIndex+1)+"."+i+"."+j, lowThreshold, highThreshold);
+ containers.set(containerIndex, containerToAdd);
+ }
+
+ // Assert that the items become balanced again
+ Asserts.succeedsEventually(MutableMap.of("timeout", TIMEOUT_MS), new Runnable() {
+ @Override public void run() {
+ Iterable<Double> containerRates = Iterables.transform(containers, new Function<MockContainerEntity, Double>() {
+ @Override public Double apply(MockContainerEntity input) {
+ return (double) input.getWorkrate();
+ }});
+
+ String errMsg;
+ if (verbose) {
+ errMsg = verboseDumpToString(containers, items)+"; itemRates="+itemRates;
+ } else {
+ errMsg = containerRates+"; totalMoves="+MockItemEntityImpl.totalMoveCount;
+ }
+
+ // Check that haven't lost any items
+ // (as observed in one jenkins build failure: 2014-03-18; but that could also be
+ // explained by errMsg generated in the middle of a move)
+ List<Entity> itemsFromModel = Lists.newArrayList();
+ List<Entity> itemsFromContainers = Lists.newArrayList();
+ for (Entity container : model.getPoolContents()) {
+ itemsFromModel.addAll(model.getItemsForContainer(container));
+ }
+ for (MockContainerEntity container : containers) {
+ itemsFromContainers.addAll(container.getBalanceableItems());
+ }
+ Asserts.assertEqualsIgnoringOrder(itemsFromModel, items, true, errMsg);
+ Asserts.assertEqualsIgnoringOrder(itemsFromContainers, items, true, errMsg);
+
+ // Check overall container rates are balanced
+ assertEquals(sum(containerRates), sum(itemRates), errMsg);
+ for (double containerRate : containerRates) {
+ assertTrue(containerRate >= lowThreshold, errMsg);
+ assertTrue(containerRate <= highThreshold, errMsg);
+ }
+ }});
+ }
+ }
+
+ private static class RunConfig {
+ int numCycles = 1;
+ int numContainers = 5;
+ int numItems = 5;
+ double lowThreshold = 200;
+ double highThreshold = 300;
+ int totalRate = (int) (5*(0.95*highThreshold));
+ int numContainerStopsPerCycle = 1;
+ int numItemStopsPerCycle = 1;
+ boolean verbose = true;
+ }
+
+ // Testing conveniences.
+
+ private double sum(Iterable<? extends Number> vals) {
+ double total = 0;;
+ for (Number val : vals) {
+ total += val.doubleValue();
+ }
+ return total;
+ }
+
+ /**
+ * Distributes a given load across a number of items randomly. The variability in load for an item is dictated by the variance,
+ * but the total will always equal totalLoad.
+ *
+ * The distribution of load is skewed: one side of the list will have bigger values than the other.
+ * Which side is skewed will vary, so when balancing a policy will find that things have entirely changed.
+ *
+ * TODO This is not particularly good at distributing load, but it's random and skewed enough to force rebalancing.
+ */
+ private List<Integer> randomlyDivideLoad(int numItems, int totalLoad, int min, int max) {
+ List<Integer> result = new ArrayList<Integer>(numItems);
+ int totalRemaining = totalLoad;
+ int variance = 3;
+ int skew = 3;
+
+ for (int i = 0; i < numItems; i++) {
+ int itemsRemaining = numItems-i;
+ int itemFairShare = (totalRemaining/itemsRemaining);
+ double skewFactor = ((double)i/numItems)*2 - 1; // a number between -1 and 1, depending how far through the item set we are
+ int itemSkew = (int) (random.nextInt(skew)*skewFactor);
+ int itemLoad = itemFairShare + (random.nextInt(variance*2)-variance) + itemSkew;
+ itemLoad = Math.max(min, itemLoad);
+ itemLoad = Math.min(totalRemaining, itemLoad);
+ itemLoad = Math.min(max, itemLoad);
+ result.add(itemLoad);
+ totalRemaining -= itemLoad;
+ }
+
+ if (random.nextBoolean()) Collections.reverse(result);
+
+ assertTrue(sum(result) <= totalLoad, "totalLoad="+totalLoad+"; result="+result);
+
+ return result;
+ }
+}
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/d30ff597/policy/src/test/java/org/apache/brooklyn/policy/loadbalancing/LoadBalancingPolicyTest.java
----------------------------------------------------------------------
diff --git a/policy/src/test/java/org/apache/brooklyn/policy/loadbalancing/LoadBalancingPolicyTest.java b/policy/src/test/java/org/apache/brooklyn/policy/loadbalancing/LoadBalancingPolicyTest.java
new file mode 100644
index 0000000..a0166f9
--- /dev/null
+++ b/policy/src/test/java/org/apache/brooklyn/policy/loadbalancing/LoadBalancingPolicyTest.java
@@ -0,0 +1,397 @@
+/*
+ * 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.policy.loadbalancing;
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertFalse;
+
+import org.apache.brooklyn.api.entity.basic.EntityLocal;
+import org.testng.annotations.Test;
+
+import brooklyn.entity.basic.Entities;
+import brooklyn.test.Asserts;
+import brooklyn.util.collections.MutableMap;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+
+public class LoadBalancingPolicyTest extends AbstractLoadBalancingPolicyTest {
+
+ // Expect no balancing to occur as container A isn't above the high threshold.
+ @Test
+ public void testNoopWhenWithinThresholds() {
+ MockContainerEntity containerA = newContainer(app, "A", 10, 100);
+ MockContainerEntity containerB = newContainer(app, "B", 20, 60);
+ MockItemEntity item1 = newItem(app, containerA, "1", 10);
+ MockItemEntity item2 = newItem(app, containerA, "2", 10);
+ MockItemEntity item3 = newItem(app, containerA, "3", 10);
+ MockItemEntity item4 = newItem(app, containerA, "4", 10);
+
+ assertWorkratesEventually(
+ ImmutableList.of(containerA, containerB),
+ ImmutableList.of(item1, item2, item3, item4),
+ ImmutableList.of(40d, 0d));
+ }
+
+ @Test
+ public void testNoopWhenAlreadyBalanced() {
+ MockContainerEntity containerA = newContainer(app, "A", 20, 80);
+ MockContainerEntity containerB = newContainer(app, "B", 20, 80);
+ MockItemEntity item1 = newItem(app, containerA, "1", 10);
+ MockItemEntity item2 = newItem(app, containerA, "2", 30);
+ MockItemEntity item3 = newItem(app, containerB, "3", 20);
+ MockItemEntity item4 = newItem(app, containerB, "4", 20);
+
+ assertWorkratesEventually(
+ ImmutableList.of(containerA, containerB),
+ ImmutableList.of(item1, item2, item3, item4),
+ ImmutableList.of(40d, 40d));
+ assertEquals(containerA.getBalanceableItems(), ImmutableSet.of(item1, item2));
+ assertEquals(containerB.getBalanceableItems(), ImmutableSet.of(item3, item4));
+ }
+
+ // Expect 20 units of workload to be migrated from hot container (A) to cold (B).
+ @Test
+ public void testSimpleBalancing() {
+ // Set-up containers and items.
+ MockContainerEntity containerA = newContainer(app, "A", 10, 25);
+ MockContainerEntity containerB = newContainer(app, "B", 20, 60);
+ MockItemEntity item1 = newItem(app, containerA, "1", 10);
+ MockItemEntity item2 = newItem(app, containerA, "2", 10);
+ MockItemEntity item3 = newItem(app, containerA, "3", 10);
+ MockItemEntity item4 = newItem(app, containerA, "4", 10);
+
+ assertWorkratesEventually(
+ ImmutableList.of(containerA, containerB),
+ ImmutableList.of(item1, item2, item3, item4),
+ ImmutableList.of(20d, 20d));
+ }
+
+ @Test
+ public void testSimpleBalancing2() {
+ MockContainerEntity containerA = newContainer(app, "A", 20, 40);
+ MockContainerEntity containerB = newContainer(app, "B", 20, 40);
+ MockItemEntity item1 = newItem(app, containerA, "1", 0);
+ MockItemEntity item2 = newItem(app, containerB, "2", 40);
+ MockItemEntity item3 = newItem(app, containerB, "3", 20);
+ MockItemEntity item4 = newItem(app, containerB, "4", 20);
+
+ assertWorkratesEventually(
+ ImmutableList.of(containerA, containerB),
+ ImmutableList.of(item1, item2, item3, item4),
+ ImmutableList.of(40d, 40d));
+ }
+
+// @Test
+// public void testAdjustedItemNotMoved() {
+// MockBalancingModel pool = new MockBalancingModel(
+// containers(
+// containerA, 20, 50,
+// containerB, 20, 50),
+// items(
+// "item1", containerA, 0,
+// "item2", containerB, -40,
+// "item3", containerB, 20,
+// "item4", containerB, 20)
+// );
+//
+// BalancingStrategy<String, String> policy = new BalancingStrategy<String, String>("Test", pool);
+// policy.rebalance();
+//
+// assertEquals((Object)pool.getItemsForContainer(containerA), ImmutableSet.of("item1", "item3", "item4"), pool.itemDistributionToString());
+// assertEquals((Object)pool.getItemsForContainer(containerB), ImmutableSet.of("item2"), pool.itemDistributionToString());
+// }
+
+ @Test
+ public void testMultiMoveBalancing() {
+ MockContainerEntity containerA = newContainer(app, "A", 20, 50);
+ MockContainerEntity containerB = newContainer(app, "B", 20, 50);
+ MockItemEntity item1 = newItem(app, containerA, "1", 10);
+ MockItemEntity item2 = newItem(app, containerA, "2", 10);
+ MockItemEntity item3 = newItem(app, containerA, "3", 10);
+ MockItemEntity item4 = newItem(app, containerA, "4", 10);
+ MockItemEntity item5 = newItem(app, containerA, "5", 10);
+ MockItemEntity item6 = newItem(app, containerA, "6", 10);
+ MockItemEntity item7 = newItem(app, containerA, "7", 10);
+ MockItemEntity item8 = newItem(app, containerA, "8", 10);
+ MockItemEntity item9 = newItem(app, containerA, "9", 10);
+ MockItemEntity item10 = newItem(app, containerA, "10", 10);
+
+ // non-deterministic which items will be moved; but can assert how many (given they all have same workrate)
+ assertWorkratesEventually(
+ ImmutableList.of(containerA, containerB),
+ ImmutableList.of(item1, item2, item3, item4, item5, item6, item7, item8, item9, item10),
+ ImmutableList.of(50d, 50d));
+ assertEquals(containerA.getBalanceableItems().size(), 5);
+ assertEquals(containerB.getBalanceableItems().size(), 5);
+ }
+
+ @Test
+ public void testRebalanceWhenWorkratesChange() {
+ // Set-up containers and items.
+ MockContainerEntity containerA = newContainer(app, "A", 10, 50);
+ MockContainerEntity containerB = newContainer(app, "B", 10, 50);
+ MockItemEntity item1 = newItem(app, containerA, "1", 0);
+ MockItemEntity item2 = newItem(app, containerA, "2", 0);
+
+ ((EntityLocal)item1).setAttribute(MockItemEntity.TEST_METRIC, 40);
+ ((EntityLocal)item2).setAttribute(MockItemEntity.TEST_METRIC, 40);
+
+ assertWorkratesEventually(
+ ImmutableList.of(containerA, containerB),
+ ImmutableList.of(item1, item2),
+ ImmutableList.of(40d, 40d));
+ }
+
+ // Expect no balancing to occur in hot pool (2 containers over-threshold at 40).
+ // On addition of new container, expect hot containers to offload 10 each.
+ @Test
+ public void testAddContainerWhenHot() {
+ // Set-up containers and items.
+ MockContainerEntity containerA = newContainer(app, "A", 10, 30);
+ MockContainerEntity containerB = newContainer(app, "B", 10, 30);
+ MockItemEntity item1 = newItem(app, containerA, "1", 10);
+ MockItemEntity item2 = newItem(app, containerA, "2", 10);
+ MockItemEntity item3 = newItem(app, containerA, "3", 10);
+ MockItemEntity item4 = newItem(app, containerA, "4", 10);
+ MockItemEntity item5 = newItem(app, containerB, "5", 10);
+ MockItemEntity item6 = newItem(app, containerB, "6", 10);
+ MockItemEntity item7 = newItem(app, containerB, "7", 10);
+ MockItemEntity item8 = newItem(app, containerB, "8", 10);
+ // Both containers are over-threshold at this point; should not rebalance.
+
+ MockContainerEntity containerC = newAsyncContainer(app, "C", 10, 30, CONTAINER_STARTUP_DELAY_MS);
+ // New container allows hot ones to offload work.
+
+ assertWorkratesEventually(
+ ImmutableList.of(containerA, containerB, containerC),
+ ImmutableList.of(item1, item2, item3, item4, item5, item6, item7, item8),
+ ImmutableList.of(30d, 30d, 20d));
+ }
+
+ // On addition of new container, expect no rebalancing to occur as no existing container is hot.
+ @Test
+ public void testAddContainerWhenCold() {
+ // Set-up containers and items.
+ MockContainerEntity containerA = newContainer(app, "A", 10, 50);
+ MockContainerEntity containerB = newContainer(app, "B", 10, 50);
+ MockItemEntity item1 = newItem(app, containerA, "1", 10);
+ MockItemEntity item2 = newItem(app, containerA, "2", 10);
+ MockItemEntity item3 = newItem(app, containerA, "3", 10);
+ MockItemEntity item4 = newItem(app, containerA, "4", 10);
+ MockItemEntity item5 = newItem(app, containerB, "5", 10);
+ MockItemEntity item6 = newItem(app, containerB, "6", 10);
+ MockItemEntity item7 = newItem(app, containerB, "7", 10);
+ MockItemEntity item8 = newItem(app, containerB, "8", 10);
+
+ assertWorkratesEventually(
+ ImmutableList.of(containerA, containerB),
+ ImmutableList.of(item1, item2, item3, item4, item5, item6, item7, item8),
+ ImmutableList.of(40d, 40d));
+
+ MockContainerEntity containerC = newAsyncContainer(app, "C", 10, 50, CONTAINER_STARTUP_DELAY_MS);
+
+ assertWorkratesEventually(
+ ImmutableList.of(containerA, containerB, containerC),
+ ImmutableList.of(item1, item2, item3, item4, item5, item6, item7, item8),
+ ImmutableList.of(40d, 40d, 0d));
+ }
+
+ // Expect no balancing to occur in cool pool (2 containers under-threshold at 30).
+ // On addition of new item, expect over-threshold container (A) to offload 20 to B.
+ @Test
+ public void testAddItem() {
+ // Set-up containers and items.
+ MockContainerEntity containerA = newContainer(app, "A", 10, 50);
+ MockContainerEntity containerB = newContainer(app, "B", 10, 50);
+ MockItemEntity item1 = newItem(app, containerA, "1", 10);
+ MockItemEntity item2 = newItem(app, containerA, "2", 10);
+ MockItemEntity item3 = newItem(app, containerA, "3", 10);
+ MockItemEntity item4 = newItem(app, containerB, "4", 10);
+ MockItemEntity item5 = newItem(app, containerB, "5", 10);
+ MockItemEntity item6 = newItem(app, containerB, "6", 10);
+
+ assertWorkratesEventually(
+ ImmutableList.of(containerA, containerB),
+ ImmutableList.of(item1, item2, item3, item4, item5, item6),
+ ImmutableList.of(30d, 30d));
+
+ newItem(app, containerA, "7", 40);
+
+ assertWorkratesEventually(
+ ImmutableList.of(containerA, containerB),
+ ImmutableList.of(item1, item2, item3, item4, item5, item6),
+ ImmutableList.of(50d, 50d));
+ }
+
+ // FIXME Failed in build repeatedly (e.g. #1035), but couldn't reproduce locally yet with invocationCount=100
+ @Test(groups="WIP")
+ public void testRemoveContainerCausesRebalancing() {
+ // Set-up containers and items.
+ MockContainerEntity containerA = newContainer(app, "A", 10, 30);
+ MockContainerEntity containerB = newContainer(app, "B", 10, 30);
+ MockContainerEntity containerC = newContainer(app, "C", 10, 30);
+ MockItemEntity item1 = newItem(app, containerA, "1", 10);
+ MockItemEntity item2 = newItem(app, containerA, "2", 10);
+ MockItemEntity item3 = newItem(app, containerB, "3", 10);
+ MockItemEntity item4 = newItem(app, containerB, "4", 10);
+ MockItemEntity item5 = newItem(app, containerC, "5", 10);
+ MockItemEntity item6 = newItem(app, containerC, "6", 10);
+
+ Entities.unmanage(containerC);
+ item5.move(containerA);
+ item6.move(containerA);
+
+ assertWorkratesEventually(
+ ImmutableList.of(containerA, containerB),
+ ImmutableList.of(item1, item2, item3, item4, item5, item6),
+ ImmutableList.of(30d, 30d));
+ }
+
+ @Test
+ public void testRemoveItemCausesRebalancing() {
+ // Set-up containers and items.
+ MockContainerEntity containerA = newContainer(app, "A", 10, 30);
+ MockContainerEntity containerB = newContainer(app, "B", 10, 30);
+ MockItemEntity item1 = newItem(app, containerA, "1", 30);
+ MockItemEntity item2 = newItem(app, containerB, "2", 20);
+ MockItemEntity item3 = newItem(app, containerB, "3", 20);
+
+ item1.stop();
+ Entities.unmanage(item1);
+
+ assertWorkratesEventually(
+ ImmutableList.of(containerA, containerB),
+ ImmutableList.of(item1, item2, item3),
+ ImmutableList.of(20d, 20d));
+ }
+
+ @Test
+ public void testRebalancesAfterManualMove() {
+ // Set-up containers and items.
+ MockContainerEntity containerA = newContainer(app, "A", 10, 50);
+ MockContainerEntity containerB = newContainer(app, "B", 10, 50);
+ MockItemEntity item1 = newItem(app, containerA, "1", 20);
+ MockItemEntity item2 = newItem(app, containerA, "2", 20);
+ MockItemEntity item3 = newItem(app, containerB, "3", 20);
+ MockItemEntity item4 = newItem(app, containerB, "4", 20);
+
+ // Move everything onto containerA, and expect it to be automatically re-balanced
+ item3.move(containerA);
+ item4.move(containerA);
+
+ assertWorkratesEventually(
+ ImmutableList.of(containerA, containerB),
+ ImmutableList.of(item1, item2, item3, item4),
+ ImmutableList.of(40d, 40d));
+ }
+
+ @SuppressWarnings({ "unchecked", "rawtypes" })
+ @Test
+ public void testModelIncludesItemsAndContainersStartedBeforePolicyCreated() {
+ pool.removePolicy(policy);
+ policy.destroy();
+
+ // Set-up containers and items.
+ final MockContainerEntity containerA = newContainer(app, "A", 10, 100);
+ newItem(app, containerA, "1", 10);
+
+ policy = new LoadBalancingPolicy(MutableMap.of(), TEST_METRIC, model);
+ pool.addPolicy(policy);
+
+ Asserts.succeedsEventually(MutableMap.of("timeout", TIMEOUT_MS), new Runnable() {
+ public void run() {
+ assertEquals(model.getContainerWorkrates(), ImmutableMap.of(containerA, 10d));
+ }
+ });
+ }
+
+ @Test
+ public void testLockedItemsNotMoved() {
+ // Set-up containers and items.
+ MockContainerEntity containerA = newContainer(app, "A", 10, 50);
+ MockContainerEntity containerB = newContainer(app, "B", 10, 50);
+ MockItemEntity item1 = newLockedItem(app, containerA, "1", 40);
+ MockItemEntity item2 = newLockedItem(app, containerA, "2", 40);
+
+ assertWorkratesContinually(
+ ImmutableList.of(containerA, containerB),
+ ImmutableList.of(item1, item2),
+ ImmutableList.of(80d, 0d));
+ }
+
+ @Test
+ public void testLockedItemsContributeToOverloadedMeasurements() {
+ // Set-up containers and items.
+ MockContainerEntity containerA = newContainer(app, "A", 10, 50);
+ MockContainerEntity containerB = newContainer(app, "B", 10, 50);
+ MockItemEntity item1 = newLockedItem(app, containerA, "1", 40);
+ MockItemEntity item2 = newItem(app, containerA, "2", 25);
+ MockItemEntity item3 = newItem(app, containerA, "3", 25);
+
+ assertWorkratesEventually(
+ ImmutableList.of(containerA, containerB),
+ ImmutableList.of(item1, item2, item3),
+ ImmutableList.of(40d, 50d));
+ }
+
+ @Test
+ public void testOverloadedLockedItemsPreventMoreWorkEnteringContainer() throws Exception {
+ // Set-up containers and items.
+ MockContainerEntity containerA = newContainer(app, "A", 10, 50);
+ MockContainerEntity containerB = newContainer(app, "B", 10, 50);
+ MockItemEntity item1 = newLockedItem(app, containerA, "1", 50);
+ Thread.sleep(1); // increase chances of item1's workrate having been received first
+ MockItemEntity item2 = newItem(app, containerB, "2", 30);
+ MockItemEntity item3 = newItem(app, containerB, "3", 30);
+
+ assertWorkratesContinually(
+ ImmutableList.of(containerA, containerB),
+ ImmutableList.of(item1, item2, item3),
+ ImmutableList.of(50d, 60d));
+ }
+
+ @Test
+ public void testPolicyUpdatesModel() {
+ final MockContainerEntity containerA = newContainer(app, "A", 10, 20);
+ final MockContainerEntity containerB = newContainer(app, "B", 11, 21);
+ final MockItemEntity item1 = newItem(app, containerA, "1", 12);
+ final MockItemEntity item2 = newItem(app, containerB, "2", 13);
+
+ Asserts.succeedsEventually(MutableMap.of("timeout", TIMEOUT_MS), new Runnable() {
+ public void run() {
+ assertEquals(model.getPoolSize(), 2);
+ assertEquals(model.getPoolContents(), ImmutableSet.of(containerA, containerB));
+ assertEquals(model.getItemWorkrate(item1), 12d);
+ assertEquals(model.getItemWorkrate(item2), 13d);
+
+ assertEquals(model.getParentContainer(item1), containerA);
+ assertEquals(model.getParentContainer(item2), containerB);
+ assertEquals(model.getContainerWorkrates(), ImmutableMap.of(containerA, 12d, containerB, 13d));
+
+ assertEquals(model.getPoolLowThreshold(), 10+11d);
+ assertEquals(model.getPoolHighThreshold(), 20+21d);
+ assertEquals(model.getCurrentPoolWorkrate(), 12+13d);
+ assertFalse(model.isHot());
+ assertFalse(model.isCold());
+ }
+ });
+ }
+}
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/d30ff597/policy/src/test/java/org/apache/brooklyn/policy/loadbalancing/MockContainerEntity.java
----------------------------------------------------------------------
diff --git a/policy/src/test/java/org/apache/brooklyn/policy/loadbalancing/MockContainerEntity.java b/policy/src/test/java/org/apache/brooklyn/policy/loadbalancing/MockContainerEntity.java
new file mode 100644
index 0000000..1937d9a
--- /dev/null
+++ b/policy/src/test/java/org/apache/brooklyn/policy/loadbalancing/MockContainerEntity.java
@@ -0,0 +1,61 @@
+/*
+ * 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.policy.loadbalancing;
+
+import java.util.Map;
+
+import org.apache.brooklyn.api.entity.Effector;
+import org.apache.brooklyn.api.entity.Entity;
+import org.apache.brooklyn.api.entity.proxying.ImplementedBy;
+import org.apache.brooklyn.core.util.flags.SetFromFlag;
+
+import brooklyn.config.ConfigKey;
+import brooklyn.entity.annotation.EffectorParam;
+import brooklyn.entity.basic.AbstractGroup;
+import brooklyn.entity.basic.MethodEffector;
+import brooklyn.entity.trait.Startable;
+import brooklyn.event.basic.BasicConfigKey;
+
+@ImplementedBy(MockContainerEntityImpl.class)
+public interface MockContainerEntity extends AbstractGroup, BalanceableContainer<Movable>, Startable {
+
+ @SetFromFlag("membership")
+ public static final ConfigKey<String> MOCK_MEMBERSHIP = new BasicConfigKey<String>(
+ String.class, "mock.container.membership", "For testing ItemsInContainersGroup");
+
+ @SetFromFlag("delay")
+ public static final ConfigKey<Long> DELAY = new BasicConfigKey<Long>(
+ Long.class, "mock.container.delay", "", 0L);
+
+ public static final Effector<Void> OFFLOAD_AND_STOP = new MethodEffector<Void>(MockContainerEntity.class, "offloadAndStop");
+
+ public void lock();
+
+ public void unlock();
+
+ public int getWorkrate();
+
+ public Map<Entity, Double> getItemUsage();
+
+ public void addItem(Entity item);
+
+ public void removeItem(Entity item);
+
+ public void offloadAndStop(@EffectorParam(name="otherContianer") MockContainerEntity otherContainer);
+}
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/d30ff597/policy/src/test/java/org/apache/brooklyn/policy/loadbalancing/MockContainerEntityImpl.java
----------------------------------------------------------------------
diff --git a/policy/src/test/java/org/apache/brooklyn/policy/loadbalancing/MockContainerEntityImpl.java b/policy/src/test/java/org/apache/brooklyn/policy/loadbalancing/MockContainerEntityImpl.java
new file mode 100644
index 0000000..de9da37
--- /dev/null
+++ b/policy/src/test/java/org/apache/brooklyn/policy/loadbalancing/MockContainerEntityImpl.java
@@ -0,0 +1,208 @@
+/*
+ * 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.policy.loadbalancing;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.locks.ReentrantLock;
+
+import org.apache.brooklyn.api.entity.Entity;
+import org.apache.brooklyn.api.event.AttributeSensor;
+import org.apache.brooklyn.api.location.Location;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import brooklyn.entity.basic.AbstractGroupImpl;
+import brooklyn.entity.basic.Attributes;
+import brooklyn.util.collections.MutableList;
+import brooklyn.util.time.Time;
+
+import com.google.common.base.Predicates;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+import com.google.common.collect.Sets;
+
+
+public class MockContainerEntityImpl extends AbstractGroupImpl implements MockContainerEntity {
+
+ private static final Logger LOG = LoggerFactory.getLogger(MockContainerEntity.class);
+
+ volatile boolean offloading;
+ volatile boolean running;
+
+ ReentrantLock _lock = new ReentrantLock();
+
+ @Override
+ public <T> T setAttribute(AttributeSensor<T> attribute, T val) {
+ if (LOG.isDebugEnabled()) LOG.debug("Mocks: container {} setting {} to {}", new Object[] {this, attribute, val});
+ return super.setAttribute(attribute, val);
+ }
+
+ @Override
+ public void lock() {
+ _lock.lock();
+ if (!running) {
+ _lock.unlock();
+ throw new IllegalStateException("Container lock "+this+"; it is not running");
+ }
+ }
+
+ @Override
+ public void unlock() {
+ _lock.unlock();
+ }
+
+ @Override
+ public int getWorkrate() {
+ int result = 0;
+ for (Entity member : getMembers()) {
+ Integer memberMetric = member.getAttribute(MockItemEntity.TEST_METRIC);
+ result += ((memberMetric != null) ? memberMetric : 0);
+ }
+ return result;
+ }
+
+ @Override
+ public Map<Entity, Double> getItemUsage() {
+ Map<Entity, Double> result = Maps.newLinkedHashMap();
+ for (Entity member : getMembers()) {
+ Map<Entity, Double> memberItemUsage = member.getAttribute(MockItemEntity.ITEM_USAGE_METRIC);
+ if (memberItemUsage != null) {
+ for (Map.Entry<Entity, Double> entry : memberItemUsage.entrySet()) {
+ double val = (result.containsKey(entry.getKey()) ? result.get(entry.getKey()) : 0d);
+ val += ((entry.getValue() != null) ? entry.getValue() : 0);
+ result.put(entry.getKey(), val);
+ }
+ }
+ }
+ return result;
+ }
+
+ @Override
+ public void addItem(Entity item) {
+ if (LOG.isDebugEnabled()) LOG.debug("Mocks: adding item {} to container {}", item, this);
+ if (!running || offloading) throw new IllegalStateException("Container "+getDisplayName()+" is not running; cannot add item "+item);
+ addMember(item);
+ emit(ITEM_ADDED, item);
+ }
+
+ @Override
+ public void removeItem(Entity item) {
+ if (LOG.isDebugEnabled()) LOG.debug("Mocks: removing item {} from container {}", item, this);
+ if (!running) throw new IllegalStateException("Container "+getDisplayName()+" is not running; cannot remove item "+item);
+ removeMember(item);
+ emit(ITEM_REMOVED, item);
+ }
+
+ @SuppressWarnings({ "unchecked", "rawtypes" })
+ @Override
+ public Set<Movable> getBalanceableItems() {
+ return (Set) Sets.newLinkedHashSet(getMembers());
+ }
+
+ public String toString() {
+ return "MockContainer["+getDisplayName()+"]";
+ }
+
+ private long getDelay() {
+ return getConfig(DELAY);
+ }
+
+ @Override
+ public void start(Collection<? extends Location> locs) {
+ if (LOG.isDebugEnabled()) LOG.debug("Mocks: starting container {}", this);
+ _lock.lock();
+ try {
+ Time.sleep(getDelay());
+ running = true;
+ addLocations(locs);
+ emit(Attributes.LOCATION_CHANGED, null);
+ setAttribute(SERVICE_UP, true);
+ } finally {
+ _lock.unlock();
+ }
+ }
+
+ @Override
+ public void stop() {
+ if (LOG.isDebugEnabled()) LOG.debug("Mocks: stopping container {}", this);
+ _lock.lock();
+ try {
+ running = false;
+ Time.sleep(getDelay());
+ setAttribute(SERVICE_UP, false);
+ } finally {
+ _lock.unlock();
+ }
+ }
+
+ private void stopWithoutLock() {
+ running = false;
+ Time.sleep(getDelay());
+ setAttribute(SERVICE_UP, false);
+ }
+
+ public void offloadAndStop(final MockContainerEntity otherContainer) {
+ if (LOG.isDebugEnabled()) LOG.debug("Mocks: offloading container {} to {} (items {})", new Object[] {this, otherContainer, getBalanceableItems()});
+ runWithLock(ImmutableList.of(this, otherContainer), new Runnable() {
+ public void run() {
+ offloading = false;
+ for (Movable item : getBalanceableItems()) {
+ ((MockItemEntity)item).moveNonEffector(otherContainer);
+ }
+ if (LOG.isDebugEnabled()) LOG.debug("Mocks: stopping offloaded container {}", this);
+ stopWithoutLock();
+ }});
+ }
+
+ @Override
+ public void restart() {
+ if (LOG.isDebugEnabled()) LOG.debug("Mocks: restarting {}", this);
+ throw new UnsupportedOperationException();
+ }
+
+ public static void runWithLock(List<MockContainerEntity> entitiesToLock, Runnable r) {
+ List<MockContainerEntity> entitiesToLockCopy = MutableList.copyOf(Iterables.filter(entitiesToLock, Predicates.notNull()));
+ List<MockContainerEntity> entitiesLocked = Lists.newArrayList();
+ Collections.sort(entitiesToLockCopy, new Comparator<MockContainerEntity>() {
+ public int compare(MockContainerEntity o1, MockContainerEntity o2) {
+ return o1.getId().compareTo(o2.getId());
+ }});
+
+ try {
+ for (MockContainerEntity it : entitiesToLockCopy) {
+ it.lock();
+ entitiesLocked.add(it);
+ }
+
+ r.run();
+
+ } finally {
+ for (MockContainerEntity it : entitiesLocked) {
+ it.unlock();
+ }
+ }
+ }
+}
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/d30ff597/policy/src/test/java/org/apache/brooklyn/policy/loadbalancing/MockItemEntity.java
----------------------------------------------------------------------
diff --git a/policy/src/test/java/org/apache/brooklyn/policy/loadbalancing/MockItemEntity.java b/policy/src/test/java/org/apache/brooklyn/policy/loadbalancing/MockItemEntity.java
new file mode 100644
index 0000000..83a54c0
--- /dev/null
+++ b/policy/src/test/java/org/apache/brooklyn/policy/loadbalancing/MockItemEntity.java
@@ -0,0 +1,46 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.brooklyn.policy.loadbalancing;
+
+import java.util.Map;
+
+import org.apache.brooklyn.api.entity.Entity;
+import org.apache.brooklyn.api.entity.proxying.ImplementedBy;
+import org.apache.brooklyn.api.event.AttributeSensor;
+
+import brooklyn.event.basic.Sensors;
+
+import com.google.common.reflect.TypeToken;
+
+@ImplementedBy(MockItemEntityImpl.class)
+public interface MockItemEntity extends Entity, Movable {
+
+ public static final AttributeSensor<Integer> TEST_METRIC = Sensors.newIntegerSensor(
+ "test.metric", "Dummy workrate for test entities");
+
+ @SuppressWarnings("serial")
+ public static final AttributeSensor<Map<Entity, Double>> ITEM_USAGE_METRIC = Sensors.newSensor(
+ new TypeToken<Map<Entity, Double>>() {}, "test.itemUsage.metric", "Dummy item usage for test entities");
+
+ public boolean isStopped();
+
+ public void moveNonEffector(Entity rawDestination);
+
+ public void stop();
+}
[17/24] incubator-brooklyn git commit: [BROOKLYN-162] Renaming
package policy
Posted by he...@apache.org.
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/d30ff597/policy/src/main/java/brooklyn/policy/loadbalancing/BalancingStrategy.java
----------------------------------------------------------------------
diff --git a/policy/src/main/java/brooklyn/policy/loadbalancing/BalancingStrategy.java b/policy/src/main/java/brooklyn/policy/loadbalancing/BalancingStrategy.java
deleted file mode 100644
index 714b70c..0000000
--- a/policy/src/main/java/brooklyn/policy/loadbalancing/BalancingStrategy.java
+++ /dev/null
@@ -1,622 +0,0 @@
-/*
- * 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 brooklyn.policy.loadbalancing;
-
-import java.text.MessageFormat;
-import java.util.Collection;
-import java.util.LinkedHashSet;
-import java.util.Map;
-import java.util.Map.Entry;
-import java.util.Set;
-
-import org.apache.brooklyn.api.entity.Entity;
-import org.apache.brooklyn.api.location.Location;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-/**
- * Represents an abstract algorithm for optimally balancing worker "items" among several "containers" based on the workloads
- * of the items, and corresponding high- and low-thresholds on the containers.
- *
- * TODO: extract interface, provide default implementation
- * TODO: remove legacy code comments
- */
-public class BalancingStrategy<NodeType extends Entity, ItemType extends Movable> {
-
- // FIXME Bad idea to use MessageFormat.format in this way; if toString of entity contains
- // special characters interpreted by MessageFormat, then it will all break!
-
- // This is a modified version of the watermark elasticity policy from Monterey v3.
-
- private static final Logger LOG = LoggerFactory.getLogger(BalancingStrategy.class);
-
- private static final int MAX_MIGRATIONS_PER_BALANCING_NODE = 20; // arbitrary (Splodge)
- private static final boolean BALANCE_COLD_PULLS_IN_SAME_RUN_AS_HOT_PUSHES = false;
-
- private final String name;
- private final BalanceablePoolModel<NodeType, ItemType> model;
- private final PolicyUtilForPool<NodeType, ItemType> helper;
-// private boolean loggedColdestTooHigh = false;
-// private boolean loggedHottestTooLow = false;
-
-
- public BalancingStrategy(String name, BalanceablePoolModel<NodeType, ItemType> model) {
- this.name = name;
- this.model = model;
- this.helper = new PolicyUtilForPool<NodeType, ItemType>(model);
- }
-
- public String getName() {
- return name;
- }
-
- public void rebalance() {
- checkAndApplyOn(model.getPoolContents());
- }
-
- public int getMaxMigrationsPerBalancingNode() {
- return MAX_MIGRATIONS_PER_BALANCING_NODE;
- }
-
- public BalanceablePoolModel<NodeType, ItemType> getDataProvider() {
- return model;
- }
-
- // This was the entry point for the legacy policy.
- private void checkAndApplyOn(final Collection<NodeType> dirtyNodesSupplied) {
- Collection<NodeType> dirtyNodes = dirtyNodesSupplied;
-
-// if (startTime + FORCE_ALL_NODES_IF_DELAYED_FOR_MILLIS < System.currentTimeMillis()) {
-// Set<NodeType> allNodes = new LinkedHashSet<NodeType>();
-// allNodes.addAll(dirtyNodes);
-// dirtyNodes = allNodes;
-// allNodes.addAll(getDataProvider().getPoolContents());
-// if (LOG.isDebugEnabled())
-// LOG.debug("policy "+getDataProvider().getAbbr()+" running after delay ("+
-// TimeUtils.makeTimeString(System.currentTimeMillis() - startTime)+", analysing all nodes: "+
-// dirtyNodes);
-// }
-
-// nodeFinder.optionalCachedNodesWithBacklogDetected.clear();
-// boolean gonnaGrow = growPool(dirtyNodes);
-// getDataProvider().waitForAllTransitionsComplete();
- boolean gonnaGrow = false;
-
- Set<NodeType> nonFrozenDirtyNodes = new LinkedHashSet<NodeType>(dirtyNodes);
-// boolean gonnaShrink = false;
-// if (!gonnaGrow && !DONT_SHRINK_UNLESS_BALANCED) {
-// gonnaShrink = shrinkPool(nonFrozenDirtyNodes);
-// getDataProvider().waitForAllTransitionsComplete();
-// }
-
- if (getDataProvider().getPoolSize() >= 2) {
- boolean didBalancing = false;
- for (NodeType a : nonFrozenDirtyNodes) {
- didBalancing |= balanceItemsOnNodesInQuestion(a, gonnaGrow);
-// getMutator().waitForAllTransitionsComplete();
- }
- if (didBalancing) {
- return;
- }
- }
-
-// if (!gonnaGrow && DONT_SHRINK_UNLESS_BALANCED) {
-// gonnaShrink = shrinkPool(nonFrozenDirtyNodes);
-// getDataProvider().waitForAllTransitionsComplete();
-// }
-
-// if (gonnaGrow || gonnaShrink)
-// //don't log 'doing nothing' message
-// return;
-
-// if (LOG.isDebugEnabled()) {
-// double poolTotal = getDataProvider().getPoolPredictedWorkrateTotal();
-// int poolSize = getDataProvider().getPoolPredictedSize();
-// LOG.debug(MessageFormat.format("policy "+getDataProvider().getAbbr()+" did nothing; pool workrate is {0,number,#.##} x {1} nodes",
-// 1.0*poolTotal/poolSize, poolSize));
-// }
- }
-
- protected boolean balanceItemsOnNodesInQuestion(NodeType questionedNode, boolean gonnaGrow) {
- double questionedNodeTotalWorkrate = getDataProvider().getTotalWorkrate(questionedNode);
-
- boolean balanced = balanceItemsOnHotNode(questionedNode, questionedNodeTotalWorkrate, gonnaGrow);
-// getMutator().waitForAllTransitionsComplete();
-
- if (!balanced || BALANCE_COLD_PULLS_IN_SAME_RUN_AS_HOT_PUSHES) {
- balanced |= balanceItemsOnColdNode(questionedNode, questionedNodeTotalWorkrate, gonnaGrow);
-// getMutator().waitForAllTransitionsComplete();
- }
- if (balanced)
- return true;
-
- if (LOG.isDebugEnabled()) {
- LOG.debug( MessageFormat.format(
- "policy "+getDataProvider().getName()+" not balancing "+questionedNode+"; " +
- "its workrate {0,number,#.##} is acceptable (or cannot be balanced)", questionedNodeTotalWorkrate) );
- }
- return false;
- }
-
- protected boolean balanceItemsOnHotNode(NodeType node, double nodeWorkrate, boolean gonnaGrow) {
- double originalNodeWorkrate = nodeWorkrate;
- int migrationCount = 0;
- int iterationCount = 0;
-
- Set<ItemType> itemsMoved = new LinkedHashSet<ItemType>();
- Set<NodeType> nodesChecked = new LinkedHashSet<NodeType>();
-
-// if (nodeFinder.config.COUNT_BACKLOG_AS_EXTRA_WORKRATE) {
-// int backlog = nodeFinder.getBacklogQueueLength(questionedNode);
-// if (backlog>0) {
-// Level l = backlog>1000 ? Level.INFO : backlog>10 ? Level.FINE : Level.FINER;
-// if (LOG.isLoggable(l)) {
-// LOG.log( l, MessageFormat.format(
-// "policy "+getDataProvider().getAbbr()+" detected queue at node "+questionedNode+", " +
-// "inflating workrate {0,number,#.##} + "+backlog, questionedNodeTotalWorkrate) );
-// }
-// questionedNodeTotalWorkrate += backlog;
-// }
-// }
-
- Double highThreshold = model.getHighThreshold(node);
- if (highThreshold == -1) {
- // node presumably has been removed; TODO log
- return false;
- }
-
- while (nodeWorkrate > highThreshold && migrationCount < getMaxMigrationsPerBalancingNode()) {
- iterationCount++;
-
- if (LOG.isDebugEnabled()) {
- LOG.debug(MessageFormat.format(
- "policy "+getDataProvider().getName()+" considering balancing hot node "+node+" " +
- "(workrate {0,number,#.##}); iteration "+iterationCount, nodeWorkrate) );
- }
-
- // move from hot node, to coldest
-
- NodeType coldNode = helper.findColdestContainer(nodesChecked);
-
- if (coldNode == null) {
- if (LOG.isDebugEnabled()) {
- LOG.debug( MessageFormat.format(
- "policy "+getDataProvider().getName()+" not balancing hot node "+node+" " +
- "(workrate {0,number,#.##}); no coldest node available", nodeWorkrate) );
- }
- break;
- }
-
- if (coldNode.equals(node)) {
- if (LOG.isDebugEnabled()) {
- LOG.debug( MessageFormat.format(
- "policy "+getDataProvider().getName()+" not balancing hot node "+node+" " +
- "(workrate {0,number,#.##}); it is also the coldest modifiable node", nodeWorkrate) );
- }
- break;
- }
-
- double coldNodeWorkrate = getDataProvider().getTotalWorkrate(coldNode);
- boolean emergencyLoadBalancing = coldNodeWorkrate < nodeWorkrate*2/3;
- double coldNodeHighThreshold = model.getHighThreshold(coldNode);
- if (coldNodeWorkrate >= coldNodeHighThreshold && !emergencyLoadBalancing) {
- //don't balance if all nodes are approx equally hot (and very hot)
-
- //for now, stop warning if it is a recurring theme!
-// Level level = loggedColdestTooHigh ? Level.FINER : Level.INFO;
-// LOG.log(level, MessageFormat.format(
-// "policy "+getDataProvider().getAbbr()+" not balancing hot node "+questionedNode+" " +
-// "(workrate {0,number,#.##}); coldest node "+coldNode+" has workrate {1,number,#.##} also too high"+
-// (loggedColdestTooHigh ? "" : " (future cases will be logged at finer)"),
-// questionedNodeTotalWorkrate, coldNodeWorkrate) );
-// loggedColdestTooHigh = true;
- break;
- }
- double poolLowWatermark = Double.MAX_VALUE; // TODO
- if (gonnaGrow && (coldNodeWorkrate >= poolLowWatermark && !emergencyLoadBalancing)) {
- //if we're growing the pool, refuse to balance unless the cold node is indeed very cold, or hot node very hot
-
- //for now, stop warning if it is a recurring theme!
-// Level level = loggedColdestTooHigh ? Level.FINER : Level.INFO;
-// LOG.log(level, MessageFormat.format(
-// "policy "+getDataProvider().getAbbr()+" not balancing hot node "+questionedNode+" " +
-// "(workrate {0,number,#.##}); coldest node "+coldNode+" has workrate {1,number,#.##} also too high to accept while pool is growing" +
-// (loggedColdestTooHigh ? "" : " (future cases will be logged at finer)"),
-// questionedNodeTotalWorkrate, coldNodeWorkrate) );
-// loggedColdestTooHigh = true;
- break;
- }
-
- String questionedNodeName = getDataProvider().getName(node);
- String coldNodeName = getDataProvider().getName(coldNode);
- Location coldNodeLocation = getDataProvider().getLocation(coldNode);
-
- if (LOG.isDebugEnabled()) {
- LOG.debug( MessageFormat.format(
- "policy "+getDataProvider().getName()+" balancing hot node "+questionedNodeName+" " +
- "("+node+", workrate {0,number,#.##}), " +
- "considering target "+coldNodeName+" ("+coldNode+", workrate {1,number,#.##})",
- nodeWorkrate, coldNodeWorkrate) );
- }
-
- double idealSizeToMove = (nodeWorkrate - coldNodeWorkrate) / 2;
- //if the 'ideal' amount to move would cause cold to be too hot, then reduce ideal amount
-
- if (idealSizeToMove + coldNodeWorkrate > coldNodeHighThreshold)
- idealSizeToMove = coldNodeHighThreshold - coldNodeWorkrate;
-
-
- double maxSizeToMoveIdeally = Math.min(
- nodeWorkrate/2 + 0.00001,
- //permit it to exceed node high if there is no alternative (this is 'max' not 'ideal'),
- //so long as it still gives a significant benefit
- // getConfiguration().nodeHighWaterMark - coldNodeWorkrate,
- (nodeWorkrate - coldNodeWorkrate)*0.9);
- double maxSizeToMoveIfNoSmallButLarger = nodeWorkrate*3/4;
-
- Map<ItemType, Double> questionedNodeItems = getDataProvider().getItemWorkrates(node);
- if (questionedNodeItems == null) {
- if (LOG.isDebugEnabled())
- LOG.debug(MessageFormat.format(
- "policy "+getDataProvider().getName()+" balancing hot node "+questionedNodeName+" " +
- "("+node+", workrate {0,number,#.##}), abandoned; " +
- "item report for " + questionedNodeName + " unavailable",
- nodeWorkrate));
- break;
- }
- ItemType itemToMove = findBestItemToMove(questionedNodeItems, idealSizeToMove, maxSizeToMoveIdeally,
- maxSizeToMoveIfNoSmallButLarger, itemsMoved, coldNodeLocation);
-
- if (itemToMove == null) {
- if (LOG.isDebugEnabled())
- LOG.debug(MessageFormat.format(
- "policy "+getDataProvider().getName()+" balancing hot node "+questionedNodeName+" " +
- "("+node+", workrate {0,number,#.##}), ending; " +
- "no suitable segment found " +
- "(ideal transition item size {1,number,#.##}, max {2,number,#.##}, " +
- "moving to coldest node "+coldNodeName+" ("+coldNode+", workrate {3,number,#.##}); available items: {4}",
- nodeWorkrate, idealSizeToMove, maxSizeToMoveIdeally, coldNodeWorkrate, questionedNodeItems) );
- break;
- }
-
- itemsMoved.add(itemToMove);
- double itemWorkrate = questionedNodeItems.get(itemToMove);
-
-// if (LOG.isLoggable(Level.FINE))
-// LOG.fine( MessageFormat.format(
-// "policy "+getDataProvider().getAbbr()+" balancing hot node "+questionedNodeName+" " +
-// "(workrate {0,number,#.##}, too high), transitioning " + itemToMove +
-// " to "+coldNodeName+" (workrate {1,number,#.##}, now += {2,number,#.##})",
-// questionedNodeTotalWorkrate, coldNodeWorkrate, segmentRate) );
-
- nodeWorkrate -= itemWorkrate;
- coldNodeWorkrate += itemWorkrate;
-
- moveItem(itemToMove, node, coldNode);
- ++migrationCount;
- }
-
- if (LOG.isDebugEnabled()) {
- if (iterationCount == 0) {
- if (LOG.isTraceEnabled())
- LOG.trace( MessageFormat.format(
- "policy "+getDataProvider().getName()+" balancing if hot finished at node "+node+"; " +
- "workrate {0,number,#.##} not hot",
- originalNodeWorkrate) );
- }
- else if (itemsMoved.isEmpty()) {
- if (LOG.isTraceEnabled())
- LOG.trace( MessageFormat.format(
- "policy "+getDataProvider().getName()+" balancing finished at hot node "+node+" " +
- "(workrate {0,number,#.##}); no way to improve it",
- originalNodeWorkrate) );
- } else {
- LOG.debug( MessageFormat.format(
- "policy "+getDataProvider().getName()+" balancing finished at hot node "+node+"; " +
- "workrate from {0,number,#.##} to {1,number,#.##} (report now says {2,number,#.##}) " +
- "by moving off {3}",
- originalNodeWorkrate,
- nodeWorkrate,
- getDataProvider().getTotalWorkrate(node),
- itemsMoved
- ) );
- }
- }
- return !itemsMoved.isEmpty();
- }
-
- protected boolean balanceItemsOnColdNode(NodeType questionedNode, double questionedNodeTotalWorkrate, boolean gonnaGrow) {
- // Abort if the node has pending adjustments.
- Map<ItemType, Double> items = getDataProvider().getItemWorkrates(questionedNode);
- if (items == null) {
- if (LOG.isDebugEnabled()) {
- LOG.debug( MessageFormat.format(
- "policy "+getDataProvider().getName()+" not balancing cold node "+questionedNode+" " +
- "(workrate {0,number,#.##}); workrate breakdown unavailable (probably reverting)",
- questionedNodeTotalWorkrate) );
- }
- return false;
- }
- for (ItemType item : items.keySet()) {
- if (!model.isItemMoveable(item)) {
- if (LOG.isDebugEnabled()) {
- LOG.debug( MessageFormat.format(
- "policy "+getDataProvider().getName()+" not balancing cold node "+questionedNode+" " +
- "(workrate {0,number,#.##}); at least one item ("+item+") is in flux",
- questionedNodeTotalWorkrate) );
- }
- return false;
- }
- }
-
- double originalQuestionedNodeTotalWorkrate = questionedNodeTotalWorkrate;
- int numMigrations = 0;
-
- Set<ItemType> itemsMoved = new LinkedHashSet<ItemType>();
- Set<NodeType> nodesChecked = new LinkedHashSet<NodeType>();
-
- int iters = 0;
- Location questionedLocation = getDataProvider().getLocation(questionedNode);
-
- double lowThreshold = model.getLowThreshold(questionedNode);
- while (questionedNodeTotalWorkrate < lowThreshold) {
- iters++;
-
- if (LOG.isDebugEnabled()) {
- LOG.debug( MessageFormat.format(
- "policy "+getDataProvider().getName()+" considering balancing cold node "+questionedNode+" " +
- "(workrate {0,number,#.##}); iteration "+iters, questionedNodeTotalWorkrate));
- }
-
- // move from cold node, to hottest
-
- NodeType hotNode = helper.findHottestContainer(nodesChecked);
-
- if (hotNode == null) {
- if (LOG.isDebugEnabled()) {
- LOG.debug( MessageFormat.format(
- "policy "+getDataProvider().getName()+" not balancing cold node "+questionedNode+" " +
- "(workrate {0,number,#.##}); no hottest node available", questionedNodeTotalWorkrate) );
- }
-
- break;
- }
- if (hotNode.equals(questionedNode)) {
- if (LOG.isDebugEnabled()) {
- LOG.debug( MessageFormat.format(
- "policy "+getDataProvider().getName()+" not balancing cold node "+questionedNode+" " +
- "(workrate {0,number,#.##}); it is also the hottest modfiable node", questionedNodeTotalWorkrate) );
- }
- break;
- }
-
-
- double hotNodeWorkrate = getDataProvider().getTotalWorkrate(hotNode);
- double hotNodeLowThreshold = model.getLowThreshold(hotNode);
- double hotNodeHighThreshold = model.getHighThreshold(hotNode);
- boolean emergencyLoadBalancing = false; //doesn't apply to cold
- if (hotNodeWorkrate == -1 || hotNodeLowThreshold == -1 || hotNodeHighThreshold == -1) {
- // hotNode presumably has been removed; TODO log
- break;
- }
- if (hotNodeWorkrate <= hotNodeLowThreshold && !emergencyLoadBalancing) {
- //don't balance if all nodes are too low
-
- //for now, stop warning if it is a recurring theme!
-// Level level = loggedHottestTooLow ? Level.FINER : Level.INFO;
-// LOG.log(level, MessageFormat.format(
-// "policy "+getDataProvider().getAbbr()+" not balancing cold node "+questionedNode+" " +
-// "(workrate {0,number,#.##}); hottest node "+hotNode+" has workrate {1,number,#.##} also too low" +
-// (loggedHottestTooLow ? "" : " (future cases will be logged at finer)"),
-// questionedNodeTotalWorkrate, hotNodeWorkrate) );
-// loggedHottestTooLow = true;
- break;
- }
- if (gonnaGrow && (hotNodeWorkrate <= hotNodeHighThreshold && !emergencyLoadBalancing)) {
- //if we're growing the pool, refuse to balance unless the hot node is quite hot
-
- //again, stop warning if it is a recurring theme!
-// Level level = loggedHottestTooLow ? Level.FINER : Level.INFO;
-// LOG.log(level, MessageFormat.format(
-// "policy "+getDataProvider().getAbbr()+" not balancing cold node "+questionedNode+" " +
-// "(workrate {0,number,#.##}); hottest node "+hotNode+" has workrate {1,number,#.##} also too low to accept while pool is growing"+
-// (loggedHottestTooLow ? "" : " (future cases will be logged at finer)"),
-// questionedNodeTotalWorkrate, hotNodeWorkrate) );
-// loggedHottestTooLow = true;
- break;
- }
-
- String questionedNodeName = getDataProvider().getName(questionedNode);
- String hotNodeName = getDataProvider().getName(hotNode);
-
- if (LOG.isDebugEnabled()) {
- LOG.debug( MessageFormat.format(
- "policy "+getDataProvider().getName()+" balancing cold node "+questionedNodeName+" " +
- "("+questionedNode+", workrate {0,number,#.##}), " +
- "considering source "+hotNodeName+" ("+hotNode+", workrate {1,number,#.##})",
- questionedNodeTotalWorkrate, hotNodeWorkrate) );
- }
-
- double idealSizeToMove = (hotNodeWorkrate - questionedNodeTotalWorkrate) / 2;
- //if the 'ideal' amount to move would cause cold to be too hot, then reduce ideal amount
- double targetNodeHighThreshold = model.getHighThreshold(questionedNode);
- if (idealSizeToMove + questionedNodeTotalWorkrate > targetNodeHighThreshold)
- idealSizeToMove = targetNodeHighThreshold - questionedNodeTotalWorkrate;
- double maxSizeToMoveIdeally = Math.min(
- hotNodeWorkrate/2,
- //allow to swap order, but not very much (0.9 was allowed when balancing high)
- (hotNodeWorkrate - questionedNodeTotalWorkrate)*0.6);
- double maxSizeToMoveIfNoSmallButLarger = questionedNodeTotalWorkrate*3/4;
-
- Map<ItemType, Double> hotNodeItems = getDataProvider().getItemWorkrates(hotNode);
- if (hotNodeItems == null) {
- if (LOG.isDebugEnabled())
- LOG.debug(MessageFormat.format(
- "policy "+getDataProvider().getName()+" balancing cold node "+questionedNodeName+" " +
- "("+questionedNode+", workrate {0,number,#.##}), " +
- "excluding hot node "+hotNodeName+" because its item report unavailable",
- questionedNodeTotalWorkrate));
- nodesChecked.add(hotNode);
- continue;
- }
-
- ItemType itemToMove = findBestItemToMove(hotNodeItems, idealSizeToMove, maxSizeToMoveIdeally,
- maxSizeToMoveIfNoSmallButLarger, itemsMoved, questionedLocation);
- if (itemToMove == null) {
- if (LOG.isDebugEnabled())
- LOG.debug(MessageFormat.format(
- "policy "+getDataProvider().getName()+" balancing cold node "+questionedNodeName+" " +
- "("+questionedNode+", workrate {0,number,#.##}), " +
- "excluding hot node "+hotNodeName+" because it has no appilcable items " +
- "(ideal transition item size {1,number,#.##}, max {2,number,#.##}, " +
- "moving from hot node "+hotNodeName+" ("+hotNode+", workrate {3,number,#.##}); available items: {4}",
- questionedNodeTotalWorkrate, idealSizeToMove, maxSizeToMoveIdeally, hotNodeWorkrate, hotNodeItems) );
-
- nodesChecked.add(hotNode);
- continue;
- }
-
- itemsMoved.add(itemToMove);
- double segmentRate = hotNodeItems.get(itemToMove);
-
-// if (LOG.isLoggable(Level.FINE))
-// LOG.fine( MessageFormat.format(
-// "policy "+getDataProvider().getAbbr()+" balancing cold node "+questionedNodeName+" " +
-// "(workrate {0,number,#.##}, too low), transitioning " + itemToMove +
-// " from "+hotNodeName+" (workrate {1,number,#.##}, now -= {2,number,#.##})",
-// questionedNodeTotalWorkrate, hotNodeWorkrate, segmentRate) );
-
- questionedNodeTotalWorkrate += segmentRate;
- hotNodeWorkrate -= segmentRate;
-
- moveItem(itemToMove, hotNode, questionedNode);
-
- if (++numMigrations >= getMaxMigrationsPerBalancingNode()) {
- break;
- }
- }
-
- if (LOG.isDebugEnabled()) {
- if (iters == 0) {
- if (LOG.isTraceEnabled())
- LOG.trace( MessageFormat.format(
- "policy "+getDataProvider().getName()+" balancing if cold finished at node "+questionedNode+"; " +
- "workrate {0,number,#.##} not cold",
- originalQuestionedNodeTotalWorkrate) );
- }
- else if (itemsMoved.isEmpty()) {
- if (LOG.isTraceEnabled())
- LOG.trace( MessageFormat.format(
- "policy "+getDataProvider().getName()+" balancing finished at cold node "+questionedNode+" " +
- "(workrate {0,number,#.##}); no way to improve it",
- originalQuestionedNodeTotalWorkrate) );
- } else {
- LOG.debug( MessageFormat.format(
- "policy "+getDataProvider().getName()+" balancing finished at cold node "+questionedNode+"; " +
- "workrate from {0,number,#.##} to {1,number,#.##} (report now says {2,number,#.##}) " +
- "by moving in {3}",
- originalQuestionedNodeTotalWorkrate,
- questionedNodeTotalWorkrate,
- getDataProvider().getTotalWorkrate(questionedNode),
- itemsMoved) );
- }
- }
- return !itemsMoved.isEmpty();
- }
-
- protected void moveItem(ItemType item, NodeType oldNode, NodeType newNode) {
- item.move(newNode);
- model.onItemMoved(item, newNode);
- }
-
- /**
- * "Best" is defined as nearest to the targetCost, without exceeding maxCost, unless maxCostIfNothingSmallerButLarger > 0
- * which does just that (useful if the ideal and target are estimates and aren't quite right, typically it will take
- * something larger than maxRate but less than half the total rate, which is only possible when the estimates don't agree)
- */
- protected ItemType findBestItemToMove(Map<ItemType, Double> costsPerItem, double targetCost, double maxCost,
- double maxCostIfNothingSmallerButLarger, Set<ItemType> excludedItems, Location locationIfKnown) {
-
- ItemType closestMatch = null;
- ItemType smallestMoveable = null, largest = null;
- double minDiff = Double.MAX_VALUE, smallestC = Double.MAX_VALUE, largestC = Double.MIN_VALUE;
- boolean exclusions = false;
-
- for (Entry<ItemType, Double> entry : costsPerItem.entrySet()) {
- ItemType item = entry.getKey();
- Double cost = entry.getValue();
-
- if (cost == null) {
- if (LOG.isDebugEnabled()) LOG.debug(MessageFormat.format("Item ''{0}'' has null workrate: skipping", item));
- continue;
- }
-
- if (!model.isItemMoveable(item)) {
- if (LOG.isDebugEnabled()) LOG.debug(MessageFormat.format("Item ''{0}'' cannot be moved: skipping", item));
- continue;
- }
- if (cost < 0) {
- if (LOG.isDebugEnabled()) LOG.debug(MessageFormat.format("Item ''{0}'' subject to recent adjustment: skipping", item));
- continue;
- }
- if (excludedItems.contains(item)) {
- exclusions = true;
- continue;
- }
- if (cost < 0) { // FIXME: already tested above
- exclusions = true;
- continue;
- }
- if (cost <= 0) { // FIXME: overlaps previous condition
- continue;
- }
- if (largest == null || cost > largestC) {
- largest = item;
- largestC = cost;
- }
- if (!model.isItemMoveable(item)) { // FIXME: already tested above
- continue;
- }
- if (locationIfKnown != null && !model.isItemAllowedIn(item, locationIfKnown)) {
- continue;
- }
- if (smallestMoveable == null || cost < smallestC) {
- smallestMoveable = item;
- smallestC = cost;
- }
- if (cost > maxCost) {
- continue;
- }
- double diff = Math.abs(targetCost - cost);
- if (closestMatch == null || diff < minDiff) {
- closestMatch = item;
- minDiff = diff;
- }
- }
-
- if (closestMatch != null)
- return closestMatch;
-
- if (smallestC < maxCostIfNothingSmallerButLarger && smallestC < largestC && !exclusions)
- return smallestMoveable;
-
- return null;
- }
-
-}
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/d30ff597/policy/src/main/java/brooklyn/policy/loadbalancing/DefaultBalanceablePoolModel.java
----------------------------------------------------------------------
diff --git a/policy/src/main/java/brooklyn/policy/loadbalancing/DefaultBalanceablePoolModel.java b/policy/src/main/java/brooklyn/policy/loadbalancing/DefaultBalanceablePoolModel.java
deleted file mode 100644
index 7e273ba..0000000
--- a/policy/src/main/java/brooklyn/policy/loadbalancing/DefaultBalanceablePoolModel.java
+++ /dev/null
@@ -1,280 +0,0 @@
-/*
- * 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 brooklyn.policy.loadbalancing;
-
-import java.io.ByteArrayOutputStream;
-import java.io.PrintStream;
-import java.util.Collections;
-import java.util.LinkedHashMap;
-import java.util.Map;
-import java.util.Set;
-import java.util.concurrent.ConcurrentHashMap;
-
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.apache.brooklyn.api.location.Location;
-
-import com.google.common.annotations.VisibleForTesting;
-import com.google.common.collect.HashMultimap;
-import com.google.common.collect.ImmutableSet;
-import com.google.common.collect.Multimaps;
-import com.google.common.collect.SetMultimap;
-
-/**
- * Standard implementation of {@link BalanceablePoolModel}, providing essential arithmetic for item and container
- * workrates and thresholds. See subclasses for specific requirements for migrating items.
- */
-public class DefaultBalanceablePoolModel<ContainerType, ItemType> implements BalanceablePoolModel<ContainerType, ItemType> {
-
- private static final Logger LOG = LoggerFactory.getLogger(DefaultBalanceablePoolModel.class);
-
- /*
- * Performance comments.
- * - Used hprof with LoadBalancingPolicySoakTest.testLoadBalancingManyManyItemsTest (1000 items)
- * - Prior to adding containerToItems, it created a new set by iterating over all items.
- * This was the biggest percentage of any brooklyn code.
- * Hence it's worth duplicating the values, keyed by item and keyed by container.
- * - Unfortunately changing threading model (so have a "rebalancer" thread, and a thread that
- * processes events to update the model), get ConcurrentModificationException if don't take
- * copy of containerToItems.get(node)...
- */
-
- // Concurrent maps cannot have null value; use this to represent when no container is supplied for an item
- private static final String NULL_CONTAINER = "null-container";
-
- private final String name;
- private final Set<ContainerType> containers = Collections.newSetFromMap(new ConcurrentHashMap<ContainerType,Boolean>());
- private final Map<ContainerType, Double> containerToLowThreshold = new ConcurrentHashMap<ContainerType, Double>();
- private final Map<ContainerType, Double> containerToHighThreshold = new ConcurrentHashMap<ContainerType, Double>();
- private final Map<ItemType, ContainerType> itemToContainer = new ConcurrentHashMap<ItemType, ContainerType>();
- private final SetMultimap<ContainerType, ItemType> containerToItems = Multimaps.synchronizedSetMultimap(HashMultimap.<ContainerType, ItemType>create());
- private final Map<ItemType, Double> itemToWorkrate = new ConcurrentHashMap<ItemType, Double>();
- private final Set<ItemType> immovableItems = Collections.newSetFromMap(new ConcurrentHashMap<ItemType, Boolean>());
-
- private volatile double poolLowThreshold = 0;
- private volatile double poolHighThreshold = 0;
- private volatile double currentPoolWorkrate = 0;
-
- public DefaultBalanceablePoolModel(String name) {
- this.name = name;
- }
-
- public ContainerType getParentContainer(ItemType item) {
- ContainerType result = itemToContainer.get(item);
- return (result != NULL_CONTAINER) ? result : null;
- }
-
- public Set<ItemType> getItemsForContainer(ContainerType node) {
- Set<ItemType> result = containerToItems.get(node);
- synchronized (containerToItems) {
- return (result != null) ? ImmutableSet.copyOf(result) : Collections.<ItemType>emptySet();
- }
- }
-
- public Double getItemWorkrate(ItemType item) {
- return itemToWorkrate.get(item);
- }
-
- @Override public double getPoolLowThreshold() { return poolLowThreshold; }
- @Override public double getPoolHighThreshold() { return poolHighThreshold; }
- @Override public double getCurrentPoolWorkrate() { return currentPoolWorkrate; }
- @Override public boolean isHot() { return !containers.isEmpty() && currentPoolWorkrate > poolHighThreshold; }
- @Override public boolean isCold() { return !containers.isEmpty() && currentPoolWorkrate < poolLowThreshold; }
-
-
- // Provider methods.
-
- @Override public String getName() { return name; }
- @Override public int getPoolSize() { return containers.size(); }
- @Override public Set<ContainerType> getPoolContents() { return containers; }
- @Override public String getName(ContainerType container) { return container.toString(); } // TODO: delete?
- @Override public Location getLocation(ContainerType container) { return null; } // TODO?
-
- @Override public double getLowThreshold(ContainerType container) {
- Double result = containerToLowThreshold.get(container);
- return (result != null) ? result : -1;
- }
-
- @Override public double getHighThreshold(ContainerType container) {
- Double result = containerToHighThreshold.get(container);
- return (result != null) ? result : -1;
- }
-
- @Override public double getTotalWorkrate(ContainerType container) {
- double totalWorkrate = 0;
- for (ItemType item : getItemsForContainer(container)) {
- Double workrate = itemToWorkrate.get(item);
- if (workrate != null)
- totalWorkrate += Math.abs(workrate);
- }
- return totalWorkrate;
- }
-
- @Override public Map<ContainerType, Double> getContainerWorkrates() {
- Map<ContainerType, Double> result = new LinkedHashMap<ContainerType, Double>();
- for (ContainerType node : containers)
- result.put(node, getTotalWorkrate(node));
- return result;
- }
-
- @Override public Map<ItemType, Double> getItemWorkrates(ContainerType node) {
- Map<ItemType, Double> result = new LinkedHashMap<ItemType, Double>();
- for (ItemType item : getItemsForContainer(node))
- result.put(item, itemToWorkrate.get(item));
- return result;
- }
-
- @Override public boolean isItemMoveable(ItemType item) {
- // If don't know about item, then assume not movable; otherwise has this item been explicitly flagged as immovable?
- return itemToContainer.containsKey(item) && !immovableItems.contains(item);
- }
-
- @Override public boolean isItemAllowedIn(ItemType item, Location location) {
- return true; // TODO?
- }
-
-
- // Mutators.
-
- @Override
- public void onItemMoved(ItemType item, ContainerType newNode) {
- if (!itemToContainer.containsKey(item)) {
- // Item may have been deleted; order of events received from different sources
- // (i.e. item itself and for itemGroup membership) is non-deterministic.
- LOG.info("Balanceable pool model ignoring onItemMoved for unknown item {} to container {}; " +
- "if onItemAdded subsequently received will get new container then", item, newNode);
- return;
- }
- ContainerType newNodeNonNull = toNonNullContainer(newNode);
- ContainerType oldNode = itemToContainer.put(item, newNodeNonNull);
- if (oldNode != null && oldNode != NULL_CONTAINER) containerToItems.remove(oldNode, item);
- if (newNode != null) containerToItems.put(newNode, item);
- }
-
- @Override
- public void onContainerAdded(ContainerType newContainer, double lowThreshold, double highThreshold) {
- boolean added = containers.add(newContainer);
- if (!added) {
- // See LoadBalancingPolicy.onContainerAdded for possible explanation of why can get duplicate calls
- LOG.debug("Duplicate container-added event for {}; ignoring", newContainer);
- return;
- }
- containerToLowThreshold.put(newContainer, lowThreshold);
- containerToHighThreshold.put(newContainer, highThreshold);
- poolLowThreshold += lowThreshold;
- poolHighThreshold += highThreshold;
- }
-
- @Override
- public void onContainerRemoved(ContainerType oldContainer) {
- containers.remove(oldContainer);
- Double containerLowThreshold = containerToLowThreshold.remove(oldContainer);
- Double containerHighThresold = containerToHighThreshold.remove(oldContainer);
- poolLowThreshold -= (containerLowThreshold != null ? containerLowThreshold : 0);
- poolHighThreshold -= (containerHighThresold != null ? containerHighThresold : 0);
-
- // TODO: assert no orphaned items
- }
-
- @Override
- public void onItemAdded(ItemType item, ContainerType parentContainer) {
- onItemAdded(item, parentContainer, false);
- }
-
- @Override
- public void onItemAdded(ItemType item, ContainerType parentContainer, boolean immovable) {
- // Duplicate calls to onItemAdded do no harm, as long as most recent is most accurate!
- // Important that it stays that way for now - See LoadBalancingPolicy.onContainerAdded for explanation.
-
- if (immovable)
- immovableItems.add(item);
-
- ContainerType parentContainerNonNull = toNonNullContainer(parentContainer);
- ContainerType oldNode = itemToContainer.put(item, parentContainerNonNull);
- if (oldNode != null && oldNode != NULL_CONTAINER) containerToItems.remove(oldNode, item);
- if (parentContainer != null) containerToItems.put(parentContainer, item);
- }
-
- @Override
- public void onItemRemoved(ItemType item) {
- ContainerType oldNode = itemToContainer.remove(item);
- if (oldNode != null && oldNode != NULL_CONTAINER) containerToItems.remove(oldNode, item);
- Double workrate = itemToWorkrate.remove(item);
- if (workrate != null)
- currentPoolWorkrate -= workrate;
- immovableItems.remove(item);
- }
-
- @Override
- public void onItemWorkrateUpdated(ItemType item, double newValue) {
- if (hasItem(item)) {
- Double oldValue = itemToWorkrate.put(item, newValue);
- double delta = ( newValue - (oldValue != null ? oldValue : 0) );
- currentPoolWorkrate += delta;
- } else {
- // Can happen when item removed - get notification of removal and workrate from group and item
- // respectively, so can overtake each other
- if (LOG.isDebugEnabled()) LOG.debug("Ignoring setting of workrate for unknown item {}, to {}", item, newValue);
- }
- }
-
- private boolean hasItem(ItemType item) {
- return itemToContainer.containsKey(item);
- }
-
-
- // Additional methods for tests.
-
- /**
- * Warning: this can be an expensive (time and memory) operation if there are a lot of items/containers.
- */
- @VisibleForTesting
- public String itemDistributionToString() {
- ByteArrayOutputStream baos = new ByteArrayOutputStream();
- dumpItemDistribution(new PrintStream(baos));
- return new String(baos.toByteArray());
- }
-
- @VisibleForTesting
- public void dumpItemDistribution() {
- dumpItemDistribution(System.out);
- }
-
- @VisibleForTesting
- public void dumpItemDistribution(PrintStream out) {
- for (ContainerType container : getPoolContents()) {
- out.println("Container '"+container+"': ");
- for (ItemType item : getItemsForContainer(container)) {
- Double workrate = getItemWorkrate(item);
- out.println("\t"+"Item '"+item+"' ("+workrate+")");
- }
- }
- out.flush();
- }
-
- @SuppressWarnings("unchecked")
- private ContainerType nullContainer() {
- return (ContainerType) NULL_CONTAINER; // relies on erasure
- }
-
- private ContainerType toNonNullContainer(ContainerType container) {
- return (container != null) ? container : nullContainer();
- }
-
-}
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/d30ff597/policy/src/main/java/brooklyn/policy/loadbalancing/ItemsInContainersGroup.java
----------------------------------------------------------------------
diff --git a/policy/src/main/java/brooklyn/policy/loadbalancing/ItemsInContainersGroup.java b/policy/src/main/java/brooklyn/policy/loadbalancing/ItemsInContainersGroup.java
deleted file mode 100644
index efd41d8..0000000
--- a/policy/src/main/java/brooklyn/policy/loadbalancing/ItemsInContainersGroup.java
+++ /dev/null
@@ -1,52 +0,0 @@
-/*
- * 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 brooklyn.policy.loadbalancing;
-
-import org.apache.brooklyn.api.entity.Entity;
-import org.apache.brooklyn.api.entity.Group;
-import org.apache.brooklyn.api.entity.proxying.ImplementedBy;
-import org.apache.brooklyn.core.util.flags.SetFromFlag;
-
-import brooklyn.config.ConfigKey;
-import brooklyn.entity.basic.DynamicGroup;
-import brooklyn.event.basic.BasicConfigKey;
-
-import com.google.common.base.Predicate;
-import com.google.common.base.Predicates;
-
-/**
- * A group of items that are contained within a given (dynamically changing) set of containers.
- *
- * The {@link setContainers(Group)} sets the group of containers. The membership of that group
- * is dynamically tracked.
- *
- * When containers are added/removed, or when an items is added/removed, or when an {@link Moveable} item
- * is moved then the membership of this group of items is automatically updated accordingly.
- *
- * For example: in Monterey, this could be used to track the actors that are within a given cluster of venues.
- */
-@ImplementedBy(ItemsInContainersGroupImpl.class)
-public interface ItemsInContainersGroup extends DynamicGroup {
-
- @SetFromFlag("itemFilter")
- public static final ConfigKey<Predicate<? super Entity>> ITEM_FILTER = new BasicConfigKey(
- Predicate.class, "itemsInContainerGroup.itemFilter", "Filter for which items within the containers will automatically be in group", Predicates.alwaysTrue());
-
- public void setContainers(Group containerGroup);
-}
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/d30ff597/policy/src/main/java/brooklyn/policy/loadbalancing/ItemsInContainersGroupImpl.java
----------------------------------------------------------------------
diff --git a/policy/src/main/java/brooklyn/policy/loadbalancing/ItemsInContainersGroupImpl.java b/policy/src/main/java/brooklyn/policy/loadbalancing/ItemsInContainersGroupImpl.java
deleted file mode 100644
index daf5dfe..0000000
--- a/policy/src/main/java/brooklyn/policy/loadbalancing/ItemsInContainersGroupImpl.java
+++ /dev/null
@@ -1,148 +0,0 @@
-/*
- * 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 brooklyn.policy.loadbalancing;
-
-import org.apache.brooklyn.api.entity.Entity;
-import org.apache.brooklyn.api.entity.Group;
-import org.apache.brooklyn.api.event.Sensor;
-import org.apache.brooklyn.api.event.SensorEvent;
-import org.apache.brooklyn.api.event.SensorEventListener;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import brooklyn.entity.basic.AbstractGroup;
-import brooklyn.entity.basic.DynamicGroupImpl;
-
-import com.google.common.base.Predicate;
-
-/**
- * A group of items that are contained within a given (dynamically changing) set of containers.
- *
- * The {@link setContainers(Group)} sets the group of containers. The membership of that group
- * is dynamically tracked.
- *
- * When containers are added/removed, or when an items is added/removed, or when an {@link Moveable} item
- * is moved then the membership of this group of items is automatically updated accordingly.
- *
- * For example: in Monterey, this could be used to track the actors that are within a given cluster of venues.
- */
-public class ItemsInContainersGroupImpl extends DynamicGroupImpl implements ItemsInContainersGroup {
-
- // TODO Inefficient: will not scale to many 1000s of items
-
- private static final Logger LOG = LoggerFactory.getLogger(ItemsInContainersGroup.class);
-
- private Group containerGroup;
-
- private final SensorEventListener<Object> eventHandler = new SensorEventListener<Object>() {
- @Override
- public void onEvent(SensorEvent<Object> event) {
- Entity source = event.getSource();
- Object value = event.getValue();
- Sensor sensor = event.getSensor();
-
- if (sensor.equals(AbstractGroup.MEMBER_ADDED)) {
- onContainerAdded((Entity) value);
- } else if (sensor.equals(AbstractGroup.MEMBER_REMOVED)) {
- onContainerRemoved((Entity) value);
- } else if (sensor.equals(Movable.CONTAINER)) {
- onItemMoved((Movable)source, (BalanceableContainer<?>) value);
- } else {
- throw new IllegalStateException("Unhandled event type "+sensor+": "+event);
- }
- }
- };
-
- public ItemsInContainersGroupImpl() {
- }
-
- @Override
- public void init() {
- super.init();
- setEntityFilter(new Predicate<Entity>() {
- @Override public boolean apply(Entity e) {
- return acceptsEntity(e);
- }});
- }
-
- protected Predicate<? super Entity> getItemFilter() {
- return getConfig(ITEM_FILTER);
- }
-
- @Override
- protected boolean acceptsEntity(Entity e) {
- if (e instanceof Movable) {
- return acceptsItem((Movable)e, ((Movable)e).getAttribute(Movable.CONTAINER));
- }
- return false;
- }
-
- boolean acceptsItem(Movable e, BalanceableContainer c) {
- return (containerGroup != null && c != null) ? getItemFilter().apply(e) && containerGroup.hasMember(c) : false;
- }
-
- @Override
- public void setContainers(Group containerGroup) {
- this.containerGroup = containerGroup;
- subscribe(containerGroup, AbstractGroup.MEMBER_ADDED, eventHandler);
- subscribe(containerGroup, AbstractGroup.MEMBER_REMOVED, eventHandler);
- subscribe(null, Movable.CONTAINER, eventHandler);
-
- if (LOG.isTraceEnabled()) LOG.trace("{} scanning entities on container group set", this);
- rescanEntities();
- }
-
- private void onContainerAdded(Entity newContainer) {
- if (LOG.isTraceEnabled()) LOG.trace("{} rescanning entities on container {} added", this, newContainer);
- rescanEntities();
- }
-
- private void onContainerRemoved(Entity oldContainer) {
- if (LOG.isTraceEnabled()) LOG.trace("{} rescanning entities on container {} removed", this, oldContainer);
- rescanEntities();
- }
-
- protected void onEntityAdded(Entity item) {
- if (acceptsEntity(item)) {
- if (LOG.isDebugEnabled()) LOG.debug("{} adding new item {}", this, item);
- addMember(item);
- }
- }
-
- protected void onEntityRemoved(Entity item) {
- if (removeMember(item)) {
- if (LOG.isDebugEnabled()) LOG.debug("{} removing deleted item {}", this, item);
- }
- }
-
- private void onItemMoved(Movable item, BalanceableContainer container) {
- if (LOG.isTraceEnabled()) LOG.trace("{} processing moved item {}, to container {}", new Object[] {this, item, container});
- if (hasMember(item)) {
- if (!acceptsItem(item, container)) {
- if (LOG.isDebugEnabled()) LOG.debug("{} removing moved item {} from group, as new container {} is not a member", new Object[] {this, item, container});
- removeMember(item);
- }
- } else {
- if (acceptsItem(item, container)) {
- if (LOG.isDebugEnabled()) LOG.debug("{} adding moved item {} to group, as new container {} is a member", new Object[] {this, item, container});
- addMember(item);
- }
- }
- }
-}
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/d30ff597/policy/src/main/java/brooklyn/policy/loadbalancing/LoadBalancingPolicy.java
----------------------------------------------------------------------
diff --git a/policy/src/main/java/brooklyn/policy/loadbalancing/LoadBalancingPolicy.java b/policy/src/main/java/brooklyn/policy/loadbalancing/LoadBalancingPolicy.java
deleted file mode 100644
index 57b736b..0000000
--- a/policy/src/main/java/brooklyn/policy/loadbalancing/LoadBalancingPolicy.java
+++ /dev/null
@@ -1,343 +0,0 @@
-/*
- * 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 brooklyn.policy.loadbalancing;
-
-import static brooklyn.util.JavaGroovyEquivalents.elvis;
-import static brooklyn.util.JavaGroovyEquivalents.groovyTruth;
-
-import java.util.Map;
-import java.util.Map.Entry;
-import java.util.concurrent.Executors;
-import java.util.concurrent.ScheduledExecutorService;
-import java.util.concurrent.ThreadFactory;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.atomic.AtomicBoolean;
-
-import org.apache.brooklyn.api.entity.Entity;
-import org.apache.brooklyn.api.entity.basic.EntityLocal;
-import org.apache.brooklyn.api.event.AttributeSensor;
-import org.apache.brooklyn.api.event.Sensor;
-import org.apache.brooklyn.api.event.SensorEvent;
-import org.apache.brooklyn.api.event.SensorEventListener;
-import org.apache.brooklyn.core.policy.basic.AbstractPolicy;
-import org.apache.brooklyn.core.util.flags.SetFromFlag;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import brooklyn.config.ConfigKey;
-import brooklyn.entity.basic.EntityInternal;
-import brooklyn.policy.autoscaling.AutoScalerPolicy;
-import brooklyn.policy.loadbalancing.BalanceableWorkerPool.ContainerItemPair;
-import brooklyn.util.collections.MutableMap;
-
-import com.google.common.base.Preconditions;
-import com.google.common.collect.ImmutableMap;
-import com.google.common.util.concurrent.ThreadFactoryBuilder;
-
-
-/**
- * <p>Policy that is attached to a pool of "containers", each of which can host one or more migratable "items".
- * The policy monitors the workrates of the items and effects migrations in an attempt to ensure that the containers
- * are all sufficiently utilized without any of them being overloaded.
- *
- * <p>The particular sensor that defines the items' workrates is specified when the policy is constructed. High- and
- * low-thresholds are defined as <strong>configuration keys</strong> on each of the container entities in the pool:
- * for an item sensor named {@code foo.bar.sensorName}, the corresponding container config keys would be named
- * {@code foo.bar.sensorName.threshold.low} and {@code foo.bar.sensorName.threshold.high}.
- *
- * <p>In addition to balancing items among the available containers, this policy causes the pool Entity to emit
- * {@code POOL_COLD} and {@code POOL_HOT} events when it is determined that there is a surplus or shortfall
- * of container resource in the pool respectively. These events may be consumed by a separate policy that is capable
- * of resizing the container pool.
- */
- // removed from catalog because it cannot currently be configured via catalog mechanisms
- // PolicySpec.create fails due to no no-arg constructor
- // TODO make metric and model things which can be initialized from config then reinstate in catalog
-//@Catalog(name="Load Balancer", description="Policy that is attached to a pool of \"containers\", each of which "
-// + "can host one or more migratable \"items\". The policy monitors the workrates of the items and effects "
-// + "migrations in an attempt to ensure that the containers are all sufficiently utilized without any of "
-// + "them being overloaded.")
-public class LoadBalancingPolicy<NodeType extends Entity, ItemType extends Movable> extends AbstractPolicy {
-
- private static final Logger LOG = LoggerFactory.getLogger(LoadBalancingPolicy.class);
-
- @SetFromFlag(defaultVal="100")
- private long minPeriodBetweenExecs;
-
- private final AttributeSensor<? extends Number> metric;
- private final String lowThresholdConfigKeyName;
- private final String highThresholdConfigKeyName;
- private final BalanceablePoolModel<NodeType, ItemType> model;
- private final BalancingStrategy<NodeType, ItemType> strategy;
- private BalanceableWorkerPool poolEntity;
-
- private volatile ScheduledExecutorService executor;
- private final AtomicBoolean executorQueued = new AtomicBoolean(false);
- private volatile long executorTime = 0;
-
- private int lastEmittedDesiredPoolSize = 0;
- private static enum TemperatureStates { COLD, HOT }
- private TemperatureStates lastEmittedPoolTemperature = null; // "cold" or "hot"
-
- private final SensorEventListener<Object> eventHandler = new SensorEventListener<Object>() {
- @SuppressWarnings({ "rawtypes", "unchecked" })
- public void onEvent(SensorEvent<Object> event) {
- if (LOG.isTraceEnabled()) LOG.trace("{} received event {}", LoadBalancingPolicy.this, event);
- Entity source = event.getSource();
- Object value = event.getValue();
- Sensor sensor = event.getSensor();
-
- if (sensor.equals(metric)) {
- onItemMetricUpdate((ItemType)source, ((Number) value).doubleValue(), true);
- } else if (sensor.equals(BalanceableWorkerPool.CONTAINER_ADDED)) {
- onContainerAdded((NodeType) value, true);
- } else if (sensor.equals(BalanceableWorkerPool.CONTAINER_REMOVED)) {
- onContainerRemoved((NodeType) value, true);
- } else if (sensor.equals(BalanceableWorkerPool.ITEM_ADDED)) {
- ContainerItemPair pair = (ContainerItemPair) value;
- onItemAdded((ItemType)pair.item, (NodeType)pair.container, true);
- } else if (sensor.equals(BalanceableWorkerPool.ITEM_REMOVED)) {
- ContainerItemPair pair = (ContainerItemPair) value;
- onItemRemoved((ItemType)pair.item, (NodeType)pair.container, true);
- } else if (sensor.equals(BalanceableWorkerPool.ITEM_MOVED)) {
- ContainerItemPair pair = (ContainerItemPair) value;
- onItemMoved((ItemType)pair.item, (NodeType)pair.container, true);
- }
- }
- };
-
- public LoadBalancingPolicy() {
- this(null, null);
- }
-
- public LoadBalancingPolicy(AttributeSensor<? extends Number> metric,
- BalanceablePoolModel<NodeType, ItemType> model) {
- this(MutableMap.of(), metric, model);
- }
- @SuppressWarnings({ "unchecked", "rawtypes" })
- public LoadBalancingPolicy(Map props, AttributeSensor<? extends Number> metric,
- BalanceablePoolModel<NodeType, ItemType> model) {
-
- super(props);
- this.metric = metric;
- this.lowThresholdConfigKeyName = metric.getName()+".threshold.low";
- this.highThresholdConfigKeyName = metric.getName()+".threshold.high";
- this.model = model;
- this.strategy = new BalancingStrategy(getDisplayName(), model); // TODO: extract interface, inject impl
-
- // TODO Should re-use the execution manager's thread pool, somehow
- executor = Executors.newSingleThreadScheduledExecutor(newThreadFactory());
- }
-
- @SuppressWarnings("unchecked")
- @Override
- public void setEntity(EntityLocal entity) {
- Preconditions.checkArgument(entity instanceof BalanceableWorkerPool, "Provided entity must be a BalanceableWorkerPool");
- super.setEntity(entity);
- this.poolEntity = (BalanceableWorkerPool) entity;
-
- // Detect when containers are added to or removed from the pool.
- subscribe(poolEntity, BalanceableWorkerPool.CONTAINER_ADDED, eventHandler);
- subscribe(poolEntity, BalanceableWorkerPool.CONTAINER_REMOVED, eventHandler);
- subscribe(poolEntity, BalanceableWorkerPool.ITEM_ADDED, eventHandler);
- subscribe(poolEntity, BalanceableWorkerPool.ITEM_REMOVED, eventHandler);
- subscribe(poolEntity, BalanceableWorkerPool.ITEM_MOVED, eventHandler);
-
- // Take heed of any extant containers.
- for (Entity container : poolEntity.getContainerGroup().getMembers()) {
- onContainerAdded((NodeType)container, false);
- }
- for (Entity item : poolEntity.getItemGroup().getMembers()) {
- onItemAdded((ItemType)item, (NodeType)item.getAttribute(Movable.CONTAINER), false);
- }
-
- scheduleRebalance();
- }
-
- @Override
- public void suspend() {
- // TODO unsubscribe from everything? And resubscribe on resume?
- super.suspend();
- if (executor != null) executor.shutdownNow();;
- executorQueued.set(false);
- }
-
- @Override
- public void resume() {
- super.resume();
- executor = Executors.newSingleThreadScheduledExecutor(newThreadFactory());
- executorTime = 0;
- executorQueued.set(false);
- }
-
- private ThreadFactory newThreadFactory() {
- return new ThreadFactoryBuilder()
- .setNameFormat("brooklyn-followthesunpolicy-%d")
- .build();
- }
-
- private void scheduleRebalance() {
- if (isRunning() && executorQueued.compareAndSet(false, true)) {
- long now = System.currentTimeMillis();
- long delay = Math.max(0, (executorTime + minPeriodBetweenExecs) - now);
-
- executor.schedule(new Runnable() {
- @SuppressWarnings("rawtypes")
- public void run() {
- try {
- executorTime = System.currentTimeMillis();
- executorQueued.set(false);
- strategy.rebalance();
-
- if (LOG.isDebugEnabled()) LOG.debug("{} post-rebalance: poolSize={}; workrate={}; lowThreshold={}; " +
- "highThreshold={}", new Object[] {this, model.getPoolSize(), model.getCurrentPoolWorkrate(),
- model.getPoolLowThreshold(), model.getPoolHighThreshold()});
-
- if (model.isCold()) {
- Map eventVal = ImmutableMap.of(
- AutoScalerPolicy.POOL_CURRENT_SIZE_KEY, model.getPoolSize(),
- AutoScalerPolicy.POOL_CURRENT_WORKRATE_KEY, model.getCurrentPoolWorkrate(),
- AutoScalerPolicy.POOL_LOW_THRESHOLD_KEY, model.getPoolLowThreshold(),
- AutoScalerPolicy.POOL_HIGH_THRESHOLD_KEY, model.getPoolHighThreshold());
-
- ((EntityLocal)poolEntity).emit(AutoScalerPolicy.DEFAULT_POOL_COLD_SENSOR, eventVal);
-
- if (LOG.isInfoEnabled()) {
- int desiredPoolSize = (int) Math.ceil(model.getCurrentPoolWorkrate() / (model.getPoolLowThreshold()/model.getPoolSize()));
- if (desiredPoolSize != lastEmittedDesiredPoolSize || lastEmittedPoolTemperature != TemperatureStates.COLD) {
- LOG.info("{} emitted COLD (suggesting {}): {}", new Object[] {this, desiredPoolSize, eventVal});
- lastEmittedDesiredPoolSize = desiredPoolSize;
- lastEmittedPoolTemperature = TemperatureStates.COLD;
- }
- }
-
- } else if (model.isHot()) {
- Map eventVal = ImmutableMap.of(
- AutoScalerPolicy.POOL_CURRENT_SIZE_KEY, model.getPoolSize(),
- AutoScalerPolicy.POOL_CURRENT_WORKRATE_KEY, model.getCurrentPoolWorkrate(),
- AutoScalerPolicy.POOL_LOW_THRESHOLD_KEY, model.getPoolLowThreshold(),
- AutoScalerPolicy.POOL_HIGH_THRESHOLD_KEY, model.getPoolHighThreshold());
-
- ((EntityLocal)poolEntity).emit(AutoScalerPolicy.DEFAULT_POOL_HOT_SENSOR, eventVal);
-
- if (LOG.isInfoEnabled()) {
- int desiredPoolSize = (int) Math.ceil(model.getCurrentPoolWorkrate() / (model.getPoolHighThreshold()/model.getPoolSize()));
- if (desiredPoolSize != lastEmittedDesiredPoolSize || lastEmittedPoolTemperature != TemperatureStates.HOT) {
- LOG.info("{} emitted HOT (suggesting {}): {}", new Object[] {this, desiredPoolSize, eventVal});
- lastEmittedDesiredPoolSize = desiredPoolSize;
- lastEmittedPoolTemperature = TemperatureStates.HOT;
- }
- }
- }
-
- } catch (Exception e) {
- if (isRunning()) {
- LOG.error("Error rebalancing", e);
- } else {
- LOG.debug("Error rebalancing, but no longer running", e);
- }
- }
- }},
- delay,
- TimeUnit.MILLISECONDS);
- }
- }
-
- // TODO Can get duplicate onContainerAdded events.
- // I presume it's because we subscribe and then iterate over the extant containers.
- // Solution would be for subscription to give you events for existing / current value(s).
- // Also current impl messes up single-threaded updates model: the setEntity is a different thread than for subscription events.
- private void onContainerAdded(NodeType newContainer, boolean rebalanceNow) {
- Preconditions.checkArgument(newContainer instanceof BalanceableContainer, "Added container must be a BalanceableContainer");
- if (LOG.isTraceEnabled()) LOG.trace("{} recording addition of container {}", this, newContainer);
- // Low and high thresholds for the metric we're interested in are assumed to be present
- // in the container's configuration.
- Number lowThreshold = (Number) findConfigValue(newContainer, lowThresholdConfigKeyName);
- Number highThreshold = (Number) findConfigValue(newContainer, highThresholdConfigKeyName);
- if (lowThreshold == null || highThreshold == null) {
- LOG.warn(
- "Balanceable container '"+newContainer+"' does not define low- and high- threshold configuration keys: '"+
- lowThresholdConfigKeyName+"' and '"+highThresholdConfigKeyName+"', skipping");
- return;
- }
-
- model.onContainerAdded(newContainer, lowThreshold.doubleValue(), highThreshold.doubleValue());
-
- // Note: no need to scan the container for items; they will appear via the ITEM_ADDED events.
- // Also, must abide by any item-filters etc defined in the pool.
-
- if (rebalanceNow) scheduleRebalance();
- }
-
- private static Object findConfigValue(Entity entity, String configKeyName) {
- Map<ConfigKey<?>, Object> config = ((EntityInternal)entity).getAllConfig();
- for (Entry<ConfigKey<?>, Object> entry : config.entrySet()) {
- if (configKeyName.equals(entry.getKey().getName()))
- return entry.getValue();
- }
- return null;
- }
-
- // TODO Receiving duplicates of onContainerRemoved (e.g. when running LoadBalancingInmemorySoakTest)
- private void onContainerRemoved(NodeType oldContainer, boolean rebalanceNow) {
- if (LOG.isTraceEnabled()) LOG.trace("{} recording removal of container {}", this, oldContainer);
- model.onContainerRemoved(oldContainer);
- if (rebalanceNow) scheduleRebalance();
- }
-
- private void onItemAdded(ItemType item, NodeType parentContainer, boolean rebalanceNow) {
- Preconditions.checkArgument(item instanceof Movable, "Added item "+item+" must implement Movable");
- if (LOG.isTraceEnabled()) LOG.trace("{} recording addition of item {} in container {}", new Object[] {this, item, parentContainer});
-
- subscribe(item, metric, eventHandler);
-
- // Update the model, including the current metric value (if any).
- boolean immovable = (Boolean)elvis(item.getConfig(Movable.IMMOVABLE), false);
- Number currentValue = item.getAttribute(metric);
- model.onItemAdded(item, parentContainer, immovable);
- if (currentValue != null)
- model.onItemWorkrateUpdated(item, currentValue.doubleValue());
-
- if (rebalanceNow) scheduleRebalance();
- }
-
- private void onItemRemoved(ItemType item, NodeType parentContainer, boolean rebalanceNow) {
- if (LOG.isTraceEnabled()) LOG.trace("{} recording removal of item {}", this, item);
- unsubscribe(item);
- model.onItemRemoved(item);
- if (rebalanceNow) scheduleRebalance();
- }
-
- private void onItemMoved(ItemType item, NodeType parentContainer, boolean rebalanceNow) {
- if (LOG.isTraceEnabled()) LOG.trace("{} recording moving of item {} to {}", new Object[] {this, item, parentContainer});
- model.onItemMoved(item, parentContainer);
- if (rebalanceNow) scheduleRebalance();
- }
-
- private void onItemMetricUpdate(ItemType item, double newValue, boolean rebalanceNow) {
- if (LOG.isTraceEnabled()) LOG.trace("{} recording metric update for item {}, new value {}", new Object[] {this, item, newValue});
- model.onItemWorkrateUpdated(item, newValue);
- if (rebalanceNow) scheduleRebalance();
- }
-
- @Override
- public String toString() {
- return getClass().getSimpleName() + (groovyTruth(name) ? "("+name+")" : "");
- }
-}
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/d30ff597/policy/src/main/java/brooklyn/policy/loadbalancing/LocationConstraint.java
----------------------------------------------------------------------
diff --git a/policy/src/main/java/brooklyn/policy/loadbalancing/LocationConstraint.java b/policy/src/main/java/brooklyn/policy/loadbalancing/LocationConstraint.java
deleted file mode 100644
index 5ae5723..0000000
--- a/policy/src/main/java/brooklyn/policy/loadbalancing/LocationConstraint.java
+++ /dev/null
@@ -1,28 +0,0 @@
-/*
- * 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 brooklyn.policy.loadbalancing;
-
-import org.apache.brooklyn.api.location.Location;
-
-/**
- * Temporary stub to resolve dependencies in ported LoadBalancingPolicy.
- */
-public class LocationConstraint {
- public boolean isPermitted(Location l) { return true; } // TODO
-}
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/d30ff597/policy/src/main/java/brooklyn/policy/loadbalancing/Movable.java
----------------------------------------------------------------------
diff --git a/policy/src/main/java/brooklyn/policy/loadbalancing/Movable.java b/policy/src/main/java/brooklyn/policy/loadbalancing/Movable.java
deleted file mode 100644
index b0f1658..0000000
--- a/policy/src/main/java/brooklyn/policy/loadbalancing/Movable.java
+++ /dev/null
@@ -1,51 +0,0 @@
-/*
- * 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 brooklyn.policy.loadbalancing;
-
-import org.apache.brooklyn.api.entity.Entity;
-import org.apache.brooklyn.core.util.flags.SetFromFlag;
-
-import brooklyn.config.ConfigKey;
-import brooklyn.entity.annotation.Effector;
-import brooklyn.entity.annotation.EffectorParam;
-import brooklyn.entity.basic.MethodEffector;
-import brooklyn.event.basic.BasicAttributeSensor;
-import brooklyn.event.basic.BasicConfigKey;
-
-
-/**
- * Represents an item that can be migrated between balanceable containers.
- */
-public interface Movable extends Entity {
-
- @SetFromFlag("immovable")
- public static ConfigKey<Boolean> IMMOVABLE = new BasicConfigKey<Boolean>(
- Boolean.class, "movable.item.immovable", "Indicates whether this item instance is immovable, so cannot be moved by policies", false);
-
- public static BasicAttributeSensor<BalanceableContainer> CONTAINER = new BasicAttributeSensor<BalanceableContainer>(
- BalanceableContainer.class, "movable.item.container", "The container that this item is on");
-
- public static final MethodEffector<Void> MOVE = new MethodEffector<Void>(Movable.class, "move");
-
- public String getContainerId();
-
- @Effector(description="Moves this entity to the given container")
- public void move(@EffectorParam(name="destination") Entity destination);
-
-}
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/d30ff597/policy/src/main/java/brooklyn/policy/loadbalancing/PolicyUtilForPool.java
----------------------------------------------------------------------
diff --git a/policy/src/main/java/brooklyn/policy/loadbalancing/PolicyUtilForPool.java b/policy/src/main/java/brooklyn/policy/loadbalancing/PolicyUtilForPool.java
deleted file mode 100644
index ceead1c..0000000
--- a/policy/src/main/java/brooklyn/policy/loadbalancing/PolicyUtilForPool.java
+++ /dev/null
@@ -1,96 +0,0 @@
-/*
- * 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 brooklyn.policy.loadbalancing;
-
-import java.util.Set;
-
-/**
- * Provides conveniences for searching for hot/cold containers in a provided pool model.
- * Ported from Monterey v3, with irrelevant bits removed.
- */
-public class PolicyUtilForPool<ContainerType, ItemType> {
-
- private final BalanceablePoolModel<ContainerType, ItemType> model;
-
-
- public PolicyUtilForPool (BalanceablePoolModel<ContainerType, ItemType> model) {
- this.model = model;
- }
-
- public ContainerType findColdestContainer(Set<ContainerType> excludedContainers) {
- return findColdestContainer(excludedContainers, null);
- }
-
- /**
- * Identifies the container with the maximum spare capacity (highThreshold - currentWorkrate),
- * returns null if none of the model's nodes has spare capacity.
- */
- public ContainerType findColdestContainer(Set<ContainerType> excludedContainers, LocationConstraint locationConstraint) {
- double maxSpareCapacity = 0;
- ContainerType coldest = null;
-
- for (ContainerType c : model.getPoolContents()) {
- if (excludedContainers.contains(c))
- continue;
- if (locationConstraint != null && !locationConstraint.isPermitted(model.getLocation(c)))
- continue;
-
- double highThreshold = model.getHighThreshold(c);
- double totalWorkrate = model.getTotalWorkrate(c);
- double spareCapacity = highThreshold - totalWorkrate;
-
- if (highThreshold == -1 || totalWorkrate == -1) {
- continue; // container presumably has been removed
- }
- if (spareCapacity > maxSpareCapacity) {
- maxSpareCapacity = spareCapacity;
- coldest = c;
- }
- }
- return coldest;
- }
-
- /**
- * Identifies the container with the maximum overshoot (currentWorkrate - highThreshold),
- * returns null if none of the model's nodes has an overshoot.
- */
- public ContainerType findHottestContainer(Set<ContainerType> excludedContainers) {
- double maxOvershoot = 0;
- ContainerType hottest = null;
-
- for (ContainerType c : model.getPoolContents()) {
- if (excludedContainers.contains(c))
- continue;
-
- double totalWorkrate = model.getTotalWorkrate(c);
- double highThreshold = model.getHighThreshold(c);
- double overshoot = totalWorkrate - highThreshold;
-
- if (highThreshold == -1 || totalWorkrate == -1) {
- continue; // container presumably has been removed
- }
- if (overshoot > maxOvershoot) {
- maxOvershoot = overshoot;
- hottest = c;
- }
- }
- return hottest;
- }
-
-}
\ No newline at end of file
[19/24] incubator-brooklyn git commit: [BROOKLYN-162] Renaming
package policy
Posted by he...@apache.org.
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/d30ff597/policy/src/main/java/brooklyn/policy/autoscaling/SizeHistory.java
----------------------------------------------------------------------
diff --git a/policy/src/main/java/brooklyn/policy/autoscaling/SizeHistory.java b/policy/src/main/java/brooklyn/policy/autoscaling/SizeHistory.java
deleted file mode 100644
index 525ba20..0000000
--- a/policy/src/main/java/brooklyn/policy/autoscaling/SizeHistory.java
+++ /dev/null
@@ -1,166 +0,0 @@
-/*
- * 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 brooklyn.policy.autoscaling;
-
-import java.util.List;
-
-import brooklyn.util.collections.MutableMap;
-import brooklyn.util.collections.TimeWindowedList;
-import brooklyn.util.collections.TimestampedValue;
-import brooklyn.util.time.Duration;
-
-import com.google.common.base.Objects;
-
-/**
- * Using a {@link TimeWindowedList}, tracks the recent history of values to allow a summary of
- * those values to be obtained.
- *
- * @author aled
- */
-public class SizeHistory {
-
- public static class WindowSummary {
- /** The most recent value (or -1 if there has been no value) */
- public final long latest;
-
- /** The minimum vaule within the given time period */
- public final long min;
-
- /** The maximum vaule within the given time period */
- public final long max;
-
- /** true if, since that max value, there have not been any higher values */
- public final boolean stableForGrowth;
-
- /** true if, since that low value, there have not been any lower values */
- public final boolean stableForShrinking;
-
- public WindowSummary(long latest, long min, long max, boolean stableForGrowth, boolean stableForShrinking) {
- this.latest = latest;
- this.min = min;
- this.max = max;
- this.stableForGrowth = stableForGrowth;
- this.stableForShrinking = stableForShrinking;
- }
-
- @Override
- public String toString() {
- return Objects.toStringHelper(this).add("latest", latest).add("min", min).add("max", max)
- .add("stableForGrowth", stableForGrowth).add("stableForShrinking", stableForShrinking).toString();
- }
- }
-
- private final TimeWindowedList<Number> recentDesiredResizes;
-
- public SizeHistory(long windowSize) {
- recentDesiredResizes = new TimeWindowedList<Number>(MutableMap.of("timePeriod", windowSize, "minExpiredVals", 1));
- }
-
- public void add(final int val) {
- recentDesiredResizes.add(val);
- }
-
- public void setWindowSize(Duration newWindowSize) {
- recentDesiredResizes.setTimePeriod(newWindowSize);
- }
-
- /**
- * Summarises the history of values in this time window, with a few special things:
- * <ul>
- * <li>If entire time-window is not covered by the given values, then min is Integer.MIN_VALUE and max is Integer.MAX_VALUE
- * <li>If no values, then latest is -1
- * <li>If no recent values, then keeps last-seen value (no matter how old), to use that
- * <li>"stable for growth" means that since that max value, there have not been any higher values
- * <li>"stable for shrinking" means that since that low value, there have not been any lower values
- * </ul>
- */
- public WindowSummary summarizeWindow(Duration windowSize) {
- long now = System.currentTimeMillis();
- List<TimestampedValue<Number>> windowVals = recentDesiredResizes.getValuesInWindow(now, windowSize);
-
- Number latestObj = latestInWindow(windowVals);
- long latest = (latestObj == null) ? -1: latestObj.longValue();
- long max = maxInWindow(windowVals, windowSize).longValue();
- long min = minInWindow(windowVals, windowSize).longValue();
-
- // TODO Could do more sophisticated "stable" check; this is the easiest code - correct but not most efficient
- // in terms of the caller having to schedule additional stability checks.
- boolean stable = (min == max);
-
- return new WindowSummary(latest, min, max, stable, stable);
- }
-
- /**
- * If the entire time-window is not covered by the given values, then returns Integer.MAX_VALUE.
- */
- private <T extends Number> T maxInWindow(List<TimestampedValue<T>> vals, Duration timeWindow) {
- // TODO bad casting from Integer default result to T
- long now = System.currentTimeMillis();
- long epoch = now - timeWindow.toMilliseconds();
- T result = null;
- double resultAsDouble = Integer.MAX_VALUE;
- for (TimestampedValue<T> val : vals) {
- T valAsNum = val.getValue();
- double valAsDouble = (valAsNum != null) ? valAsNum.doubleValue() : 0;
- if (result == null && val.getTimestamp() > epoch) {
- result = withDefault(null, Integer.MAX_VALUE);
- resultAsDouble = result.doubleValue();
- }
- if (result == null || (valAsNum != null && valAsDouble > resultAsDouble)) {
- result = valAsNum;
- resultAsDouble = valAsDouble;
- }
- }
- return withDefault(result, Integer.MAX_VALUE);
- }
-
- /**
- * If the entire time-window is not covered by the given values, then returns Integer.MIN_VALUE
- */
- private <T extends Number> T minInWindow(List<TimestampedValue<T>> vals, Duration timeWindow) {
- long now = System.currentTimeMillis();
- long epoch = now - timeWindow.toMilliseconds();
- T result = null;
- double resultAsDouble = Integer.MIN_VALUE;
- for (TimestampedValue<T> val : vals) {
- T valAsNum = val.getValue();
- double valAsDouble = (valAsNum != null) ? valAsNum.doubleValue() : 0;
- if (result == null && val.getTimestamp() > epoch) {
- result = withDefault(null, Integer.MIN_VALUE);
- resultAsDouble = result.doubleValue();
- }
- if (result == null || (val.getValue() != null && valAsDouble < resultAsDouble)) {
- result = valAsNum;
- resultAsDouble = valAsDouble;
- }
- }
- return withDefault(result, Integer.MIN_VALUE);
- }
-
- @SuppressWarnings("unchecked")
- private <T> T withDefault(T result, Integer defaultValue) {
- return result!=null ? result : (T) defaultValue;
- }
- /**
- * @return null if empty, or the most recent value
- */
- private <T extends Number> T latestInWindow(List<TimestampedValue<T>> vals) {
- return vals.isEmpty() ? null : vals.get(vals.size()-1).getValue();
- }
-}
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/d30ff597/policy/src/main/java/brooklyn/policy/followthesun/DefaultFollowTheSunModel.java
----------------------------------------------------------------------
diff --git a/policy/src/main/java/brooklyn/policy/followthesun/DefaultFollowTheSunModel.java b/policy/src/main/java/brooklyn/policy/followthesun/DefaultFollowTheSunModel.java
deleted file mode 100644
index 922b33d..0000000
--- a/policy/src/main/java/brooklyn/policy/followthesun/DefaultFollowTheSunModel.java
+++ /dev/null
@@ -1,328 +0,0 @@
-/*
- * 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 brooklyn.policy.followthesun;
-
-import static com.google.common.base.Preconditions.checkNotNull;
-
-import java.io.ByteArrayOutputStream;
-import java.io.PrintStream;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.LinkedHashMap;
-import java.util.LinkedHashSet;
-import java.util.Map;
-import java.util.Set;
-import java.util.concurrent.ConcurrentHashMap;
-
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.apache.brooklyn.api.location.Location;
-import org.apache.brooklyn.location.basic.AbstractLocation;
-
-import com.google.common.annotations.VisibleForTesting;
-import com.google.common.collect.ImmutableSet;
-import com.google.common.collect.Maps;
-
-public class DefaultFollowTheSunModel<ContainerType, ItemType> implements FollowTheSunModel<ContainerType, ItemType> {
-
- private static final Logger LOG = LoggerFactory.getLogger(DefaultFollowTheSunModel.class);
-
- // Concurrent maps cannot have null value; use this to represent when no container is supplied for an item
- private static final String NULL = "null-val";
- private static final Location NULL_LOCATION = new AbstractLocation(newHashMap("name","null-location")) {};
-
- private final String name;
- private final Set<ContainerType> containers = Collections.newSetFromMap(new ConcurrentHashMap<ContainerType,Boolean>());
- private final Map<ItemType, ContainerType> itemToContainer = new ConcurrentHashMap<ItemType, ContainerType>();
- private final Map<ContainerType, Location> containerToLocation = new ConcurrentHashMap<ContainerType, Location>();
- private final Map<ItemType, Location> itemToLocation = new ConcurrentHashMap<ItemType, Location>();
- private final Map<ItemType, Map<? extends ItemType, Double>> itemUsage = new ConcurrentHashMap<ItemType, Map<? extends ItemType,Double>>();
- private final Set<ItemType> immovableItems = Collections.newSetFromMap(new ConcurrentHashMap<ItemType, Boolean>());
-
- public DefaultFollowTheSunModel(String name) {
- this.name = name;
- }
-
- @Override
- public Set<ItemType> getItems() {
- return itemToContainer.keySet();
- }
-
- @Override
- public ContainerType getItemContainer(ItemType item) {
- ContainerType result = itemToContainer.get(item);
- return (isNull(result) ? null : result);
- }
-
- @Override
- public Location getItemLocation(ItemType item) {
- Location result = itemToLocation.get(item);
- return (isNull(result) ? null : result);
- }
-
- @Override
- public Location getContainerLocation(ContainerType container) {
- Location result = containerToLocation.get(container);
- return (isNull(result) ? null : result);
- }
-
- // Provider methods.
-
- @Override public String getName() {
- return name;
- }
-
- // TODO: delete?
- @Override public String getName(ItemType item) {
- return item.toString();
- }
-
- @Override public boolean isItemMoveable(ItemType item) {
- // If don't know about item, then assume not movable; otherwise has this item been explicitly flagged as immovable?
- return hasItem(item) && !immovableItems.contains(item);
- }
-
- @Override public boolean isItemAllowedIn(ItemType item, Location location) {
- return true; // TODO?
- }
-
- @Override public boolean hasActiveMigration(ItemType item) {
- return false; // TODO?
- }
-
- @Override
- // FIXME Too expensive to compute; store in a different data structure?
- public Map<ItemType, Map<Location, Double>> getDirectSendsToItemByLocation() {
- Map<ItemType, Map<Location, Double>> result = new LinkedHashMap<ItemType, Map<Location,Double>>(getNumItems());
-
- for (Map.Entry<ItemType, Map<? extends ItemType, Double>> entry : itemUsage.entrySet()) {
- ItemType targetItem = entry.getKey();
- Map<? extends ItemType, Double> sources = entry.getValue();
- if (sources.isEmpty()) continue; // no-one talking to us
-
- Map<Location, Double> targetUsageByLocation = new LinkedHashMap<Location, Double>();
- result.put(targetItem, targetUsageByLocation);
-
- for (Map.Entry<? extends ItemType, Double> entry2 : sources.entrySet()) {
- ItemType sourceItem = entry2.getKey();
- Location sourceLocation = getItemLocation(sourceItem);
- double usageVal = (entry.getValue() != null) ? entry2.getValue() : 0d;
- if (sourceLocation == null) continue; // don't know where to attribute this load; e.g. item may have just terminated
- if (sourceItem.equals(targetItem)) continue; // ignore msgs to self
-
- Double usageValTotal = targetUsageByLocation.get(sourceLocation);
- double newUsageValTotal = (usageValTotal != null ? usageValTotal : 0d) + usageVal;
- targetUsageByLocation.put(sourceLocation, newUsageValTotal);
- }
- }
-
- return result;
- }
-
- @Override
- public Set<ContainerType> getAvailableContainersFor(ItemType item, Location location) {
- checkNotNull(location);
- return getContainersInLocation(location);
- }
-
-
- // Mutators.
-
- @Override
- public void onItemMoved(ItemType item, ContainerType newContainer) {
- // idempotent, as may be called multiple times
- Location newLocation = (newContainer != null) ? containerToLocation.get(newContainer) : null;
- ContainerType newContainerNonNull = toNonNullContainer(newContainer);
- Location newLocationNonNull = toNonNullLocation(newLocation);
- ContainerType oldContainer = itemToContainer.put(item, newContainerNonNull);
- Location oldLocation = itemToLocation.put(item, newLocationNonNull);
- }
-
- @Override
- public void onContainerAdded(ContainerType container, Location location) {
- Location locationNonNull = toNonNullLocation(location);
- containers.add(container);
- containerToLocation.put(container, locationNonNull);
- for (ItemType item : getItemsOnContainer(container)) {
- itemToLocation.put(item, locationNonNull);
- }
- }
-
- @Override
- public void onContainerRemoved(ContainerType container) {
- containers.remove(container);
- containerToLocation.remove(container);
- }
-
- public void onContainerLocationUpdated(ContainerType container, Location location) {
- if (!containers.contains(container)) {
- // unknown container; probably just stopped?
- // If this overtook onContainerAdded, then assume we'll lookup the location and get it right in onContainerAdded
- if (LOG.isDebugEnabled()) LOG.debug("Ignoring setting of location for unknown container {}, to {}", container, location);
- return;
- }
- Location locationNonNull = toNonNullLocation(location);
- containerToLocation.put(container, locationNonNull);
- for (ItemType item : getItemsOnContainer(container)) {
- itemToLocation.put(item, locationNonNull);
- }
- }
-
- @Override
- public void onItemAdded(ItemType item, ContainerType container, boolean immovable) {
- // idempotent, as may be called multiple times
-
- if (immovable) {
- immovableItems.add(item);
- }
- Location location = (container != null) ? containerToLocation.get(container) : null;
- ContainerType containerNonNull = toNonNullContainer(container);
- Location locationNonNull = toNonNullLocation(location);
- ContainerType oldContainer = itemToContainer.put(item, containerNonNull);
- Location oldLocation = itemToLocation.put(item, locationNonNull);
- }
-
- @Override
- public void onItemRemoved(ItemType item) {
- itemToContainer.remove(item);
- itemToLocation.remove(item);
- itemUsage.remove(item);
- immovableItems.remove(item);
- }
-
- @Override
- public void onItemUsageUpdated(ItemType item, Map<? extends ItemType, Double> newValue) {
- if (hasItem(item)) {
- itemUsage.put(item, newValue);
- } else {
- // Can happen when item removed - get notification of removal and workrate from group and item
- // respectively, so can overtake each other
- if (LOG.isDebugEnabled()) LOG.debug("Ignoring setting of usage for unknown item {}, to {}", item, newValue);
- }
- }
-
-
- // Additional methods for tests.
-
- /**
- * Warning: this can be an expensive (time and memory) operation if there are a lot of items/containers.
- */
- @VisibleForTesting
- public String itemDistributionToString() {
- ByteArrayOutputStream baos = new ByteArrayOutputStream();
- dumpItemDistribution(new PrintStream(baos));
- return new String(baos.toByteArray());
- }
-
- @VisibleForTesting
- public void dumpItemDistribution() {
- dumpItemDistribution(System.out);
- }
-
- @VisibleForTesting
- public void dumpItemDistribution(PrintStream out) {
- Map<ItemType, Map<Location, Double>> directSendsToItemByLocation = getDirectSendsToItemByLocation();
-
- out.println("Follow-The-Sun dump: ");
- for (Location location: getLocations()) {
- out.println("\t"+"Location "+location);
- for (ContainerType container : getContainersInLocation(location)) {
- out.println("\t\t"+"Container "+container);
- for (ItemType item : getItemsOnContainer(container)) {
- Map<Location, Double> inboundUsage = directSendsToItemByLocation.get(item);
- Map<? extends ItemType, Double> outboundUsage = itemUsage.get(item);
- double totalInboundByLocation = (inboundUsage != null) ? sum(inboundUsage.values()) : 0d;
- double totalInboundByActor = (outboundUsage != null) ? sum(outboundUsage.values()) : 0d;
- out.println("\t\t\t"+"Item "+item);
- out.println("\t\t\t\t"+"Inbound-by-location: "+totalInboundByLocation+": "+inboundUsage);
- out.println("\t\t\t\t"+"Inbound-by-actor: "+totalInboundByActor+": "+outboundUsage);
- }
- }
- }
- out.flush();
- }
-
- private boolean hasItem(ItemType item) {
- return itemToContainer.containsKey(item);
- }
-
- private Set<Location> getLocations() {
- return ImmutableSet.copyOf(containerToLocation.values());
- }
-
- private Set<ContainerType> getContainersInLocation(Location location) {
- Set<ContainerType> result = new LinkedHashSet<ContainerType>();
- for (Map.Entry<ContainerType, Location> entry : containerToLocation.entrySet()) {
- if (location.equals(entry.getValue())) {
- result.add(entry.getKey());
- }
- }
- return result;
- }
-
- private Set<ItemType> getItemsOnContainer(ContainerType container) {
- Set<ItemType> result = new LinkedHashSet<ItemType>();
- for (Map.Entry<ItemType, ContainerType> entry : itemToContainer.entrySet()) {
- if (container.equals(entry.getValue())) {
- result.add(entry.getKey());
- }
- }
- return result;
- }
-
- private int getNumItems() {
- return itemToContainer.size();
- }
-
- @SuppressWarnings("unchecked")
- private ContainerType nullContainer() {
- return (ContainerType) NULL; // relies on erasure
- }
-
- private Location nullLocation() {
- return NULL_LOCATION;
- }
-
- private ContainerType toNonNullContainer(ContainerType val) {
- return (val != null) ? val : nullContainer();
- }
-
- private Location toNonNullLocation(Location val) {
- return (val != null) ? val : nullLocation();
- }
-
- private boolean isNull(Object val) {
- return val == NULL || val == NULL_LOCATION;
- }
-
- // TODO Move to utils; or stop AbstractLocation from removing things from the map!
- public static <K,V> Map<K,V> newHashMap(K k, V v) {
- Map<K,V> result = Maps.newLinkedHashMap();
- result.put(k, v);
- return result;
- }
-
- public static double sum(Collection<? extends Number> values) {
- double total = 0;
- for (Number d : values) {
- total += d.doubleValue();
- }
- return total;
- }
-}
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/d30ff597/policy/src/main/java/brooklyn/policy/followthesun/FollowTheSunModel.java
----------------------------------------------------------------------
diff --git a/policy/src/main/java/brooklyn/policy/followthesun/FollowTheSunModel.java b/policy/src/main/java/brooklyn/policy/followthesun/FollowTheSunModel.java
deleted file mode 100644
index a181ae4..0000000
--- a/policy/src/main/java/brooklyn/policy/followthesun/FollowTheSunModel.java
+++ /dev/null
@@ -1,56 +0,0 @@
-/*
- * 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 brooklyn.policy.followthesun;
-
-import java.util.Map;
-import java.util.Set;
-
-import org.apache.brooklyn.api.location.Location;
-
-/**
- * Captures the state of items, containers and locations for the purpose of moving items around
- * to minimise latency. For consumption by a {@link FollowTheSunStrategy}.
- */
-public interface FollowTheSunModel<ContainerType, ItemType> {
-
- // Attributes of the pool.
- public String getName();
-
- // Attributes of containers and items.
- public String getName(ItemType item);
- public Set<ItemType> getItems();
- public Map<ItemType, Map<Location, Double>> getDirectSendsToItemByLocation();
- public Location getItemLocation(ItemType item);
- public ContainerType getItemContainer(ItemType item);
- public Location getContainerLocation(ContainerType container);
- public boolean hasActiveMigration(ItemType item);
- public Set<ContainerType> getAvailableContainersFor(ItemType item, Location location);
- public boolean isItemMoveable(ItemType item);
- public boolean isItemAllowedIn(ItemType item, Location location);
-
- // Mutators for keeping the model in-sync with the observed world
- public void onContainerAdded(ContainerType container, Location location);
- public void onContainerRemoved(ContainerType container);
- public void onContainerLocationUpdated(ContainerType container, Location location);
-
- public void onItemAdded(ItemType item, ContainerType parentContainer, boolean immovable);
- public void onItemRemoved(ItemType item);
- public void onItemUsageUpdated(ItemType item, Map<? extends ItemType, Double> newValues);
- public void onItemMoved(ItemType item, ContainerType newContainer);
-}
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/d30ff597/policy/src/main/java/brooklyn/policy/followthesun/FollowTheSunParameters.java
----------------------------------------------------------------------
diff --git a/policy/src/main/java/brooklyn/policy/followthesun/FollowTheSunParameters.java b/policy/src/main/java/brooklyn/policy/followthesun/FollowTheSunParameters.java
deleted file mode 100644
index 2b2d9af..0000000
--- a/policy/src/main/java/brooklyn/policy/followthesun/FollowTheSunParameters.java
+++ /dev/null
@@ -1,95 +0,0 @@
-/*
- * 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 brooklyn.policy.followthesun;
-
-import java.util.LinkedHashSet;
-import java.util.Set;
-
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.apache.brooklyn.api.location.Location;
-
-public class FollowTheSunParameters {
-
- private static final Logger LOG = LoggerFactory.getLogger(FollowTheSunParameters.class);
-
- private FollowTheSunParameters() {}
-
- /** trigger for moving segment X from geo A to geo B:
- * where x is total number of requests submitted in X across the CDM network,
- * and x_A is number of reqs from geo A, with A the most prolific geography
- * (arbitrarily chosen in case of ties so recommended to choose at least a small percent_majority or delta_above_percent_majority, in addition to this field);
- * this parameter T defines a number such that x_A > T*x in order for X to be migrated to A
- * (but see also DELTA_ABOVE_PERCENT_TOTAL, below) */
- public double triggerPercentTotal = 0.3;
- /** fields as above, and T as above,
- * this parameter T' defines a number such that x_A > T*x + T' in order for X to be migrated to A */
- public double triggerDeltaAbovePercentTotal = 0;
- /** fields as above,
- * this parameter T defines a number such that x_A > T in order for X to be migrated to A */
- public double triggerAbsoluteTotal = 2;
-
- /** fields as above, with X_B the number from a different geography B,
- * where A and B are the two most prolific requesters of X, and X_A >= X_B;
- * this parameter T defines a number such that x_A-x_B > T*x in order for X to be migrated to A */
- public double triggerPercentMajority = 0.2;
- /** as corresponding majority and total fields, with x_A-x_B on the LHS of inequality */
- public double triggerDeltaAbovePercentMajority = 1;
- /** as corresponding majority and total fields, with x_A-x_B on the LHS of inequality */
- public double triggerAbsoluteMajority = 4;
-
- /** a list of excluded locations */
- public Set<Location> excludedLocations = new LinkedHashSet<Location>();
-
- public static FollowTheSunParameters newDefault() {
- return new FollowTheSunParameters();
- }
-
- private static double parseDouble(String text, double defaultValue) {
- try {
- double d = Double.parseDouble(text);
- if (!Double.isNaN(d)) return d;
- } catch (Exception e) {
- LOG.warn("Illegal double value '"+text+"', using default "+defaultValue+": "+e, e);
- }
- return defaultValue;
- }
-
- private static String[] parseCommaSeparatedList(String csv) {
- if (csv==null || csv.trim().length()==0) return new String[0];
- return csv.split(",");
- }
-
- public boolean isTriggered(double highest, double total, double nextHighest, double current) {
- if (highest <= current) return false;
- if (highest < total*triggerPercentTotal + triggerDeltaAbovePercentTotal) return false;
- if (highest < triggerAbsoluteTotal) return false;
- //TODO more params about nextHighest vs current
- if (highest-current < total*triggerPercentMajority + triggerDeltaAbovePercentMajority) return false;
- if (highest-current < triggerAbsoluteMajority) return false;
- return true;
- }
-
- public String toString() {
- return "Inter-geography policy params: percentTotal="+triggerPercentTotal+"; deltaAbovePercentTotal="+triggerDeltaAbovePercentTotal+
- "; absoluteTotal="+triggerAbsoluteTotal+"; percentMajority="+triggerPercentMajority+
- "; deltaAbovePercentMajority="+triggerDeltaAbovePercentMajority+"; absoluteMajority="+triggerAbsoluteMajority;
-
- }
-}
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/d30ff597/policy/src/main/java/brooklyn/policy/followthesun/FollowTheSunPolicy.java
----------------------------------------------------------------------
diff --git a/policy/src/main/java/brooklyn/policy/followthesun/FollowTheSunPolicy.java b/policy/src/main/java/brooklyn/policy/followthesun/FollowTheSunPolicy.java
deleted file mode 100644
index b76682c..0000000
--- a/policy/src/main/java/brooklyn/policy/followthesun/FollowTheSunPolicy.java
+++ /dev/null
@@ -1,280 +0,0 @@
-/*
- * 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 brooklyn.policy.followthesun;
-
-import static brooklyn.util.JavaGroovyEquivalents.elvis;
-import static brooklyn.util.JavaGroovyEquivalents.groovyTruth;
-import static com.google.common.base.Preconditions.checkArgument;
-
-import java.util.Collection;
-import java.util.Map;
-import java.util.concurrent.Executors;
-import java.util.concurrent.ScheduledExecutorService;
-import java.util.concurrent.ThreadFactory;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.atomic.AtomicBoolean;
-
-import org.apache.brooklyn.api.entity.Entity;
-import org.apache.brooklyn.api.entity.basic.EntityLocal;
-import org.apache.brooklyn.api.event.AttributeSensor;
-import org.apache.brooklyn.api.event.Sensor;
-import org.apache.brooklyn.api.event.SensorEvent;
-import org.apache.brooklyn.api.event.SensorEventListener;
-import org.apache.brooklyn.api.location.Location;
-import org.apache.brooklyn.api.location.MachineProvisioningLocation;
-import org.apache.brooklyn.core.policy.basic.AbstractPolicy;
-import org.apache.brooklyn.core.util.flags.SetFromFlag;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import brooklyn.entity.basic.Attributes;
-import brooklyn.policy.followthesun.FollowTheSunPool.ContainerItemPair;
-import brooklyn.policy.loadbalancing.Movable;
-import brooklyn.util.collections.MutableMap;
-
-import com.google.common.base.Function;
-import com.google.common.collect.Iterables;
-import com.google.common.util.concurrent.ThreadFactoryBuilder;
-
- // removed from catalog because it cannot currently be configured via catalog mechanisms -
- // PolicySpec.create fails due to no no-arg constructor
- // TODO make model and parameters things which can be initialized from config then reinstate in catalog
-//@Catalog(name="Follow the Sun", description="Policy for moving \"work\" around to follow the demand; "
-// + "the work can be any \"Movable\" entity")
-public class FollowTheSunPolicy extends AbstractPolicy {
-
- private static final Logger LOG = LoggerFactory.getLogger(FollowTheSunPolicy.class);
-
- public static final String NAME = "Follow the Sun (Inter-Geography Latency Optimization)";
-
- @SetFromFlag(defaultVal="100")
- private long minPeriodBetweenExecs;
-
- @SetFromFlag
- private Function<Entity, Location> locationFinder;
-
- private final AttributeSensor<Map<? extends Movable, Double>> itemUsageMetric;
- private final FollowTheSunModel<Entity, Movable> model;
- private final FollowTheSunStrategy<Entity, Movable> strategy;
- private final FollowTheSunParameters parameters;
-
- private FollowTheSunPool poolEntity;
-
- private volatile ScheduledExecutorService executor;
- private final AtomicBoolean executorQueued = new AtomicBoolean(false);
- private volatile long executorTime = 0;
- private boolean loggedConstraintsIgnored = false;
-
- private final Function<Entity, Location> defaultLocationFinder = new Function<Entity, Location>() {
- public Location apply(Entity e) {
- Collection<Location> locs = e.getLocations();
- if (locs.isEmpty()) return null;
- Location contender = Iterables.get(locs, 0);
- while (contender.getParent() != null && !(contender instanceof MachineProvisioningLocation)) {
- contender = contender.getParent();
- }
- return contender;
- }
- };
-
- private final SensorEventListener<Object> eventHandler = new SensorEventListener<Object>() {
- @Override
- public void onEvent(SensorEvent<Object> event) {
- if (LOG.isTraceEnabled()) LOG.trace("{} received event {}", FollowTheSunPolicy.this, event);
- Entity source = event.getSource();
- Object value = event.getValue();
- Sensor<?> sensor = event.getSensor();
-
- if (sensor.equals(itemUsageMetric)) {
- onItemMetricUpdated((Movable)source, (Map<? extends Movable, Double>) value, true);
- } else if (sensor.equals(Attributes.LOCATION_CHANGED)) {
- onContainerLocationUpdated(source, true);
- } else if (sensor.equals(FollowTheSunPool.CONTAINER_ADDED)) {
- onContainerAdded((Entity) value, true);
- } else if (sensor.equals(FollowTheSunPool.CONTAINER_REMOVED)) {
- onContainerRemoved((Entity) value, true);
- } else if (sensor.equals(FollowTheSunPool.ITEM_ADDED)) {
- onItemAdded((Movable) value, true);
- } else if (sensor.equals(FollowTheSunPool.ITEM_REMOVED)) {
- onItemRemoved((Movable) value, true);
- } else if (sensor.equals(FollowTheSunPool.ITEM_MOVED)) {
- ContainerItemPair pair = (ContainerItemPair) value;
- onItemMoved((Movable)pair.item, pair.container, true);
- }
- }
- };
-
- // FIXME parameters: use a more groovy way of doing it, that's consistent with other policies/entities?
- public FollowTheSunPolicy(AttributeSensor itemUsageMetric,
- FollowTheSunModel<Entity, Movable> model, FollowTheSunParameters parameters) {
- this(MutableMap.of(), itemUsageMetric, model, parameters);
- }
-
- public FollowTheSunPolicy(Map props, AttributeSensor itemUsageMetric,
- FollowTheSunModel<Entity, Movable> model, FollowTheSunParameters parameters) {
- super(props);
- this.itemUsageMetric = itemUsageMetric;
- this.model = model;
- this.parameters = parameters;
- this.strategy = new FollowTheSunStrategy<Entity, Movable>(model, parameters); // TODO: extract interface, inject impl
- this.locationFinder = elvis(locationFinder, defaultLocationFinder);
-
- // TODO Should re-use the execution manager's thread pool, somehow
- executor = Executors.newSingleThreadScheduledExecutor(newThreadFactory());
- }
-
- @Override
- public void setEntity(EntityLocal entity) {
- checkArgument(entity instanceof FollowTheSunPool, "Provided entity must be a FollowTheSunPool");
- super.setEntity(entity);
- this.poolEntity = (FollowTheSunPool) entity;
-
- // Detect when containers are added to or removed from the pool.
- subscribe(poolEntity, FollowTheSunPool.CONTAINER_ADDED, eventHandler);
- subscribe(poolEntity, FollowTheSunPool.CONTAINER_REMOVED, eventHandler);
- subscribe(poolEntity, FollowTheSunPool.ITEM_ADDED, eventHandler);
- subscribe(poolEntity, FollowTheSunPool.ITEM_REMOVED, eventHandler);
- subscribe(poolEntity, FollowTheSunPool.ITEM_MOVED, eventHandler);
-
- // Take heed of any extant containers.
- for (Entity container : poolEntity.getContainerGroup().getMembers()) {
- onContainerAdded(container, false);
- }
- for (Entity item : poolEntity.getItemGroup().getMembers()) {
- onItemAdded((Movable)item, false);
- }
-
- scheduleLatencyReductionJig();
- }
-
- @Override
- public void suspend() {
- // TODO unsubscribe from everything? And resubscribe on resume?
- super.suspend();
- if (executor != null) executor.shutdownNow();
- executorQueued.set(false);
- }
-
- @Override
- public void resume() {
- super.resume();
- executor = Executors.newSingleThreadScheduledExecutor(newThreadFactory());
- executorTime = 0;
- executorQueued.set(false);
- }
-
- private ThreadFactory newThreadFactory() {
- return new ThreadFactoryBuilder()
- .setNameFormat("brooklyn-followthesunpolicy-%d")
- .build();
- }
-
- private void scheduleLatencyReductionJig() {
- if (isRunning() && executorQueued.compareAndSet(false, true)) {
- long now = System.currentTimeMillis();
- long delay = Math.max(0, (executorTime + minPeriodBetweenExecs) - now);
-
- executor.schedule(new Runnable() {
- public void run() {
- try {
- executorTime = System.currentTimeMillis();
- executorQueued.set(false);
-
- if (LOG.isTraceEnabled()) LOG.trace("{} executing follow-the-sun migration-strategy", this);
- strategy.rebalance();
-
- } catch (RuntimeException e) {
- if (isRunning()) {
- LOG.error("Error during latency-reduction-jig", e);
- } else {
- LOG.debug("Error during latency-reduction-jig, but no longer running", e);
- }
- }
- }},
- delay,
- TimeUnit.MILLISECONDS);
- }
- }
-
- private void onContainerAdded(Entity container, boolean rebalanceNow) {
- subscribe(container, Attributes.LOCATION_CHANGED, eventHandler);
- Location location = locationFinder.apply(container);
-
- if (LOG.isTraceEnabled()) LOG.trace("{} recording addition of container {} in location {}", new Object[] {this, container, location});
- model.onContainerAdded(container, location);
-
- if (rebalanceNow) scheduleLatencyReductionJig();
- }
-
- private void onContainerRemoved(Entity container, boolean rebalanceNow) {
- if (LOG.isTraceEnabled()) LOG.trace("{} recording removal of container {}", this, container);
- model.onContainerRemoved(container);
- if (rebalanceNow) scheduleLatencyReductionJig();
- }
-
- private void onItemAdded(Movable item, boolean rebalanceNow) {
- Entity parentContainer = (Entity) item.getAttribute(Movable.CONTAINER);
-
- if (LOG.isTraceEnabled()) LOG.trace("{} recording addition of item {} in container {}", new Object[] {this, item, parentContainer});
-
- subscribe(item, itemUsageMetric, eventHandler);
-
- // Update the model, including the current metric value (if any).
- Map<? extends Movable, Double> currentValue = item.getAttribute(itemUsageMetric);
- boolean immovable = (Boolean)elvis(item.getConfig(Movable.IMMOVABLE), false);
- model.onItemAdded(item, parentContainer, immovable);
-
- if (currentValue != null) {
- model.onItemUsageUpdated(item, currentValue);
- }
-
- if (rebalanceNow) scheduleLatencyReductionJig();
- }
-
- private void onItemRemoved(Movable item, boolean rebalanceNow) {
- if (LOG.isTraceEnabled()) LOG.trace("{} recording removal of item {}", this, item);
- unsubscribe(item);
- model.onItemRemoved(item);
- if (rebalanceNow) scheduleLatencyReductionJig();
- }
-
- private void onItemMoved(Movable item, Entity parentContainer, boolean rebalanceNow) {
- if (LOG.isTraceEnabled()) LOG.trace("{} recording moving of item {} to {}", new Object[] {this, item, parentContainer});
- model.onItemMoved(item, parentContainer);
- if (rebalanceNow) scheduleLatencyReductionJig();
- }
-
- private void onContainerLocationUpdated(Entity container, boolean rebalanceNow) {
- Location location = locationFinder.apply(container);
- if (LOG.isTraceEnabled()) LOG.trace("{} recording location for container {}, new value {}", new Object[] {this, container, location});
- model.onContainerLocationUpdated(container, location);
- if (rebalanceNow) scheduleLatencyReductionJig();
- }
-
- private void onItemMetricUpdated(Movable item, Map<? extends Movable, Double> newValues, boolean rebalanceNow) {
- if (LOG.isTraceEnabled()) LOG.trace("{} recording usage update for item {}, new value {}", new Object[] {this, item, newValues});
- model.onItemUsageUpdated(item, newValues);
- if (rebalanceNow) scheduleLatencyReductionJig();
- }
-
- @Override
- public String toString() {
- return getClass().getSimpleName() + (groovyTruth(name) ? "("+name+")" : "");
- }
-}
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/d30ff597/policy/src/main/java/brooklyn/policy/followthesun/FollowTheSunPool.java
----------------------------------------------------------------------
diff --git a/policy/src/main/java/brooklyn/policy/followthesun/FollowTheSunPool.java b/policy/src/main/java/brooklyn/policy/followthesun/FollowTheSunPool.java
deleted file mode 100644
index 402eeef..0000000
--- a/policy/src/main/java/brooklyn/policy/followthesun/FollowTheSunPool.java
+++ /dev/null
@@ -1,75 +0,0 @@
-/*
- * 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 brooklyn.policy.followthesun;
-
-import static com.google.common.base.Preconditions.checkNotNull;
-
-import java.io.Serializable;
-
-import org.apache.brooklyn.api.entity.Entity;
-import org.apache.brooklyn.api.entity.Group;
-import org.apache.brooklyn.api.entity.proxying.ImplementedBy;
-
-import brooklyn.entity.trait.Resizable;
-import brooklyn.event.basic.BasicNotificationSensor;
-
-@ImplementedBy(FollowTheSunPoolImpl.class)
-public interface FollowTheSunPool extends Entity, Resizable {
-
- // FIXME Remove duplication from BalanceableWorkerPool?
-
- // FIXME Asymmetry between loadbalancing and followTheSun: ITEM_ADDED and ITEM_REMOVED in loadbalancing
- // are of type ContainerItemPair, but in followTheSun it is just the `Entity item`.
-
- /** Encapsulates an item and a container; emitted by sensors.
- */
- public static class ContainerItemPair implements Serializable {
- private static final long serialVersionUID = 1L;
- public final Entity container;
- public final Entity item;
-
- public ContainerItemPair(Entity container, Entity item) {
- this.container = container;
- this.item = checkNotNull(item);
- }
-
- @Override
- public String toString() {
- return ""+item+" @ "+container;
- }
- }
-
- // Pool constituent notifications.
- public static BasicNotificationSensor<Entity> CONTAINER_ADDED = new BasicNotificationSensor<Entity>(
- Entity.class, "followthesun.container.added", "Container added");
- public static BasicNotificationSensor<Entity> CONTAINER_REMOVED = new BasicNotificationSensor<Entity>(
- Entity.class, "followthesun.container.removed", "Container removed");
- public static BasicNotificationSensor<Entity> ITEM_ADDED = new BasicNotificationSensor<Entity>(
- Entity.class, "followthesun.item.added", "Item added");
- public static BasicNotificationSensor<Entity> ITEM_REMOVED = new BasicNotificationSensor<Entity>(
- Entity.class, "followthesun.item.removed", "Item removed");
- public static BasicNotificationSensor<ContainerItemPair> ITEM_MOVED = new BasicNotificationSensor<ContainerItemPair>(
- ContainerItemPair.class, "followthesun.item.moved", "Item moved to the given container");
-
- public void setContents(Group containerGroup, Group itemGroup);
-
- public Group getContainerGroup();
-
- public Group getItemGroup();
-}
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/d30ff597/policy/src/main/java/brooklyn/policy/followthesun/FollowTheSunPoolImpl.java
----------------------------------------------------------------------
diff --git a/policy/src/main/java/brooklyn/policy/followthesun/FollowTheSunPoolImpl.java b/policy/src/main/java/brooklyn/policy/followthesun/FollowTheSunPoolImpl.java
deleted file mode 100644
index 4d74441..0000000
--- a/policy/src/main/java/brooklyn/policy/followthesun/FollowTheSunPoolImpl.java
+++ /dev/null
@@ -1,178 +0,0 @@
-/*
- * 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 brooklyn.policy.followthesun;
-
-import java.util.Collections;
-import java.util.HashSet;
-import java.util.Set;
-
-import org.apache.brooklyn.api.entity.Entity;
-import org.apache.brooklyn.api.entity.Group;
-import org.apache.brooklyn.api.event.Sensor;
-import org.apache.brooklyn.api.event.SensorEvent;
-import org.apache.brooklyn.api.event.SensorEventListener;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import brooklyn.entity.basic.AbstractEntity;
-import brooklyn.entity.basic.AbstractGroup;
-import brooklyn.entity.trait.Resizable;
-import brooklyn.entity.trait.Startable;
-import brooklyn.policy.loadbalancing.Movable;
-
-public class FollowTheSunPoolImpl extends AbstractEntity implements FollowTheSunPool {
-
- // FIXME Remove duplication from BalanceableWorkerPool?
-
- // FIXME Asymmetry between loadbalancing and followTheSun: ITEM_ADDED and ITEM_REMOVED in loadbalancing
- // are of type ContainerItemPair, but in followTheSun it is just the `Entity item`.
-
- private static final Logger LOG = LoggerFactory.getLogger(FollowTheSunPool.class);
-
- private Group containerGroup;
- private Group itemGroup;
-
- private final Set<Entity> containers = Collections.synchronizedSet(new HashSet<Entity>());
- private final Set<Entity> items = Collections.synchronizedSet(new HashSet<Entity>());
-
- private final SensorEventListener<Object> eventHandler = new SensorEventListener<Object>() {
- @Override
- public void onEvent(SensorEvent<Object> event) {
- if (LOG.isTraceEnabled()) LOG.trace("{} received event {}", FollowTheSunPoolImpl.this, event);
- Entity source = event.getSource();
- Object value = event.getValue();
- Sensor sensor = event.getSensor();
-
- if (sensor.equals(AbstractGroup.MEMBER_ADDED)) {
- if (source.equals(containerGroup)) {
- onContainerAdded((Entity) value);
- } else if (source.equals(itemGroup)) {
- onItemAdded((Entity)value);
- } else {
- throw new IllegalStateException("unexpected event source="+source);
- }
- } else if (sensor.equals(AbstractGroup.MEMBER_REMOVED)) {
- if (source.equals(containerGroup)) {
- onContainerRemoved((Entity) value);
- } else if (source.equals(itemGroup)) {
- onItemRemoved((Entity) value);
- } else {
- throw new IllegalStateException("unexpected event source="+source);
- }
- } else if (sensor.equals(Startable.SERVICE_UP)) {
- // TODO What if start has failed? Is there a sensor to indicate that?
- if ((Boolean)value) {
- onContainerUp(source);
- } else {
- onContainerDown(source);
- }
- } else if (sensor.equals(Movable.CONTAINER)) {
- onItemMoved(source, (Entity) value);
- } else {
- throw new IllegalStateException("Unhandled event type "+sensor+": "+event);
- }
- }
- };
-
- public FollowTheSunPoolImpl() {
- }
-
- @Override
- public void setContents(Group containerGroup, Group itemGroup) {
- this.containerGroup = containerGroup;
- this.itemGroup = itemGroup;
- subscribe(containerGroup, AbstractGroup.MEMBER_ADDED, eventHandler);
- subscribe(containerGroup, AbstractGroup.MEMBER_REMOVED, eventHandler);
- subscribe(itemGroup, AbstractGroup.MEMBER_ADDED, eventHandler);
- subscribe(itemGroup, AbstractGroup.MEMBER_REMOVED, eventHandler);
-
- // Process extant containers and items
- for (Entity existingContainer : containerGroup.getMembers()) {
- onContainerAdded(existingContainer);
- }
- for (Entity existingItem : itemGroup.getMembers()) {
- onItemAdded((Entity)existingItem);
- }
- }
-
- @Override
- public Group getContainerGroup() {
- return containerGroup;
- }
-
- @Override
- public Group getItemGroup() {
- return itemGroup;
- }
-
- @Override
- public Integer getCurrentSize() {
- return containerGroup.getCurrentSize();
- }
-
- @Override
- public Integer resize(Integer desiredSize) {
- if (containerGroup instanceof Resizable) return ((Resizable) containerGroup).resize(desiredSize);
-
- throw new UnsupportedOperationException("Container group is not resizable");
- }
-
-
- private void onContainerAdded(Entity newContainer) {
- subscribe(newContainer, Startable.SERVICE_UP, eventHandler);
- if (!(newContainer instanceof Startable) || Boolean.TRUE.equals(newContainer.getAttribute(Startable.SERVICE_UP))) {
- onContainerUp(newContainer);
- }
- }
-
- private void onContainerUp(Entity newContainer) {
- if (containers.add(newContainer)) {
- emit(CONTAINER_ADDED, newContainer);
- }
- }
-
- private void onContainerDown(Entity oldContainer) {
- if (containers.remove(oldContainer)) {
- emit(CONTAINER_REMOVED, oldContainer);
- }
- }
-
- private void onContainerRemoved(Entity oldContainer) {
- unsubscribe(oldContainer);
- onContainerDown(oldContainer);
- }
-
- private void onItemAdded(Entity item) {
- if (items.add(item)) {
- subscribe(item, Movable.CONTAINER, eventHandler);
- emit(ITEM_ADDED, item);
- }
- }
-
- private void onItemRemoved(Entity item) {
- if (items.remove(item)) {
- unsubscribe(item);
- emit(ITEM_REMOVED, item);
- }
- }
-
- private void onItemMoved(Entity item, Entity container) {
- emit(ITEM_MOVED, new ContainerItemPair(container, item));
- }
-}
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/d30ff597/policy/src/main/java/brooklyn/policy/followthesun/FollowTheSunStrategy.java
----------------------------------------------------------------------
diff --git a/policy/src/main/java/brooklyn/policy/followthesun/FollowTheSunStrategy.java b/policy/src/main/java/brooklyn/policy/followthesun/FollowTheSunStrategy.java
deleted file mode 100644
index e8ca636..0000000
--- a/policy/src/main/java/brooklyn/policy/followthesun/FollowTheSunStrategy.java
+++ /dev/null
@@ -1,161 +0,0 @@
-/*
- * 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 brooklyn.policy.followthesun;
-
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-
-import org.apache.brooklyn.api.entity.Entity;
-import org.apache.brooklyn.api.location.Location;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import brooklyn.policy.loadbalancing.Movable;
-
-import com.google.common.collect.Iterables;
-
-// TODO: extract interface
-public class FollowTheSunStrategy<ContainerType extends Entity, ItemType extends Movable> {
-
- // This is a modified version of the InterGeographyLatencyPolicy (aka Follow-The-Sun) policy from Monterey v3.
-
- // TODO location constraints
-
- private static final Logger LOG = LoggerFactory.getLogger(FollowTheSunStrategy.class);
-
- private final FollowTheSunParameters parameters;
- private final FollowTheSunModel<ContainerType,ItemType> model;
- private final String name;
-
- public FollowTheSunStrategy(FollowTheSunModel<ContainerType,ItemType> model, FollowTheSunParameters parameters) {
- this.model = model;
- this.parameters = parameters;
- this.name = model.getName();
- }
-
- public void rebalance() {
- try {
- Set<ItemType> items = model.getItems();
- Map<ItemType, Map<Location, Double>> directSendsToItemByLocation = model.getDirectSendsToItemByLocation();
-
- for (ItemType item : items) {
- String itemName = model.getName(item);
- Location activeLocation = model.getItemLocation(item);
- ContainerType activeContainer = model.getItemContainer(item);
- Map<Location, Double> sendsByLocation = directSendsToItemByLocation.get(item);
- if (sendsByLocation == null) sendsByLocation = Collections.emptyMap();
-
- if (parameters.excludedLocations.contains(activeLocation)) {
- if (LOG.isTraceEnabled()) LOG.trace("Ignoring segment {} as it is in {}", itemName, activeLocation);
- continue;
- }
- if (!model.isItemMoveable(item)) {
- if (LOG.isDebugEnabled()) LOG.debug("POLICY {} skipping any migration of {}, it is not moveable", name, itemName);
- continue;
- }
- if (model.hasActiveMigration(item)) {
- LOG.info("POLICY {} skipping any migration of {}, it is involved in an active migration already", name, itemName);
- continue;
- }
-
- double total = DefaultFollowTheSunModel.sum(sendsByLocation.values());
-
- if (LOG.isTraceEnabled()) LOG.trace("POLICY {} detected {} msgs/sec in {}, split up as: {}", new Object[] {name, total, itemName, sendsByLocation});
-
- Double current = sendsByLocation.get(activeLocation);
- if (current == null) current=0d;
- List<WeightedObject<Location>> locationsWtd = new ArrayList<WeightedObject<Location>>();
- if (total > 0) {
- for (Map.Entry<Location, Double> entry : sendsByLocation.entrySet()) {
- Location l = entry.getKey();
- Double d = entry.getValue();
- if (d > current) locationsWtd.add(new WeightedObject<Location>(l, d));
- }
- }
- Collections.sort(locationsWtd);
- Collections.reverse(locationsWtd);
-
- double highestMsgRate = -1;
- Location highestLocation = null;
- ContainerType optimalContainerInHighest = null;
- while (!locationsWtd.isEmpty()) {
- WeightedObject<Location> weightedObject = locationsWtd.remove(0);
- highestMsgRate = weightedObject.getWeight();
- highestLocation = weightedObject.getObject();
- optimalContainerInHighest = findOptimal(model.getAvailableContainersFor(item, highestLocation));
- if (optimalContainerInHighest != null) {
- break;
- }
- }
- if (optimalContainerInHighest == null) {
- if (LOG.isDebugEnabled()) LOG.debug("POLICY {} detected {} is already in optimal permitted location ({} of {} msgs/sec)", new Object[] {name, itemName, highestMsgRate, total});
- continue;
- }
-
- double nextHighestMsgRate = -1;
- ContainerType optimalContainerInNextHighest = null;
- while (!locationsWtd.isEmpty()) {
- WeightedObject<Location> weightedObject = locationsWtd.remove(0);
- nextHighestMsgRate = weightedObject.getWeight();
- Location nextHighestLocation = weightedObject.getObject();
- optimalContainerInNextHighest = findOptimal(model.getAvailableContainersFor(item, nextHighestLocation));
- if (optimalContainerInNextHighest != null) {
- break;
- }
- }
- if (optimalContainerInNextHighest == null) {
- nextHighestMsgRate = current;
- }
-
- if (parameters.isTriggered(highestMsgRate, total, nextHighestMsgRate, current)) {
- LOG.info("POLICY "+name+" detected "+itemName+" should be in location "+highestLocation+" on "+optimalContainerInHighest+" ("+highestMsgRate+" of "+total+" msgs/sec), migrating");
- try {
- if (activeContainer.equals(optimalContainerInHighest)) {
- //shouldn't happen
- LOG.warn("POLICY "+name+" detected "+itemName+" should move to "+optimalContainerInHighest+" ("+highestMsgRate+" of "+total+" msgs/sec) but it is already there with "+current+" msgs/sec");
- } else {
- item.move(optimalContainerInHighest);
- model.onItemMoved(item, optimalContainerInHighest);
- }
- } catch (Exception e) {
- LOG.warn("POLICY "+name+" detected "+itemName+" should be on "+optimalContainerInHighest+", but can't move it: "+e, e);
- }
- } else {
- if (LOG.isTraceEnabled()) LOG.trace("POLICY "+name+" detected "+itemName+" need not move to "+optimalContainerInHighest+" ("+highestMsgRate+" of "+total+" msgs/sec not much better than "+current+" at "+activeContainer+")");
- }
- }
- } catch (Exception e) {
- LOG.warn("Error in policy "+name+" (ignoring): "+e, e);
- }
- }
-
- private ContainerType findOptimal(Collection<ContainerType> contenders) {
- /*
- * TODO should choose the least loaded mediator. Currently chooses first available, and relies
- * on a load-balancer to move it again; would be good if these could share decision code so move
- * it to the right place immediately. e.g.
- * policyUtil.findLeastLoadedMediator(nodesInLocation);
- */
- return (contenders.isEmpty() ? null : Iterables.get(contenders, 0));
- }
-}
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/d30ff597/policy/src/main/java/brooklyn/policy/followthesun/WeightedObject.java
----------------------------------------------------------------------
diff --git a/policy/src/main/java/brooklyn/policy/followthesun/WeightedObject.java b/policy/src/main/java/brooklyn/policy/followthesun/WeightedObject.java
deleted file mode 100644
index b1d506d..0000000
--- a/policy/src/main/java/brooklyn/policy/followthesun/WeightedObject.java
+++ /dev/null
@@ -1,71 +0,0 @@
-/*
- * 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 brooklyn.policy.followthesun;
-
-public class WeightedObject<T> implements Comparable<WeightedObject<T>>{
-
- final T object;
- final double weight;
-
- public WeightedObject(T obj, double weight) {
- this.object = obj;
- this.weight = weight;
- }
-
- public T getObject() {
- return object;
- }
-
- public double getWeight() {
- return weight;
- }
-
- /**
- * Note that equals and compareTo are not consistent: x.compareTo(y)==0 iff x.equals(y) is
- * highly recommended in Java, but is not required. This can make TreeSet etc behave poorly...
- */
- public int compareTo(WeightedObject<T> o) {
- double diff = o.getWeight() - weight;
- if (diff>0.0000000000000001) return -1;
- if (diff<-0.0000000000000001) return 1;
- return 0;
- }
-
- @Override
- /** true irrespective of weight */
- public boolean equals(Object obj) {
- if (!(obj instanceof WeightedObject<?>)) return false;
- if (getObject()==null) {
- return ((WeightedObject<?>)obj).getObject() == null;
- } else {
- return getObject().equals( ((WeightedObject<?>)obj).getObject() );
- }
- }
-
- @Override
- public int hashCode() {
- if (getObject()==null) return 234519078;
- return getObject().hashCode();
- }
-
- @Override
- public String toString() {
- return ""+getObject()+"["+getWeight()+"]";
- }
-}
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/d30ff597/policy/src/main/java/brooklyn/policy/ha/AbstractFailureDetector.java
----------------------------------------------------------------------
diff --git a/policy/src/main/java/brooklyn/policy/ha/AbstractFailureDetector.java b/policy/src/main/java/brooklyn/policy/ha/AbstractFailureDetector.java
deleted file mode 100644
index 9d8c58f..0000000
--- a/policy/src/main/java/brooklyn/policy/ha/AbstractFailureDetector.java
+++ /dev/null
@@ -1,361 +0,0 @@
-/*
- * 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 brooklyn.policy.ha;
-
-import static brooklyn.util.time.Time.makeTimeStringRounded;
-
-import java.util.concurrent.Callable;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.atomic.AtomicBoolean;
-import java.util.concurrent.atomic.AtomicReference;
-
-import org.apache.brooklyn.api.entity.basic.EntityLocal;
-import org.apache.brooklyn.api.event.Sensor;
-import org.apache.brooklyn.api.management.Task;
-import org.apache.brooklyn.core.policy.basic.AbstractPolicy;
-import org.apache.brooklyn.core.util.flags.SetFromFlag;
-import org.apache.brooklyn.core.util.task.BasicTask;
-import org.apache.brooklyn.core.util.task.ScheduledTask;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import brooklyn.config.ConfigKey;
-import brooklyn.entity.basic.BrooklynTaskTags;
-import brooklyn.entity.basic.ConfigKeys;
-import brooklyn.entity.basic.EntityInternal;
-import brooklyn.policy.ha.HASensors.FailureDescriptor;
-import brooklyn.util.collections.MutableMap;
-import brooklyn.util.exceptions.Exceptions;
-import brooklyn.util.time.Duration;
-import brooklyn.util.time.Time;
-
-import com.google.common.reflect.TypeToken;
-
-public abstract class AbstractFailureDetector extends AbstractPolicy {
-
- // TODO Remove duplication from ServiceFailureDetector, particularly for the stabilisation delays.
-
- private static final Logger LOG = LoggerFactory.getLogger(AbstractFailureDetector.class);
-
- private static final long MIN_PERIOD_BETWEEN_EXECS_MILLIS = 100;
-
- public static final ConfigKey<Duration> POLL_PERIOD = ConfigKeys.newDurationConfigKey(
- "failureDetector.pollPeriod", "", Duration.ONE_SECOND);
-
- @SetFromFlag("failedStabilizationDelay")
- public static final ConfigKey<Duration> FAILED_STABILIZATION_DELAY = ConfigKeys.newDurationConfigKey(
- "failureDetector.serviceFailedStabilizationDelay",
- "Time period for which the health check consistently fails "
- + "(e.g. doesn't report failed-ok-faled) before concluding failure.",
- Duration.ZERO);
-
- @SetFromFlag("recoveredStabilizationDelay")
- public static final ConfigKey<Duration> RECOVERED_STABILIZATION_DELAY = ConfigKeys.newDurationConfigKey(
- "failureDetector.serviceRecoveredStabilizationDelay",
- "Time period for which the health check succeeds continiually " +
- "(e.g. doesn't report ok-failed-ok) before concluding recovered",
- Duration.ZERO);
-
- @SuppressWarnings("serial")
- public static final ConfigKey<Sensor<FailureDescriptor>> SENSOR_FAILED = ConfigKeys.newConfigKey(new TypeToken<Sensor<FailureDescriptor>>() {},
- "failureDetector.sensor.fail", "A sensor which will indicate failure when set", HASensors.ENTITY_FAILED);
-
- @SuppressWarnings("serial")
- public static final ConfigKey<Sensor<FailureDescriptor>> SENSOR_RECOVERED = ConfigKeys.newConfigKey(new TypeToken<Sensor<FailureDescriptor>>() {},
- "failureDetector.sensor.recover", "A sensor which will indicate recovery from failure when set", HASensors.ENTITY_RECOVERED);
-
- public interface CalculatedStatus {
- boolean isHealthy();
- String getDescription();
- }
-
- private final class PublishJob implements Runnable {
- @Override public void run() {
- try {
- executorTime = System.currentTimeMillis();
- executorQueued.set(false);
-
- publishNow();
-
- } catch (Exception e) {
- if (isRunning()) {
- LOG.error("Problem resizing: "+e, e);
- } else {
- if (LOG.isDebugEnabled()) LOG.debug("Problem resizing, but no longer running: "+e, e);
- }
- } catch (Throwable t) {
- LOG.error("Problem in service-failure-detector: "+t, t);
- throw Exceptions.propagate(t);
- }
- }
- }
-
- private final class HealthPoller implements Runnable {
- @Override
- public void run() {
- checkHealth();
- }
- }
-
- private final class HealthPollingTaskFactory implements Callable<Task<?>> {
- @Override
- public Task<?> call() {
- BasicTask<Void> task = new BasicTask<Void>(new HealthPoller());
- BrooklynTaskTags.setTransient(task);
- return task;
- }
- }
-
- protected static class BasicCalculatedStatus implements CalculatedStatus {
- private boolean healthy;
- private String description;
-
- public BasicCalculatedStatus(boolean healthy, String description) {
- this.healthy = healthy;
- this.description = description;
- }
-
- @Override
- public boolean isHealthy() {
- return healthy;
- }
-
- @Override
- public String getDescription() {
- return description;
- }
- }
-
- public enum LastPublished {
- NONE,
- FAILED,
- RECOVERED;
- }
-
- protected final AtomicReference<Long> stateLastGood = new AtomicReference<Long>();
- protected final AtomicReference<Long> stateLastFail = new AtomicReference<Long>();
-
- protected Long currentFailureStartTime = null;
- protected Long currentRecoveryStartTime = null;
-
- protected LastPublished lastPublished = LastPublished.NONE;
-
- private final AtomicBoolean executorQueued = new AtomicBoolean(false);
- private volatile long executorTime = 0;
-
- private Callable<Task<?>> pollingTaskFactory = new HealthPollingTaskFactory();
-
- private Task<?> scheduledTask;
-
- protected abstract CalculatedStatus calculateStatus();
-
- @Override
- public void setEntity(EntityLocal entity) {
- super.setEntity(entity);
-
- if (isRunning()) {
- doStartPolling();
- }
- }
-
- @Override
- public void suspend() {
- scheduledTask.cancel(true);
- super.suspend();
- }
-
- @Override
- public void resume() {
- currentFailureStartTime = null;
- currentRecoveryStartTime = null;
- lastPublished = LastPublished.NONE;
- executorQueued.set(false);
- executorTime = 0;
-
- super.resume();
- doStartPolling();
- }
-
- @SuppressWarnings("unchecked")
- protected void doStartPolling() {
- if (scheduledTask == null || scheduledTask.isDone()) {
- ScheduledTask task = new ScheduledTask(MutableMap.of("period", getPollPeriod(), "displayName", getTaskName()), pollingTaskFactory);
- scheduledTask = ((EntityInternal)entity).getExecutionContext().submit(task);
- }
- }
-
- private String getTaskName() {
- return getDisplayName();
- }
-
- protected Duration getPollPeriod() {
- return getConfig(POLL_PERIOD);
- }
-
- protected Duration getFailedStabilizationDelay() {
- return getConfig(FAILED_STABILIZATION_DELAY);
- }
-
- protected Duration getRecoveredStabilizationDelay() {
- return getConfig(RECOVERED_STABILIZATION_DELAY);
- }
-
- protected Sensor<FailureDescriptor> getSensorFailed() {
- return getConfig(SENSOR_FAILED);
- }
-
- protected Sensor<FailureDescriptor> getSensorRecovered() {
- return getConfig(SENSOR_RECOVERED);
- }
-
- private synchronized void checkHealth() {
- CalculatedStatus status = calculateStatus();
- boolean healthy = status.isHealthy();
- long now = System.currentTimeMillis();
-
- if (healthy) {
- stateLastGood.set(now);
- if (lastPublished == LastPublished.FAILED) {
- if (currentRecoveryStartTime == null) {
- LOG.info("{} check for {}, now recovering: {}", new Object[] {this, entity, getDescription(status)});
- currentRecoveryStartTime = now;
- schedulePublish();
- } else {
- if (LOG.isTraceEnabled()) LOG.trace("{} check for {}, continuing recovering: {}", new Object[] {this, entity, getDescription(status)});
- }
- } else {
- if (currentFailureStartTime != null) {
- LOG.info("{} check for {}, now healthy: {}", new Object[] {this, entity, getDescription(status)});
- currentFailureStartTime = null;
- } else {
- if (LOG.isTraceEnabled()) LOG.trace("{} check for {}, still healthy: {}", new Object[] {this, entity, getDescription(status)});
- }
- }
- } else {
- stateLastFail.set(now);
- if (lastPublished != LastPublished.FAILED) {
- if (currentFailureStartTime == null) {
- LOG.info("{} check for {}, now failing: {}", new Object[] {this, entity, getDescription(status)});
- currentFailureStartTime = now;
- schedulePublish();
- } else {
- if (LOG.isTraceEnabled()) LOG.trace("{} check for {}, continuing failing: {}", new Object[] {this, entity, getDescription(status)});
- }
- } else {
- if (currentRecoveryStartTime != null) {
- LOG.info("{} check for {}, now failing: {}", new Object[] {this, entity, getDescription(status)});
- currentRecoveryStartTime = null;
- } else {
- if (LOG.isTraceEnabled()) LOG.trace("{} check for {}, still failed: {}", new Object[] {this, entity, getDescription(status)});
- }
- }
- }
- }
-
- protected void schedulePublish() {
- schedulePublish(0);
- }
-
- @SuppressWarnings("unchecked")
- protected void schedulePublish(long delay) {
- if (isRunning() && executorQueued.compareAndSet(false, true)) {
- long now = System.currentTimeMillis();
- delay = Math.max(0, Math.max(delay, (executorTime + MIN_PERIOD_BETWEEN_EXECS_MILLIS) - now));
- if (LOG.isTraceEnabled()) LOG.trace("{} scheduling publish in {}ms", this, delay);
-
- Runnable job = new PublishJob();
-
- ScheduledTask task = new ScheduledTask(MutableMap.of("delay", Duration.of(delay, TimeUnit.MILLISECONDS)), new BasicTask<Void>(job));
- ((EntityInternal)entity).getExecutionContext().submit(task);
- }
- }
-
- private synchronized void publishNow() {
- if (!isRunning()) return;
-
- CalculatedStatus calculatedStatus = calculateStatus();
- boolean healthy = calculatedStatus.isHealthy();
-
- Long lastUpTime = stateLastGood.get();
- Long lastDownTime = stateLastFail.get();
- long serviceFailedStabilizationDelay = getFailedStabilizationDelay().toMilliseconds();
- long serviceRecoveredStabilizationDelay = getRecoveredStabilizationDelay().toMilliseconds();
- long now = System.currentTimeMillis();
-
- if (healthy) {
- if (lastPublished == LastPublished.FAILED) {
- // only publish if consistently up for serviceRecoveredStabilizationDelay
- long currentRecoveryPeriod = getTimeDiff(now, currentRecoveryStartTime);
- long sinceLastDownPeriod = getTimeDiff(now, lastDownTime);
- if (currentRecoveryPeriod > serviceRecoveredStabilizationDelay && sinceLastDownPeriod > serviceRecoveredStabilizationDelay) {
- String description = getDescription(calculatedStatus);
- LOG.warn("{} check for {}, publishing recovered: {}", new Object[] {this, entity, description});
- entity.emit(getSensorRecovered(), new HASensors.FailureDescriptor(entity, description));
- lastPublished = LastPublished.RECOVERED;
- currentFailureStartTime = null;
- } else {
- long nextAttemptTime = Math.max(serviceRecoveredStabilizationDelay - currentRecoveryPeriod, serviceRecoveredStabilizationDelay - sinceLastDownPeriod);
- schedulePublish(nextAttemptTime);
- }
- }
- } else {
- if (lastPublished != LastPublished.FAILED) {
- // only publish if consistently down for serviceFailedStabilizationDelay
- long currentFailurePeriod = getTimeDiff(now, currentFailureStartTime);
- long sinceLastUpPeriod = getTimeDiff(now, lastUpTime);
- if (currentFailurePeriod > serviceFailedStabilizationDelay && sinceLastUpPeriod > serviceFailedStabilizationDelay) {
- String description = getDescription(calculatedStatus);
- LOG.warn("{} connectivity-check for {}, publishing failed: {}", new Object[] {this, entity, description});
- entity.emit(getSensorFailed(), new HASensors.FailureDescriptor(entity, description));
- lastPublished = LastPublished.FAILED;
- currentRecoveryStartTime = null;
- } else {
- long nextAttemptTime = Math.max(serviceFailedStabilizationDelay - currentFailurePeriod, serviceFailedStabilizationDelay - sinceLastUpPeriod);
- schedulePublish(nextAttemptTime);
- }
- }
- }
- }
-
- protected String getDescription(CalculatedStatus status) {
- Long lastUpTime = stateLastGood.get();
- Long lastDownTime = stateLastGood.get();
- Duration serviceFailedStabilizationDelay = getFailedStabilizationDelay();
- Duration serviceRecoveredStabilizationDelay = getRecoveredStabilizationDelay();
-
- return String.format("%s; healthy=%s; timeNow=%s; lastUp=%s; lastDown=%s; lastPublished=%s; "+
- "currentFailurePeriod=%s; currentRecoveryPeriod=%s",
- status.getDescription(),
- status.isHealthy(),
- Time.makeDateString(System.currentTimeMillis()),
- (lastUpTime != null ? Time.makeDateString(lastUpTime) : "<never>"),
- (lastDownTime != null ? Time.makeDateString(lastDownTime) : "<never>"),
- lastPublished,
- (currentFailureStartTime != null ? getTimeStringSince(currentFailureStartTime) : "<none>") + " (stabilization "+makeTimeStringRounded(serviceFailedStabilizationDelay) + ")",
- (currentRecoveryStartTime != null ? getTimeStringSince(currentRecoveryStartTime) : "<none>") + " (stabilization "+makeTimeStringRounded(serviceRecoveredStabilizationDelay) + ")");
- }
-
- private long getTimeDiff(Long recent, Long previous) {
- return (previous == null) ? recent : (recent - previous);
- }
-
- private String getTimeStringSince(Long time) {
- return time == null ? null : Time.makeTimeStringRounded(System.currentTimeMillis() - time);
- }
-}
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/d30ff597/policy/src/main/java/brooklyn/policy/ha/ConditionalSuspendPolicy.java
----------------------------------------------------------------------
diff --git a/policy/src/main/java/brooklyn/policy/ha/ConditionalSuspendPolicy.java b/policy/src/main/java/brooklyn/policy/ha/ConditionalSuspendPolicy.java
deleted file mode 100644
index b347949..0000000
--- a/policy/src/main/java/brooklyn/policy/ha/ConditionalSuspendPolicy.java
+++ /dev/null
@@ -1,103 +0,0 @@
-/*
- * 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 brooklyn.policy.ha;
-
-import org.apache.brooklyn.api.entity.basic.EntityLocal;
-import org.apache.brooklyn.api.event.Sensor;
-import org.apache.brooklyn.api.event.SensorEvent;
-import org.apache.brooklyn.api.event.SensorEventListener;
-import org.apache.brooklyn.api.policy.Policy;
-import org.apache.brooklyn.core.policy.basic.AbstractPolicy;
-import org.apache.brooklyn.core.util.flags.SetFromFlag;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import brooklyn.config.ConfigKey;
-import brooklyn.entity.basic.ConfigKeys;
-import brooklyn.util.javalang.JavaClassNames;
-
-import com.google.common.base.Preconditions;
-
-public class ConditionalSuspendPolicy extends AbstractPolicy {
- private static final Logger LOG = LoggerFactory.getLogger(ConditionalSuspendPolicy.class);
-
- @SetFromFlag("suppressSensor")
- @SuppressWarnings({ "rawtypes", "unchecked" })
- public static final ConfigKey<Sensor<?>> SUSPEND_SENSOR = (ConfigKey) ConfigKeys.newConfigKey(Sensor.class,
- "suppressSensor", "Sensor which will suppress the target policy", HASensors.CONNECTION_FAILED);
-
- @SetFromFlag("resetSensor")
- @SuppressWarnings({ "rawtypes", "unchecked" })
- public static final ConfigKey<Sensor<?>> RESUME_SENSOR = (ConfigKey) ConfigKeys.newConfigKey(Sensor.class,
- "resetSensor", "Resume target policy when this sensor is observed", HASensors.CONNECTION_RECOVERED);
-
- @SetFromFlag("target")
- public static final ConfigKey<Object> SUSPEND_TARGET = ConfigKeys.newConfigKey(Object.class,
- "target", "The target policy to suspend. Either direct reference or the value of the suspendTarget config on a policy from the same entity.");
-
- @Override
- public void setEntity(EntityLocal entity) {
- super.setEntity(entity);
- Object target = config().get(SUSPEND_TARGET);
- Preconditions.checkNotNull(target, "Suspend target required");
- Preconditions.checkNotNull(getTargetPolicy(), "Can't find target policy set in " + SUSPEND_TARGET.getName() + ": " + target);
- subscribe();
- uniqueTag = JavaClassNames.simpleClassName(getClass())+":"+getConfig(SUSPEND_SENSOR).getName()+":"+getConfig(RESUME_SENSOR).getName();
- }
-
- private void subscribe() {
- subscribe(entity, getConfig(SUSPEND_SENSOR), new SensorEventListener<Object>() {
- @Override public void onEvent(final SensorEvent<Object> event) {
- if (isRunning()) {
- Policy target = getTargetPolicy();
- target.suspend();
- LOG.debug("Suspended policy " + target + ", triggered by " + event.getSensor() + " = " + event.getValue());
- }
- }
-
- });
- subscribe(entity, getConfig(RESUME_SENSOR), new SensorEventListener<Object>() {
- @Override public void onEvent(final SensorEvent<Object> event) {
- if (isRunning()) {
- Policy target = getTargetPolicy();
- target.resume();
- LOG.debug("Resumed policy " + target + ", triggered by " + event.getSensor() + " = " + event.getValue());
- }
- }
- });
- }
-
- private Policy getTargetPolicy() {
- Object target = config().get(SUSPEND_TARGET);
- if (target instanceof Policy) {
- return (Policy)target;
- } else if (target instanceof String) {
- for (Policy policy : entity.getPolicies()) {
- // No way to set config values for keys NOT declared in the policy,
- // so must use displayName as a generally available config value.
- if (target.equals(policy.getDisplayName()) || target.equals(policy.getClass().getName())) {
- return policy;
- }
- }
- } else {
- throw new IllegalStateException("Unexpected type " + target.getClass() + " for target " + target);
- }
- return null;
- }
-}
[02/24] incubator-brooklyn git commit: add the creation of default
database and user with generated password.
Posted by he...@apache.org.
add the creation of default database and user with generated password.
Also publishes these credentials as sensors
Project: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/commit/9458e151
Tree: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/tree/9458e151
Diff: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/diff/9458e151
Branch: refs/heads/master
Commit: 9458e15167555f5fce4f7cc0ba66056f92d0cf78
Parents: 5ef0b61
Author: Robert Moss <ro...@gmail.com>
Authored: Mon Aug 17 13:45:42 2015 +0100
Committer: Robert Moss <ro...@gmail.com>
Committed: Mon Aug 17 13:45:42 2015 +0100
----------------------------------------------------------------------
.../database/postgresql/PostgreSqlNode.java | 17 ++++++++++-
.../postgresql/PostgreSqlSshDriver.java | 31 +++++++++++++++++++-
2 files changed, 46 insertions(+), 2 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/9458e151/software/database/src/main/java/brooklyn/entity/database/postgresql/PostgreSqlNode.java
----------------------------------------------------------------------
diff --git a/software/database/src/main/java/brooklyn/entity/database/postgresql/PostgreSqlNode.java b/software/database/src/main/java/brooklyn/entity/database/postgresql/PostgreSqlNode.java
index 793debf..e8496f3 100644
--- a/software/database/src/main/java/brooklyn/entity/database/postgresql/PostgreSqlNode.java
+++ b/software/database/src/main/java/brooklyn/entity/database/postgresql/PostgreSqlNode.java
@@ -22,6 +22,7 @@ import org.apache.brooklyn.api.catalog.Catalog;
import org.apache.brooklyn.api.entity.Effector;
import org.apache.brooklyn.api.entity.proxying.ImplementedBy;
import org.apache.brooklyn.api.entity.trait.HasShortName;
+import org.apache.brooklyn.location.basic.PortRanges;
import brooklyn.config.ConfigKey;
import brooklyn.entity.basic.ConfigKeys;
@@ -30,8 +31,8 @@ import brooklyn.entity.database.DatabaseNode;
import brooklyn.entity.database.DatastoreMixins;
import brooklyn.entity.database.DatastoreMixins.DatastoreCommon;
import brooklyn.entity.effector.Effectors;
+import brooklyn.event.basic.BasicAttributeSensorAndConfigKey;
import brooklyn.event.basic.PortAttributeSensorAndConfigKey;
-import org.apache.brooklyn.location.basic.PortRanges;
import brooklyn.util.flags.SetFromFlag;
/**
@@ -80,6 +81,20 @@ public interface PostgreSqlNode extends SoftwareProcess, HasShortName, Datastore
@SetFromFlag("pollPeriod")
ConfigKey<Long> POLL_PERIOD = ConfigKeys.newLongConfigKey(
"postgresql.sensorpoll", "Poll period (in milliseconds)", 1000L);
+
+ @SetFromFlag("username")
+ BasicAttributeSensorAndConfigKey<String> USERNAME = new BasicAttributeSensorAndConfigKey<>(
+ String.class, "postgresql.username", "Username of the database user",
+ "postgresuser");
+
+ @SetFromFlag("password")
+ BasicAttributeSensorAndConfigKey<String> PASSWORD = new BasicAttributeSensorAndConfigKey<>(
+ String.class, "postgresql.password",
+ "Password for the database user, auto-generated if not set");
+
+ @SetFromFlag("database")
+ BasicAttributeSensorAndConfigKey<String> DATABASE = new BasicAttributeSensorAndConfigKey<>(
+ String.class, "postgresql.database", "Database to be used", "db");
Effector<String> EXECUTE_SCRIPT = Effectors.effector(DatastoreMixins.EXECUTE_SCRIPT)
.description("Executes the given script contents using psql")
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/9458e151/software/database/src/main/java/brooklyn/entity/database/postgresql/PostgreSqlSshDriver.java
----------------------------------------------------------------------
diff --git a/software/database/src/main/java/brooklyn/entity/database/postgresql/PostgreSqlSshDriver.java b/software/database/src/main/java/brooklyn/entity/database/postgresql/PostgreSqlSshDriver.java
index a23f2bc..18dc9a4 100644
--- a/software/database/src/main/java/brooklyn/entity/database/postgresql/PostgreSqlSshDriver.java
+++ b/software/database/src/main/java/brooklyn/entity/database/postgresql/PostgreSqlSshDriver.java
@@ -47,6 +47,7 @@ import brooklyn.entity.basic.SoftwareProcess;
import brooklyn.entity.database.DatastoreMixins;
import brooklyn.entity.software.SshEffectorTasks;
+import org.apache.brooklyn.api.entity.basic.EntityLocal;
import org.apache.brooklyn.api.location.OsDetails;
import org.apache.brooklyn.location.basic.SshMachineLocation;
@@ -294,7 +295,24 @@ public class PostgreSqlSshDriver extends AbstractSoftwareProcessSshDriver implem
// Wait for commands to complete before running the creation script
DynamicTasks.waitForLast();
-
+ String createUserCommand = String.format(
+ "\"CREATE USER %s WITH PASSWORD '%s'; \"",
+ entity.getConfig(PostgreSqlNode.USERNAME), getUserPassword()
+ );
+ String createDatabaseCommand = String.format(
+ "\"CREATE DATABASE %s OWNER %s\"",
+ entity.getConfig(PostgreSqlNode.DATABASE),
+ entity.getConfig(PostgreSqlNode.USERNAME));
+ newScript("initializing user and database")
+ .body.append(
+ "cd " + getInstallDir(),
+ callPgctl("start", true),
+ sudoAsUser("postgres", getInstallDir() + "/bin/psql -p " + entity.getAttribute(PostgreSqlNode.POSTGRESQL_PORT) +
+ " --command="+ createUserCommand),
+ sudoAsUser("postgres", getInstallDir() + "/bin/psql -p " + entity.getAttribute(PostgreSqlNode.POSTGRESQL_PORT) +
+ " --command="+ createDatabaseCommand),
+ callPgctl("stop", true))
+ .failOnNonZeroResultCode().execute();
// Capture log file contents if there is an error configuring the database
try {
executeDatabaseCreationScript();
@@ -307,6 +325,17 @@ public class PostgreSqlSshDriver extends AbstractSoftwareProcessSshDriver implem
// on port 5432?" error then the port is probably closed. Check that the firewall allows external TCP/IP
// connections (netstat -nap). You can open a port with lokkit or by configuring the iptables.
}
+
+ protected String getUserPassword() {
+ String password = entity.getConfig(PostgreSqlNode.PASSWORD);
+ if (Strings.isEmpty(password)) {
+ log.debug(entity + " has no password specified for " + PostgreSqlNode.PASSWORD + "; using a random string");
+ password = brooklyn.util.text.Strings.makeRandomId(8);
+ entity.setAttribute(PostgreSqlNode.PASSWORD, password);
+ entity.config().set(PostgreSqlNode.PASSWORD, password);
+ }
+ return password;
+ }
protected void executeDatabaseCreationScript() {
if (copyDatabaseCreationScript()) {
[15/24] incubator-brooklyn git commit: [BROOKLYN-162] Renaming
package policy
Posted by he...@apache.org.
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/d30ff597/policy/src/main/java/org/apache/brooklyn/policy/followthesun/FollowTheSunParameters.java
----------------------------------------------------------------------
diff --git a/policy/src/main/java/org/apache/brooklyn/policy/followthesun/FollowTheSunParameters.java b/policy/src/main/java/org/apache/brooklyn/policy/followthesun/FollowTheSunParameters.java
new file mode 100644
index 0000000..c8742e4
--- /dev/null
+++ b/policy/src/main/java/org/apache/brooklyn/policy/followthesun/FollowTheSunParameters.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.policy.followthesun;
+
+import java.util.LinkedHashSet;
+import java.util.Set;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.apache.brooklyn.api.location.Location;
+
+public class FollowTheSunParameters {
+
+ private static final Logger LOG = LoggerFactory.getLogger(FollowTheSunParameters.class);
+
+ private FollowTheSunParameters() {}
+
+ /** trigger for moving segment X from geo A to geo B:
+ * where x is total number of requests submitted in X across the CDM network,
+ * and x_A is number of reqs from geo A, with A the most prolific geography
+ * (arbitrarily chosen in case of ties so recommended to choose at least a small percent_majority or delta_above_percent_majority, in addition to this field);
+ * this parameter T defines a number such that x_A > T*x in order for X to be migrated to A
+ * (but see also DELTA_ABOVE_PERCENT_TOTAL, below) */
+ public double triggerPercentTotal = 0.3;
+ /** fields as above, and T as above,
+ * this parameter T' defines a number such that x_A > T*x + T' in order for X to be migrated to A */
+ public double triggerDeltaAbovePercentTotal = 0;
+ /** fields as above,
+ * this parameter T defines a number such that x_A > T in order for X to be migrated to A */
+ public double triggerAbsoluteTotal = 2;
+
+ /** fields as above, with X_B the number from a different geography B,
+ * where A and B are the two most prolific requesters of X, and X_A >= X_B;
+ * this parameter T defines a number such that x_A-x_B > T*x in order for X to be migrated to A */
+ public double triggerPercentMajority = 0.2;
+ /** as corresponding majority and total fields, with x_A-x_B on the LHS of inequality */
+ public double triggerDeltaAbovePercentMajority = 1;
+ /** as corresponding majority and total fields, with x_A-x_B on the LHS of inequality */
+ public double triggerAbsoluteMajority = 4;
+
+ /** a list of excluded locations */
+ public Set<Location> excludedLocations = new LinkedHashSet<Location>();
+
+ public static FollowTheSunParameters newDefault() {
+ return new FollowTheSunParameters();
+ }
+
+ private static double parseDouble(String text, double defaultValue) {
+ try {
+ double d = Double.parseDouble(text);
+ if (!Double.isNaN(d)) return d;
+ } catch (Exception e) {
+ LOG.warn("Illegal double value '"+text+"', using default "+defaultValue+": "+e, e);
+ }
+ return defaultValue;
+ }
+
+ private static String[] parseCommaSeparatedList(String csv) {
+ if (csv==null || csv.trim().length()==0) return new String[0];
+ return csv.split(",");
+ }
+
+ public boolean isTriggered(double highest, double total, double nextHighest, double current) {
+ if (highest <= current) return false;
+ if (highest < total*triggerPercentTotal + triggerDeltaAbovePercentTotal) return false;
+ if (highest < triggerAbsoluteTotal) return false;
+ //TODO more params about nextHighest vs current
+ if (highest-current < total*triggerPercentMajority + triggerDeltaAbovePercentMajority) return false;
+ if (highest-current < triggerAbsoluteMajority) return false;
+ return true;
+ }
+
+ public String toString() {
+ return "Inter-geography policy params: percentTotal="+triggerPercentTotal+"; deltaAbovePercentTotal="+triggerDeltaAbovePercentTotal+
+ "; absoluteTotal="+triggerAbsoluteTotal+"; percentMajority="+triggerPercentMajority+
+ "; deltaAbovePercentMajority="+triggerDeltaAbovePercentMajority+"; absoluteMajority="+triggerAbsoluteMajority;
+
+ }
+}
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/d30ff597/policy/src/main/java/org/apache/brooklyn/policy/followthesun/FollowTheSunPolicy.java
----------------------------------------------------------------------
diff --git a/policy/src/main/java/org/apache/brooklyn/policy/followthesun/FollowTheSunPolicy.java b/policy/src/main/java/org/apache/brooklyn/policy/followthesun/FollowTheSunPolicy.java
new file mode 100644
index 0000000..a02285e
--- /dev/null
+++ b/policy/src/main/java/org/apache/brooklyn/policy/followthesun/FollowTheSunPolicy.java
@@ -0,0 +1,282 @@
+/*
+ * 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.policy.followthesun;
+
+import static brooklyn.util.JavaGroovyEquivalents.elvis;
+import static brooklyn.util.JavaGroovyEquivalents.groovyTruth;
+import static com.google.common.base.Preconditions.checkArgument;
+
+import java.util.Collection;
+import java.util.Map;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.ThreadFactory;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import org.apache.brooklyn.api.entity.Entity;
+import org.apache.brooklyn.api.entity.basic.EntityLocal;
+import org.apache.brooklyn.api.event.AttributeSensor;
+import org.apache.brooklyn.api.event.Sensor;
+import org.apache.brooklyn.api.event.SensorEvent;
+import org.apache.brooklyn.api.event.SensorEventListener;
+import org.apache.brooklyn.api.location.Location;
+import org.apache.brooklyn.api.location.MachineProvisioningLocation;
+import org.apache.brooklyn.core.policy.basic.AbstractPolicy;
+import org.apache.brooklyn.core.util.flags.SetFromFlag;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import brooklyn.entity.basic.Attributes;
+
+import org.apache.brooklyn.policy.followthesun.FollowTheSunPool.ContainerItemPair;
+import org.apache.brooklyn.policy.loadbalancing.Movable;
+
+import brooklyn.util.collections.MutableMap;
+
+import com.google.common.base.Function;
+import com.google.common.collect.Iterables;
+import com.google.common.util.concurrent.ThreadFactoryBuilder;
+
+ // removed from catalog because it cannot currently be configured via catalog mechanisms -
+ // PolicySpec.create fails due to no no-arg constructor
+ // TODO make model and parameters things which can be initialized from config then reinstate in catalog
+//@Catalog(name="Follow the Sun", description="Policy for moving \"work\" around to follow the demand; "
+// + "the work can be any \"Movable\" entity")
+public class FollowTheSunPolicy extends AbstractPolicy {
+
+ private static final Logger LOG = LoggerFactory.getLogger(FollowTheSunPolicy.class);
+
+ public static final String NAME = "Follow the Sun (Inter-Geography Latency Optimization)";
+
+ @SetFromFlag(defaultVal="100")
+ private long minPeriodBetweenExecs;
+
+ @SetFromFlag
+ private Function<Entity, Location> locationFinder;
+
+ private final AttributeSensor<Map<? extends Movable, Double>> itemUsageMetric;
+ private final FollowTheSunModel<Entity, Movable> model;
+ private final FollowTheSunStrategy<Entity, Movable> strategy;
+ private final FollowTheSunParameters parameters;
+
+ private FollowTheSunPool poolEntity;
+
+ private volatile ScheduledExecutorService executor;
+ private final AtomicBoolean executorQueued = new AtomicBoolean(false);
+ private volatile long executorTime = 0;
+ private boolean loggedConstraintsIgnored = false;
+
+ private final Function<Entity, Location> defaultLocationFinder = new Function<Entity, Location>() {
+ public Location apply(Entity e) {
+ Collection<Location> locs = e.getLocations();
+ if (locs.isEmpty()) return null;
+ Location contender = Iterables.get(locs, 0);
+ while (contender.getParent() != null && !(contender instanceof MachineProvisioningLocation)) {
+ contender = contender.getParent();
+ }
+ return contender;
+ }
+ };
+
+ private final SensorEventListener<Object> eventHandler = new SensorEventListener<Object>() {
+ @Override
+ public void onEvent(SensorEvent<Object> event) {
+ if (LOG.isTraceEnabled()) LOG.trace("{} received event {}", FollowTheSunPolicy.this, event);
+ Entity source = event.getSource();
+ Object value = event.getValue();
+ Sensor<?> sensor = event.getSensor();
+
+ if (sensor.equals(itemUsageMetric)) {
+ onItemMetricUpdated((Movable)source, (Map<? extends Movable, Double>) value, true);
+ } else if (sensor.equals(Attributes.LOCATION_CHANGED)) {
+ onContainerLocationUpdated(source, true);
+ } else if (sensor.equals(FollowTheSunPool.CONTAINER_ADDED)) {
+ onContainerAdded((Entity) value, true);
+ } else if (sensor.equals(FollowTheSunPool.CONTAINER_REMOVED)) {
+ onContainerRemoved((Entity) value, true);
+ } else if (sensor.equals(FollowTheSunPool.ITEM_ADDED)) {
+ onItemAdded((Movable) value, true);
+ } else if (sensor.equals(FollowTheSunPool.ITEM_REMOVED)) {
+ onItemRemoved((Movable) value, true);
+ } else if (sensor.equals(FollowTheSunPool.ITEM_MOVED)) {
+ ContainerItemPair pair = (ContainerItemPair) value;
+ onItemMoved((Movable)pair.item, pair.container, true);
+ }
+ }
+ };
+
+ // FIXME parameters: use a more groovy way of doing it, that's consistent with other policies/entities?
+ public FollowTheSunPolicy(AttributeSensor itemUsageMetric,
+ FollowTheSunModel<Entity, Movable> model, FollowTheSunParameters parameters) {
+ this(MutableMap.of(), itemUsageMetric, model, parameters);
+ }
+
+ public FollowTheSunPolicy(Map props, AttributeSensor itemUsageMetric,
+ FollowTheSunModel<Entity, Movable> model, FollowTheSunParameters parameters) {
+ super(props);
+ this.itemUsageMetric = itemUsageMetric;
+ this.model = model;
+ this.parameters = parameters;
+ this.strategy = new FollowTheSunStrategy<Entity, Movable>(model, parameters); // TODO: extract interface, inject impl
+ this.locationFinder = elvis(locationFinder, defaultLocationFinder);
+
+ // TODO Should re-use the execution manager's thread pool, somehow
+ executor = Executors.newSingleThreadScheduledExecutor(newThreadFactory());
+ }
+
+ @Override
+ public void setEntity(EntityLocal entity) {
+ checkArgument(entity instanceof FollowTheSunPool, "Provided entity must be a FollowTheSunPool");
+ super.setEntity(entity);
+ this.poolEntity = (FollowTheSunPool) entity;
+
+ // Detect when containers are added to or removed from the pool.
+ subscribe(poolEntity, FollowTheSunPool.CONTAINER_ADDED, eventHandler);
+ subscribe(poolEntity, FollowTheSunPool.CONTAINER_REMOVED, eventHandler);
+ subscribe(poolEntity, FollowTheSunPool.ITEM_ADDED, eventHandler);
+ subscribe(poolEntity, FollowTheSunPool.ITEM_REMOVED, eventHandler);
+ subscribe(poolEntity, FollowTheSunPool.ITEM_MOVED, eventHandler);
+
+ // Take heed of any extant containers.
+ for (Entity container : poolEntity.getContainerGroup().getMembers()) {
+ onContainerAdded(container, false);
+ }
+ for (Entity item : poolEntity.getItemGroup().getMembers()) {
+ onItemAdded((Movable)item, false);
+ }
+
+ scheduleLatencyReductionJig();
+ }
+
+ @Override
+ public void suspend() {
+ // TODO unsubscribe from everything? And resubscribe on resume?
+ super.suspend();
+ if (executor != null) executor.shutdownNow();
+ executorQueued.set(false);
+ }
+
+ @Override
+ public void resume() {
+ super.resume();
+ executor = Executors.newSingleThreadScheduledExecutor(newThreadFactory());
+ executorTime = 0;
+ executorQueued.set(false);
+ }
+
+ private ThreadFactory newThreadFactory() {
+ return new ThreadFactoryBuilder()
+ .setNameFormat("brooklyn-followthesunpolicy-%d")
+ .build();
+ }
+
+ private void scheduleLatencyReductionJig() {
+ if (isRunning() && executorQueued.compareAndSet(false, true)) {
+ long now = System.currentTimeMillis();
+ long delay = Math.max(0, (executorTime + minPeriodBetweenExecs) - now);
+
+ executor.schedule(new Runnable() {
+ public void run() {
+ try {
+ executorTime = System.currentTimeMillis();
+ executorQueued.set(false);
+
+ if (LOG.isTraceEnabled()) LOG.trace("{} executing follow-the-sun migration-strategy", this);
+ strategy.rebalance();
+
+ } catch (RuntimeException e) {
+ if (isRunning()) {
+ LOG.error("Error during latency-reduction-jig", e);
+ } else {
+ LOG.debug("Error during latency-reduction-jig, but no longer running", e);
+ }
+ }
+ }},
+ delay,
+ TimeUnit.MILLISECONDS);
+ }
+ }
+
+ private void onContainerAdded(Entity container, boolean rebalanceNow) {
+ subscribe(container, Attributes.LOCATION_CHANGED, eventHandler);
+ Location location = locationFinder.apply(container);
+
+ if (LOG.isTraceEnabled()) LOG.trace("{} recording addition of container {} in location {}", new Object[] {this, container, location});
+ model.onContainerAdded(container, location);
+
+ if (rebalanceNow) scheduleLatencyReductionJig();
+ }
+
+ private void onContainerRemoved(Entity container, boolean rebalanceNow) {
+ if (LOG.isTraceEnabled()) LOG.trace("{} recording removal of container {}", this, container);
+ model.onContainerRemoved(container);
+ if (rebalanceNow) scheduleLatencyReductionJig();
+ }
+
+ private void onItemAdded(Movable item, boolean rebalanceNow) {
+ Entity parentContainer = (Entity) item.getAttribute(Movable.CONTAINER);
+
+ if (LOG.isTraceEnabled()) LOG.trace("{} recording addition of item {} in container {}", new Object[] {this, item, parentContainer});
+
+ subscribe(item, itemUsageMetric, eventHandler);
+
+ // Update the model, including the current metric value (if any).
+ Map<? extends Movable, Double> currentValue = item.getAttribute(itemUsageMetric);
+ boolean immovable = (Boolean)elvis(item.getConfig(Movable.IMMOVABLE), false);
+ model.onItemAdded(item, parentContainer, immovable);
+
+ if (currentValue != null) {
+ model.onItemUsageUpdated(item, currentValue);
+ }
+
+ if (rebalanceNow) scheduleLatencyReductionJig();
+ }
+
+ private void onItemRemoved(Movable item, boolean rebalanceNow) {
+ if (LOG.isTraceEnabled()) LOG.trace("{} recording removal of item {}", this, item);
+ unsubscribe(item);
+ model.onItemRemoved(item);
+ if (rebalanceNow) scheduleLatencyReductionJig();
+ }
+
+ private void onItemMoved(Movable item, Entity parentContainer, boolean rebalanceNow) {
+ if (LOG.isTraceEnabled()) LOG.trace("{} recording moving of item {} to {}", new Object[] {this, item, parentContainer});
+ model.onItemMoved(item, parentContainer);
+ if (rebalanceNow) scheduleLatencyReductionJig();
+ }
+
+ private void onContainerLocationUpdated(Entity container, boolean rebalanceNow) {
+ Location location = locationFinder.apply(container);
+ if (LOG.isTraceEnabled()) LOG.trace("{} recording location for container {}, new value {}", new Object[] {this, container, location});
+ model.onContainerLocationUpdated(container, location);
+ if (rebalanceNow) scheduleLatencyReductionJig();
+ }
+
+ private void onItemMetricUpdated(Movable item, Map<? extends Movable, Double> newValues, boolean rebalanceNow) {
+ if (LOG.isTraceEnabled()) LOG.trace("{} recording usage update for item {}, new value {}", new Object[] {this, item, newValues});
+ model.onItemUsageUpdated(item, newValues);
+ if (rebalanceNow) scheduleLatencyReductionJig();
+ }
+
+ @Override
+ public String toString() {
+ return getClass().getSimpleName() + (groovyTruth(name) ? "("+name+")" : "");
+ }
+}
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/d30ff597/policy/src/main/java/org/apache/brooklyn/policy/followthesun/FollowTheSunPool.java
----------------------------------------------------------------------
diff --git a/policy/src/main/java/org/apache/brooklyn/policy/followthesun/FollowTheSunPool.java b/policy/src/main/java/org/apache/brooklyn/policy/followthesun/FollowTheSunPool.java
new file mode 100644
index 0000000..7dc668e
--- /dev/null
+++ b/policy/src/main/java/org/apache/brooklyn/policy/followthesun/FollowTheSunPool.java
@@ -0,0 +1,75 @@
+/*
+ * 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.policy.followthesun;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import java.io.Serializable;
+
+import org.apache.brooklyn.api.entity.Entity;
+import org.apache.brooklyn.api.entity.Group;
+import org.apache.brooklyn.api.entity.proxying.ImplementedBy;
+
+import brooklyn.entity.trait.Resizable;
+import brooklyn.event.basic.BasicNotificationSensor;
+
+@ImplementedBy(FollowTheSunPoolImpl.class)
+public interface FollowTheSunPool extends Entity, Resizable {
+
+ // FIXME Remove duplication from BalanceableWorkerPool?
+
+ // FIXME Asymmetry between loadbalancing and followTheSun: ITEM_ADDED and ITEM_REMOVED in loadbalancing
+ // are of type ContainerItemPair, but in followTheSun it is just the `Entity item`.
+
+ /** Encapsulates an item and a container; emitted by sensors.
+ */
+ public static class ContainerItemPair implements Serializable {
+ private static final long serialVersionUID = 1L;
+ public final Entity container;
+ public final Entity item;
+
+ public ContainerItemPair(Entity container, Entity item) {
+ this.container = container;
+ this.item = checkNotNull(item);
+ }
+
+ @Override
+ public String toString() {
+ return ""+item+" @ "+container;
+ }
+ }
+
+ // Pool constituent notifications.
+ public static BasicNotificationSensor<Entity> CONTAINER_ADDED = new BasicNotificationSensor<Entity>(
+ Entity.class, "followthesun.container.added", "Container added");
+ public static BasicNotificationSensor<Entity> CONTAINER_REMOVED = new BasicNotificationSensor<Entity>(
+ Entity.class, "followthesun.container.removed", "Container removed");
+ public static BasicNotificationSensor<Entity> ITEM_ADDED = new BasicNotificationSensor<Entity>(
+ Entity.class, "followthesun.item.added", "Item added");
+ public static BasicNotificationSensor<Entity> ITEM_REMOVED = new BasicNotificationSensor<Entity>(
+ Entity.class, "followthesun.item.removed", "Item removed");
+ public static BasicNotificationSensor<ContainerItemPair> ITEM_MOVED = new BasicNotificationSensor<ContainerItemPair>(
+ ContainerItemPair.class, "followthesun.item.moved", "Item moved to the given container");
+
+ public void setContents(Group containerGroup, Group itemGroup);
+
+ public Group getContainerGroup();
+
+ public Group getItemGroup();
+}
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/d30ff597/policy/src/main/java/org/apache/brooklyn/policy/followthesun/FollowTheSunPoolImpl.java
----------------------------------------------------------------------
diff --git a/policy/src/main/java/org/apache/brooklyn/policy/followthesun/FollowTheSunPoolImpl.java b/policy/src/main/java/org/apache/brooklyn/policy/followthesun/FollowTheSunPoolImpl.java
new file mode 100644
index 0000000..b9c597e
--- /dev/null
+++ b/policy/src/main/java/org/apache/brooklyn/policy/followthesun/FollowTheSunPoolImpl.java
@@ -0,0 +1,178 @@
+/*
+ * 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.policy.followthesun;
+
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
+
+import org.apache.brooklyn.api.entity.Entity;
+import org.apache.brooklyn.api.entity.Group;
+import org.apache.brooklyn.api.event.Sensor;
+import org.apache.brooklyn.api.event.SensorEvent;
+import org.apache.brooklyn.api.event.SensorEventListener;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import brooklyn.entity.basic.AbstractEntity;
+import brooklyn.entity.basic.AbstractGroup;
+import brooklyn.entity.trait.Resizable;
+import brooklyn.entity.trait.Startable;
+import org.apache.brooklyn.policy.loadbalancing.Movable;
+
+public class FollowTheSunPoolImpl extends AbstractEntity implements FollowTheSunPool {
+
+ // FIXME Remove duplication from BalanceableWorkerPool?
+
+ // FIXME Asymmetry between loadbalancing and followTheSun: ITEM_ADDED and ITEM_REMOVED in loadbalancing
+ // are of type ContainerItemPair, but in followTheSun it is just the `Entity item`.
+
+ private static final Logger LOG = LoggerFactory.getLogger(FollowTheSunPool.class);
+
+ private Group containerGroup;
+ private Group itemGroup;
+
+ private final Set<Entity> containers = Collections.synchronizedSet(new HashSet<Entity>());
+ private final Set<Entity> items = Collections.synchronizedSet(new HashSet<Entity>());
+
+ private final SensorEventListener<Object> eventHandler = new SensorEventListener<Object>() {
+ @Override
+ public void onEvent(SensorEvent<Object> event) {
+ if (LOG.isTraceEnabled()) LOG.trace("{} received event {}", FollowTheSunPoolImpl.this, event);
+ Entity source = event.getSource();
+ Object value = event.getValue();
+ Sensor sensor = event.getSensor();
+
+ if (sensor.equals(AbstractGroup.MEMBER_ADDED)) {
+ if (source.equals(containerGroup)) {
+ onContainerAdded((Entity) value);
+ } else if (source.equals(itemGroup)) {
+ onItemAdded((Entity)value);
+ } else {
+ throw new IllegalStateException("unexpected event source="+source);
+ }
+ } else if (sensor.equals(AbstractGroup.MEMBER_REMOVED)) {
+ if (source.equals(containerGroup)) {
+ onContainerRemoved((Entity) value);
+ } else if (source.equals(itemGroup)) {
+ onItemRemoved((Entity) value);
+ } else {
+ throw new IllegalStateException("unexpected event source="+source);
+ }
+ } else if (sensor.equals(Startable.SERVICE_UP)) {
+ // TODO What if start has failed? Is there a sensor to indicate that?
+ if ((Boolean)value) {
+ onContainerUp(source);
+ } else {
+ onContainerDown(source);
+ }
+ } else if (sensor.equals(Movable.CONTAINER)) {
+ onItemMoved(source, (Entity) value);
+ } else {
+ throw new IllegalStateException("Unhandled event type "+sensor+": "+event);
+ }
+ }
+ };
+
+ public FollowTheSunPoolImpl() {
+ }
+
+ @Override
+ public void setContents(Group containerGroup, Group itemGroup) {
+ this.containerGroup = containerGroup;
+ this.itemGroup = itemGroup;
+ subscribe(containerGroup, AbstractGroup.MEMBER_ADDED, eventHandler);
+ subscribe(containerGroup, AbstractGroup.MEMBER_REMOVED, eventHandler);
+ subscribe(itemGroup, AbstractGroup.MEMBER_ADDED, eventHandler);
+ subscribe(itemGroup, AbstractGroup.MEMBER_REMOVED, eventHandler);
+
+ // Process extant containers and items
+ for (Entity existingContainer : containerGroup.getMembers()) {
+ onContainerAdded(existingContainer);
+ }
+ for (Entity existingItem : itemGroup.getMembers()) {
+ onItemAdded((Entity)existingItem);
+ }
+ }
+
+ @Override
+ public Group getContainerGroup() {
+ return containerGroup;
+ }
+
+ @Override
+ public Group getItemGroup() {
+ return itemGroup;
+ }
+
+ @Override
+ public Integer getCurrentSize() {
+ return containerGroup.getCurrentSize();
+ }
+
+ @Override
+ public Integer resize(Integer desiredSize) {
+ if (containerGroup instanceof Resizable) return ((Resizable) containerGroup).resize(desiredSize);
+
+ throw new UnsupportedOperationException("Container group is not resizable");
+ }
+
+
+ private void onContainerAdded(Entity newContainer) {
+ subscribe(newContainer, Startable.SERVICE_UP, eventHandler);
+ if (!(newContainer instanceof Startable) || Boolean.TRUE.equals(newContainer.getAttribute(Startable.SERVICE_UP))) {
+ onContainerUp(newContainer);
+ }
+ }
+
+ private void onContainerUp(Entity newContainer) {
+ if (containers.add(newContainer)) {
+ emit(CONTAINER_ADDED, newContainer);
+ }
+ }
+
+ private void onContainerDown(Entity oldContainer) {
+ if (containers.remove(oldContainer)) {
+ emit(CONTAINER_REMOVED, oldContainer);
+ }
+ }
+
+ private void onContainerRemoved(Entity oldContainer) {
+ unsubscribe(oldContainer);
+ onContainerDown(oldContainer);
+ }
+
+ private void onItemAdded(Entity item) {
+ if (items.add(item)) {
+ subscribe(item, Movable.CONTAINER, eventHandler);
+ emit(ITEM_ADDED, item);
+ }
+ }
+
+ private void onItemRemoved(Entity item) {
+ if (items.remove(item)) {
+ unsubscribe(item);
+ emit(ITEM_REMOVED, item);
+ }
+ }
+
+ private void onItemMoved(Entity item, Entity container) {
+ emit(ITEM_MOVED, new ContainerItemPair(container, item));
+ }
+}
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/d30ff597/policy/src/main/java/org/apache/brooklyn/policy/followthesun/FollowTheSunStrategy.java
----------------------------------------------------------------------
diff --git a/policy/src/main/java/org/apache/brooklyn/policy/followthesun/FollowTheSunStrategy.java b/policy/src/main/java/org/apache/brooklyn/policy/followthesun/FollowTheSunStrategy.java
new file mode 100644
index 0000000..68d6ae2
--- /dev/null
+++ b/policy/src/main/java/org/apache/brooklyn/policy/followthesun/FollowTheSunStrategy.java
@@ -0,0 +1,161 @@
+/*
+ * 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.policy.followthesun;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.brooklyn.api.entity.Entity;
+import org.apache.brooklyn.api.location.Location;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import org.apache.brooklyn.policy.loadbalancing.Movable;
+
+import com.google.common.collect.Iterables;
+
+// TODO: extract interface
+public class FollowTheSunStrategy<ContainerType extends Entity, ItemType extends Movable> {
+
+ // This is a modified version of the InterGeographyLatencyPolicy (aka Follow-The-Sun) policy from Monterey v3.
+
+ // TODO location constraints
+
+ private static final Logger LOG = LoggerFactory.getLogger(FollowTheSunStrategy.class);
+
+ private final FollowTheSunParameters parameters;
+ private final FollowTheSunModel<ContainerType,ItemType> model;
+ private final String name;
+
+ public FollowTheSunStrategy(FollowTheSunModel<ContainerType,ItemType> model, FollowTheSunParameters parameters) {
+ this.model = model;
+ this.parameters = parameters;
+ this.name = model.getName();
+ }
+
+ public void rebalance() {
+ try {
+ Set<ItemType> items = model.getItems();
+ Map<ItemType, Map<Location, Double>> directSendsToItemByLocation = model.getDirectSendsToItemByLocation();
+
+ for (ItemType item : items) {
+ String itemName = model.getName(item);
+ Location activeLocation = model.getItemLocation(item);
+ ContainerType activeContainer = model.getItemContainer(item);
+ Map<Location, Double> sendsByLocation = directSendsToItemByLocation.get(item);
+ if (sendsByLocation == null) sendsByLocation = Collections.emptyMap();
+
+ if (parameters.excludedLocations.contains(activeLocation)) {
+ if (LOG.isTraceEnabled()) LOG.trace("Ignoring segment {} as it is in {}", itemName, activeLocation);
+ continue;
+ }
+ if (!model.isItemMoveable(item)) {
+ if (LOG.isDebugEnabled()) LOG.debug("POLICY {} skipping any migration of {}, it is not moveable", name, itemName);
+ continue;
+ }
+ if (model.hasActiveMigration(item)) {
+ LOG.info("POLICY {} skipping any migration of {}, it is involved in an active migration already", name, itemName);
+ continue;
+ }
+
+ double total = DefaultFollowTheSunModel.sum(sendsByLocation.values());
+
+ if (LOG.isTraceEnabled()) LOG.trace("POLICY {} detected {} msgs/sec in {}, split up as: {}", new Object[] {name, total, itemName, sendsByLocation});
+
+ Double current = sendsByLocation.get(activeLocation);
+ if (current == null) current=0d;
+ List<WeightedObject<Location>> locationsWtd = new ArrayList<WeightedObject<Location>>();
+ if (total > 0) {
+ for (Map.Entry<Location, Double> entry : sendsByLocation.entrySet()) {
+ Location l = entry.getKey();
+ Double d = entry.getValue();
+ if (d > current) locationsWtd.add(new WeightedObject<Location>(l, d));
+ }
+ }
+ Collections.sort(locationsWtd);
+ Collections.reverse(locationsWtd);
+
+ double highestMsgRate = -1;
+ Location highestLocation = null;
+ ContainerType optimalContainerInHighest = null;
+ while (!locationsWtd.isEmpty()) {
+ WeightedObject<Location> weightedObject = locationsWtd.remove(0);
+ highestMsgRate = weightedObject.getWeight();
+ highestLocation = weightedObject.getObject();
+ optimalContainerInHighest = findOptimal(model.getAvailableContainersFor(item, highestLocation));
+ if (optimalContainerInHighest != null) {
+ break;
+ }
+ }
+ if (optimalContainerInHighest == null) {
+ if (LOG.isDebugEnabled()) LOG.debug("POLICY {} detected {} is already in optimal permitted location ({} of {} msgs/sec)", new Object[] {name, itemName, highestMsgRate, total});
+ continue;
+ }
+
+ double nextHighestMsgRate = -1;
+ ContainerType optimalContainerInNextHighest = null;
+ while (!locationsWtd.isEmpty()) {
+ WeightedObject<Location> weightedObject = locationsWtd.remove(0);
+ nextHighestMsgRate = weightedObject.getWeight();
+ Location nextHighestLocation = weightedObject.getObject();
+ optimalContainerInNextHighest = findOptimal(model.getAvailableContainersFor(item, nextHighestLocation));
+ if (optimalContainerInNextHighest != null) {
+ break;
+ }
+ }
+ if (optimalContainerInNextHighest == null) {
+ nextHighestMsgRate = current;
+ }
+
+ if (parameters.isTriggered(highestMsgRate, total, nextHighestMsgRate, current)) {
+ LOG.info("POLICY "+name+" detected "+itemName+" should be in location "+highestLocation+" on "+optimalContainerInHighest+" ("+highestMsgRate+" of "+total+" msgs/sec), migrating");
+ try {
+ if (activeContainer.equals(optimalContainerInHighest)) {
+ //shouldn't happen
+ LOG.warn("POLICY "+name+" detected "+itemName+" should move to "+optimalContainerInHighest+" ("+highestMsgRate+" of "+total+" msgs/sec) but it is already there with "+current+" msgs/sec");
+ } else {
+ item.move(optimalContainerInHighest);
+ model.onItemMoved(item, optimalContainerInHighest);
+ }
+ } catch (Exception e) {
+ LOG.warn("POLICY "+name+" detected "+itemName+" should be on "+optimalContainerInHighest+", but can't move it: "+e, e);
+ }
+ } else {
+ if (LOG.isTraceEnabled()) LOG.trace("POLICY "+name+" detected "+itemName+" need not move to "+optimalContainerInHighest+" ("+highestMsgRate+" of "+total+" msgs/sec not much better than "+current+" at "+activeContainer+")");
+ }
+ }
+ } catch (Exception e) {
+ LOG.warn("Error in policy "+name+" (ignoring): "+e, e);
+ }
+ }
+
+ private ContainerType findOptimal(Collection<ContainerType> contenders) {
+ /*
+ * TODO should choose the least loaded mediator. Currently chooses first available, and relies
+ * on a load-balancer to move it again; would be good if these could share decision code so move
+ * it to the right place immediately. e.g.
+ * policyUtil.findLeastLoadedMediator(nodesInLocation);
+ */
+ return (contenders.isEmpty() ? null : Iterables.get(contenders, 0));
+ }
+}
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/d30ff597/policy/src/main/java/org/apache/brooklyn/policy/followthesun/WeightedObject.java
----------------------------------------------------------------------
diff --git a/policy/src/main/java/org/apache/brooklyn/policy/followthesun/WeightedObject.java b/policy/src/main/java/org/apache/brooklyn/policy/followthesun/WeightedObject.java
new file mode 100644
index 0000000..e1caf1f
--- /dev/null
+++ b/policy/src/main/java/org/apache/brooklyn/policy/followthesun/WeightedObject.java
@@ -0,0 +1,71 @@
+/*
+ * 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.policy.followthesun;
+
+public class WeightedObject<T> implements Comparable<WeightedObject<T>>{
+
+ final T object;
+ final double weight;
+
+ public WeightedObject(T obj, double weight) {
+ this.object = obj;
+ this.weight = weight;
+ }
+
+ public T getObject() {
+ return object;
+ }
+
+ public double getWeight() {
+ return weight;
+ }
+
+ /**
+ * Note that equals and compareTo are not consistent: x.compareTo(y)==0 iff x.equals(y) is
+ * highly recommended in Java, but is not required. This can make TreeSet etc behave poorly...
+ */
+ public int compareTo(WeightedObject<T> o) {
+ double diff = o.getWeight() - weight;
+ if (diff>0.0000000000000001) return -1;
+ if (diff<-0.0000000000000001) return 1;
+ return 0;
+ }
+
+ @Override
+ /** true irrespective of weight */
+ public boolean equals(Object obj) {
+ if (!(obj instanceof WeightedObject<?>)) return false;
+ if (getObject()==null) {
+ return ((WeightedObject<?>)obj).getObject() == null;
+ } else {
+ return getObject().equals( ((WeightedObject<?>)obj).getObject() );
+ }
+ }
+
+ @Override
+ public int hashCode() {
+ if (getObject()==null) return 234519078;
+ return getObject().hashCode();
+ }
+
+ @Override
+ public String toString() {
+ return ""+getObject()+"["+getWeight()+"]";
+ }
+}
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/d30ff597/policy/src/main/java/org/apache/brooklyn/policy/ha/AbstractFailureDetector.java
----------------------------------------------------------------------
diff --git a/policy/src/main/java/org/apache/brooklyn/policy/ha/AbstractFailureDetector.java b/policy/src/main/java/org/apache/brooklyn/policy/ha/AbstractFailureDetector.java
new file mode 100644
index 0000000..d7af68a
--- /dev/null
+++ b/policy/src/main/java/org/apache/brooklyn/policy/ha/AbstractFailureDetector.java
@@ -0,0 +1,363 @@
+/*
+ * 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.policy.ha;
+
+import static brooklyn.util.time.Time.makeTimeStringRounded;
+
+import java.util.concurrent.Callable;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicReference;
+
+import org.apache.brooklyn.api.entity.basic.EntityLocal;
+import org.apache.brooklyn.api.event.Sensor;
+import org.apache.brooklyn.api.management.Task;
+import org.apache.brooklyn.core.policy.basic.AbstractPolicy;
+import org.apache.brooklyn.core.util.flags.SetFromFlag;
+import org.apache.brooklyn.core.util.task.BasicTask;
+import org.apache.brooklyn.core.util.task.ScheduledTask;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import brooklyn.config.ConfigKey;
+import brooklyn.entity.basic.BrooklynTaskTags;
+import brooklyn.entity.basic.ConfigKeys;
+import brooklyn.entity.basic.EntityInternal;
+
+import org.apache.brooklyn.policy.ha.HASensors.FailureDescriptor;
+
+import brooklyn.util.collections.MutableMap;
+import brooklyn.util.exceptions.Exceptions;
+import brooklyn.util.time.Duration;
+import brooklyn.util.time.Time;
+
+import com.google.common.reflect.TypeToken;
+
+public abstract class AbstractFailureDetector extends AbstractPolicy {
+
+ // TODO Remove duplication from ServiceFailureDetector, particularly for the stabilisation delays.
+
+ private static final Logger LOG = LoggerFactory.getLogger(AbstractFailureDetector.class);
+
+ private static final long MIN_PERIOD_BETWEEN_EXECS_MILLIS = 100;
+
+ public static final ConfigKey<Duration> POLL_PERIOD = ConfigKeys.newDurationConfigKey(
+ "failureDetector.pollPeriod", "", Duration.ONE_SECOND);
+
+ @SetFromFlag("failedStabilizationDelay")
+ public static final ConfigKey<Duration> FAILED_STABILIZATION_DELAY = ConfigKeys.newDurationConfigKey(
+ "failureDetector.serviceFailedStabilizationDelay",
+ "Time period for which the health check consistently fails "
+ + "(e.g. doesn't report failed-ok-faled) before concluding failure.",
+ Duration.ZERO);
+
+ @SetFromFlag("recoveredStabilizationDelay")
+ public static final ConfigKey<Duration> RECOVERED_STABILIZATION_DELAY = ConfigKeys.newDurationConfigKey(
+ "failureDetector.serviceRecoveredStabilizationDelay",
+ "Time period for which the health check succeeds continiually " +
+ "(e.g. doesn't report ok-failed-ok) before concluding recovered",
+ Duration.ZERO);
+
+ @SuppressWarnings("serial")
+ public static final ConfigKey<Sensor<FailureDescriptor>> SENSOR_FAILED = ConfigKeys.newConfigKey(new TypeToken<Sensor<FailureDescriptor>>() {},
+ "failureDetector.sensor.fail", "A sensor which will indicate failure when set", HASensors.ENTITY_FAILED);
+
+ @SuppressWarnings("serial")
+ public static final ConfigKey<Sensor<FailureDescriptor>> SENSOR_RECOVERED = ConfigKeys.newConfigKey(new TypeToken<Sensor<FailureDescriptor>>() {},
+ "failureDetector.sensor.recover", "A sensor which will indicate recovery from failure when set", HASensors.ENTITY_RECOVERED);
+
+ public interface CalculatedStatus {
+ boolean isHealthy();
+ String getDescription();
+ }
+
+ private final class PublishJob implements Runnable {
+ @Override public void run() {
+ try {
+ executorTime = System.currentTimeMillis();
+ executorQueued.set(false);
+
+ publishNow();
+
+ } catch (Exception e) {
+ if (isRunning()) {
+ LOG.error("Problem resizing: "+e, e);
+ } else {
+ if (LOG.isDebugEnabled()) LOG.debug("Problem resizing, but no longer running: "+e, e);
+ }
+ } catch (Throwable t) {
+ LOG.error("Problem in service-failure-detector: "+t, t);
+ throw Exceptions.propagate(t);
+ }
+ }
+ }
+
+ private final class HealthPoller implements Runnable {
+ @Override
+ public void run() {
+ checkHealth();
+ }
+ }
+
+ private final class HealthPollingTaskFactory implements Callable<Task<?>> {
+ @Override
+ public Task<?> call() {
+ BasicTask<Void> task = new BasicTask<Void>(new HealthPoller());
+ BrooklynTaskTags.setTransient(task);
+ return task;
+ }
+ }
+
+ protected static class BasicCalculatedStatus implements CalculatedStatus {
+ private boolean healthy;
+ private String description;
+
+ public BasicCalculatedStatus(boolean healthy, String description) {
+ this.healthy = healthy;
+ this.description = description;
+ }
+
+ @Override
+ public boolean isHealthy() {
+ return healthy;
+ }
+
+ @Override
+ public String getDescription() {
+ return description;
+ }
+ }
+
+ public enum LastPublished {
+ NONE,
+ FAILED,
+ RECOVERED;
+ }
+
+ protected final AtomicReference<Long> stateLastGood = new AtomicReference<Long>();
+ protected final AtomicReference<Long> stateLastFail = new AtomicReference<Long>();
+
+ protected Long currentFailureStartTime = null;
+ protected Long currentRecoveryStartTime = null;
+
+ protected LastPublished lastPublished = LastPublished.NONE;
+
+ private final AtomicBoolean executorQueued = new AtomicBoolean(false);
+ private volatile long executorTime = 0;
+
+ private Callable<Task<?>> pollingTaskFactory = new HealthPollingTaskFactory();
+
+ private Task<?> scheduledTask;
+
+ protected abstract CalculatedStatus calculateStatus();
+
+ @Override
+ public void setEntity(EntityLocal entity) {
+ super.setEntity(entity);
+
+ if (isRunning()) {
+ doStartPolling();
+ }
+ }
+
+ @Override
+ public void suspend() {
+ scheduledTask.cancel(true);
+ super.suspend();
+ }
+
+ @Override
+ public void resume() {
+ currentFailureStartTime = null;
+ currentRecoveryStartTime = null;
+ lastPublished = LastPublished.NONE;
+ executorQueued.set(false);
+ executorTime = 0;
+
+ super.resume();
+ doStartPolling();
+ }
+
+ @SuppressWarnings("unchecked")
+ protected void doStartPolling() {
+ if (scheduledTask == null || scheduledTask.isDone()) {
+ ScheduledTask task = new ScheduledTask(MutableMap.of("period", getPollPeriod(), "displayName", getTaskName()), pollingTaskFactory);
+ scheduledTask = ((EntityInternal)entity).getExecutionContext().submit(task);
+ }
+ }
+
+ private String getTaskName() {
+ return getDisplayName();
+ }
+
+ protected Duration getPollPeriod() {
+ return getConfig(POLL_PERIOD);
+ }
+
+ protected Duration getFailedStabilizationDelay() {
+ return getConfig(FAILED_STABILIZATION_DELAY);
+ }
+
+ protected Duration getRecoveredStabilizationDelay() {
+ return getConfig(RECOVERED_STABILIZATION_DELAY);
+ }
+
+ protected Sensor<FailureDescriptor> getSensorFailed() {
+ return getConfig(SENSOR_FAILED);
+ }
+
+ protected Sensor<FailureDescriptor> getSensorRecovered() {
+ return getConfig(SENSOR_RECOVERED);
+ }
+
+ private synchronized void checkHealth() {
+ CalculatedStatus status = calculateStatus();
+ boolean healthy = status.isHealthy();
+ long now = System.currentTimeMillis();
+
+ if (healthy) {
+ stateLastGood.set(now);
+ if (lastPublished == LastPublished.FAILED) {
+ if (currentRecoveryStartTime == null) {
+ LOG.info("{} check for {}, now recovering: {}", new Object[] {this, entity, getDescription(status)});
+ currentRecoveryStartTime = now;
+ schedulePublish();
+ } else {
+ if (LOG.isTraceEnabled()) LOG.trace("{} check for {}, continuing recovering: {}", new Object[] {this, entity, getDescription(status)});
+ }
+ } else {
+ if (currentFailureStartTime != null) {
+ LOG.info("{} check for {}, now healthy: {}", new Object[] {this, entity, getDescription(status)});
+ currentFailureStartTime = null;
+ } else {
+ if (LOG.isTraceEnabled()) LOG.trace("{} check for {}, still healthy: {}", new Object[] {this, entity, getDescription(status)});
+ }
+ }
+ } else {
+ stateLastFail.set(now);
+ if (lastPublished != LastPublished.FAILED) {
+ if (currentFailureStartTime == null) {
+ LOG.info("{} check for {}, now failing: {}", new Object[] {this, entity, getDescription(status)});
+ currentFailureStartTime = now;
+ schedulePublish();
+ } else {
+ if (LOG.isTraceEnabled()) LOG.trace("{} check for {}, continuing failing: {}", new Object[] {this, entity, getDescription(status)});
+ }
+ } else {
+ if (currentRecoveryStartTime != null) {
+ LOG.info("{} check for {}, now failing: {}", new Object[] {this, entity, getDescription(status)});
+ currentRecoveryStartTime = null;
+ } else {
+ if (LOG.isTraceEnabled()) LOG.trace("{} check for {}, still failed: {}", new Object[] {this, entity, getDescription(status)});
+ }
+ }
+ }
+ }
+
+ protected void schedulePublish() {
+ schedulePublish(0);
+ }
+
+ @SuppressWarnings("unchecked")
+ protected void schedulePublish(long delay) {
+ if (isRunning() && executorQueued.compareAndSet(false, true)) {
+ long now = System.currentTimeMillis();
+ delay = Math.max(0, Math.max(delay, (executorTime + MIN_PERIOD_BETWEEN_EXECS_MILLIS) - now));
+ if (LOG.isTraceEnabled()) LOG.trace("{} scheduling publish in {}ms", this, delay);
+
+ Runnable job = new PublishJob();
+
+ ScheduledTask task = new ScheduledTask(MutableMap.of("delay", Duration.of(delay, TimeUnit.MILLISECONDS)), new BasicTask<Void>(job));
+ ((EntityInternal)entity).getExecutionContext().submit(task);
+ }
+ }
+
+ private synchronized void publishNow() {
+ if (!isRunning()) return;
+
+ CalculatedStatus calculatedStatus = calculateStatus();
+ boolean healthy = calculatedStatus.isHealthy();
+
+ Long lastUpTime = stateLastGood.get();
+ Long lastDownTime = stateLastFail.get();
+ long serviceFailedStabilizationDelay = getFailedStabilizationDelay().toMilliseconds();
+ long serviceRecoveredStabilizationDelay = getRecoveredStabilizationDelay().toMilliseconds();
+ long now = System.currentTimeMillis();
+
+ if (healthy) {
+ if (lastPublished == LastPublished.FAILED) {
+ // only publish if consistently up for serviceRecoveredStabilizationDelay
+ long currentRecoveryPeriod = getTimeDiff(now, currentRecoveryStartTime);
+ long sinceLastDownPeriod = getTimeDiff(now, lastDownTime);
+ if (currentRecoveryPeriod > serviceRecoveredStabilizationDelay && sinceLastDownPeriod > serviceRecoveredStabilizationDelay) {
+ String description = getDescription(calculatedStatus);
+ LOG.warn("{} check for {}, publishing recovered: {}", new Object[] {this, entity, description});
+ entity.emit(getSensorRecovered(), new HASensors.FailureDescriptor(entity, description));
+ lastPublished = LastPublished.RECOVERED;
+ currentFailureStartTime = null;
+ } else {
+ long nextAttemptTime = Math.max(serviceRecoveredStabilizationDelay - currentRecoveryPeriod, serviceRecoveredStabilizationDelay - sinceLastDownPeriod);
+ schedulePublish(nextAttemptTime);
+ }
+ }
+ } else {
+ if (lastPublished != LastPublished.FAILED) {
+ // only publish if consistently down for serviceFailedStabilizationDelay
+ long currentFailurePeriod = getTimeDiff(now, currentFailureStartTime);
+ long sinceLastUpPeriod = getTimeDiff(now, lastUpTime);
+ if (currentFailurePeriod > serviceFailedStabilizationDelay && sinceLastUpPeriod > serviceFailedStabilizationDelay) {
+ String description = getDescription(calculatedStatus);
+ LOG.warn("{} connectivity-check for {}, publishing failed: {}", new Object[] {this, entity, description});
+ entity.emit(getSensorFailed(), new HASensors.FailureDescriptor(entity, description));
+ lastPublished = LastPublished.FAILED;
+ currentRecoveryStartTime = null;
+ } else {
+ long nextAttemptTime = Math.max(serviceFailedStabilizationDelay - currentFailurePeriod, serviceFailedStabilizationDelay - sinceLastUpPeriod);
+ schedulePublish(nextAttemptTime);
+ }
+ }
+ }
+ }
+
+ protected String getDescription(CalculatedStatus status) {
+ Long lastUpTime = stateLastGood.get();
+ Long lastDownTime = stateLastGood.get();
+ Duration serviceFailedStabilizationDelay = getFailedStabilizationDelay();
+ Duration serviceRecoveredStabilizationDelay = getRecoveredStabilizationDelay();
+
+ return String.format("%s; healthy=%s; timeNow=%s; lastUp=%s; lastDown=%s; lastPublished=%s; "+
+ "currentFailurePeriod=%s; currentRecoveryPeriod=%s",
+ status.getDescription(),
+ status.isHealthy(),
+ Time.makeDateString(System.currentTimeMillis()),
+ (lastUpTime != null ? Time.makeDateString(lastUpTime) : "<never>"),
+ (lastDownTime != null ? Time.makeDateString(lastDownTime) : "<never>"),
+ lastPublished,
+ (currentFailureStartTime != null ? getTimeStringSince(currentFailureStartTime) : "<none>") + " (stabilization "+makeTimeStringRounded(serviceFailedStabilizationDelay) + ")",
+ (currentRecoveryStartTime != null ? getTimeStringSince(currentRecoveryStartTime) : "<none>") + " (stabilization "+makeTimeStringRounded(serviceRecoveredStabilizationDelay) + ")");
+ }
+
+ private long getTimeDiff(Long recent, Long previous) {
+ return (previous == null) ? recent : (recent - previous);
+ }
+
+ private String getTimeStringSince(Long time) {
+ return time == null ? null : Time.makeTimeStringRounded(System.currentTimeMillis() - time);
+ }
+}
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/d30ff597/policy/src/main/java/org/apache/brooklyn/policy/ha/ConditionalSuspendPolicy.java
----------------------------------------------------------------------
diff --git a/policy/src/main/java/org/apache/brooklyn/policy/ha/ConditionalSuspendPolicy.java b/policy/src/main/java/org/apache/brooklyn/policy/ha/ConditionalSuspendPolicy.java
new file mode 100644
index 0000000..4bc1dc1
--- /dev/null
+++ b/policy/src/main/java/org/apache/brooklyn/policy/ha/ConditionalSuspendPolicy.java
@@ -0,0 +1,103 @@
+/*
+ * 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.policy.ha;
+
+import org.apache.brooklyn.api.entity.basic.EntityLocal;
+import org.apache.brooklyn.api.event.Sensor;
+import org.apache.brooklyn.api.event.SensorEvent;
+import org.apache.brooklyn.api.event.SensorEventListener;
+import org.apache.brooklyn.api.policy.Policy;
+import org.apache.brooklyn.core.policy.basic.AbstractPolicy;
+import org.apache.brooklyn.core.util.flags.SetFromFlag;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import brooklyn.config.ConfigKey;
+import brooklyn.entity.basic.ConfigKeys;
+import brooklyn.util.javalang.JavaClassNames;
+
+import com.google.common.base.Preconditions;
+
+public class ConditionalSuspendPolicy extends AbstractPolicy {
+ private static final Logger LOG = LoggerFactory.getLogger(ConditionalSuspendPolicy.class);
+
+ @SetFromFlag("suppressSensor")
+ @SuppressWarnings({ "rawtypes", "unchecked" })
+ public static final ConfigKey<Sensor<?>> SUSPEND_SENSOR = (ConfigKey) ConfigKeys.newConfigKey(Sensor.class,
+ "suppressSensor", "Sensor which will suppress the target policy", HASensors.CONNECTION_FAILED);
+
+ @SetFromFlag("resetSensor")
+ @SuppressWarnings({ "rawtypes", "unchecked" })
+ public static final ConfigKey<Sensor<?>> RESUME_SENSOR = (ConfigKey) ConfigKeys.newConfigKey(Sensor.class,
+ "resetSensor", "Resume target policy when this sensor is observed", HASensors.CONNECTION_RECOVERED);
+
+ @SetFromFlag("target")
+ public static final ConfigKey<Object> SUSPEND_TARGET = ConfigKeys.newConfigKey(Object.class,
+ "target", "The target policy to suspend. Either direct reference or the value of the suspendTarget config on a policy from the same entity.");
+
+ @Override
+ public void setEntity(EntityLocal entity) {
+ super.setEntity(entity);
+ Object target = config().get(SUSPEND_TARGET);
+ Preconditions.checkNotNull(target, "Suspend target required");
+ Preconditions.checkNotNull(getTargetPolicy(), "Can't find target policy set in " + SUSPEND_TARGET.getName() + ": " + target);
+ subscribe();
+ uniqueTag = JavaClassNames.simpleClassName(getClass())+":"+getConfig(SUSPEND_SENSOR).getName()+":"+getConfig(RESUME_SENSOR).getName();
+ }
+
+ private void subscribe() {
+ subscribe(entity, getConfig(SUSPEND_SENSOR), new SensorEventListener<Object>() {
+ @Override public void onEvent(final SensorEvent<Object> event) {
+ if (isRunning()) {
+ Policy target = getTargetPolicy();
+ target.suspend();
+ LOG.debug("Suspended policy " + target + ", triggered by " + event.getSensor() + " = " + event.getValue());
+ }
+ }
+
+ });
+ subscribe(entity, getConfig(RESUME_SENSOR), new SensorEventListener<Object>() {
+ @Override public void onEvent(final SensorEvent<Object> event) {
+ if (isRunning()) {
+ Policy target = getTargetPolicy();
+ target.resume();
+ LOG.debug("Resumed policy " + target + ", triggered by " + event.getSensor() + " = " + event.getValue());
+ }
+ }
+ });
+ }
+
+ private Policy getTargetPolicy() {
+ Object target = config().get(SUSPEND_TARGET);
+ if (target instanceof Policy) {
+ return (Policy)target;
+ } else if (target instanceof String) {
+ for (Policy policy : entity.getPolicies()) {
+ // No way to set config values for keys NOT declared in the policy,
+ // so must use displayName as a generally available config value.
+ if (target.equals(policy.getDisplayName()) || target.equals(policy.getClass().getName())) {
+ return policy;
+ }
+ }
+ } else {
+ throw new IllegalStateException("Unexpected type " + target.getClass() + " for target " + target);
+ }
+ return null;
+ }
+}
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/d30ff597/policy/src/main/java/org/apache/brooklyn/policy/ha/ConnectionFailureDetector.java
----------------------------------------------------------------------
diff --git a/policy/src/main/java/org/apache/brooklyn/policy/ha/ConnectionFailureDetector.java b/policy/src/main/java/org/apache/brooklyn/policy/ha/ConnectionFailureDetector.java
new file mode 100644
index 0000000..2cca77f
--- /dev/null
+++ b/policy/src/main/java/org/apache/brooklyn/policy/ha/ConnectionFailureDetector.java
@@ -0,0 +1,128 @@
+/*
+ * 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.policy.ha;
+
+import org.apache.brooklyn.api.catalog.Catalog;
+import org.apache.brooklyn.api.event.Sensor;
+import org.apache.brooklyn.core.util.flags.SetFromFlag;
+
+import brooklyn.config.ConfigKey;
+import brooklyn.entity.basic.ConfigKeys;
+import brooklyn.event.basic.BasicConfigKey;
+import brooklyn.event.basic.BasicNotificationSensor;
+
+import org.apache.brooklyn.policy.ha.HASensors.FailureDescriptor;
+
+import brooklyn.util.guava.Maybe;
+import brooklyn.util.net.Networking;
+import brooklyn.util.time.Duration;
+
+import com.google.common.net.HostAndPort;
+
+/**
+ * Monitors a given {@link HostAndPort}, to emit HASensors.CONNECTION_FAILED and HASensors.CONNECTION_RECOVERED
+ * if the connection is lost/restored.
+ */
+@Catalog(name="Connection Failure Detector", description="HA policy for monitoring a host:port, "
+ + "emitting an event if the connection is lost/restored")
+public class ConnectionFailureDetector extends AbstractFailureDetector {
+
+ public static final ConfigKey<HostAndPort> ENDPOINT = ConfigKeys.newConfigKey(HostAndPort.class, "connectionFailureDetector.endpoint");
+
+ public static final ConfigKey<Duration> POLL_PERIOD = ConfigKeys.newConfigKey(Duration.class, "connectionFailureDetector.pollPeriod", "", Duration.ONE_SECOND);
+
+ public static final BasicNotificationSensor<FailureDescriptor> CONNECTION_FAILED = HASensors.CONNECTION_FAILED;
+
+ public static final BasicNotificationSensor<FailureDescriptor> CONNECTION_RECOVERED = HASensors.CONNECTION_RECOVERED;
+
+ @SetFromFlag("connectionFailedStabilizationDelay")
+ public static final ConfigKey<Duration> CONNECTION_FAILED_STABILIZATION_DELAY = BasicConfigKey.builder(Duration.class)
+ .name("connectionFailureDetector.serviceFailedStabilizationDelay")
+ .description("Time period for which the connection must be consistently down for "
+ + "(e.g. doesn't report down-up-down) before concluding failure. "
+ + "Note that long TCP timeouts mean there can be long (e.g. 70 second) "
+ + "delays in noticing a connection refused condition.")
+ .defaultValue(Duration.ZERO)
+ .build();
+
+ @SetFromFlag("connectionRecoveredStabilizationDelay")
+ public static final ConfigKey<Duration> CONNECTION_RECOVERED_STABILIZATION_DELAY = BasicConfigKey.builder(Duration.class)
+ .name("connectionFailureDetector.serviceRecoveredStabilizationDelay")
+ .description("For a failed connection, time period for which the connection must be consistently up for (e.g. doesn't report up-down-up) before concluding recovered")
+ .defaultValue(Duration.ZERO)
+ .build();
+
+ @Override
+ public void init() {
+ super.init();
+ getRequiredConfig(ENDPOINT); // just to confirm it's set, failing fast
+ if (config().getRaw(SENSOR_FAILED).isAbsent()) {
+ config().set(SENSOR_FAILED, CONNECTION_FAILED);
+ }
+ if (config().getRaw(SENSOR_RECOVERED).isAbsent()) {
+ config().set(SENSOR_RECOVERED, CONNECTION_RECOVERED);
+ }
+ }
+
+ @Override
+ protected CalculatedStatus calculateStatus() {
+ HostAndPort endpoint = getConfig(ENDPOINT);
+ boolean isHealthy = Networking.isReachable(endpoint);
+ return new BasicCalculatedStatus(isHealthy, "endpoint=" + endpoint);
+ }
+
+ //Persistence compatibility overrides
+ @Override
+ protected Duration getPollPeriod() {
+ return getConfig(POLL_PERIOD);
+ }
+
+ @Override
+ protected Duration getFailedStabilizationDelay() {
+ return getConfig(CONNECTION_FAILED_STABILIZATION_DELAY);
+ }
+
+ @Override
+ protected Duration getRecoveredStabilizationDelay() {
+ return getConfig(CONNECTION_RECOVERED_STABILIZATION_DELAY);
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ protected Sensor<FailureDescriptor> getSensorFailed() {
+ Maybe<Object> sensorFailed = config().getRaw(SENSOR_FAILED);
+ if (sensorFailed.isPresent()) {
+ return (Sensor<FailureDescriptor>)sensorFailed.get();
+ } else {
+ return CONNECTION_FAILED;
+ }
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ protected Sensor<FailureDescriptor> getSensorRecovered() {
+ Maybe<Object> sensorRecovered = config().getRaw(SENSOR_RECOVERED);
+ if (sensorRecovered.isPresent()) {
+ return (Sensor<FailureDescriptor>)sensorRecovered.get();
+ } else {
+ return CONNECTION_RECOVERED;
+ }
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/d30ff597/policy/src/main/java/org/apache/brooklyn/policy/ha/HASensors.java
----------------------------------------------------------------------
diff --git a/policy/src/main/java/org/apache/brooklyn/policy/ha/HASensors.java b/policy/src/main/java/org/apache/brooklyn/policy/ha/HASensors.java
new file mode 100644
index 0000000..24763f0
--- /dev/null
+++ b/policy/src/main/java/org/apache/brooklyn/policy/ha/HASensors.java
@@ -0,0 +1,62 @@
+/*
+ * 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.policy.ha;
+
+import brooklyn.event.basic.BasicNotificationSensor;
+
+import com.google.common.base.Objects;
+
+public class HASensors {
+
+ public static final BasicNotificationSensor<FailureDescriptor> ENTITY_FAILED = new BasicNotificationSensor<FailureDescriptor>(
+ FailureDescriptor.class, "ha.entityFailed", "Indicates that an entity has failed");
+
+ public static final BasicNotificationSensor<FailureDescriptor> ENTITY_RECOVERED = new BasicNotificationSensor<FailureDescriptor>(
+ FailureDescriptor.class, "ha.entityRecovered", "Indicates that a previously failed entity has recovered");
+
+ public static final BasicNotificationSensor<FailureDescriptor> CONNECTION_FAILED = new BasicNotificationSensor<FailureDescriptor>(
+ FailureDescriptor.class, "ha.connectionFailed", "Indicates that a connection has failed");
+
+ public static final BasicNotificationSensor<FailureDescriptor> CONNECTION_RECOVERED = new BasicNotificationSensor<FailureDescriptor>(
+ FailureDescriptor.class, "ha.connectionRecovered", "Indicates that a previously failed connection has recovered");
+
+ // TODO How to make this serializable with the entity reference
+ public static class FailureDescriptor {
+ private final Object component;
+ private final String description;
+
+ public FailureDescriptor(Object component, String description) {
+ this.component = component;
+ this.description = description;
+ }
+
+ public Object getComponent() {
+ return component;
+ }
+
+ public String getDescription() {
+ return description;
+ }
+
+ @Override
+ public String toString() {
+ return Objects.toStringHelper(this).add("component", component).add("description", description).toString();
+ }
+ }
+}
[10/24] incubator-brooklyn git commit: [BROOKLYN-162] Renaming
package policy
Posted by he...@apache.org.
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/d30ff597/policy/src/test/java/brooklyn/policy/ha/ServiceReplacerTest.java
----------------------------------------------------------------------
diff --git a/policy/src/test/java/brooklyn/policy/ha/ServiceReplacerTest.java b/policy/src/test/java/brooklyn/policy/ha/ServiceReplacerTest.java
deleted file mode 100644
index c679224..0000000
--- a/policy/src/test/java/brooklyn/policy/ha/ServiceReplacerTest.java
+++ /dev/null
@@ -1,340 +0,0 @@
-/*
- * 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 brooklyn.policy.ha;
-
-import static org.testng.Assert.assertEquals;
-import static org.testng.Assert.assertFalse;
-import static org.testng.Assert.assertNotEquals;
-import static org.testng.Assert.assertTrue;
-
-import java.util.List;
-import java.util.Set;
-import java.util.concurrent.atomic.AtomicInteger;
-
-import org.apache.brooklyn.api.entity.Entity;
-import org.apache.brooklyn.api.entity.proxying.EntitySpec;
-import org.apache.brooklyn.api.event.SensorEvent;
-import org.apache.brooklyn.api.event.SensorEventListener;
-import org.apache.brooklyn.api.location.Location;
-import org.apache.brooklyn.api.location.LocationSpec;
-import org.apache.brooklyn.api.management.ManagementContext;
-import org.apache.brooklyn.api.policy.PolicySpec;
-import org.apache.brooklyn.core.util.config.ConfigBag;
-import org.apache.brooklyn.test.EntityTestUtils;
-import org.apache.brooklyn.test.entity.LocalManagementContextForTests;
-import org.apache.brooklyn.test.entity.TestApplication;
-import org.apache.brooklyn.test.entity.TestEntity;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.testng.Assert;
-import org.testng.annotations.AfterMethod;
-import org.testng.annotations.BeforeMethod;
-import org.testng.annotations.Test;
-
-import brooklyn.entity.basic.ApplicationBuilder;
-import brooklyn.entity.basic.Attributes;
-import brooklyn.entity.basic.Entities;
-import brooklyn.entity.basic.EntityInternal;
-import brooklyn.entity.basic.Lifecycle;
-import brooklyn.entity.basic.QuorumCheck;
-import brooklyn.entity.basic.ServiceStateLogic.ComputeServiceIndicatorsFromChildrenAndMembers;
-import brooklyn.entity.group.DynamicCluster;
-import brooklyn.entity.trait.FailingEntity;
-
-import org.apache.brooklyn.location.basic.SimulatedLocation;
-
-import brooklyn.policy.ha.HASensors.FailureDescriptor;
-import brooklyn.test.Asserts;
-import brooklyn.util.javalang.JavaClassNames;
-
-import com.google.common.base.Predicate;
-import com.google.common.base.Predicates;
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.ImmutableSet;
-import com.google.common.collect.Iterables;
-import com.google.common.collect.Lists;
-import com.google.common.collect.Sets;
-
-public class ServiceReplacerTest {
-
- private static final Logger log = LoggerFactory.getLogger(ServiceReplacerTest.class);
-
- private ManagementContext managementContext;
- private TestApplication app;
- private SimulatedLocation loc;
- private SensorEventListener<Object> eventListener;
- private List<SensorEvent<?>> events;
-
- @BeforeMethod(alwaysRun=true)
- public void setUp() throws Exception {
- managementContext = new LocalManagementContextForTests();
- app = ApplicationBuilder.newManagedApp(TestApplication.class, managementContext);
- loc = managementContext.getLocationManager().createLocation(LocationSpec.create(SimulatedLocation.class));
- events = Lists.newCopyOnWriteArrayList();
- eventListener = new SensorEventListener<Object>() {
- @Override public void onEvent(SensorEvent<Object> event) {
- events.add(event);
- }
- };
- }
-
- @AfterMethod(alwaysRun=true)
- public void tearDown() throws Exception {
- if (managementContext != null) Entities.destroyAll(managementContext);
- }
-
- @Test
- public void testReplacesFailedMember() throws Exception {
- final DynamicCluster cluster = app.createAndManageChild(EntitySpec.create(DynamicCluster.class)
- .configure(DynamicCluster.MEMBER_SPEC, EntitySpec.create(TestEntity.class))
- .configure(DynamicCluster.INITIAL_SIZE, 3));
- app.start(ImmutableList.<Location>of(loc));
-
- ServiceReplacer policy = new ServiceReplacer(new ConfigBag().configure(ServiceReplacer.FAILURE_SENSOR_TO_MONITOR, HASensors.ENTITY_FAILED));
- cluster.addPolicy(policy);
-
- final Set<Entity> initialMembers = ImmutableSet.copyOf(cluster.getMembers());
- final TestEntity e1 = (TestEntity) Iterables.get(initialMembers, 1);
-
- e1.emit(HASensors.ENTITY_FAILED, new FailureDescriptor(e1, "simulate failure"));
-
- // Expect e1 to be replaced
- Asserts.succeedsEventually(new Runnable() {
- @Override public void run() {
- Set<Entity> newMembers = Sets.difference(ImmutableSet.copyOf(cluster.getMembers()), initialMembers);
- Set<Entity> removedMembers = Sets.difference(initialMembers, ImmutableSet.copyOf(cluster.getMembers()));
- assertEquals(removedMembers, ImmutableSet.of(e1));
- assertEquals(newMembers.size(), 1);
- assertEquals(((TestEntity)Iterables.getOnlyElement(newMembers)).getCallHistory(), ImmutableList.of("start"));
- assertEquals(e1.getCallHistory(), ImmutableList.of("start", "stop"));
- assertFalse(Entities.isManaged(e1));
- }});
- }
-
- @Test(invocationCount=100)
- public void testSetsOnFireWhenFailToReplaceMemberManyTimes() throws Exception {
- testSetsOnFireWhenFailToReplaceMember();
- }
-
- // fails the startup of the replacement entity (but not the original).
- @Test
- public void testSetsOnFireWhenFailToReplaceMember() throws Exception {
- app.subscribe(null, ServiceReplacer.ENTITY_REPLACEMENT_FAILED, eventListener);
-
- final DynamicCluster cluster = app.createAndManageChild(EntitySpec.create(DynamicCluster.class)
- .configure(DynamicCluster.MEMBER_SPEC, EntitySpec.create(FailingEntity.class)
- .configure(FailingEntity.FAIL_ON_START_CONDITION, predicateOnlyTrueForCallAtOrAfter(2)))
- .configure(DynamicCluster.INITIAL_SIZE, 1)
- .configure(DynamicCluster.QUARANTINE_FAILED_ENTITIES, true)
- .configure(ComputeServiceIndicatorsFromChildrenAndMembers.UP_QUORUM_CHECK, QuorumCheck.QuorumChecks.alwaysTrue())
- .configure(ComputeServiceIndicatorsFromChildrenAndMembers.RUNNING_QUORUM_CHECK, QuorumCheck.QuorumChecks.alwaysTrue()));
- app.start(ImmutableList.<Location>of(loc));
-
- // should not be on fire
- Assert.assertNotEquals(cluster.getAttribute(Attributes.SERVICE_STATE_ACTUAL), Lifecycle.ON_FIRE);
- // and should eventually be running
- EntityTestUtils.assertAttributeEqualsEventually(cluster, Attributes.SERVICE_STATE_ACTUAL, Lifecycle.RUNNING);
-
- log.info("started "+app+" for "+JavaClassNames.niceClassAndMethod());
-
- ServiceReplacer policy = new ServiceReplacer(new ConfigBag().configure(ServiceReplacer.FAILURE_SENSOR_TO_MONITOR, HASensors.ENTITY_FAILED));
- cluster.addPolicy(policy);
-
- final Set<Entity> initialMembers = ImmutableSet.copyOf(cluster.getMembers());
- final TestEntity e1 = (TestEntity) Iterables.get(initialMembers, 0);
-
- e1.emit(HASensors.ENTITY_FAILED, new FailureDescriptor(e1, "simulate failure"));
-
- // Expect cluster to go on-fire when fails to start replacement
- // Note that we've set up-quorum and running-quorum to be "alwaysTrue" so that we don't get a transient onFire
- // when the failed node fails to start (but before it has been removed from the group to be put in quarantine).
- EntityTestUtils.assertAttributeEqualsEventually(cluster, Attributes.SERVICE_STATE_ACTUAL, Lifecycle.ON_FIRE);
-
- // Expect to have the second failed entity still kicking around as proof (in quarantine)
- // The cluster should NOT go on fire until after the 2nd failure
- Iterable<Entity> members = Iterables.filter(managementContext.getEntityManager().getEntities(), Predicates.instanceOf(FailingEntity.class));
- assertEquals(Iterables.size(members), 2);
-
- // e2 failed to start, so it won't have called stop on e1
- TestEntity e2 = (TestEntity) Iterables.getOnlyElement(Sets.difference(ImmutableSet.copyOf(members), initialMembers));
- assertEquals(e1.getCallHistory(), ImmutableList.of("start"), "e1.history="+e1.getCallHistory());
- assertEquals(e2.getCallHistory(), ImmutableList.of("start"), "e2.history="+e2.getCallHistory());
-
- // And will have received notification event about it
- assertEventuallyHasEntityReplacementFailedEvent(cluster);
- }
-
- @Test(groups="Integration") // has a 1 second wait
- public void testDoesNotOnFireWhenFailToReplaceMember() throws Exception {
- app.subscribe(null, ServiceReplacer.ENTITY_REPLACEMENT_FAILED, eventListener);
-
- final DynamicCluster cluster = app.createAndManageChild(EntitySpec.create(DynamicCluster.class)
- .configure(DynamicCluster.MEMBER_SPEC, EntitySpec.create(FailingEntity.class)
- .configure(FailingEntity.FAIL_ON_START_CONDITION, predicateOnlyTrueForCallAtOrAfter(2)))
- .configure(DynamicCluster.INITIAL_SIZE, 1)
- .configure(DynamicCluster.QUARANTINE_FAILED_ENTITIES, true));
- app.start(ImmutableList.<Location>of(loc));
-
- ServiceReplacer policy = new ServiceReplacer(new ConfigBag()
- .configure(ServiceReplacer.FAILURE_SENSOR_TO_MONITOR, HASensors.ENTITY_FAILED)
- .configure(ServiceReplacer.SET_ON_FIRE_ON_FAILURE, false));
- cluster.addPolicy(policy);
-
- final Set<Entity> initialMembers = ImmutableSet.copyOf(cluster.getMembers());
- final TestEntity e1 = (TestEntity) Iterables.get(initialMembers, 0);
-
- e1.emit(HASensors.ENTITY_FAILED, new FailureDescriptor(e1, "simulate failure"));
-
- // Configured to not mark cluster as on fire
- Asserts.succeedsContinually(new Runnable() {
- @Override public void run() {
- assertNotEquals(cluster.getAttribute(Attributes.SERVICE_STATE_ACTUAL), Lifecycle.ON_FIRE);
- }});
-
- // And will have received notification event about it
- assertEventuallyHasEntityReplacementFailedEvent(cluster);
- }
-
- @Test(groups="Integration") // 1s wait
- public void testStopFailureOfOldEntityDoesNotSetClusterOnFire() throws Exception {
- app.subscribe(null, ServiceReplacer.ENTITY_REPLACEMENT_FAILED, eventListener);
-
- final DynamicCluster cluster = app.createAndManageChild(EntitySpec.create(DynamicCluster.class)
- .configure(DynamicCluster.MEMBER_SPEC, EntitySpec.create(FailingEntity.class)
- .configure(FailingEntity.FAIL_ON_STOP_CONDITION, predicateOnlyTrueForCallAt(1)))
- .configure(DynamicCluster.INITIAL_SIZE, 2));
- app.start(ImmutableList.<Location>of(loc));
-
- cluster.addPolicy(PolicySpec.create(ServiceReplacer.class)
- .configure(ServiceReplacer.FAILURE_SENSOR_TO_MONITOR, HASensors.ENTITY_FAILED));
-
- final Set<Entity> initialMembers = ImmutableSet.copyOf(cluster.getMembers());
- final TestEntity e1 = (TestEntity) Iterables.get(initialMembers, 0);
-
- e1.emit(HASensors.ENTITY_FAILED, new FailureDescriptor(e1, "simulate failure"));
-
- // Expect e1 to be replaced
- Asserts.succeedsEventually(new Runnable() {
- @Override public void run() {
- Set<Entity> newMembers = Sets.difference(ImmutableSet.copyOf(cluster.getMembers()), initialMembers);
- Set<Entity> removedMembers = Sets.difference(initialMembers, ImmutableSet.copyOf(cluster.getMembers()));
- assertEquals(removedMembers, ImmutableSet.of(e1));
- assertEquals(newMembers.size(), 1);
- assertEquals(((TestEntity)Iterables.getOnlyElement(newMembers)).getCallHistory(), ImmutableList.of("start"));
- assertEquals(e1.getCallHistory(), ImmutableList.of("start", "stop"));
- assertFalse(Entities.isManaged(e1));
- }});
-
- // Failure to stop the failed member should not cause "on-fire" of cluster
- Asserts.succeedsContinually(new Runnable() {
- @Override public void run() {
- assertNotEquals(cluster.getAttribute(Attributes.SERVICE_STATE_ACTUAL), Lifecycle.ON_FIRE);
- }});
- }
-
- /**
- * If we keep on getting failure reports, never managing to replace the failed node, then don't keep trying
- * (i.e. avoid infinite loop).
- *
- * TODO This code + configuration needs some work; it's not testing quite the scenarios that I
- * was thinking of!
- * I saw problem where a node failed, and the replacements failed, and we ended up trying thousands of times.
- * (describing this scenario is made more complex by me having temporarily disabled the cluster from
- * removing failed members, for debugging purposes!)
- * Imagine these two scenarios:
- * <ol>
- * <li>Entity fails during call to start().
- * Here, the cluster removes it as a member (either unmanages it or puts it in quarantine)
- * So the ENTITY_FAILED is ignored because the entity is not a member at that point.
- * <li>Entity returns from start(), but quickly goes to service-down.
- * Here we'll keep trying to replace that entity. Depending how long that takes, we'll either
- * enter a horrible infinite loop, or we'll just provision a huge number of VMs over a long
- * time period.
- * Unfortunately this scenario is not catered for in the code yet.
- * </ol>
- */
- @Test(groups="Integration") // because takes 1.2 seconds
- public void testAbandonsReplacementAfterNumFailures() throws Exception {
- app.subscribe(null, ServiceReplacer.ENTITY_REPLACEMENT_FAILED, eventListener);
-
- final DynamicCluster cluster = app.createAndManageChild(EntitySpec.create(DynamicCluster.class)
- .configure(DynamicCluster.MEMBER_SPEC, EntitySpec.create(FailingEntity.class)
- .configure(FailingEntity.FAIL_ON_START_CONDITION, predicateOnlyTrueForCallAtOrAfter(11)))
- .configure(DynamicCluster.INITIAL_SIZE, 10)
- .configure(DynamicCluster.QUARANTINE_FAILED_ENTITIES, true));
- app.start(ImmutableList.<Location>of(loc));
-
- ServiceReplacer policy = new ServiceReplacer(new ConfigBag()
- .configure(ServiceReplacer.FAILURE_SENSOR_TO_MONITOR, HASensors.ENTITY_FAILED)
- .configure(ServiceReplacer.FAIL_ON_NUM_RECURRING_FAILURES, 3));
- cluster.addPolicy(policy);
-
- final Set<Entity> initialMembers = ImmutableSet.copyOf(cluster.getMembers());
- for (int i = 0; i < 5; i++) {
- final int counter = i+1;
- EntityInternal entity = (EntityInternal) Iterables.get(initialMembers, i);
- entity.emit(HASensors.ENTITY_FAILED, new FailureDescriptor(entity, "simulate failure"));
- if (i <= 3) {
- Asserts.succeedsEventually(new Runnable() {
- @Override public void run() {
- Set<FailingEntity> all = ImmutableSet.copyOf(Iterables.filter(managementContext.getEntityManager().getEntities(), FailingEntity.class));
- Set<FailingEntity> replacements = Sets.difference(all, initialMembers);
- Set<?> replacementMembers = Sets.intersection(ImmutableSet.of(cluster.getMembers()), replacements);
- assertTrue(replacementMembers.isEmpty());
- assertEquals(replacements.size(), counter);
- }});
- } else {
- Asserts.succeedsContinually(new Runnable() {
- @Override public void run() {
- Set<FailingEntity> all = ImmutableSet.copyOf(Iterables.filter(managementContext.getEntityManager().getEntities(), FailingEntity.class));
- Set<FailingEntity> replacements = Sets.difference(all, initialMembers);
- assertEquals(replacements.size(), 4);
- }});
- }
- }
- }
-
-
- private Predicate<Object> predicateOnlyTrueForCallAt(final int callNumber) {
- return predicateOnlyTrueForCallRange(callNumber, callNumber);
- }
-
- private Predicate<Object> predicateOnlyTrueForCallAtOrAfter(final int callLowerNumber) {
- return predicateOnlyTrueForCallRange(callLowerNumber, Integer.MAX_VALUE);
- }
-
- private Predicate<Object> predicateOnlyTrueForCallRange(final int callLowerNumber, final int callUpperNumber) {
- return new Predicate<Object>() {
- private final AtomicInteger counter = new AtomicInteger(0);
- @Override public boolean apply(Object input) {
- int num = counter.incrementAndGet();
- return num >= callLowerNumber && num <= callUpperNumber;
- }
- };
- }
-
- private void assertEventuallyHasEntityReplacementFailedEvent(final Entity expectedCluster) {
- Asserts.succeedsEventually(new Runnable() {
- @Override public void run() {
- assertEquals(Iterables.getOnlyElement(events).getSensor(), ServiceReplacer.ENTITY_REPLACEMENT_FAILED, "events="+events);
- assertEquals(Iterables.getOnlyElement(events).getSource(), expectedCluster, "events="+events);
- assertEquals(((FailureDescriptor)Iterables.getOnlyElement(events).getValue()).getComponent(), expectedCluster, "events="+events);
- }});
- }
-}
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/d30ff597/policy/src/test/java/brooklyn/policy/ha/ServiceRestarterTest.java
----------------------------------------------------------------------
diff --git a/policy/src/test/java/brooklyn/policy/ha/ServiceRestarterTest.java b/policy/src/test/java/brooklyn/policy/ha/ServiceRestarterTest.java
deleted file mode 100644
index 81cff78..0000000
--- a/policy/src/test/java/brooklyn/policy/ha/ServiceRestarterTest.java
+++ /dev/null
@@ -1,190 +0,0 @@
-/*
- * 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 brooklyn.policy.ha;
-
-import static org.testng.Assert.assertEquals;
-import static org.testng.Assert.assertNotEquals;
-import static org.testng.Assert.assertTrue;
-
-import java.util.List;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-
-import org.apache.brooklyn.api.entity.proxying.EntitySpec;
-import org.apache.brooklyn.api.event.SensorEvent;
-import org.apache.brooklyn.api.event.SensorEventListener;
-import org.apache.brooklyn.api.management.ManagementContext;
-import org.apache.brooklyn.api.policy.PolicySpec;
-import org.apache.brooklyn.core.util.config.ConfigBag;
-import org.apache.brooklyn.test.entity.LocalManagementContextForTests;
-import org.apache.brooklyn.test.entity.TestApplication;
-import org.apache.brooklyn.test.entity.TestEntity;
-import org.testng.annotations.AfterMethod;
-import org.testng.annotations.BeforeMethod;
-import org.testng.annotations.Test;
-
-import brooklyn.entity.basic.ApplicationBuilder;
-import brooklyn.entity.basic.Attributes;
-import brooklyn.entity.basic.Entities;
-import brooklyn.entity.basic.Lifecycle;
-import brooklyn.entity.trait.FailingEntity;
-import brooklyn.policy.ha.HASensors.FailureDescriptor;
-import brooklyn.test.Asserts;
-import brooklyn.util.exceptions.Exceptions;
-
-import com.google.common.base.Function;
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.Iterables;
-import com.google.common.collect.Lists;
-
-public class ServiceRestarterTest {
-
- private static final int TIMEOUT_MS = 10*1000;
-
- private ManagementContext managementContext;
- private TestApplication app;
- private TestEntity e1;
- private ServiceRestarter policy;
- private SensorEventListener<Object> eventListener;
- private List<SensorEvent<?>> events;
-
- @BeforeMethod(alwaysRun=true)
- public void setUp() throws Exception {
- managementContext = new LocalManagementContextForTests();
- app = ApplicationBuilder.newManagedApp(TestApplication.class, managementContext);
- e1 = app.createAndManageChild(EntitySpec.create(TestEntity.class));
- events = Lists.newCopyOnWriteArrayList();
- eventListener = new SensorEventListener<Object>() {
- @Override public void onEvent(SensorEvent<Object> event) {
- events.add(event);
- }
- };
- }
-
- @AfterMethod(alwaysRun=true)
- public void tearDown() throws Exception {
- if (managementContext != null) Entities.destroyAll(managementContext);
- }
-
- @Test
- public void testRestartsOnFailure() throws Exception {
- policy = new ServiceRestarter(new ConfigBag().configure(ServiceRestarter.FAILURE_SENSOR_TO_MONITOR, HASensors.ENTITY_FAILED));
- e1.addPolicy(policy);
-
- e1.emit(HASensors.ENTITY_FAILED, new FailureDescriptor(e1, "simulate failure"));
-
- Asserts.succeedsEventually(new Runnable() {
- @Override public void run() {
- assertEquals(e1.getCallHistory(), ImmutableList.of("restart"));
- }});
- }
-
- @Test(groups="Integration") // Has a 1 second wait
- public void testDoesNotRestartsWhenHealthy() throws Exception {
- policy = new ServiceRestarter(new ConfigBag().configure(ServiceRestarter.FAILURE_SENSOR_TO_MONITOR, HASensors.ENTITY_FAILED));
- e1.addPolicy(policy);
-
- e1.emit(HASensors.ENTITY_RECOVERED, new FailureDescriptor(e1, "not a failure"));
-
- Asserts.succeedsContinually(new Runnable() {
- @Override public void run() {
- assertEquals(e1.getCallHistory(), ImmutableList.of());
- }});
- }
-
- @Test
- public void testEmitsFailureEventWhenRestarterFails() throws Exception {
- final FailingEntity e2 = app.createAndManageChild(EntitySpec.create(FailingEntity.class)
- .configure(FailingEntity.FAIL_ON_RESTART, true));
- app.subscribe(e2, ServiceRestarter.ENTITY_RESTART_FAILED, eventListener);
-
- policy = new ServiceRestarter(new ConfigBag().configure(ServiceRestarter.FAILURE_SENSOR_TO_MONITOR, HASensors.ENTITY_FAILED));
- e2.addPolicy(policy);
-
- e2.emit(HASensors.ENTITY_FAILED, new FailureDescriptor(e2, "simulate failure"));
-
- Asserts.succeedsEventually(new Runnable() {
- @Override public void run() {
- assertEquals(Iterables.getOnlyElement(events).getSensor(), ServiceRestarter.ENTITY_RESTART_FAILED, "events="+events);
- assertEquals(Iterables.getOnlyElement(events).getSource(), e2, "events="+events);
- assertEquals(((FailureDescriptor)Iterables.getOnlyElement(events).getValue()).getComponent(), e2, "events="+events);
- }});
-
- assertEquals(e2.getAttribute(Attributes.SERVICE_STATE_ACTUAL), Lifecycle.ON_FIRE);
- }
-
- @Test
- public void testDoesNotSetOnFireOnFailure() throws Exception {
- final FailingEntity e2 = app.createAndManageChild(EntitySpec.create(FailingEntity.class)
- .configure(FailingEntity.FAIL_ON_RESTART, true));
- app.subscribe(e2, ServiceRestarter.ENTITY_RESTART_FAILED, eventListener);
-
- policy = new ServiceRestarter(new ConfigBag()
- .configure(ServiceRestarter.FAILURE_SENSOR_TO_MONITOR, HASensors.ENTITY_FAILED)
- .configure(ServiceRestarter.SET_ON_FIRE_ON_FAILURE, false));
- e2.addPolicy(policy);
-
- e2.emit(HASensors.ENTITY_FAILED, new FailureDescriptor(e2, "simulate failure"));
-
- Asserts.succeedsContinually(new Runnable() {
- @Override public void run() {
- assertNotEquals(e2.getAttribute(Attributes.SERVICE_STATE_ACTUAL), Lifecycle.ON_FIRE);
- }});
- }
-
- // Previously RestarterPolicy called entity.restart inside the event-listener thread.
- // That caused all other events for that entity's subscriptions to be queued until that
- // entity's single event handler thread was free again.
- @Test
- public void testRestartDoesNotBlockOtherSubscriptions() throws Exception {
- final CountDownLatch inRestartLatch = new CountDownLatch(1);
- final CountDownLatch continueRestartLatch = new CountDownLatch(1);
-
- final FailingEntity e2 = app.createAndManageChild(EntitySpec.create(FailingEntity.class)
- .configure(FailingEntity.FAIL_ON_RESTART, true)
- .configure(FailingEntity.EXEC_ON_FAILURE, new Function<Object, Void>() {
- @Override public Void apply(Object input) {
- inRestartLatch.countDown();
- try {
- continueRestartLatch.await();
- } catch (InterruptedException e) {
- throw Exceptions.propagate(e);
- }
- return null;
- }}));
-
- e2.addPolicy(PolicySpec.create(ServiceRestarter.class)
- .configure(ServiceRestarter.FAILURE_SENSOR_TO_MONITOR, HASensors.ENTITY_FAILED));
- e2.subscribe(e2, TestEntity.SEQUENCE, eventListener);
-
- // Cause failure, and wait for entity.restart to be blocking
- e2.emit(HASensors.ENTITY_FAILED, new FailureDescriptor(e1, "simulate failure"));
- assertTrue(inRestartLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-
- // Expect other notifications to continue to get through
- e2.setAttribute(TestEntity.SEQUENCE, 1);
- Asserts.succeedsEventually(new Runnable() {
- @Override public void run() {
- assertEquals(Iterables.getOnlyElement(events).getValue(), 1);
- }});
-
- // Allow restart to finish
- continueRestartLatch.countDown();
- }
-}
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/d30ff597/policy/src/test/java/brooklyn/policy/loadbalancing/AbstractLoadBalancingPolicyTest.java
----------------------------------------------------------------------
diff --git a/policy/src/test/java/brooklyn/policy/loadbalancing/AbstractLoadBalancingPolicyTest.java b/policy/src/test/java/brooklyn/policy/loadbalancing/AbstractLoadBalancingPolicyTest.java
deleted file mode 100644
index 68db44c..0000000
--- a/policy/src/test/java/brooklyn/policy/loadbalancing/AbstractLoadBalancingPolicyTest.java
+++ /dev/null
@@ -1,253 +0,0 @@
-/*
- * 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 brooklyn.policy.loadbalancing;
-
-import static org.testng.Assert.assertEquals;
-
-import java.util.Collection;
-import java.util.List;
-import java.util.Map;
-import java.util.Random;
-import java.util.Set;
-
-import org.apache.brooklyn.api.entity.Entity;
-import org.apache.brooklyn.api.entity.Group;
-import org.apache.brooklyn.api.entity.basic.EntityLocal;
-import org.apache.brooklyn.api.entity.proxying.EntitySpec;
-import org.apache.brooklyn.api.event.AttributeSensor;
-import org.apache.brooklyn.test.entity.TestApplication;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.testng.annotations.AfterMethod;
-import org.testng.annotations.BeforeMethod;
-
-import brooklyn.config.ConfigKey;
-import brooklyn.entity.basic.DynamicGroup;
-import brooklyn.entity.basic.Entities;
-import brooklyn.event.basic.BasicConfigKey;
-import brooklyn.event.basic.Sensors;
-import org.apache.brooklyn.location.basic.SimulatedLocation;
-import brooklyn.test.Asserts;
-import brooklyn.util.collections.MutableMap;
-import brooklyn.util.time.Time;
-
-import com.google.common.base.Function;
-import com.google.common.base.Preconditions;
-import com.google.common.base.Predicates;
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.Iterables;
-import com.google.common.collect.Lists;
-import com.google.common.collect.Maps;
-
-public class AbstractLoadBalancingPolicyTest {
-
- private static final Logger LOG = LoggerFactory.getLogger(AbstractLoadBalancingPolicyTest.class);
-
- protected static final long TIMEOUT_MS = 10*1000;
- protected static final long SHORT_WAIT_MS = 250;
-
- protected static final long CONTAINER_STARTUP_DELAY_MS = 100;
-
- public static final AttributeSensor<Integer> TEST_METRIC =
- Sensors.newIntegerSensor("test.metric", "Dummy workrate for test entities");
-
- public static final ConfigKey<Double> LOW_THRESHOLD_CONFIG_KEY = new BasicConfigKey<Double>(Double.class, TEST_METRIC.getName()+".threshold.low", "desc", 0.0);
- public static final ConfigKey<Double> HIGH_THRESHOLD_CONFIG_KEY = new BasicConfigKey<Double>(Double.class, TEST_METRIC.getName()+".threshold.high", "desc", 0.0);
-
- protected TestApplication app;
- protected SimulatedLocation loc;
- protected BalanceableWorkerPool pool;
- protected DefaultBalanceablePoolModel<Entity, Entity> model;
- protected LoadBalancingPolicy policy;
- protected Group containerGroup;
- protected Group itemGroup;
- protected Random random = new Random();
-
- @BeforeMethod(alwaysRun=true)
- public void before() {
- LOG.debug("In AbstractLoadBalancingPolicyTest.before()");
-
- MockItemEntityImpl.totalMoveCount.set(0);
- MockItemEntityImpl.lastMoveTime.set(0);
-
- loc = new SimulatedLocation(MutableMap.of("name", "loc"));
-
- model = new DefaultBalanceablePoolModel<Entity, Entity>("pool-model");
-
- app = TestApplication.Factory.newManagedInstanceForTests();
- containerGroup = app.createAndManageChild(EntitySpec.create(DynamicGroup.class)
- .displayName("containerGroup")
- .configure(DynamicGroup.ENTITY_FILTER, Predicates.instanceOf(MockContainerEntity.class)));
- itemGroup = app.createAndManageChild(EntitySpec.create(DynamicGroup.class)
- .displayName("itemGroup")
- .configure(DynamicGroup.ENTITY_FILTER, Predicates.instanceOf(MockItemEntity.class)));
- pool = app.createAndManageChild(EntitySpec.create(BalanceableWorkerPool.class));
- pool.setContents(containerGroup, itemGroup);
- policy = new LoadBalancingPolicy(MutableMap.of("minPeriodBetweenExecs", 1), TEST_METRIC, model);
- pool.addPolicy(policy);
- app.start(ImmutableList.of(loc));
- }
-
- @AfterMethod(alwaysRun=true)
- public void after() {
- if (policy != null) policy.destroy();
- if (app != null) Entities.destroyAll(app.getManagementContext());
- }
-
- // Using this utility, as it gives more info about the workrates of all containers rather than just the one that differs
- protected void assertWorkrates(Collection<MockContainerEntity> containers, Collection<Double> expectedC, double precision) {
- Iterable<Double> actual = Iterables.transform(containers, new Function<MockContainerEntity, Double>() {
- public Double apply(MockContainerEntity input) {
- return getContainerWorkrate(input);
- }});
-
- List<Double> expected = Lists.newArrayList(expectedC);
- String errMsg = "actual="+actual+"; expected="+expected;
- assertEquals(containers.size(), expected.size(), errMsg);
- for (int i = 0; i < containers.size(); i++) {
- assertEquals(Iterables.get(actual, i), expected.get(i), precision, errMsg);
- }
- }
-
- protected void assertWorkratesEventually(Collection<MockContainerEntity> containers, Iterable<? extends Movable> items, Collection<Double> expected) {
- assertWorkratesEventually(containers, items, expected, 0d);
- }
-
- /**
- * Asserts that the given container have the given expected workrates (by querying the containers directly).
- * Accepts an accuracy of "precision" for each container's workrate.
- */
- protected void assertWorkratesEventually(final Collection<MockContainerEntity> containers, final Iterable<? extends Movable> items, final Collection<Double> expected, final double precision) {
- try {
- Asserts.succeedsEventually(MutableMap.of("timeout", TIMEOUT_MS), new Runnable() {
- public void run() {
- assertWorkrates(containers, expected, precision);
- }});
- } catch (AssertionError e) {
- String errMsg = e.getMessage()+"; "+verboseDumpToString(containers, items);
- throw new RuntimeException(errMsg, e);
- }
- }
-
- // Using this utility, as it gives more info about the workrates of all containers rather than just the one that differs
- protected void assertWorkratesContinually(List<MockContainerEntity> containers, Iterable<? extends Movable> items, List<Double> expected) {
- assertWorkratesContinually(containers, items, expected, 0d);
- }
-
- /**
- * Asserts that the given containers have the given expected workrates (by querying the containers directly)
- * continuously for SHORT_WAIT_MS.
- * Accepts an accuracy of "precision" for each container's workrate.
- */
- protected void assertWorkratesContinually(final List<MockContainerEntity> containers, Iterable<? extends Movable> items, final List<Double> expected, final double precision) {
- try {
- Asserts.succeedsContinually(MutableMap.of("timeout", SHORT_WAIT_MS), new Runnable() {
- public void run() {
- assertWorkrates(containers, expected, precision);
- }});
- } catch (AssertionError e) {
- String errMsg = e.getMessage()+"; "+verboseDumpToString(containers, items);
- throw new RuntimeException(errMsg, e);
- }
- }
-
- protected String verboseDumpToString(Iterable<MockContainerEntity> containers, Iterable<? extends Movable> items) {
- Iterable<Double> containerRates = Iterables.transform(containers, new Function<MockContainerEntity, Double>() {
- @Override public Double apply(MockContainerEntity input) {
- return (double) input.getWorkrate();
- }});
-
- Map<MockContainerEntity, Set<Movable>> itemDistributionByContainer = Maps.newLinkedHashMap();
- for (MockContainerEntity container : containers) {
- itemDistributionByContainer.put(container, container.getBalanceableItems());
- }
-
- Map<Movable, BalanceableContainer<?>> itemDistributionByItem = Maps.newLinkedHashMap();
- for (Movable item : items) {
- itemDistributionByItem.put(item, item.getAttribute(Movable.CONTAINER));
- }
-
- String modelItemDistribution = model.itemDistributionToString();
- return "containers="+containers+"; containerRates="+containerRates
- +"; itemDistributionByContainer="+itemDistributionByContainer
- +"; itemDistributionByItem="+itemDistributionByItem
- +"; model="+modelItemDistribution
- +"; totalMoves="+MockItemEntityImpl.totalMoveCount
- +"; lastMoveTime="+Time.makeDateString(MockItemEntityImpl.lastMoveTime.get());
- }
-
- protected MockContainerEntity newContainer(TestApplication app, String name, double lowThreshold, double highThreshold) {
- return newAsyncContainer(app, name, lowThreshold, highThreshold, 0);
- }
-
- /**
- * Creates a new container that will take "delay" millis to complete its start-up.
- */
- protected MockContainerEntity newAsyncContainer(TestApplication app, String name, double lowThreshold, double highThreshold, long delay) {
- MockContainerEntity container = app.createAndManageChild(EntitySpec.create(MockContainerEntity.class)
- .displayName(name)
- .configure(MockContainerEntity.DELAY, delay)
- .configure(LOW_THRESHOLD_CONFIG_KEY, lowThreshold)
- .configure(HIGH_THRESHOLD_CONFIG_KEY, highThreshold));
- LOG.debug("Managed new container {}", container);
- container.start(ImmutableList.of(loc));
- return container;
- }
-
- protected static MockItemEntity newItem(TestApplication app, MockContainerEntity container, String name, double workrate) {
- MockItemEntity item = app.createAndManageChild(EntitySpec.create(MockItemEntity.class)
- .displayName(name));
- LOG.debug("Managing new item {} on container {}", item, container);
- item.move(container);
- ((EntityLocal)item).setAttribute(TEST_METRIC, (int)workrate);
- return item;
- }
-
- protected static MockItemEntity newLockedItem(TestApplication app, MockContainerEntity container, String name, double workrate) {
- MockItemEntity item = app.createAndManageChild(EntitySpec.create(MockItemEntity.class)
- .displayName(name)
- .configure(Movable.IMMOVABLE, true));
- LOG.debug("Managed new item {} on container {}", item, container);
- item.move(container);
- ((EntityLocal)item).setAttribute(TEST_METRIC, (int)workrate);
- return item;
- }
-
- /**
- * Asks the item directly for its workrate.
- */
- protected static double getItemWorkrate(MockItemEntity item) {
- Object result = item.getAttribute(TEST_METRIC);
- return (result == null ? 0 : ((Number) result).doubleValue());
- }
-
- /**
- * Asks the container for its items, and then each of those items directly for their workrates; returns the total.
- */
- protected static double getContainerWorkrate(MockContainerEntity container) {
- double result = 0.0;
- Preconditions.checkNotNull(container, "container");
- for (Movable item : container.getBalanceableItems()) {
- Preconditions.checkNotNull(item, "item in container");
- assertEquals(item.getContainerId(), container.getId());
- result += getItemWorkrate((MockItemEntity)item);
- }
- return result;
- }
-}
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/d30ff597/policy/src/test/java/brooklyn/policy/loadbalancing/BalanceableWorkerPoolTest.java
----------------------------------------------------------------------
diff --git a/policy/src/test/java/brooklyn/policy/loadbalancing/BalanceableWorkerPoolTest.java b/policy/src/test/java/brooklyn/policy/loadbalancing/BalanceableWorkerPoolTest.java
deleted file mode 100644
index e744720..0000000
--- a/policy/src/test/java/brooklyn/policy/loadbalancing/BalanceableWorkerPoolTest.java
+++ /dev/null
@@ -1,133 +0,0 @@
-/*
- * 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 brooklyn.policy.loadbalancing;
-
-import static org.testng.Assert.assertEquals;
-import static org.testng.Assert.fail;
-
-import org.apache.brooklyn.api.entity.Group;
-import org.apache.brooklyn.api.entity.proxying.EntitySpec;
-import org.apache.brooklyn.api.entity.proxying.ImplementedBy;
-import org.apache.brooklyn.test.entity.TestApplication;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.testng.annotations.AfterMethod;
-import org.testng.annotations.BeforeMethod;
-import org.testng.annotations.Test;
-
-import brooklyn.entity.basic.AbstractGroup;
-import brooklyn.entity.basic.AbstractGroupImpl;
-import brooklyn.entity.basic.ApplicationBuilder;
-import brooklyn.entity.basic.DynamicGroup;
-import brooklyn.entity.basic.Entities;
-import brooklyn.entity.trait.Resizable;
-import org.apache.brooklyn.location.basic.SimulatedLocation;
-import brooklyn.util.collections.MutableMap;
-import brooklyn.util.exceptions.Exceptions;
-
-import com.google.common.base.Predicates;
-import com.google.common.collect.ImmutableList;
-
-public class BalanceableWorkerPoolTest {
-
- private static final Logger LOG = LoggerFactory.getLogger(BalanceableWorkerPoolTest.class);
-
- protected static final long TIMEOUT_MS = 10*1000;
- protected static final long SHORT_WAIT_MS = 250;
-
- protected static final long CONTAINER_STARTUP_DELAY_MS = 100;
-
- protected TestApplication app;
- protected SimulatedLocation loc;
- protected BalanceableWorkerPool pool;
- protected Group containerGroup;
- protected Group itemGroup;
-
- @BeforeMethod(alwaysRun=true)
- public void before() {
- loc = new SimulatedLocation(MutableMap.of("name", "loc"));
-
- app = ApplicationBuilder.newManagedApp(TestApplication.class);
- containerGroup = app.createAndManageChild(EntitySpec.create(DynamicGroup.class)
- .displayName("containerGroup")
- .configure(DynamicGroup.ENTITY_FILTER, Predicates.instanceOf(MockContainerEntity.class)));
- itemGroup = app.createAndManageChild(EntitySpec.create(DynamicGroup.class)
- .displayName("itemGroup")
- .configure(DynamicGroup.ENTITY_FILTER, Predicates.instanceOf(MockItemEntity.class)));
- pool = app.createAndManageChild(EntitySpec.create(BalanceableWorkerPool.class));
- pool.setContents(containerGroup, itemGroup);
-
- app.start(ImmutableList.of(loc));
- }
-
- @AfterMethod(alwaysRun=true)
- public void after() {
- if (app != null) Entities.destroyAll(app.getManagementContext());
- }
-
- @Test
- public void testDefaultResizeFailsIfContainerGroupNotResizable() throws Exception {
- try {
- pool.resize(1);
- fail();
- } catch (Exception e) {
- if (Exceptions.getFirstThrowableOfType(e, UnsupportedOperationException.class) == null) throw e;
- }
- }
-
- @Test
- public void testDefaultResizeCallsResizeOnContainerGroup() {
- LocallyResizableGroup resizable = app.createAndManageChild(EntitySpec.create(LocallyResizableGroup.class));
-
- BalanceableWorkerPool pool2 = app.createAndManageChild(EntitySpec.create(BalanceableWorkerPool.class));
- pool2.setContents(resizable, itemGroup);
- Entities.manage(pool2);
-
- pool2.resize(123);
- assertEquals(resizable.getCurrentSize(), (Integer) 123);
- }
-
- @Test
- public void testCustomResizableCalledWhenResizing() {
- LocallyResizableGroup resizable = app.createAndManageChild(EntitySpec.create(LocallyResizableGroup.class));
-
- pool.setResizable(resizable);
-
- pool.resize(123);
- assertEquals(resizable.getCurrentSize(), (Integer)123);
- }
-
- @ImplementedBy(LocallyResizableGroupImpl.class)
- public static interface LocallyResizableGroup extends AbstractGroup, Resizable {
- }
-
- public static class LocallyResizableGroupImpl extends AbstractGroupImpl implements LocallyResizableGroup {
- private int size = 0;
-
- @Override
- public Integer resize(Integer newSize) {
- size = newSize;
- return size;
- }
- @Override
- public Integer getCurrentSize() {
- return size;
- }
- }
-}
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/d30ff597/policy/src/test/java/brooklyn/policy/loadbalancing/ItemsInContainersGroupTest.java
----------------------------------------------------------------------
diff --git a/policy/src/test/java/brooklyn/policy/loadbalancing/ItemsInContainersGroupTest.java b/policy/src/test/java/brooklyn/policy/loadbalancing/ItemsInContainersGroupTest.java
deleted file mode 100644
index cf47359..0000000
--- a/policy/src/test/java/brooklyn/policy/loadbalancing/ItemsInContainersGroupTest.java
+++ /dev/null
@@ -1,189 +0,0 @@
-/*
- * 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 brooklyn.policy.loadbalancing;
-
-import static org.testng.Assert.assertEquals;
-
-import org.apache.brooklyn.api.entity.Entity;
-import org.apache.brooklyn.api.entity.Group;
-import org.apache.brooklyn.api.entity.proxying.EntitySpec;
-import org.apache.brooklyn.test.entity.TestApplication;
-import org.testng.annotations.AfterMethod;
-import org.testng.annotations.BeforeMethod;
-import org.testng.annotations.Test;
-
-import brooklyn.entity.basic.ApplicationBuilder;
-import brooklyn.entity.basic.DynamicGroup;
-import brooklyn.entity.basic.Entities;
-import org.apache.brooklyn.location.basic.SimulatedLocation;
-import brooklyn.test.Asserts;
-import brooklyn.util.collections.MutableMap;
-
-import com.google.common.base.Predicate;
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.ImmutableSet;
-
-public class ItemsInContainersGroupTest {
-
- // all tests are 20ms or less, but use a big timeout just in case very slow machine!
- private static final long TIMEOUT_MS = 15000;
-
- private TestApplication app;
- private SimulatedLocation loc;
- private Group containerGroup;
- private ItemsInContainersGroup itemGroup;
-
- @BeforeMethod(alwaysRun=true)
- public void setUp() throws Exception {
- loc = new SimulatedLocation(MutableMap.of("name", "loc"));
-
- app = ApplicationBuilder.newManagedApp(TestApplication.class);
- containerGroup = app.createAndManageChild(EntitySpec.create(DynamicGroup.class)
- .displayName("containerGroup")
- .configure(DynamicGroup.ENTITY_FILTER, new Predicate<Entity>() {
- public boolean apply(Entity input) {
- return input instanceof MockContainerEntity &&
- input.getConfig(MockContainerEntity.MOCK_MEMBERSHIP) == "ingroup";
- }}));
- itemGroup = app.createAndManageChild(EntitySpec.create(ItemsInContainersGroup.class)
- .displayName("itemGroup"));
- itemGroup.setContainers(containerGroup);
-
- app.start(ImmutableList.of(loc));
- }
-
- @AfterMethod(alwaysRun=true)
- public void tearDown() throws Exception {
- if (app != null) Entities.destroyAll(app.getManagementContext());
- }
-
- @Test
- public void testSimpleMembership() throws Exception {
- MockContainerEntity containerIn = newContainer(app, "A", "ingroup");
- MockItemEntity item1 = newItem(app, containerIn, "1");
- MockItemEntity item2 = newItem(app, containerIn, "2");
-
- assertItemsEventually(item1, item2);
- }
-
- @Test
- public void testFilterIsAppliedToItems() throws Exception {
- itemGroup.stop();
- Entities.unmanage(itemGroup);
-
- itemGroup = app.createAndManageChild(EntitySpec.create(ItemsInContainersGroup.class)
- .displayName("itemGroupWithDispName2")
- .configure(ItemsInContainersGroup.ITEM_FILTER, new Predicate<Entity>() {
- public boolean apply(Entity input) {
- return "2".equals(input.getDisplayName());
- }}));
- itemGroup.setContainers(containerGroup);
-
- MockContainerEntity containerIn = newContainer(app, "A", "ingroup");
- MockItemEntity item1 = newItem(app, containerIn, "1");
- MockItemEntity item2 = newItem(app, containerIn, "2");
-
- assertItemsEventually(item2); // does not include item1
- }
-
- @Test
- public void testItemsInOtherContainersIgnored() throws Exception {
- MockContainerEntity containerOut = newContainer(app, "A", "outgroup");
- MockItemEntity item1 = newItem(app, containerOut, "1");
-
- assertItemsEventually();
- }
-
- @Test
- public void testItemMovedInIsAdded() throws Exception {
- MockContainerEntity containerIn = newContainer(app, "A", "ingroup");
- MockContainerEntity containerOut = newContainer(app, "A", "outgroup");
- MockItemEntity item1 = newItem(app, containerOut, "1");
- item1.move(containerIn);
-
- assertItemsEventually(item1);
- }
-
- @Test
- public void testItemMovedOutIsRemoved() throws Exception {
- MockContainerEntity containerIn = newContainer(app, "A", "ingroup");
- MockContainerEntity containerOut = newContainer(app, "A", "outgroup");
- MockItemEntity item1 = newItem(app, containerIn, "1");
- assertItemsEventually(item1);
-
- item1.move(containerOut);
- assertItemsEventually();
- }
-
- /*
- * Previously could fail if...
- * ItemsInContainersGroupImpl listener got notified of Movable.CONTAINER after entity was unmanaged
- * (because being done in concurrent threads).
- * This called ItemsInContainersGroupImpl.onItemMoved, which called addMember to add it back in again.
- * In AbstractGroup.addMember, we now check if the entity is still managed, to
- * ensure there is synchronization for concurrent calls to add/remove member.
- */
- @Test
- public void testItemUnmanagedIsRemoved() throws Exception {
- MockContainerEntity containerIn = newContainer(app, "A", "ingroup");
- MockItemEntity item1 = newItem(app, containerIn, "1");
- assertItemsEventually(item1);
-
- Entities.unmanage(item1);
- assertItemsEventually();
- }
-
- // TODO How to test this? Will it be used?
- // Adding a new container then adding items to it is tested in many other methods.
- @Test(enabled=false)
- public void testContainerAddedWillAddItsItems() throws Exception {
- }
-
- @Test
- public void testContainerRemovedWillRemoveItsItems() throws Exception {
- MockContainerEntity containerA = newContainer(app, "A", "ingroup");
- MockItemEntity item1 = newItem(app, containerA, "1");
- assertItemsEventually(item1);
-
- Entities.unmanage(containerA);
- assertItemsEventually();
- }
-
- private void assertItemsEventually(final MockItemEntity... expected) {
- Asserts.succeedsEventually(MutableMap.of("timeout", TIMEOUT_MS), new Runnable() {
- public void run() {
- assertEquals(ImmutableSet.copyOf(itemGroup.getMembers()), ImmutableSet.copyOf(expected));
- }});
- }
-
- private MockContainerEntity newContainer(TestApplication app, String name, String membership) {
- MockContainerEntity container = app.createAndManageChild(EntitySpec.create(MockContainerEntity.class)
- .displayName(name)
- .configure(MockContainerEntity.MOCK_MEMBERSHIP, membership));
- container.start(ImmutableList.of(loc));
- return container;
- }
-
- private static MockItemEntity newItem(TestApplication app, MockContainerEntity container, String name) {
- MockItemEntity item = app.createAndManageChild(EntitySpec.create(MockItemEntity.class)
- .displayName(name));
- item.move(container);
- return item;
- }
-}
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/d30ff597/policy/src/test/java/brooklyn/policy/loadbalancing/LoadBalancingModelTest.java
----------------------------------------------------------------------
diff --git a/policy/src/test/java/brooklyn/policy/loadbalancing/LoadBalancingModelTest.java b/policy/src/test/java/brooklyn/policy/loadbalancing/LoadBalancingModelTest.java
deleted file mode 100644
index cae86e7..0000000
--- a/policy/src/test/java/brooklyn/policy/loadbalancing/LoadBalancingModelTest.java
+++ /dev/null
@@ -1,115 +0,0 @@
-/*
- * 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 brooklyn.policy.loadbalancing;
-
-import static org.testng.Assert.assertEquals;
-
-import java.util.Collections;
-
-import org.testng.annotations.AfterMethod;
-import org.testng.annotations.BeforeMethod;
-import org.testng.annotations.Test;
-
-import brooklyn.entity.basic.Entities;
-
-import com.google.common.collect.ImmutableMap;
-import com.google.common.collect.ImmutableSet;
-
-public class LoadBalancingModelTest {
-
- private static final double PRECISION = 0.00001;
-
- private MockContainerEntity container1 = new MockContainerEntityImpl();
- private MockContainerEntity container2 = new MockContainerEntityImpl();
- private MockItemEntity item1 = new MockItemEntityImpl();
- private MockItemEntity item2 = new MockItemEntityImpl();
- private MockItemEntity item3 = new MockItemEntityImpl();
-
- private DefaultBalanceablePoolModel<MockContainerEntity, MockItemEntity> model;
-
- @BeforeMethod(alwaysRun=true)
- public void setUp() throws Exception {
- model = new DefaultBalanceablePoolModel<MockContainerEntity, MockItemEntity>("myname");
- }
-
- @AfterMethod(alwaysRun=true)
- public void tearDown() throws Exception {
- // nothing to tear down; no management context created
- }
-
- @Test
- public void testPoolRatesCorrectlySumContainers() throws Exception {
- model.onContainerAdded(container1, 10d, 20d);
- model.onContainerAdded(container2, 11d, 22d);
-
- assertEquals(model.getPoolLowThreshold(), 10d+11d, PRECISION);
- assertEquals(model.getPoolHighThreshold(), 20d+22d, PRECISION);
- }
-
- @Test
- public void testPoolRatesCorrectlySumItems() throws Exception {
- model.onContainerAdded(container1, 10d, 20d);
- model.onItemAdded(item1, container1, true);
- model.onItemAdded(item2, container1, true);
-
- model.onItemWorkrateUpdated(item1, 1d);
- assertEquals(model.getCurrentPoolWorkrate(), 1d, PRECISION);
-
- model.onItemWorkrateUpdated(item2, 2d);
- assertEquals(model.getCurrentPoolWorkrate(), 1d+2d, PRECISION);
-
- model.onItemWorkrateUpdated(item2, 4d);
- assertEquals(model.getCurrentPoolWorkrate(), 1d+4d, PRECISION);
-
- model.onItemRemoved(item1);
- assertEquals(model.getCurrentPoolWorkrate(), 4d, PRECISION);
- }
-
- @Test
- public void testWorkrateUpdateAfterItemRemovalIsNotRecorded() throws Exception {
- model.onContainerAdded(container1, 10d, 20d);
- model.onItemAdded(item1, container1, true);
- model.onItemRemoved(item1);
- model.onItemWorkrateUpdated(item1, 123d);
-
- assertEquals(model.getCurrentPoolWorkrate(), 0d, PRECISION);
- assertEquals(model.getContainerWorkrates().get(container1), 0d, PRECISION);
- assertEquals(model.getItemWorkrate(item1), null);
- }
-
- @Test
- public void testItemMovedWillUpdateContainerWorkrates() throws Exception {
- model.onContainerAdded(container1, 10d, 20d);
- model.onContainerAdded(container2, 11d, 21d);
- model.onItemAdded(item1, container1, false);
- model.onItemWorkrateUpdated(item1, 123d);
-
- model.onItemMoved(item1, container2);
-
- assertEquals(model.getItemsForContainer(container1), Collections.emptySet());
- assertEquals(model.getItemsForContainer(container2), ImmutableSet.of(item1));
- assertEquals(model.getItemWorkrate(item1), 123d);
- assertEquals(model.getTotalWorkrate(container1), 0d);
- assertEquals(model.getTotalWorkrate(container2), 123d);
- assertEquals(model.getItemWorkrates(container1), Collections.emptyMap());
- assertEquals(model.getItemWorkrates(container2), ImmutableMap.of(item1, 123d));
- assertEquals(model.getContainerWorkrates(), ImmutableMap.of(container1, 0d, container2, 123d));
- assertEquals(model.getCurrentPoolWorkrate(), 123d);
- }
-}
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/d30ff597/policy/src/test/java/brooklyn/policy/loadbalancing/LoadBalancingPolicyConcurrencyTest.java
----------------------------------------------------------------------
diff --git a/policy/src/test/java/brooklyn/policy/loadbalancing/LoadBalancingPolicyConcurrencyTest.java b/policy/src/test/java/brooklyn/policy/loadbalancing/LoadBalancingPolicyConcurrencyTest.java
deleted file mode 100644
index 69d380b..0000000
--- a/policy/src/test/java/brooklyn/policy/loadbalancing/LoadBalancingPolicyConcurrencyTest.java
+++ /dev/null
@@ -1,211 +0,0 @@
-/*
- * 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 brooklyn.policy.loadbalancing;
-
-import java.util.Collections;
-import java.util.List;
-import java.util.Queue;
-import java.util.concurrent.Callable;
-import java.util.concurrent.ConcurrentLinkedQueue;
-import java.util.concurrent.Executors;
-import java.util.concurrent.Future;
-import java.util.concurrent.ScheduledExecutorService;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.atomic.AtomicReference;
-
-import org.apache.brooklyn.api.entity.basic.EntityLocal;
-import org.apache.brooklyn.test.entity.TestApplication;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.testng.annotations.AfterMethod;
-import org.testng.annotations.BeforeMethod;
-import org.testng.annotations.Test;
-
-import brooklyn.entity.basic.Entities;
-
-import com.google.common.collect.Lists;
-
-public class LoadBalancingPolicyConcurrencyTest extends AbstractLoadBalancingPolicyTest {
-
- private static final Logger LOG = LoggerFactory.getLogger(LoadBalancingPolicyConcurrencyTest.class);
-
- private static final double WORKRATE_JITTER = 2d;
- private static final int NUM_CONTAINERS = 20;
- private static final int WORKRATE_UPDATE_PERIOD_MS = 1000;
-
- private ScheduledExecutorService scheduledExecutor;
-
- @BeforeMethod(alwaysRun=true)
- @Override
- public void before() {
- scheduledExecutor = Executors.newScheduledThreadPool(10);
- super.before();
- }
-
- @AfterMethod(alwaysRun=true)
- @Override
- public void after() {
- if (scheduledExecutor != null) scheduledExecutor.shutdownNow();
- super.after();
- }
-
- @Test
- public void testSimplePeriodicWorkrateUpdates() {
- List<MockItemEntity> items = Lists.newArrayList();
- List<MockContainerEntity> containers = Lists.newArrayList();
-
- for (int i = 0; i < NUM_CONTAINERS; i++) {
- containers.add(newContainer(app, "container"+i, 10, 30));
- }
- for (int i = 0; i < NUM_CONTAINERS; i++) {
- newItemWithPeriodicWorkrates(app, containers.get(0), "item"+i, 20);
- }
-
- assertWorkratesEventually(containers, items, Collections.nCopies(NUM_CONTAINERS, 20d), WORKRATE_JITTER);
- }
-
- @Test
- public void testConcurrentlyAddContainers() {
- final Queue<MockContainerEntity> containers = new ConcurrentLinkedQueue<MockContainerEntity>();
- final List<MockItemEntity> items = Lists.newArrayList();
-
- containers.add(newContainer(app, "container-orig", 10, 30));
-
- for (int i = 0; i < NUM_CONTAINERS; i++) {
- items.add(newItemWithPeriodicWorkrates(app, containers.iterator().next(), "item"+i, 20));
- }
- for (int i = 0; i < NUM_CONTAINERS-1; i++) {
- final int index = i;
- scheduledExecutor.submit(new Callable<Void>() {
- @Override public Void call() {
- containers.add(newContainer(app, "container"+index, 10, 30));
- return null;
- }});
- }
-
- assertWorkratesEventually(containers, items, Collections.nCopies(NUM_CONTAINERS, 20d), WORKRATE_JITTER);
- }
-
- @Test
- public void testConcurrentlyAddItems() {
- final Queue<MockItemEntity> items = new ConcurrentLinkedQueue<MockItemEntity>();
- final List<MockContainerEntity> containers = Lists.newArrayList();
-
- for (int i = 0; i < NUM_CONTAINERS; i++) {
- containers.add(newContainer(app, "container"+i, 10, 30));
- }
- for (int i = 0; i < NUM_CONTAINERS; i++) {
- final int index = i;
- scheduledExecutor.submit(new Callable<Void>() {
- @Override public Void call() {
- items.add(newItemWithPeriodicWorkrates(app, containers.get(0), "item"+index, 20));
- return null;
- }});
- }
- assertWorkratesEventually(containers, items, Collections.nCopies(NUM_CONTAINERS, 20d), WORKRATE_JITTER);
- }
-
- // TODO Got IndexOutOfBoundsException from containers.last()
- @Test(groups="WIP", invocationCount=100)
- public void testConcurrentlyRemoveContainers() {
- List<MockItemEntity> items = Lists.newArrayList();
- final List<MockContainerEntity> containers = Lists.newArrayList();
-
- for (int i = 0; i < NUM_CONTAINERS; i++) {
- containers.add(newContainer(app, "container"+i, 15, 45));
- }
- for (int i = 0; i < NUM_CONTAINERS; i++) {
- items.add(newItemWithPeriodicWorkrates(app, containers.get(i), "item"+i, 20));
- }
-
- final List<MockContainerEntity> containersToStop = Lists.newArrayList();
- for (int i = 0; i < NUM_CONTAINERS/2; i++) {
- containersToStop.add(containers.remove(0));
- }
- for (final MockContainerEntity containerToStop : containersToStop) {
- scheduledExecutor.submit(new Callable<Void>() {
- @Override public Void call() {
- try {
- containerToStop.offloadAndStop(containers.get(containers.size()-1));
- Entities.unmanage(containerToStop);
- } catch (Throwable t) {
- LOG.error("Error stopping container "+containerToStop, t);
- }
- return null;
- }});
- }
-
- assertWorkratesEventually(containers, items, Collections.nCopies((int)(NUM_CONTAINERS/2), 40d), WORKRATE_JITTER*2);
- }
-
- @Test(groups="WIP")
- public void testConcurrentlyRemoveItems() {
- List<MockItemEntity> items = Lists.newArrayList();
- List<MockContainerEntity> containers = Lists.newArrayList();
-
- for (int i = 0; i < NUM_CONTAINERS; i++) {
- containers.add(newContainer(app, "container"+i, 15, 45));
- }
- for (int i = 0; i < NUM_CONTAINERS*2; i++) {
- items.add(newItemWithPeriodicWorkrates(app, containers.get(i%NUM_CONTAINERS), "item"+i, 20));
- }
- // should now have item0 and item{0+NUM_CONTAINERS} on container0, etc
-
- for (int i = 0; i < NUM_CONTAINERS; i++) {
- // not removing consecutive items as that would leave it balanced!
- int indexToStop = (i < NUM_CONTAINERS/2) ? NUM_CONTAINERS : 0;
- final MockItemEntity itemToStop = items.remove(indexToStop);
- scheduledExecutor.submit(new Callable<Void>() {
- @Override public Void call() {
- try {
- itemToStop.stop();
- Entities.unmanage(itemToStop);
- } catch (Throwable t) {
- LOG.error("Error stopping item "+itemToStop, t);
- }
- return null;
- }});
- }
-
- assertWorkratesEventually(containers, items, Collections.nCopies(NUM_CONTAINERS, 20d), WORKRATE_JITTER);
- }
-
- protected MockItemEntity newItemWithPeriodicWorkrates(TestApplication app, MockContainerEntity container, String name, double workrate) {
- MockItemEntity item = newItem(app, container, name, workrate);
- scheduleItemWorkrateUpdates(item, workrate, WORKRATE_JITTER);
- return item;
- }
-
- private void scheduleItemWorkrateUpdates(final MockItemEntity item, final double workrate, final double jitter) {
- final AtomicReference<Future<?>> futureRef = new AtomicReference<Future<?>>();
- Future<?> future = scheduledExecutor.scheduleAtFixedRate(
- new Runnable() {
- @Override public void run() {
- if (item.isStopped() && futureRef.get() != null) {
- futureRef.get().cancel(true);
- return;
- }
- double jitteredWorkrate = workrate + (random.nextDouble()*jitter*2 - jitter);
- ((EntityLocal)item).setAttribute(TEST_METRIC, (int) Math.max(0, jitteredWorkrate));
- }
- },
- 0, WORKRATE_UPDATE_PERIOD_MS, TimeUnit.MILLISECONDS);
- futureRef.set(future);
- }
-}
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/d30ff597/policy/src/test/java/brooklyn/policy/loadbalancing/LoadBalancingPolicySoakTest.java
----------------------------------------------------------------------
diff --git a/policy/src/test/java/brooklyn/policy/loadbalancing/LoadBalancingPolicySoakTest.java b/policy/src/test/java/brooklyn/policy/loadbalancing/LoadBalancingPolicySoakTest.java
deleted file mode 100644
index 1bb8dc4..0000000
--- a/policy/src/test/java/brooklyn/policy/loadbalancing/LoadBalancingPolicySoakTest.java
+++ /dev/null
@@ -1,273 +0,0 @@
-/*
- * 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 brooklyn.policy.loadbalancing;
-
-import static org.testng.Assert.assertEquals;
-import static org.testng.Assert.assertTrue;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-
-import org.apache.brooklyn.api.entity.Entity;
-import org.apache.brooklyn.api.entity.basic.EntityLocal;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.testng.annotations.Test;
-
-import brooklyn.entity.basic.Entities;
-import brooklyn.test.Asserts;
-import brooklyn.util.collections.MutableMap;
-
-import com.google.common.base.Function;
-import com.google.common.collect.Iterables;
-import com.google.common.collect.Lists;
-
-public class LoadBalancingPolicySoakTest extends AbstractLoadBalancingPolicyTest {
-
- private static final Logger LOG = LoggerFactory.getLogger(LoadBalancingPolicySoakTest.class);
-
- private static final long TIMEOUT_MS = 40*1000;
-
- @Test
- public void testLoadBalancingQuickTest() {
- RunConfig config = new RunConfig();
- config.numCycles = 1;
- config.numContainers = 5;
- config.numItems = 5;
- config.lowThreshold = 200;
- config.highThreshold = 300;
- config.totalRate = (int) (config.numContainers*(0.95*config.highThreshold));
-
- runLoadBalancingSoakTest(config);
- }
-
- @Test
- public void testLoadBalancingManyItemsQuickTest() {
- RunConfig config = new RunConfig();
- config.numCycles = 1;
- config.numContainers = 5;
- config.numItems = 30;
- config.lowThreshold = 200;
- config.highThreshold = 300;
- config.numContainerStopsPerCycle = 1;
- config.numItemStopsPerCycle = 1;
- config.totalRate = (int) (config.numContainers*(0.95*config.highThreshold));
-
- runLoadBalancingSoakTest(config);
- }
-
- @Test(groups={"Integration","Acceptance"}) // acceptance group, because it's slow to run many cycles
- public void testLoadBalancingSoakTest() {
- RunConfig config = new RunConfig();
- config.numCycles = 100;
- config.numContainers = 5;
- config.numItems = 5;
- config.lowThreshold = 200;
- config.highThreshold = 300;
- config.totalRate = (int) (config.numContainers*(0.95*config.highThreshold));
-
- runLoadBalancingSoakTest(config);
- }
-
- @Test(groups={"Integration","Acceptance"}) // acceptance group, because it's slow to run many cycles
- public void testLoadBalancingManyItemsSoakTest() {
- RunConfig config = new RunConfig();
- config.numCycles = 100;
- config.numContainers = 5;
- config.numItems = 30;
- config.lowThreshold = 200;
- config.highThreshold = 300;
- config.totalRate = (int) (config.numContainers*(0.95*config.highThreshold));
- config.numContainerStopsPerCycle = 3;
- config.numItemStopsPerCycle = 10;
-
- runLoadBalancingSoakTest(config);
- }
-
- @Test(groups={"Integration","Acceptance"})
- public void testLoadBalancingManyManyItemsTest() {
- RunConfig config = new RunConfig();
- config.numCycles = 1;
- config.numContainers = 5;
- config.numItems = 1000;
- config.lowThreshold = 2000;
- config.highThreshold = 3000;
- config.numContainerStopsPerCycle = 0;
- config.numItemStopsPerCycle = 0;
- config.totalRate = (int) (config.numContainers*(0.95*config.highThreshold));
- config.verbose = false;
-
- runLoadBalancingSoakTest(config);
- }
-
- private void runLoadBalancingSoakTest(RunConfig config) {
- final int numCycles = config.numCycles;
- final int numContainers = config.numContainers;
- final int numItems = config.numItems;
- final double lowThreshold = config.lowThreshold;
- final double highThreshold = config.highThreshold;
- final int totalRate = config.totalRate;
- final int numContainerStopsPerCycle = config.numContainerStopsPerCycle;
- final int numItemStopsPerCycle = config.numItemStopsPerCycle;
- final boolean verbose = config.verbose;
-
- MockItemEntityImpl.totalMoveCount.set(0);
-
- final List<MockContainerEntity> containers = new ArrayList<MockContainerEntity>();
- final List<MockItemEntity> items = new ArrayList<MockItemEntity>();
-
- for (int i = 1; i <= numContainers; i++) {
- MockContainerEntity container = newContainer(app, "container-"+i, lowThreshold, highThreshold);
- containers.add(container);
- }
- for (int i = 1; i <= numItems; i++) {
- MockItemEntity item = newItem(app, containers.get(0), "item-"+i, 5);
- items.add(item);
- }
-
- for (int i = 1; i <= numCycles; i++) {
- LOG.info(LoadBalancingPolicySoakTest.class.getSimpleName()+": cycle "+i);
-
- // Stop items, and start others
- for (int j = 1; j <= numItemStopsPerCycle; j++) {
- int itemIndex = random.nextInt(numItems);
- MockItemEntity itemToStop = items.get(itemIndex);
- itemToStop.stop();
- LOG.debug("Unmanaging item {}", itemToStop);
- Entities.unmanage(itemToStop);
- items.set(itemIndex, newItem(app, containers.get(0), "item-"+(itemIndex+1)+"."+i+"."+j, 5));
- }
-
- // Repartition the load across the items
- final List<Integer> itemRates = randomlyDivideLoad(numItems, totalRate, 0, (int)highThreshold);
-
- for (int j = 0; j < numItems; j++) {
- MockItemEntity item = items.get(j);
- ((EntityLocal)item).setAttribute(MockItemEntity.TEST_METRIC, itemRates.get(j));
- }
-
- // Stop containers, and start others
- for (int j = 1; j <= numContainerStopsPerCycle; j++) {
- int containerIndex = random.nextInt(numContainers);
- MockContainerEntity containerToStop = containers.get(containerIndex);
- containerToStop.offloadAndStop(containers.get((containerIndex+1)%numContainers));
- LOG.debug("Unmanaging container {}", containerToStop);
- Entities.unmanage(containerToStop);
-
- MockContainerEntity containerToAdd = newContainer(app, "container-"+(containerIndex+1)+"."+i+"."+j, lowThreshold, highThreshold);
- containers.set(containerIndex, containerToAdd);
- }
-
- // Assert that the items become balanced again
- Asserts.succeedsEventually(MutableMap.of("timeout", TIMEOUT_MS), new Runnable() {
- @Override public void run() {
- Iterable<Double> containerRates = Iterables.transform(containers, new Function<MockContainerEntity, Double>() {
- @Override public Double apply(MockContainerEntity input) {
- return (double) input.getWorkrate();
- }});
-
- String errMsg;
- if (verbose) {
- errMsg = verboseDumpToString(containers, items)+"; itemRates="+itemRates;
- } else {
- errMsg = containerRates+"; totalMoves="+MockItemEntityImpl.totalMoveCount;
- }
-
- // Check that haven't lost any items
- // (as observed in one jenkins build failure: 2014-03-18; but that could also be
- // explained by errMsg generated in the middle of a move)
- List<Entity> itemsFromModel = Lists.newArrayList();
- List<Entity> itemsFromContainers = Lists.newArrayList();
- for (Entity container : model.getPoolContents()) {
- itemsFromModel.addAll(model.getItemsForContainer(container));
- }
- for (MockContainerEntity container : containers) {
- itemsFromContainers.addAll(container.getBalanceableItems());
- }
- Asserts.assertEqualsIgnoringOrder(itemsFromModel, items, true, errMsg);
- Asserts.assertEqualsIgnoringOrder(itemsFromContainers, items, true, errMsg);
-
- // Check overall container rates are balanced
- assertEquals(sum(containerRates), sum(itemRates), errMsg);
- for (double containerRate : containerRates) {
- assertTrue(containerRate >= lowThreshold, errMsg);
- assertTrue(containerRate <= highThreshold, errMsg);
- }
- }});
- }
- }
-
- private static class RunConfig {
- int numCycles = 1;
- int numContainers = 5;
- int numItems = 5;
- double lowThreshold = 200;
- double highThreshold = 300;
- int totalRate = (int) (5*(0.95*highThreshold));
- int numContainerStopsPerCycle = 1;
- int numItemStopsPerCycle = 1;
- boolean verbose = true;
- }
-
- // Testing conveniences.
-
- private double sum(Iterable<? extends Number> vals) {
- double total = 0;;
- for (Number val : vals) {
- total += val.doubleValue();
- }
- return total;
- }
-
- /**
- * Distributes a given load across a number of items randomly. The variability in load for an item is dictated by the variance,
- * but the total will always equal totalLoad.
- *
- * The distribution of load is skewed: one side of the list will have bigger values than the other.
- * Which side is skewed will vary, so when balancing a policy will find that things have entirely changed.
- *
- * TODO This is not particularly good at distributing load, but it's random and skewed enough to force rebalancing.
- */
- private List<Integer> randomlyDivideLoad(int numItems, int totalLoad, int min, int max) {
- List<Integer> result = new ArrayList<Integer>(numItems);
- int totalRemaining = totalLoad;
- int variance = 3;
- int skew = 3;
-
- for (int i = 0; i < numItems; i++) {
- int itemsRemaining = numItems-i;
- int itemFairShare = (totalRemaining/itemsRemaining);
- double skewFactor = ((double)i/numItems)*2 - 1; // a number between -1 and 1, depending how far through the item set we are
- int itemSkew = (int) (random.nextInt(skew)*skewFactor);
- int itemLoad = itemFairShare + (random.nextInt(variance*2)-variance) + itemSkew;
- itemLoad = Math.max(min, itemLoad);
- itemLoad = Math.min(totalRemaining, itemLoad);
- itemLoad = Math.min(max, itemLoad);
- result.add(itemLoad);
- totalRemaining -= itemLoad;
- }
-
- if (random.nextBoolean()) Collections.reverse(result);
-
- assertTrue(sum(result) <= totalLoad, "totalLoad="+totalLoad+"; result="+result);
-
- return result;
- }
-}
[18/24] incubator-brooklyn git commit: [BROOKLYN-162] Renaming
package policy
Posted by he...@apache.org.
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/d30ff597/policy/src/main/java/brooklyn/policy/ha/ConnectionFailureDetector.java
----------------------------------------------------------------------
diff --git a/policy/src/main/java/brooklyn/policy/ha/ConnectionFailureDetector.java b/policy/src/main/java/brooklyn/policy/ha/ConnectionFailureDetector.java
deleted file mode 100644
index ff5d60e..0000000
--- a/policy/src/main/java/brooklyn/policy/ha/ConnectionFailureDetector.java
+++ /dev/null
@@ -1,126 +0,0 @@
-/*
- * 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 brooklyn.policy.ha;
-
-import org.apache.brooklyn.api.catalog.Catalog;
-import org.apache.brooklyn.api.event.Sensor;
-import org.apache.brooklyn.core.util.flags.SetFromFlag;
-
-import brooklyn.config.ConfigKey;
-import brooklyn.entity.basic.ConfigKeys;
-import brooklyn.event.basic.BasicConfigKey;
-import brooklyn.event.basic.BasicNotificationSensor;
-import brooklyn.policy.ha.HASensors.FailureDescriptor;
-import brooklyn.util.guava.Maybe;
-import brooklyn.util.net.Networking;
-import brooklyn.util.time.Duration;
-
-import com.google.common.net.HostAndPort;
-
-/**
- * Monitors a given {@link HostAndPort}, to emit HASensors.CONNECTION_FAILED and HASensors.CONNECTION_RECOVERED
- * if the connection is lost/restored.
- */
-@Catalog(name="Connection Failure Detector", description="HA policy for monitoring a host:port, "
- + "emitting an event if the connection is lost/restored")
-public class ConnectionFailureDetector extends AbstractFailureDetector {
-
- public static final ConfigKey<HostAndPort> ENDPOINT = ConfigKeys.newConfigKey(HostAndPort.class, "connectionFailureDetector.endpoint");
-
- public static final ConfigKey<Duration> POLL_PERIOD = ConfigKeys.newConfigKey(Duration.class, "connectionFailureDetector.pollPeriod", "", Duration.ONE_SECOND);
-
- public static final BasicNotificationSensor<FailureDescriptor> CONNECTION_FAILED = HASensors.CONNECTION_FAILED;
-
- public static final BasicNotificationSensor<FailureDescriptor> CONNECTION_RECOVERED = HASensors.CONNECTION_RECOVERED;
-
- @SetFromFlag("connectionFailedStabilizationDelay")
- public static final ConfigKey<Duration> CONNECTION_FAILED_STABILIZATION_DELAY = BasicConfigKey.builder(Duration.class)
- .name("connectionFailureDetector.serviceFailedStabilizationDelay")
- .description("Time period for which the connection must be consistently down for "
- + "(e.g. doesn't report down-up-down) before concluding failure. "
- + "Note that long TCP timeouts mean there can be long (e.g. 70 second) "
- + "delays in noticing a connection refused condition.")
- .defaultValue(Duration.ZERO)
- .build();
-
- @SetFromFlag("connectionRecoveredStabilizationDelay")
- public static final ConfigKey<Duration> CONNECTION_RECOVERED_STABILIZATION_DELAY = BasicConfigKey.builder(Duration.class)
- .name("connectionFailureDetector.serviceRecoveredStabilizationDelay")
- .description("For a failed connection, time period for which the connection must be consistently up for (e.g. doesn't report up-down-up) before concluding recovered")
- .defaultValue(Duration.ZERO)
- .build();
-
- @Override
- public void init() {
- super.init();
- getRequiredConfig(ENDPOINT); // just to confirm it's set, failing fast
- if (config().getRaw(SENSOR_FAILED).isAbsent()) {
- config().set(SENSOR_FAILED, CONNECTION_FAILED);
- }
- if (config().getRaw(SENSOR_RECOVERED).isAbsent()) {
- config().set(SENSOR_RECOVERED, CONNECTION_RECOVERED);
- }
- }
-
- @Override
- protected CalculatedStatus calculateStatus() {
- HostAndPort endpoint = getConfig(ENDPOINT);
- boolean isHealthy = Networking.isReachable(endpoint);
- return new BasicCalculatedStatus(isHealthy, "endpoint=" + endpoint);
- }
-
- //Persistence compatibility overrides
- @Override
- protected Duration getPollPeriod() {
- return getConfig(POLL_PERIOD);
- }
-
- @Override
- protected Duration getFailedStabilizationDelay() {
- return getConfig(CONNECTION_FAILED_STABILIZATION_DELAY);
- }
-
- @Override
- protected Duration getRecoveredStabilizationDelay() {
- return getConfig(CONNECTION_RECOVERED_STABILIZATION_DELAY);
- }
-
- @SuppressWarnings("unchecked")
- @Override
- protected Sensor<FailureDescriptor> getSensorFailed() {
- Maybe<Object> sensorFailed = config().getRaw(SENSOR_FAILED);
- if (sensorFailed.isPresent()) {
- return (Sensor<FailureDescriptor>)sensorFailed.get();
- } else {
- return CONNECTION_FAILED;
- }
- }
-
- @SuppressWarnings("unchecked")
- @Override
- protected Sensor<FailureDescriptor> getSensorRecovered() {
- Maybe<Object> sensorRecovered = config().getRaw(SENSOR_RECOVERED);
- if (sensorRecovered.isPresent()) {
- return (Sensor<FailureDescriptor>)sensorRecovered.get();
- } else {
- return CONNECTION_RECOVERED;
- }
- }
-
-}
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/d30ff597/policy/src/main/java/brooklyn/policy/ha/HASensors.java
----------------------------------------------------------------------
diff --git a/policy/src/main/java/brooklyn/policy/ha/HASensors.java b/policy/src/main/java/brooklyn/policy/ha/HASensors.java
deleted file mode 100644
index b940aa0..0000000
--- a/policy/src/main/java/brooklyn/policy/ha/HASensors.java
+++ /dev/null
@@ -1,62 +0,0 @@
-/*
- * 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 brooklyn.policy.ha;
-
-import brooklyn.event.basic.BasicNotificationSensor;
-
-import com.google.common.base.Objects;
-
-public class HASensors {
-
- public static final BasicNotificationSensor<FailureDescriptor> ENTITY_FAILED = new BasicNotificationSensor<FailureDescriptor>(
- FailureDescriptor.class, "ha.entityFailed", "Indicates that an entity has failed");
-
- public static final BasicNotificationSensor<FailureDescriptor> ENTITY_RECOVERED = new BasicNotificationSensor<FailureDescriptor>(
- FailureDescriptor.class, "ha.entityRecovered", "Indicates that a previously failed entity has recovered");
-
- public static final BasicNotificationSensor<FailureDescriptor> CONNECTION_FAILED = new BasicNotificationSensor<FailureDescriptor>(
- FailureDescriptor.class, "ha.connectionFailed", "Indicates that a connection has failed");
-
- public static final BasicNotificationSensor<FailureDescriptor> CONNECTION_RECOVERED = new BasicNotificationSensor<FailureDescriptor>(
- FailureDescriptor.class, "ha.connectionRecovered", "Indicates that a previously failed connection has recovered");
-
- // TODO How to make this serializable with the entity reference
- public static class FailureDescriptor {
- private final Object component;
- private final String description;
-
- public FailureDescriptor(Object component, String description) {
- this.component = component;
- this.description = description;
- }
-
- public Object getComponent() {
- return component;
- }
-
- public String getDescription() {
- return description;
- }
-
- @Override
- public String toString() {
- return Objects.toStringHelper(this).add("component", component).add("description", description).toString();
- }
- }
-}
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/d30ff597/policy/src/main/java/brooklyn/policy/ha/ServiceFailureDetector.java
----------------------------------------------------------------------
diff --git a/policy/src/main/java/brooklyn/policy/ha/ServiceFailureDetector.java b/policy/src/main/java/brooklyn/policy/ha/ServiceFailureDetector.java
deleted file mode 100644
index 2e4b719..0000000
--- a/policy/src/main/java/brooklyn/policy/ha/ServiceFailureDetector.java
+++ /dev/null
@@ -1,340 +0,0 @@
-/*
- * 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 brooklyn.policy.ha;
-
-import java.util.Map;
-import java.util.Map.Entry;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.atomic.AtomicBoolean;
-
-import org.apache.brooklyn.api.event.SensorEvent;
-import org.apache.brooklyn.core.util.config.ConfigBag;
-import org.apache.brooklyn.core.util.flags.SetFromFlag;
-import org.apache.brooklyn.core.util.task.BasicTask;
-import org.apache.brooklyn.core.util.task.ScheduledTask;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import brooklyn.config.ConfigKey;
-import brooklyn.entity.basic.Attributes;
-import brooklyn.entity.basic.ConfigKeys;
-import brooklyn.entity.basic.EntityInternal;
-import brooklyn.entity.basic.Lifecycle;
-import brooklyn.entity.basic.ServiceStateLogic;
-import brooklyn.entity.basic.ServiceStateLogic.ComputeServiceState;
-import brooklyn.event.basic.BasicConfigKey;
-import brooklyn.event.basic.BasicNotificationSensor;
-import brooklyn.policy.ha.HASensors.FailureDescriptor;
-import brooklyn.util.collections.MutableMap;
-import brooklyn.util.exceptions.Exceptions;
-import brooklyn.util.time.Duration;
-import brooklyn.util.time.Time;
-
-/**
- * Emits {@link HASensors#ENTITY_FAILED} whenever the parent's default logic ({@link ComputeServiceState}) would detect a problem,
- * and similarly {@link HASensors#ENTITY_RECOVERED} when recovered.
- * <p>
- * gives more control over suppressing {@link Lifecycle#ON_FIRE},
- * for some period of time
- * (or until another process manually sets {@link Attributes#SERVICE_STATE_ACTUAL} to {@value Lifecycle#ON_FIRE},
- * which this enricher will not clear until all problems have gone away)
- */
-//@Catalog(name="Service Failure Detector", description="HA policy for deteting failure of a service")
-public class ServiceFailureDetector extends ServiceStateLogic.ComputeServiceState {
-
- // TODO Remove duplication between this and MemberFailureDetectionPolicy.
- // The latter could be re-written to use this. Or could even be deprecated
- // in favour of this.
-
- public enum LastPublished {
- NONE,
- FAILED,
- RECOVERED;
- }
-
- private static final Logger LOG = LoggerFactory.getLogger(ServiceFailureDetector.class);
-
- private static final long MIN_PERIOD_BETWEEN_EXECS_MILLIS = 100;
-
- public static final BasicNotificationSensor<FailureDescriptor> ENTITY_FAILED = HASensors.ENTITY_FAILED;
-
- @SetFromFlag("onlyReportIfPreviouslyUp")
- public static final ConfigKey<Boolean> ENTITY_FAILED_ONLY_IF_PREVIOUSLY_UP = ConfigKeys.newBooleanConfigKey("onlyReportIfPreviouslyUp",
- "Prevents the policy from emitting ENTITY_FAILED if the entity fails on startup (ie has never been up)", true);
-
- public static final ConfigKey<Boolean> MONITOR_SERVICE_PROBLEMS = ConfigKeys.newBooleanConfigKey("monitorServiceProblems",
- "Whether to monitor service problems, and emit on failures there (if set to false, this monitors only service up)", true);
-
- @SetFromFlag("serviceOnFireStabilizationDelay")
- public static final ConfigKey<Duration> SERVICE_ON_FIRE_STABILIZATION_DELAY = BasicConfigKey.builder(Duration.class)
- .name("serviceOnFire.stabilizationDelay")
- .description("Time period for which the service must be consistently down for (e.g. doesn't report down-up-down) before concluding ON_FIRE")
- .defaultValue(Duration.ZERO)
- .build();
-
- @SetFromFlag("entityFailedStabilizationDelay")
- public static final ConfigKey<Duration> ENTITY_FAILED_STABILIZATION_DELAY = BasicConfigKey.builder(Duration.class)
- .name("entityFailed.stabilizationDelay")
- .description("Time period for which the service must be consistently down for (e.g. doesn't report down-up-down) before emitting ENTITY_FAILED")
- .defaultValue(Duration.ZERO)
- .build();
-
- @SetFromFlag("entityRecoveredStabilizationDelay")
- public static final ConfigKey<Duration> ENTITY_RECOVERED_STABILIZATION_DELAY = BasicConfigKey.builder(Duration.class)
- .name("entityRecovered.stabilizationDelay")
- .description("For a failed entity, time period for which the service must be consistently up for (e.g. doesn't report up-down-up) before emitting ENTITY_RECOVERED")
- .defaultValue(Duration.ZERO)
- .build();
-
- @SetFromFlag("entityFailedRepublishTime")
- public static final ConfigKey<Duration> ENTITY_FAILED_REPUBLISH_TIME = BasicConfigKey.builder(Duration.class)
- .name("entityFailed.republishTime")
- .description("Publish failed state periodically at the specified intervals, null to disable.")
- .build();
-
- protected Long firstUpTime;
-
- protected Long currentFailureStartTime = null;
- protected Long currentRecoveryStartTime = null;
-
- protected Long publishEntityFailedTime = null;
- protected Long publishEntityRecoveredTime = null;
- protected Long setEntityOnFireTime = null;
-
- protected LastPublished lastPublished = LastPublished.NONE;
-
- private final AtomicBoolean executorQueued = new AtomicBoolean(false);
- private volatile long executorTime = 0;
-
- /**
- * TODO Really don't want this mutex!
- * ServiceStateLogic.setExpectedState() will call into `onEvent(null)`, so could get concurrent calls.
- * How to handle that? I don't think `ServiceStateLogic.setExpectedState` should be making the call, but
- * presumably that is their to remove a race condition so it is set before method returns. Caller shouldn't
- * rely on that though.
- * e.g. see `ServiceFailureDetectorTest.testNotifiedOfFailureOnStateOnFire`, where we get two notifications.
- */
- private final Object mutex = new Object();
-
- public ServiceFailureDetector() {
- this(new ConfigBag());
- }
-
- public ServiceFailureDetector(Map<String,?> flags) {
- this(new ConfigBag().putAll(flags));
- }
-
- public ServiceFailureDetector(ConfigBag configBag) {
- // TODO hierarchy should use ConfigBag, and not change flags
- super(configBag.getAllConfigMutable());
- }
-
- @Override
- public void onEvent(SensorEvent<Object> event) {
- if (firstUpTime==null) {
- if (event!=null && Attributes.SERVICE_UP.equals(event.getSensor()) && Boolean.TRUE.equals(event.getValue())) {
- firstUpTime = event.getTimestamp();
- } else if (event == null && Boolean.TRUE.equals(entity.getAttribute(Attributes.SERVICE_UP))) {
- // If this enricher is registered after the entity is up, then we'll get a "synthetic" onEvent(null)
- firstUpTime = System.currentTimeMillis();
- }
- }
-
- super.onEvent(event);
- }
-
- @Override
- protected void setActualState(Lifecycle state) {
- long now = System.currentTimeMillis();
-
- synchronized (mutex) {
- if (state==Lifecycle.ON_FIRE) {
- if (lastPublished == LastPublished.FAILED) {
- if (currentRecoveryStartTime != null) {
- if (LOG.isDebugEnabled()) LOG.debug("{} health-check for {}, component was recovering, now failing: {}", new Object[] {this, entity, getExplanation(state)});
- currentRecoveryStartTime = null;
- publishEntityRecoveredTime = null;
- } else {
- if (LOG.isTraceEnabled()) LOG.trace("{} health-check for {}, component still failed: {}", new Object[] {this, entity, getExplanation(state)});
- }
- } else {
- if (firstUpTime == null && getConfig(ENTITY_FAILED_ONLY_IF_PREVIOUSLY_UP)) {
- // suppress; won't publish
- } else if (currentFailureStartTime == null) {
- if (LOG.isDebugEnabled()) LOG.debug("{} health-check for {}, component now failing: {}", new Object[] {this, entity, getExplanation(state)});
- currentFailureStartTime = now;
- publishEntityFailedTime = currentFailureStartTime + getConfig(ENTITY_FAILED_STABILIZATION_DELAY).toMilliseconds();
- } else {
- if (LOG.isTraceEnabled()) LOG.trace("{} health-check for {}, component continuing failing: {}", new Object[] {this, entity, getExplanation(state)});
- }
- }
- if (setEntityOnFireTime == null) {
- setEntityOnFireTime = now + getConfig(SERVICE_ON_FIRE_STABILIZATION_DELAY).toMilliseconds();
- }
- currentRecoveryStartTime = null;
- publishEntityRecoveredTime = null;
-
- } else if (state == Lifecycle.RUNNING) {
- if (lastPublished == LastPublished.FAILED) {
- if (currentRecoveryStartTime == null) {
- if (LOG.isDebugEnabled()) LOG.debug("{} health-check for {}, component now recovering: {}", new Object[] {this, entity, getExplanation(state)});
- currentRecoveryStartTime = now;
- publishEntityRecoveredTime = currentRecoveryStartTime + getConfig(ENTITY_RECOVERED_STABILIZATION_DELAY).toMilliseconds();
- } else {
- if (LOG.isTraceEnabled()) LOG.trace("{} health-check for {}, component continuing recovering: {}", new Object[] {this, entity, getExplanation(state)});
- }
- } else {
- if (currentFailureStartTime != null) {
- if (LOG.isDebugEnabled()) LOG.debug("{} health-check for {}, component was failing, now healthy: {}", new Object[] {this, entity, getExplanation(state)});
- } else {
- if (LOG.isTraceEnabled()) LOG.trace("{} health-check for {}, component still healthy: {}", new Object[] {this, entity, getExplanation(state)});
- }
- }
- currentFailureStartTime = null;
- publishEntityFailedTime = null;
- setEntityOnFireTime = null;
-
- } else {
- if (LOG.isTraceEnabled()) LOG.trace("{} health-check for {}, in unconfirmed sate: {}", new Object[] {this, entity, getExplanation(state)});
- }
-
- long recomputeIn = Long.MAX_VALUE; // For whether to call recomputeAfterDelay
-
- if (publishEntityFailedTime != null) {
- long delayBeforeCheck = publishEntityFailedTime - now;
- if (delayBeforeCheck<=0) {
- if (LOG.isDebugEnabled()) LOG.debug("{} publishing failed (state={}; currentFailureStartTime={}; now={}",
- new Object[] {this, state, Time.makeDateString(currentFailureStartTime), Time.makeDateString(now)});
- Duration republishDelay = getConfig(ENTITY_FAILED_REPUBLISH_TIME);
- if (republishDelay == null) {
- publishEntityFailedTime = null;
- } else {
- publishEntityFailedTime = now + republishDelay.toMilliseconds();
- recomputeIn = Math.min(recomputeIn, republishDelay.toMilliseconds());
- }
- lastPublished = LastPublished.FAILED;
- entity.emit(HASensors.ENTITY_FAILED, new HASensors.FailureDescriptor(entity, getFailureDescription(now)));
- } else {
- recomputeIn = Math.min(recomputeIn, delayBeforeCheck);
- }
- } else if (publishEntityRecoveredTime != null) {
- long delayBeforeCheck = publishEntityRecoveredTime - now;
- if (delayBeforeCheck<=0) {
- if (LOG.isDebugEnabled()) LOG.debug("{} publishing recovered (state={}; currentRecoveryStartTime={}; now={}",
- new Object[] {this, state, Time.makeDateString(currentRecoveryStartTime), Time.makeDateString(now)});
- publishEntityRecoveredTime = null;
- lastPublished = LastPublished.RECOVERED;
- entity.emit(HASensors.ENTITY_RECOVERED, new HASensors.FailureDescriptor(entity, null));
- } else {
- recomputeIn = Math.min(recomputeIn, delayBeforeCheck);
- }
- }
-
- if (setEntityOnFireTime != null) {
- long delayBeforeCheck = setEntityOnFireTime - now;
- if (delayBeforeCheck<=0) {
- if (LOG.isDebugEnabled()) LOG.debug("{} setting on-fire, now that deferred period has passed (state={})",
- new Object[] {this, state});
- setEntityOnFireTime = null;
- super.setActualState(state);
- } else {
- recomputeIn = Math.min(recomputeIn, delayBeforeCheck);
- }
- } else {
- super.setActualState(state);
- }
-
- if (recomputeIn < Long.MAX_VALUE) {
- recomputeAfterDelay(recomputeIn);
- }
- }
- }
-
- protected String getExplanation(Lifecycle state) {
- Duration serviceFailedStabilizationDelay = getConfig(ENTITY_FAILED_STABILIZATION_DELAY);
- Duration serviceRecoveredStabilizationDelay = getConfig(ENTITY_RECOVERED_STABILIZATION_DELAY);
-
- return String.format("location=%s; status=%s; lastPublished=%s; timeNow=%s; "+
- "currentFailurePeriod=%s; currentRecoveryPeriod=%s",
- entity.getLocations(),
- (state != null ? state : "<unreported>"),
- lastPublished,
- Time.makeDateString(System.currentTimeMillis()),
- (currentFailureStartTime != null ? getTimeStringSince(currentFailureStartTime) : "<none>") + " (stabilization "+Time.makeTimeStringRounded(serviceFailedStabilizationDelay) + ")",
- (currentRecoveryStartTime != null ? getTimeStringSince(currentRecoveryStartTime) : "<none>") + " (stabilization "+Time.makeTimeStringRounded(serviceRecoveredStabilizationDelay) + ")");
- }
-
- private String getFailureDescription(long now) {
- String description = null;
- Map<String, Object> serviceProblems = entity.getAttribute(Attributes.SERVICE_PROBLEMS);
- if (serviceProblems!=null && !serviceProblems.isEmpty()) {
- Entry<String, Object> problem = serviceProblems.entrySet().iterator().next();
- description = problem.getKey()+": "+problem.getValue();
- if (serviceProblems.size()>1) {
- description = serviceProblems.size()+" service problems, including "+description;
- } else {
- description = "service problem: "+description;
- }
- } else if (Boolean.FALSE.equals(entity.getAttribute(Attributes.SERVICE_UP))) {
- description = "service not up";
- } else {
- description = "service failure detected";
- }
- if (publishEntityFailedTime!=null && currentFailureStartTime!=null && publishEntityFailedTime > currentFailureStartTime)
- description = " (stabilized for "+Duration.of(now - currentFailureStartTime, TimeUnit.MILLISECONDS)+")";
- return description;
- }
-
- @SuppressWarnings({ "unchecked", "rawtypes" })
- protected void recomputeAfterDelay(long delay) {
- if (isRunning() && executorQueued.compareAndSet(false, true)) {
- long now = System.currentTimeMillis();
- delay = Math.max(0, Math.max(delay, (executorTime + MIN_PERIOD_BETWEEN_EXECS_MILLIS) - now));
- if (LOG.isTraceEnabled()) LOG.trace("{} scheduling publish in {}ms", this, delay);
-
- Runnable job = new Runnable() {
- @Override public void run() {
- try {
- executorTime = System.currentTimeMillis();
- executorQueued.set(false);
-
- onEvent(null);
-
- } catch (Exception e) {
- if (isRunning()) {
- LOG.error("Error in enricher "+this+": "+e, e);
- } else {
- if (LOG.isDebugEnabled()) LOG.debug("Error in enricher "+this+" (but no longer running): "+e, e);
- }
- } catch (Throwable t) {
- LOG.error("Error in enricher "+this+": "+t, t);
- throw Exceptions.propagate(t);
- }
- }
- };
-
- ScheduledTask task = new ScheduledTask(MutableMap.of("delay", Duration.of(delay, TimeUnit.MILLISECONDS)), new BasicTask(job));
- ((EntityInternal)entity).getExecutionContext().submit(task);
- }
- }
-
- private String getTimeStringSince(Long time) {
- return time == null ? null : Time.makeTimeStringRounded(System.currentTimeMillis() - time);
- }
-}
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/d30ff597/policy/src/main/java/brooklyn/policy/ha/ServiceReplacer.java
----------------------------------------------------------------------
diff --git a/policy/src/main/java/brooklyn/policy/ha/ServiceReplacer.java b/policy/src/main/java/brooklyn/policy/ha/ServiceReplacer.java
deleted file mode 100644
index 6bea1d4..0000000
--- a/policy/src/main/java/brooklyn/policy/ha/ServiceReplacer.java
+++ /dev/null
@@ -1,214 +0,0 @@
-/*
- * 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 brooklyn.policy.ha;
-
-import static com.google.common.base.Preconditions.checkArgument;
-import static com.google.common.base.Preconditions.checkNotNull;
-
-import java.util.Iterator;
-import java.util.List;
-import java.util.Map;
-import java.util.concurrent.TimeUnit;
-
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.apache.brooklyn.api.catalog.Catalog;
-import org.apache.brooklyn.api.entity.Entity;
-import org.apache.brooklyn.api.entity.Group;
-import org.apache.brooklyn.api.entity.basic.EntityLocal;
-import org.apache.brooklyn.api.event.Sensor;
-import org.apache.brooklyn.api.event.SensorEvent;
-import org.apache.brooklyn.api.event.SensorEventListener;
-import org.apache.brooklyn.core.policy.basic.AbstractPolicy;
-import org.apache.brooklyn.core.util.config.ConfigBag;
-import org.apache.brooklyn.core.util.flags.SetFromFlag;
-
-import brooklyn.config.ConfigKey;
-import brooklyn.entity.basic.ConfigKeys;
-import brooklyn.entity.basic.Entities;
-import brooklyn.entity.basic.EntityInternal;
-import brooklyn.entity.basic.ServiceStateLogic.ServiceProblemsLogic;
-import brooklyn.entity.group.StopFailedRuntimeException;
-import brooklyn.entity.trait.MemberReplaceable;
-import brooklyn.event.basic.BasicConfigKey;
-import brooklyn.event.basic.BasicNotificationSensor;
-import brooklyn.policy.ha.HASensors.FailureDescriptor;
-import brooklyn.util.collections.MutableMap;
-import brooklyn.util.exceptions.Exceptions;
-
-import com.google.common.base.Ticker;
-import com.google.common.collect.Lists;
-
-/** attaches to a DynamicCluster and replaces a failed member in response to HASensors.ENTITY_FAILED or other sensor;
- * if this fails, it sets the Cluster state to on-fire */
-@Catalog(name="Service Replacer", description="HA policy for replacing a failed member of a group")
-public class ServiceReplacer extends AbstractPolicy {
-
- private static final Logger LOG = LoggerFactory.getLogger(ServiceReplacer.class);
-
- // TODO if there are multiple failures perhaps we should abort quickly
-
- public static final BasicNotificationSensor<FailureDescriptor> ENTITY_REPLACEMENT_FAILED = new BasicNotificationSensor<FailureDescriptor>(
- FailureDescriptor.class, "ha.entityFailed.replacement", "Indicates that an entity replacement attempt has failed");
-
- @SetFromFlag("setOnFireOnFailure")
- public static final ConfigKey<Boolean> SET_ON_FIRE_ON_FAILURE = ConfigKeys.newBooleanConfigKey("setOnFireOnFailure", "", true);
-
- /** monitors this sensor, by default ENTITY_RESTART_FAILED */
- @SetFromFlag("failureSensorToMonitor")
- @SuppressWarnings("rawtypes")
- public static final ConfigKey<Sensor> FAILURE_SENSOR_TO_MONITOR = new BasicConfigKey<Sensor>(Sensor.class, "failureSensorToMonitor", "", ServiceRestarter.ENTITY_RESTART_FAILED);
-
- /** skips replace if replacement has failed this many times failure re-occurs within this time interval */
- @SetFromFlag("failOnRecurringFailuresInThisDuration")
- public static final ConfigKey<Long> FAIL_ON_RECURRING_FAILURES_IN_THIS_DURATION = ConfigKeys.newLongConfigKey(
- "failOnRecurringFailuresInThisDuration",
- "abandon replace if replacement has failed many times within this time interval",
- 5*60*1000L);
-
- /** skips replace if replacement has failed this many times failure re-occurs within this time interval */
- @SetFromFlag("failOnNumRecurringFailures")
- public static final ConfigKey<Integer> FAIL_ON_NUM_RECURRING_FAILURES = ConfigKeys.newIntegerConfigKey(
- "failOnNumRecurringFailures",
- "abandon replace if replacement has failed this many times (100% of attempts) within the time interval",
- 5);
-
- @SetFromFlag("ticker")
- public static final ConfigKey<Ticker> TICKER = ConfigKeys.newConfigKey(Ticker.class,
- "ticker",
- "A time source (defaults to system-clock, which is almost certainly what's wanted, except in tests)",
- null);
-
- protected final List<Long> consecutiveReplacementFailureTimes = Lists.newCopyOnWriteArrayList();
-
- public ServiceReplacer() {
- this(new ConfigBag());
- }
-
- public ServiceReplacer(Map<String,?> flags) {
- this(new ConfigBag().putAll(flags));
- }
-
- public ServiceReplacer(ConfigBag configBag) {
- // TODO hierarchy should use ConfigBag, and not change flags
- super(configBag.getAllConfigMutable());
- }
-
- public ServiceReplacer(Sensor<?> failureSensorToMonitor) {
- this(new ConfigBag().configure(FAILURE_SENSOR_TO_MONITOR, failureSensorToMonitor));
- }
-
- @Override
- public void setEntity(final EntityLocal entity) {
- checkArgument(entity instanceof MemberReplaceable, "ServiceReplacer must take a MemberReplaceable, not %s", entity);
- Sensor<?> failureSensorToMonitor = checkNotNull(getConfig(FAILURE_SENSOR_TO_MONITOR), "failureSensorToMonitor");
-
- super.setEntity(entity);
-
- subscribeToMembers((Group)entity, failureSensorToMonitor, new SensorEventListener<Object>() {
- @Override public void onEvent(final SensorEvent<Object> event) {
- // Must execute in another thread - if we called entity.replaceMember in the event-listener's thread
- // then we'd block all other events being delivered to this entity's other subscribers.
- // Relies on synchronization of `onDetectedFailure`.
- // See same pattern used in ServiceRestarter.
-
- // TODO Could use BasicExecutionManager.setTaskSchedulerForTag to prevent race of two
- // events being received in rapid succession, and onDetectedFailure being executed out-of-order
- // for them; or could write events to a blocking queue and have onDetectedFailure read from that.
-
- if (isRunning()) {
- LOG.warn("ServiceReplacer notified; dispatching job for "+entity+" ("+event.getValue()+")");
- ((EntityInternal)entity).getExecutionContext().submit(MutableMap.of(), new Runnable() {
- @Override public void run() {
- onDetectedFailure(event);
- }});
- } else {
- LOG.warn("ServiceReplacer not running, so not acting on failure detected at "+entity+" ("+event.getValue()+", child of "+entity+")");
- }
- }
- });
- }
-
- // TODO semaphores would be better to allow at-most-one-blocking behaviour
- protected synchronized void onDetectedFailure(SensorEvent<Object> event) {
- final Entity failedEntity = event.getSource();
- final Object reason = event.getValue();
-
- if (isSuspended()) {
- LOG.warn("ServiceReplacer suspended, so not acting on failure detected at "+failedEntity+" ("+reason+", child of "+entity+")");
- return;
- }
-
- if (isRepeatedlyFailingTooMuch()) {
- LOG.error("ServiceReplacer not acting on failure detected at "+failedEntity+" ("+reason+", child of "+entity+"), because too many recent replacement failures");
- return;
- }
-
- LOG.warn("ServiceReplacer acting on failure detected at "+failedEntity+" ("+reason+", child of "+entity+")");
- ((EntityInternal)entity).getManagementSupport().getExecutionContext().submit(MutableMap.of(), new Runnable() {
-
- @Override
- public void run() {
- try {
- Entities.invokeEffectorWithArgs(entity, entity, MemberReplaceable.REPLACE_MEMBER, failedEntity.getId()).get();
- consecutiveReplacementFailureTimes.clear();
- } catch (Exception e) {
- if (Exceptions.getFirstThrowableOfType(e, StopFailedRuntimeException.class) != null) {
- LOG.info("ServiceReplacer: ignoring error reported from stopping failed node "+failedEntity);
- return;
- }
- onReplacementFailed("Replace failure ("+Exceptions.collapseText(e)+") at "+entity+": "+reason);
- }
- }
- });
- }
-
- private boolean isRepeatedlyFailingTooMuch() {
- Integer failOnNumRecurringFailures = getConfig(FAIL_ON_NUM_RECURRING_FAILURES);
- long failOnRecurringFailuresInThisDuration = getConfig(FAIL_ON_RECURRING_FAILURES_IN_THIS_DURATION);
- long oldestPermitted = currentTimeMillis() - failOnRecurringFailuresInThisDuration;
-
- // trim old ones
- for (Iterator<Long> iter = consecutiveReplacementFailureTimes.iterator(); iter.hasNext();) {
- Long timestamp = iter.next();
- if (timestamp < oldestPermitted) {
- iter.remove();
- } else {
- break;
- }
- }
-
- return (consecutiveReplacementFailureTimes.size() >= failOnNumRecurringFailures);
- }
-
- protected long currentTimeMillis() {
- Ticker ticker = getConfig(TICKER);
- return (ticker == null) ? System.currentTimeMillis() : TimeUnit.NANOSECONDS.toMillis(ticker.read());
- }
-
- protected void onReplacementFailed(String msg) {
- LOG.warn("ServiceReplacer failed for "+entity+": "+msg);
- consecutiveReplacementFailureTimes.add(currentTimeMillis());
-
- if (getConfig(SET_ON_FIRE_ON_FAILURE)) {
- ServiceProblemsLogic.updateProblemsIndicator(entity, "ServiceReplacer", "replacement failed: "+msg);
- }
- entity.emit(ENTITY_REPLACEMENT_FAILED, new FailureDescriptor(entity, msg));
- }
-}
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/d30ff597/policy/src/main/java/brooklyn/policy/ha/ServiceRestarter.java
----------------------------------------------------------------------
diff --git a/policy/src/main/java/brooklyn/policy/ha/ServiceRestarter.java b/policy/src/main/java/brooklyn/policy/ha/ServiceRestarter.java
deleted file mode 100644
index ab1359d..0000000
--- a/policy/src/main/java/brooklyn/policy/ha/ServiceRestarter.java
+++ /dev/null
@@ -1,163 +0,0 @@
-/*
- * 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 brooklyn.policy.ha;
-
-import java.util.Map;
-import java.util.concurrent.atomic.AtomicReference;
-
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.apache.brooklyn.api.catalog.Catalog;
-import org.apache.brooklyn.api.entity.basic.EntityLocal;
-import org.apache.brooklyn.api.event.Sensor;
-import org.apache.brooklyn.api.event.SensorEvent;
-import org.apache.brooklyn.api.event.SensorEventListener;
-import org.apache.brooklyn.core.policy.basic.AbstractPolicy;
-import org.apache.brooklyn.core.util.config.ConfigBag;
-import org.apache.brooklyn.core.util.flags.SetFromFlag;
-
-import brooklyn.config.ConfigKey;
-import brooklyn.entity.basic.ConfigKeys;
-import brooklyn.entity.basic.Entities;
-import brooklyn.entity.basic.EntityInternal;
-import brooklyn.entity.basic.Lifecycle;
-import brooklyn.entity.basic.ServiceStateLogic;
-import brooklyn.entity.trait.Startable;
-import brooklyn.event.basic.BasicNotificationSensor;
-import brooklyn.policy.ha.HASensors.FailureDescriptor;
-import brooklyn.util.collections.MutableMap;
-import brooklyn.util.javalang.JavaClassNames;
-import brooklyn.util.time.Duration;
-import brooklyn.util.time.Time;
-
-import com.google.common.base.Preconditions;
-
-/** attaches to a SoftwareProcess (or anything Startable, emitting ENTITY_FAILED or other configurable sensor),
- * and invokes restart on failure;
- * if there is a subsequent failure within a configurable time interval, or if the restart fails,
- * this gives up and emits {@link #ENTITY_RESTART_FAILED}
- */
-@Catalog(name="Service Restarter", description="HA policy for restarting a service automatically, "
- + "and for emitting an events if the service repeatedly fails")
-public class ServiceRestarter extends AbstractPolicy {
-
- private static final Logger LOG = LoggerFactory.getLogger(ServiceRestarter.class);
-
- public static final BasicNotificationSensor<FailureDescriptor> ENTITY_RESTART_FAILED = new BasicNotificationSensor<FailureDescriptor>(
- FailureDescriptor.class, "ha.entityFailed.restart", "Indicates that an entity restart attempt has failed");
-
- /** skips retry if a failure re-occurs within this time interval */
- @SetFromFlag("failOnRecurringFailuresInThisDuration")
- public static final ConfigKey<Duration> FAIL_ON_RECURRING_FAILURES_IN_THIS_DURATION = ConfigKeys.newConfigKey(
- Duration.class,
- "failOnRecurringFailuresInThisDuration",
- "Reports entity as failed if it fails two or more times in this time window",
- Duration.minutes(3));
-
- @SetFromFlag("setOnFireOnFailure")
- public static final ConfigKey<Boolean> SET_ON_FIRE_ON_FAILURE = ConfigKeys.newBooleanConfigKey("setOnFireOnFailure", "", true);
-
- /** monitors this sensor, by default ENTITY_FAILED */
- @SetFromFlag("failureSensorToMonitor")
- @SuppressWarnings({ "rawtypes", "unchecked" })
- public static final ConfigKey<Sensor<?>> FAILURE_SENSOR_TO_MONITOR = (ConfigKey) ConfigKeys.newConfigKey(Sensor.class, "failureSensorToMonitor", "", HASensors.ENTITY_FAILED);
-
- protected final AtomicReference<Long> lastFailureTime = new AtomicReference<Long>();
-
- public ServiceRestarter() {
- this(new ConfigBag());
- }
-
- public ServiceRestarter(Map<String,?> flags) {
- this(new ConfigBag().putAll(flags));
- }
-
- public ServiceRestarter(ConfigBag configBag) {
- // TODO hierarchy should use ConfigBag, and not change flags
- super(configBag.getAllConfigMutable());
- uniqueTag = JavaClassNames.simpleClassName(getClass())+":"+getConfig(FAILURE_SENSOR_TO_MONITOR).getName();
- }
-
- public ServiceRestarter(Sensor<?> failureSensorToMonitor) {
- this(new ConfigBag().configure(FAILURE_SENSOR_TO_MONITOR, failureSensorToMonitor));
- }
-
- @Override
- public void setEntity(final EntityLocal entity) {
- Preconditions.checkArgument(entity instanceof Startable, "Restarter must take a Startable, not "+entity);
-
- super.setEntity(entity);
-
- subscribe(entity, getConfig(FAILURE_SENSOR_TO_MONITOR), new SensorEventListener<Object>() {
- @Override public void onEvent(final SensorEvent<Object> event) {
- // Must execute in another thread - if we called entity.restart in the event-listener's thread
- // then we'd block all other events being delivered to this entity's other subscribers.
- // Relies on synchronization of `onDetectedFailure`.
- // See same pattern used in ServiceReplacer.
-
- // TODO Could use BasicExecutionManager.setTaskSchedulerForTag to prevent race of two
- // events being received in rapid succession, and onDetectedFailure being executed out-of-order
- // for them; or could write events to a blocking queue and have onDetectedFailure read from that.
-
- if (isRunning()) {
- LOG.info("ServiceRestarter notified; dispatching job for "+entity+" ("+event.getValue()+")");
- ((EntityInternal)entity).getExecutionContext().submit(MutableMap.of(), new Runnable() {
- @Override public void run() {
- onDetectedFailure(event);
- }});
- } else {
- LOG.warn("ServiceRestarter not running, so not acting on failure detected at "+entity+" ("+event.getValue()+")");
- }
- }
- });
- }
-
- // TODO semaphores would be better to allow at-most-one-blocking behaviour
- // FIXME as this is called in message-dispatch (single threaded) we should do most of this in a new submitted task
- // (as has been done in ServiceReplacer)
- protected synchronized void onDetectedFailure(SensorEvent<Object> event) {
- if (isSuspended()) {
- LOG.warn("ServiceRestarter suspended, so not acting on failure detected at "+entity+" ("+event.getValue()+")");
- return;
- }
-
- LOG.warn("ServiceRestarter acting on failure detected at "+entity+" ("+event.getValue()+")");
- long current = System.currentTimeMillis();
- Long last = lastFailureTime.getAndSet(current);
- long elapsed = last==null ? -1 : current-last;
- if (elapsed>=0 && elapsed <= getConfig(FAIL_ON_RECURRING_FAILURES_IN_THIS_DURATION).toMilliseconds()) {
- onRestartFailed("Restart failure (failed again after "+Time.makeTimeStringRounded(elapsed)+") at "+entity+": "+event.getValue());
- return;
- }
- try {
- ServiceStateLogic.setExpectedState(entity, Lifecycle.STARTING);
- Entities.invokeEffector(entity, entity, Startable.RESTART).get();
- } catch (Exception e) {
- onRestartFailed("Restart failure (error "+e+") at "+entity+": "+event.getValue());
- }
- }
-
- protected void onRestartFailed(String msg) {
- LOG.warn("ServiceRestarter failed for "+entity+": "+msg);
- if (getConfig(SET_ON_FIRE_ON_FAILURE)) {
- ServiceStateLogic.setExpectedState(entity, Lifecycle.ON_FIRE);
- }
- entity.emit(ENTITY_RESTART_FAILED, new FailureDescriptor(entity, msg));
- }
-}
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/d30ff597/policy/src/main/java/brooklyn/policy/ha/SshMachineFailureDetector.java
----------------------------------------------------------------------
diff --git a/policy/src/main/java/brooklyn/policy/ha/SshMachineFailureDetector.java b/policy/src/main/java/brooklyn/policy/ha/SshMachineFailureDetector.java
deleted file mode 100644
index 1fa9982..0000000
--- a/policy/src/main/java/brooklyn/policy/ha/SshMachineFailureDetector.java
+++ /dev/null
@@ -1,102 +0,0 @@
-/*
- * 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 brooklyn.policy.ha;
-
-import java.util.Map;
-
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.apache.brooklyn.api.catalog.Catalog;
-import org.apache.brooklyn.core.util.internal.ssh.SshTool;
-
-import brooklyn.config.ConfigKey;
-import brooklyn.entity.basic.ConfigKeys;
-import brooklyn.event.basic.BasicNotificationSensor;
-
-import org.apache.brooklyn.location.basic.Machines;
-import org.apache.brooklyn.location.basic.SshMachineLocation;
-
-import brooklyn.policy.ha.HASensors.FailureDescriptor;
-import brooklyn.util.exceptions.Exceptions;
-import brooklyn.util.guava.Maybe;
-import brooklyn.util.time.Duration;
-
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.ImmutableMap;
-
-@Catalog(name="Ssh Connectivity Failure Detector", description="HA policy for monitoring an SshMachine, "
- + "emitting an event if the connection is lost/restored")
-public class SshMachineFailureDetector extends AbstractFailureDetector {
- private static final Logger LOG = LoggerFactory.getLogger(SshMachineFailureDetector.class);
- public static final String DEFAULT_UNIQUE_TAG = "failureDetector.sshMachine.tag";
-
- public static final BasicNotificationSensor<FailureDescriptor> CONNECTION_FAILED = HASensors.CONNECTION_FAILED;
-
- public static final BasicNotificationSensor<FailureDescriptor> CONNECTION_RECOVERED = HASensors.CONNECTION_RECOVERED;
-
- public static final ConfigKey<Duration> CONNECT_TIMEOUT = ConfigKeys.newDurationConfigKey(
- "ha.sshConnection.timeout", "How long to wait for conneciton before declaring failure", Duration.TEN_SECONDS);
-
- @Override
- public void init() {
- super.init();
- if (config().getRaw(SENSOR_FAILED).isAbsent()) {
- config().set(SENSOR_FAILED, CONNECTION_FAILED);
- }
- if (config().getRaw(SENSOR_RECOVERED).isAbsent()) {
- config().set(SENSOR_RECOVERED, CONNECTION_RECOVERED);
- }
- if (config().getRaw(POLL_PERIOD).isAbsent()) {
- config().set(POLL_PERIOD, Duration.ONE_MINUTE);
- }
- uniqueTag = DEFAULT_UNIQUE_TAG;
- }
-
- @Override
- protected CalculatedStatus calculateStatus() {
- Maybe<SshMachineLocation> sshMachineOption = Machines.findUniqueSshMachineLocation(entity.getLocations());
- if (sshMachineOption.isPresent()) {
- SshMachineLocation sshMachine = sshMachineOption.get();
- try {
- Duration timeout = config().get(CONNECT_TIMEOUT);
- Map<String, ?> flags = ImmutableMap.of(
- SshTool.PROP_CONNECT_TIMEOUT.getName(), timeout.toMilliseconds(),
- SshTool.PROP_SESSION_TIMEOUT.getName(), timeout.toMilliseconds(),
- SshTool.PROP_SSH_TRIES.getName(), 1);
- int exitCode = sshMachine.execCommands(flags, SshMachineFailureDetector.class.getName(), ImmutableList.of("exit"));
- return new BasicCalculatedStatus(exitCode == 0, sshMachine.toString());
- } catch (Exception e) {
- Exceptions.propagateIfFatal(e);
- boolean isFirstFailure = lastPublished != LastPublished.FAILED && currentFailureStartTime == null;
- if (isFirstFailure) {
- if (LOG.isDebugEnabled()) {
- LOG.debug("Failed connecting to machine " + sshMachine, e);
- }
- } else {
- if (LOG.isTraceEnabled()) {
- LOG.trace("Failed connecting to machine " + sshMachine, e);
- }
- }
- return new BasicCalculatedStatus(false, e.getMessage());
- }
- } else {
- return new BasicCalculatedStatus(true, "no machine started, not complaining");
- }
- }
-}
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/d30ff597/policy/src/main/java/brooklyn/policy/loadbalancing/BalanceableContainer.java
----------------------------------------------------------------------
diff --git a/policy/src/main/java/brooklyn/policy/loadbalancing/BalanceableContainer.java b/policy/src/main/java/brooklyn/policy/loadbalancing/BalanceableContainer.java
deleted file mode 100644
index 0039685..0000000
--- a/policy/src/main/java/brooklyn/policy/loadbalancing/BalanceableContainer.java
+++ /dev/null
@@ -1,51 +0,0 @@
-/*
- * 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 brooklyn.policy.loadbalancing;
-
-import java.util.Set;
-
-import org.apache.brooklyn.api.entity.Entity;
-
-import brooklyn.config.ConfigKey;
-import brooklyn.entity.basic.AbstractGroup;
-import brooklyn.entity.basic.ConfigKeys;
-import brooklyn.event.basic.BasicNotificationSensor;
-import brooklyn.util.collections.QuorumCheck;
-import brooklyn.util.collections.QuorumCheck.QuorumChecks;
-
-/**
- * Contains worker items that can be moved between this container and others to effect load balancing.
- * Membership of a balanceable container does not imply a parent-child relationship in the Brooklyn
- * management sense.
- */
-public interface BalanceableContainer<ItemType extends Movable> extends Entity, AbstractGroup {
-
- public static BasicNotificationSensor<Entity> ITEM_ADDED = new BasicNotificationSensor<Entity>(
- Entity.class, "balanceablecontainer.item.added", "Movable item added to balanceable container");
- public static BasicNotificationSensor<Entity> ITEM_REMOVED = new BasicNotificationSensor<Entity>(
- Entity.class, "balanceablecontainer.item.removed", "Movable item removed from balanceable container");
-
- public static final ConfigKey<QuorumCheck> UP_QUORUM_CHECK = ConfigKeys.newConfigKeyWithDefault(AbstractGroup.UP_QUORUM_CHECK,
- "Up check from members; default one for container overrides usual check to always return true, "
- + "i.e. not block service up simply because the container is empty or something in the container has failed",
- QuorumChecks.alwaysTrue());
-
- public Set<ItemType> getBalanceableItems();
-
-}
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/d30ff597/policy/src/main/java/brooklyn/policy/loadbalancing/BalanceablePoolModel.java
----------------------------------------------------------------------
diff --git a/policy/src/main/java/brooklyn/policy/loadbalancing/BalanceablePoolModel.java b/policy/src/main/java/brooklyn/policy/loadbalancing/BalanceablePoolModel.java
deleted file mode 100644
index 33f9e0b..0000000
--- a/policy/src/main/java/brooklyn/policy/loadbalancing/BalanceablePoolModel.java
+++ /dev/null
@@ -1,64 +0,0 @@
-/*
- * 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 brooklyn.policy.loadbalancing;
-
-import java.util.Map;
-import java.util.Set;
-
-import org.apache.brooklyn.api.location.Location;
-
-/**
- * Captures the state of a balanceable cluster of containers and all their constituent items, including workrates,
- * for consumption by a {@link BalancingStrategy}.
- */
-public interface BalanceablePoolModel<ContainerType, ItemType> {
-
- // Attributes of the pool.
- public String getName();
- public int getPoolSize();
- public Set<ContainerType> getPoolContents();
- public double getPoolLowThreshold();
- public double getPoolHighThreshold();
- public double getCurrentPoolWorkrate();
- public boolean isHot();
- public boolean isCold();
-
-
- // Attributes of containers and items.
- public String getName(ContainerType container);
- public Location getLocation(ContainerType container);
- public double getLowThreshold(ContainerType container); // -1 for not known / invalid
- public double getHighThreshold(ContainerType container); // -1 for not known / invalid
- public double getTotalWorkrate(ContainerType container); // -1 for not known / invalid
- public Map<ContainerType, Double> getContainerWorkrates(); // contains -1 for items which are unknown
- /** contains -1 instead of actual item workrate, for items which cannot be moved */
- // @Nullable("null if the node is prevented from reporting and/or being adjusted, or has no data yet")
- public Map<ItemType, Double> getItemWorkrates(ContainerType container);
- public boolean isItemMoveable(ItemType item);
- public boolean isItemAllowedIn(ItemType item, Location location);
-
- // Mutators for keeping the model in-sync with the observed world
- public void onContainerAdded(ContainerType newContainer, double lowThreshold, double highThreshold);
- public void onContainerRemoved(ContainerType oldContainer);
- public void onItemAdded(ItemType item, ContainerType parentContainer);
- public void onItemAdded(ItemType item, ContainerType parentContainer, boolean immovable);
- public void onItemRemoved(ItemType item);
- public void onItemWorkrateUpdated(ItemType item, double newValue);
- public void onItemMoved(ItemType item, ContainerType targetContainer);
-}
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/d30ff597/policy/src/main/java/brooklyn/policy/loadbalancing/BalanceableWorkerPool.java
----------------------------------------------------------------------
diff --git a/policy/src/main/java/brooklyn/policy/loadbalancing/BalanceableWorkerPool.java b/policy/src/main/java/brooklyn/policy/loadbalancing/BalanceableWorkerPool.java
deleted file mode 100644
index c8d3a8b..0000000
--- a/policy/src/main/java/brooklyn/policy/loadbalancing/BalanceableWorkerPool.java
+++ /dev/null
@@ -1,84 +0,0 @@
-/*
- * 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 brooklyn.policy.loadbalancing;
-
-import static com.google.common.base.Preconditions.checkNotNull;
-
-import java.io.Serializable;
-
-import org.apache.brooklyn.api.entity.Entity;
-import org.apache.brooklyn.api.entity.Group;
-import org.apache.brooklyn.api.entity.proxying.ImplementedBy;
-
-import brooklyn.entity.trait.Resizable;
-import brooklyn.event.basic.BasicNotificationSensor;
-
-/**
- * Represents an elastic group of "container" entities, each of which is capable of hosting "item" entities that perform
- * work and consume the container's available resources (e.g. CPU or bandwidth). Auto-scaling and load-balancing policies can
- * be attached to this pool to provide dynamic elasticity based on workrates reported by the individual item entities.
- * <p>
- * The containers must be "up" in order to receive work, thus they must NOT follow the default enricher pattern
- * for groups which says that the group must be up to receive work.
- */
-@ImplementedBy(BalanceableWorkerPoolImpl.class)
-public interface BalanceableWorkerPool extends Entity, Resizable {
-
- // FIXME Asymmetry between loadbalancing and followTheSun: ITEM_ADDED and ITEM_REMOVED in loadbalancing
- // are of type ContainerItemPair, but in followTheSun it is just the `Entity item`.
-
- /** Encapsulates an item and a container; emitted for {@code ITEM_ADDED}, {@code ITEM_REMOVED} and
- * {@code ITEM_MOVED} sensors.
- */
- public static class ContainerItemPair implements Serializable {
- private static final long serialVersionUID = 1L;
- public final BalanceableContainer<?> container;
- public final Entity item;
-
- public ContainerItemPair(BalanceableContainer<?> container, Entity item) {
- this.container = container;
- this.item = checkNotNull(item);
- }
-
- @Override
- public String toString() {
- return ""+item+" @ "+container;
- }
- }
-
- // Pool constituent notifications.
- public static BasicNotificationSensor<Entity> CONTAINER_ADDED = new BasicNotificationSensor<Entity>(
- Entity.class, "balanceablepool.container.added", "Container added to balanceable pool");
- public static BasicNotificationSensor<Entity> CONTAINER_REMOVED = new BasicNotificationSensor<Entity>(
- Entity.class, "balanceablepool.container.removed", "Container removed from balanceable pool");
- public static BasicNotificationSensor<ContainerItemPair> ITEM_ADDED = new BasicNotificationSensor<ContainerItemPair>(
- ContainerItemPair.class, "balanceablepool.item.added", "Item added to balanceable pool");
- public static BasicNotificationSensor<ContainerItemPair> ITEM_REMOVED = new BasicNotificationSensor<ContainerItemPair>(
- ContainerItemPair.class, "balanceablepool.item.removed", "Item removed from balanceable pool");
- public static BasicNotificationSensor<ContainerItemPair> ITEM_MOVED = new BasicNotificationSensor<ContainerItemPair>(
- ContainerItemPair.class, "balanceablepool.item.moved", "Item moved in balanceable pool to the given container");
-
- public void setResizable(Resizable resizable);
-
- public void setContents(Group containerGroup, Group itemGroup);
-
- public Group getContainerGroup();
-
- public Group getItemGroup();
-}
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/d30ff597/policy/src/main/java/brooklyn/policy/loadbalancing/BalanceableWorkerPoolImpl.java
----------------------------------------------------------------------
diff --git a/policy/src/main/java/brooklyn/policy/loadbalancing/BalanceableWorkerPoolImpl.java b/policy/src/main/java/brooklyn/policy/loadbalancing/BalanceableWorkerPoolImpl.java
deleted file mode 100644
index b3f9633..0000000
--- a/policy/src/main/java/brooklyn/policy/loadbalancing/BalanceableWorkerPoolImpl.java
+++ /dev/null
@@ -1,185 +0,0 @@
-/*
- * 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 brooklyn.policy.loadbalancing;
-
-import java.util.Collections;
-import java.util.HashSet;
-import java.util.Set;
-
-import org.apache.brooklyn.api.entity.Entity;
-import org.apache.brooklyn.api.entity.Group;
-import org.apache.brooklyn.api.event.Sensor;
-import org.apache.brooklyn.api.event.SensorEvent;
-import org.apache.brooklyn.api.event.SensorEventListener;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import brooklyn.entity.basic.AbstractEntity;
-import brooklyn.entity.basic.AbstractGroup;
-import brooklyn.entity.trait.Resizable;
-import brooklyn.entity.trait.Startable;
-
-/**
- * @see BalanceableWorkerPool
- */
-public class BalanceableWorkerPoolImpl extends AbstractEntity implements BalanceableWorkerPool {
-
- // FIXME Asymmetry between loadbalancing and followTheSun: ITEM_ADDED and ITEM_REMOVED in loadbalancing
- // are of type ContainerItemPair, but in followTheSun it is just the `Entity item`.
-
- private static final Logger LOG = LoggerFactory.getLogger(BalanceableWorkerPool.class);
-
- private Group containerGroup;
- private Group itemGroup;
- private Resizable resizable;
-
- private final Set<Entity> containers = Collections.synchronizedSet(new HashSet<Entity>());
- private final Set<Entity> items = Collections.synchronizedSet(new HashSet<Entity>());
-
- private final SensorEventListener<Object> eventHandler = new SensorEventListener<Object>() {
- @Override
- public void onEvent(SensorEvent<Object> event) {
- if (LOG.isTraceEnabled()) LOG.trace("{} received event {}", BalanceableWorkerPoolImpl.this, event);
- Entity source = event.getSource();
- Object value = event.getValue();
- Sensor<?> sensor = event.getSensor();
-
- if (sensor.equals(AbstractGroup.MEMBER_ADDED)) {
- if (source.equals(containerGroup)) {
- onContainerAdded((BalanceableContainer<?>) value);
- } else if (source.equals(itemGroup)) {
- onItemAdded((Entity)value);
- } else {
- throw new IllegalStateException("unexpected event source="+source);
- }
- } else if (sensor.equals(AbstractGroup.MEMBER_REMOVED)) {
- if (source.equals(containerGroup)) {
- onContainerRemoved((BalanceableContainer<?>) value);
- } else if (source.equals(itemGroup)) {
- onItemRemoved((Entity) value);
- } else {
- throw new IllegalStateException("unexpected event source="+source);
- }
- } else if (sensor.equals(Startable.SERVICE_UP)) {
- // TODO What if start has failed? Is there a sensor to indicate that?
- if ((Boolean)value) {
- onContainerUp((BalanceableContainer<?>) source);
- } else {
- onContainerDown((BalanceableContainer<?>) source);
- }
- } else if (sensor.equals(Movable.CONTAINER)) {
- onItemMoved(source, (BalanceableContainer<?>) value);
- } else {
- throw new IllegalStateException("Unhandled event type "+sensor+": "+event);
- }
- }
- };
-
- public BalanceableWorkerPoolImpl() {
- }
-
- @Override
- public void setResizable(Resizable resizable) {
- this.resizable = resizable;
- }
-
- @Override
- public void setContents(Group containerGroup, Group itemGroup) {
- this.containerGroup = containerGroup;
- this.itemGroup = itemGroup;
- if (resizable == null && containerGroup instanceof Resizable) resizable = (Resizable) containerGroup;
-
- subscribe(containerGroup, AbstractGroup.MEMBER_ADDED, eventHandler);
- subscribe(containerGroup, AbstractGroup.MEMBER_REMOVED, eventHandler);
- subscribe(itemGroup, AbstractGroup.MEMBER_ADDED, eventHandler);
- subscribe(itemGroup, AbstractGroup.MEMBER_REMOVED, eventHandler);
-
- // Process extant containers and items
- for (Entity existingContainer : containerGroup.getMembers()) {
- onContainerAdded((BalanceableContainer<?>)existingContainer);
- }
- for (Entity existingItem : itemGroup.getMembers()) {
- onItemAdded(existingItem);
- }
- }
-
- @Override
- public Group getContainerGroup() {
- return containerGroup;
- }
-
- @Override
- public Group getItemGroup() {
- return itemGroup;
- }
-
- @Override
- public Integer getCurrentSize() {
- return containerGroup.getCurrentSize();
- }
-
- @Override
- public Integer resize(Integer desiredSize) {
- if (resizable != null) return resizable.resize(desiredSize);
-
- throw new UnsupportedOperationException("Container group is not resizable, and no resizable supplied: "+containerGroup+" of type "+(containerGroup != null ? containerGroup.getClass().getCanonicalName() : null));
- }
-
- private void onContainerAdded(BalanceableContainer<?> newContainer) {
- subscribe(newContainer, Startable.SERVICE_UP, eventHandler);
- if (!(newContainer instanceof Startable) || Boolean.TRUE.equals(newContainer.getAttribute(Startable.SERVICE_UP))) {
- onContainerUp(newContainer);
- }
- }
-
- private void onContainerUp(BalanceableContainer<?> newContainer) {
- if (containers.add(newContainer)) {
- emit(CONTAINER_ADDED, newContainer);
- }
- }
-
- private void onContainerDown(BalanceableContainer<?> oldContainer) {
- if (containers.remove(oldContainer)) {
- emit(CONTAINER_REMOVED, oldContainer);
- }
- }
-
- private void onContainerRemoved(BalanceableContainer<?> oldContainer) {
- unsubscribe(oldContainer);
- onContainerDown(oldContainer);
- }
-
- private void onItemAdded(Entity item) {
- if (items.add(item)) {
- subscribe(item, Movable.CONTAINER, eventHandler);
- emit(ITEM_ADDED, new ContainerItemPair(item.getAttribute(Movable.CONTAINER), item));
- }
- }
-
- private void onItemRemoved(Entity item) {
- if (items.remove(item)) {
- unsubscribe(item);
- emit(ITEM_REMOVED, new ContainerItemPair(null, item));
- }
- }
-
- private void onItemMoved(Entity item, BalanceableContainer<?> container) {
- emit(ITEM_MOVED, new ContainerItemPair(container, item));
- }
-}
[23/24] incubator-brooklyn git commit: This closes #831
Posted by he...@apache.org.
This closes #831
Project: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/commit/bf2cfcdc
Tree: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/tree/bf2cfcdc
Diff: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/diff/bf2cfcdc
Branch: refs/heads/master
Commit: bf2cfcdcc5dfb7efdbaa5e70df2ebd597ad9ac09
Parents: 3a13452 ee1a20a
Author: Alex Heneveld <al...@cloudsoftcorp.com>
Authored: Tue Aug 18 12:03:59 2015 +0100
Committer: Alex Heneveld <al...@cloudsoftcorp.com>
Committed: Tue Aug 18 12:03:59 2015 +0100
----------------------------------------------------------------------
.../location/access/BrooklynAccessUtils.java | 52 ++++---
.../access/BrooklynAccessUtilsTest.java | 138 +++++++++++++++++++
2 files changed, 170 insertions(+), 20 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/bf2cfcdc/core/src/main/java/org/apache/brooklyn/location/access/BrooklynAccessUtils.java
----------------------------------------------------------------------
[22/24] incubator-brooklyn git commit: This closes #808
Posted by he...@apache.org.
This closes #808
Project: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/commit/3a13452e
Tree: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/tree/3a13452e
Diff: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/diff/3a13452e
Branch: refs/heads/master
Commit: 3a13452e4067f3fe82721d686278f61b74ad9c42
Parents: 42d9871 d30ff59
Author: Alex Heneveld <al...@cloudsoftcorp.com>
Authored: Tue Aug 18 12:02:46 2015 +0100
Committer: Alex Heneveld <al...@cloudsoftcorp.com>
Committed: Tue Aug 18 12:02:46 2015 +0100
----------------------------------------------------------------------
.../yaml/example_yaml/appserver-w-policy.yaml | 2 +-
.../vanilla-bash-netcat-restarter.yaml | 4 +-
.../vanilla-bash-netcat-w-client.yaml | 4 +-
...followthesun.DefaultFollowTheSunModel$1.html | 6 +-
.../brooklyn/demo/CumulusRDFApplication.java | 6 +-
.../demo/HighAvailabilityCassandraCluster.java | 6 +-
.../brooklyn/demo/ResilientMongoDbApp.java | 6 +-
.../brooklyn/demo/RiakClusterExample.java | 4 +-
.../brooklyn/demo/WideAreaCassandraCluster.java | 6 +-
.../brooklyn/demo/ha-cassandra-cluster.yaml | 6 +-
.../demo/wide-area-cassandra-cluster.yaml | 6 +-
.../demo/WebClusterDatabaseExample.java | 2 +-
.../demo/WebClusterDatabaseExampleApp.java | 3 +-
.../demo/WebClusterDatabaseExampleGroovy.groovy | 5 +-
.../apache/brooklyn/demo/WebClusterExample.java | 2 +-
...lusterDatabaseExampleAppIntegrationTest.java | 2 +-
.../policy/autoscaling/AutoScalerPolicy.java | 1090 -----------------
.../autoscaling/MaxPoolSizeReachedEvent.java | 103 --
.../policy/autoscaling/ResizeOperator.java | 31 -
.../policy/autoscaling/SizeHistory.java | 166 ---
.../followthesun/DefaultFollowTheSunModel.java | 328 ------
.../policy/followthesun/FollowTheSunModel.java | 56 -
.../followthesun/FollowTheSunParameters.java | 95 --
.../policy/followthesun/FollowTheSunPolicy.java | 280 -----
.../policy/followthesun/FollowTheSunPool.java | 75 --
.../followthesun/FollowTheSunPoolImpl.java | 178 ---
.../followthesun/FollowTheSunStrategy.java | 161 ---
.../policy/followthesun/WeightedObject.java | 71 --
.../policy/ha/AbstractFailureDetector.java | 361 ------
.../policy/ha/ConditionalSuspendPolicy.java | 103 --
.../policy/ha/ConnectionFailureDetector.java | 126 --
.../main/java/brooklyn/policy/ha/HASensors.java | 62 -
.../policy/ha/ServiceFailureDetector.java | 340 ------
.../brooklyn/policy/ha/ServiceReplacer.java | 214 ----
.../brooklyn/policy/ha/ServiceRestarter.java | 163 ---
.../policy/ha/SshMachineFailureDetector.java | 102 --
.../loadbalancing/BalanceableContainer.java | 51 -
.../loadbalancing/BalanceablePoolModel.java | 64 -
.../loadbalancing/BalanceableWorkerPool.java | 84 --
.../BalanceableWorkerPoolImpl.java | 185 ---
.../policy/loadbalancing/BalancingStrategy.java | 622 ----------
.../DefaultBalanceablePoolModel.java | 280 -----
.../loadbalancing/ItemsInContainersGroup.java | 52 -
.../ItemsInContainersGroupImpl.java | 148 ---
.../loadbalancing/LoadBalancingPolicy.java | 343 ------
.../loadbalancing/LocationConstraint.java | 28 -
.../brooklyn/policy/loadbalancing/Movable.java | 51 -
.../policy/loadbalancing/PolicyUtilForPool.java | 96 --
.../policy/autoscaling/AutoScalerPolicy.java | 1092 ++++++++++++++++++
.../autoscaling/MaxPoolSizeReachedEvent.java | 103 ++
.../policy/autoscaling/ResizeOperator.java | 31 +
.../policy/autoscaling/SizeHistory.java | 166 +++
.../followthesun/DefaultFollowTheSunModel.java | 328 ++++++
.../policy/followthesun/FollowTheSunModel.java | 56 +
.../followthesun/FollowTheSunParameters.java | 95 ++
.../policy/followthesun/FollowTheSunPolicy.java | 282 +++++
.../policy/followthesun/FollowTheSunPool.java | 75 ++
.../followthesun/FollowTheSunPoolImpl.java | 178 +++
.../followthesun/FollowTheSunStrategy.java | 161 +++
.../policy/followthesun/WeightedObject.java | 71 ++
.../policy/ha/AbstractFailureDetector.java | 363 ++++++
.../policy/ha/ConditionalSuspendPolicy.java | 103 ++
.../policy/ha/ConnectionFailureDetector.java | 128 ++
.../apache/brooklyn/policy/ha/HASensors.java | 62 +
.../policy/ha/ServiceFailureDetector.java | 340 ++++++
.../brooklyn/policy/ha/ServiceReplacer.java | 216 ++++
.../brooklyn/policy/ha/ServiceRestarter.java | 165 +++
.../policy/ha/SshMachineFailureDetector.java | 103 ++
.../loadbalancing/BalanceableContainer.java | 51 +
.../loadbalancing/BalanceablePoolModel.java | 64 +
.../loadbalancing/BalanceableWorkerPool.java | 84 ++
.../BalanceableWorkerPoolImpl.java | 185 +++
.../policy/loadbalancing/BalancingStrategy.java | 622 ++++++++++
.../DefaultBalanceablePoolModel.java | 280 +++++
.../loadbalancing/ItemsInContainersGroup.java | 52 +
.../ItemsInContainersGroupImpl.java | 148 +++
.../loadbalancing/LoadBalancingPolicy.java | 344 ++++++
.../loadbalancing/LocationConstraint.java | 28 +
.../brooklyn/policy/loadbalancing/Movable.java | 51 +
.../policy/loadbalancing/PolicyUtilForPool.java | 96 ++
.../autoscaling/AutoScalerPolicyMetricTest.java | 274 -----
.../autoscaling/AutoScalerPolicyRebindTest.java | 137 ---
.../AutoScalerPolicyReconfigurationTest.java | 190 ---
.../autoscaling/AutoScalerPolicyTest.java | 649 -----------
.../autoscaling/LocallyResizableEntity.java | 73 --
.../AbstractFollowTheSunPolicyTest.java | 239 ----
.../followthesun/FollowTheSunModelTest.java | 195 ----
.../FollowTheSunPolicySoakTest.java | 274 -----
.../followthesun/FollowTheSunPolicyTest.java | 307 -----
.../ha/ConnectionFailureDetectorTest.java | 303 -----
.../brooklyn/policy/ha/HaPolicyRebindTest.java | 173 ---
...ServiceFailureDetectorStabilizationTest.java | 234 ----
.../policy/ha/ServiceFailureDetectorTest.java | 408 -------
.../brooklyn/policy/ha/ServiceReplacerTest.java | 340 ------
.../policy/ha/ServiceRestarterTest.java | 190 ---
.../AbstractLoadBalancingPolicyTest.java | 253 ----
.../BalanceableWorkerPoolTest.java | 133 ---
.../ItemsInContainersGroupTest.java | 189 ---
.../loadbalancing/LoadBalancingModelTest.java | 115 --
.../LoadBalancingPolicyConcurrencyTest.java | 211 ----
.../LoadBalancingPolicySoakTest.java | 273 -----
.../loadbalancing/LoadBalancingPolicyTest.java | 397 -------
.../loadbalancing/MockContainerEntity.java | 61 -
.../loadbalancing/MockContainerEntityImpl.java | 208 ----
.../policy/loadbalancing/MockItemEntity.java | 46 -
.../loadbalancing/MockItemEntityImpl.java | 113 --
.../autoscaling/AutoScalerPolicyMetricTest.java | 274 +++++
.../autoscaling/AutoScalerPolicyRebindTest.java | 137 +++
.../AutoScalerPolicyReconfigurationTest.java | 190 +++
.../autoscaling/AutoScalerPolicyTest.java | 649 +++++++++++
.../autoscaling/LocallyResizableEntity.java | 73 ++
.../AbstractFollowTheSunPolicyTest.java | 239 ++++
.../followthesun/FollowTheSunModelTest.java | 195 ++++
.../FollowTheSunPolicySoakTest.java | 274 +++++
.../followthesun/FollowTheSunPolicyTest.java | 307 +++++
.../ha/ConnectionFailureDetectorTest.java | 303 +++++
.../brooklyn/policy/ha/HaPolicyRebindTest.java | 173 +++
...ServiceFailureDetectorStabilizationTest.java | 234 ++++
.../policy/ha/ServiceFailureDetectorTest.java | 407 +++++++
.../brooklyn/policy/ha/ServiceReplacerTest.java | 340 ++++++
.../policy/ha/ServiceRestarterTest.java | 190 +++
.../AbstractLoadBalancingPolicyTest.java | 253 ++++
.../BalanceableWorkerPoolTest.java | 133 +++
.../ItemsInContainersGroupTest.java | 189 +++
.../loadbalancing/LoadBalancingModelTest.java | 113 ++
.../LoadBalancingPolicyConcurrencyTest.java | 211 ++++
.../LoadBalancingPolicySoakTest.java | 273 +++++
.../loadbalancing/LoadBalancingPolicyTest.java | 397 +++++++
.../loadbalancing/MockContainerEntity.java | 61 +
.../loadbalancing/MockContainerEntityImpl.java | 208 ++++
.../policy/loadbalancing/MockItemEntity.java | 46 +
.../loadbalancing/MockItemEntityImpl.java | 113 ++
.../webapp/TomcatAutoScalerPolicyTest.java | 3 +-
.../app/ClusterWebServerDatabaseSample.java | 2 +-
.../BrooklynYamlTypeInstantiatorTest.java | 2 +-
.../brooklyn/JavaWebAppsIntegrationTest.java | 4 +-
.../brooklyn/catalog/CatalogYamlCombiTest.java | 2 +-
.../java-web-app-and-db-with-policy.yaml | 2 +-
.../resources/vanilla-bash-netcat-w-client.yaml | 4 +-
.../main/resources/brooklyn/default.catalog.bom | 10 +-
.../src/test/resources/couchbase-w-loadgen.yaml | 2 +-
.../brooklyn/qa/load/SimulatedTheeTierApp.java | 2 +-
.../qa/longevity/webcluster/WebClusterApp.java | 2 +-
.../brooklyn/rest/domain/ConfigSummary.java | 2 +-
.../rest/resources/CatalogResourceTest.java | 2 +-
145 files changed, 12162 insertions(+), 12146 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/3a13452e/docs/guide/yaml/example_yaml/appserver-w-policy.yaml
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/3a13452e/examples/simple-web-cluster/src/main/java/org/apache/brooklyn/demo/WebClusterDatabaseExample.java
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/3a13452e/examples/simple-web-cluster/src/main/java/org/apache/brooklyn/demo/WebClusterDatabaseExampleApp.java
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/3a13452e/examples/simple-web-cluster/src/test/java/org/apache/brooklyn/demo/RebindWebClusterDatabaseExampleAppIntegrationTest.java
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/3a13452e/usage/archetypes/quickstart/src/brooklyn-sample/src/main/java/com/acme/sample/brooklyn/sample/app/ClusterWebServerDatabaseSample.java
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/3a13452e/usage/camp/src/test/resources/java-web-app-and-db-with-policy.yaml
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/3a13452e/usage/qa/src/main/java/org/apache/brooklyn/qa/load/SimulatedTheeTierApp.java
----------------------------------------------------------------------
[16/24] incubator-brooklyn git commit: [BROOKLYN-162] Renaming
package policy
Posted by he...@apache.org.
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/d30ff597/policy/src/main/java/org/apache/brooklyn/policy/autoscaling/AutoScalerPolicy.java
----------------------------------------------------------------------
diff --git a/policy/src/main/java/org/apache/brooklyn/policy/autoscaling/AutoScalerPolicy.java b/policy/src/main/java/org/apache/brooklyn/policy/autoscaling/AutoScalerPolicy.java
new file mode 100644
index 0000000..98d814f
--- /dev/null
+++ b/policy/src/main/java/org/apache/brooklyn/policy/autoscaling/AutoScalerPolicy.java
@@ -0,0 +1,1092 @@
+/*
+ * 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.policy.autoscaling;
+
+import static brooklyn.util.JavaGroovyEquivalents.groovyTruth;
+import static com.google.common.base.Preconditions.checkNotNull;
+import groovy.lang.Closure;
+
+import java.util.Map;
+import java.util.concurrent.Callable;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.ThreadFactory;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.apache.brooklyn.api.catalog.Catalog;
+import org.apache.brooklyn.api.entity.Entity;
+import org.apache.brooklyn.api.entity.basic.EntityLocal;
+import org.apache.brooklyn.api.event.AttributeSensor;
+import org.apache.brooklyn.api.event.Sensor;
+import org.apache.brooklyn.api.event.SensorEvent;
+import org.apache.brooklyn.api.event.SensorEventListener;
+import org.apache.brooklyn.api.policy.PolicySpec;
+import org.apache.brooklyn.core.policy.basic.AbstractPolicy;
+import org.apache.brooklyn.core.util.flags.SetFromFlag;
+import org.apache.brooklyn.core.util.flags.TypeCoercions;
+import org.apache.brooklyn.core.util.task.Tasks;
+
+import brooklyn.config.ConfigKey;
+import brooklyn.entity.basic.BrooklynTaskTags;
+import brooklyn.entity.basic.Entities;
+import brooklyn.entity.trait.Resizable;
+import brooklyn.entity.trait.Startable;
+import brooklyn.event.basic.BasicConfigKey;
+import brooklyn.event.basic.BasicNotificationSensor;
+
+import org.apache.brooklyn.policy.autoscaling.SizeHistory.WindowSummary;
+import org.apache.brooklyn.policy.loadbalancing.LoadBalancingPolicy;
+
+import brooklyn.util.collections.MutableMap;
+import brooklyn.util.time.Duration;
+
+import com.google.common.base.Function;
+import com.google.common.base.Preconditions;
+import com.google.common.base.Throwables;
+import com.google.common.reflect.TypeToken;
+import com.google.common.util.concurrent.ThreadFactoryBuilder;
+
+
+/**
+ * Policy that is attached to a {@link Resizable} entity and dynamically adjusts its size in response to
+ * emitted {@code POOL_COLD} and {@code POOL_HOT} events. Alternatively, the policy can be configured to
+ * keep a given metric within a required range.
+ * <p>
+ * TThis policy does not itself determine whether the pool is hot or cold, but instead relies on these
+ * events being emitted by the monitored entity itself, or by another policy that is attached to it; see,
+ * for example, {@link LoadBalancingPolicy}.)
+ */
+@SuppressWarnings({"rawtypes", "unchecked"})
+@Catalog(name="Auto-scaler", description="Policy that is attached to a Resizable entity and dynamically "
+ + "adjusts its size in response to either keep a metric within a given range, or in response to "
+ + "POOL_COLD and POOL_HOT events")
+public class AutoScalerPolicy extends AbstractPolicy {
+
+ private static final Logger LOG = LoggerFactory.getLogger(AutoScalerPolicy.class);
+
+ public static Builder builder() {
+ return new Builder();
+ }
+
+ public static class Builder {
+ private String id;
+ private String name;
+ private AttributeSensor<? extends Number> metric;
+ private Entity entityWithMetric;
+ private Number metricUpperBound;
+ private Number metricLowerBound;
+ private int minPoolSize = 1;
+ private int maxPoolSize = Integer.MAX_VALUE;
+ private Integer resizeDownIterationIncrement;
+ private Integer resizeDownIterationMax;
+ private Integer resizeUpIterationIncrement;
+ private Integer resizeUpIterationMax;
+ private Duration minPeriodBetweenExecs;
+ private Duration resizeUpStabilizationDelay;
+ private Duration resizeDownStabilizationDelay;
+ private ResizeOperator resizeOperator;
+ private Function<Entity,Integer> currentSizeOperator;
+ private BasicNotificationSensor<?> poolHotSensor;
+ private BasicNotificationSensor<?> poolColdSensor;
+ private BasicNotificationSensor<?> poolOkSensor;
+ private BasicNotificationSensor<? super MaxPoolSizeReachedEvent> maxSizeReachedSensor;
+ private Duration maxReachedNotificationDelay;
+
+ public Builder id(String val) {
+ this.id = val; return this;
+ }
+ public Builder name(String val) {
+ this.name = val; return this;
+ }
+ public Builder metric(AttributeSensor<? extends Number> val) {
+ this.metric = val; return this;
+ }
+ public Builder entityWithMetric(Entity val) {
+ this.entityWithMetric = val; return this;
+ }
+ public Builder metricLowerBound(Number val) {
+ this.metricLowerBound = val; return this;
+ }
+ public Builder metricUpperBound(Number val) {
+ this.metricUpperBound = val; return this;
+ }
+ public Builder metricRange(Number min, Number max) {
+ metricLowerBound = checkNotNull(min);
+ metricUpperBound = checkNotNull(max);
+ return this;
+ }
+ public Builder minPoolSize(int val) {
+ this.minPoolSize = val; return this;
+ }
+ public Builder maxPoolSize(int val) {
+ this.maxPoolSize = val; return this;
+ }
+ public Builder sizeRange(int min, int max) {
+ minPoolSize = min;
+ maxPoolSize = max;
+ return this;
+ }
+
+ public Builder resizeUpIterationIncrement(Integer val) {
+ this.resizeUpIterationIncrement = val; return this;
+ }
+ public Builder resizeUpIterationMax(Integer val) {
+ this.resizeUpIterationMax = val; return this;
+ }
+ public Builder resizeDownIterationIncrement(Integer val) {
+ this.resizeUpIterationIncrement = val; return this;
+ }
+ public Builder resizeDownIterationMax(Integer val) {
+ this.resizeUpIterationMax = val; return this;
+ }
+
+ public Builder minPeriodBetweenExecs(Duration val) {
+ this.minPeriodBetweenExecs = val; return this;
+ }
+ public Builder resizeUpStabilizationDelay(Duration val) {
+ this.resizeUpStabilizationDelay = val; return this;
+ }
+ public Builder resizeDownStabilizationDelay(Duration val) {
+ this.resizeDownStabilizationDelay = val; return this;
+ }
+ public Builder resizeOperator(ResizeOperator val) {
+ this.resizeOperator = val; return this;
+ }
+ public Builder currentSizeOperator(Function<Entity, Integer> val) {
+ this.currentSizeOperator = val; return this;
+ }
+ public Builder poolHotSensor(BasicNotificationSensor<?> val) {
+ this.poolHotSensor = val; return this;
+ }
+ public Builder poolColdSensor(BasicNotificationSensor<?> val) {
+ this.poolColdSensor = val; return this;
+ }
+ public Builder poolOkSensor(BasicNotificationSensor<?> val) {
+ this.poolOkSensor = val; return this;
+ }
+ public Builder maxSizeReachedSensor(BasicNotificationSensor<? super MaxPoolSizeReachedEvent> val) {
+ this.maxSizeReachedSensor = val; return this;
+ }
+ public Builder maxReachedNotificationDelay(Duration val) {
+ this.maxReachedNotificationDelay = val; return this;
+ }
+ public AutoScalerPolicy build() {
+ return new AutoScalerPolicy(toFlags());
+ }
+ public PolicySpec<AutoScalerPolicy> buildSpec() {
+ return PolicySpec.create(AutoScalerPolicy.class)
+ .configure(toFlags());
+ }
+ private Map<String,?> toFlags() {
+ return MutableMap.<String,Object>builder()
+ .putIfNotNull("id", id)
+ .putIfNotNull("name", name)
+ .putIfNotNull("metric", metric)
+ .putIfNotNull("entityWithMetric", entityWithMetric)
+ .putIfNotNull("metricUpperBound", metricUpperBound)
+ .putIfNotNull("metricLowerBound", metricLowerBound)
+ .putIfNotNull("minPoolSize", minPoolSize)
+ .putIfNotNull("maxPoolSize", maxPoolSize)
+ .putIfNotNull("resizeUpIterationMax", resizeUpIterationMax)
+ .putIfNotNull("resizeUpIterationIncrement", resizeUpIterationIncrement)
+ .putIfNotNull("resizeDownIterationMax", resizeDownIterationMax)
+ .putIfNotNull("resizeDownIterationIncrement", resizeDownIterationIncrement)
+ .putIfNotNull("minPeriodBetweenExecs", minPeriodBetweenExecs)
+ .putIfNotNull("resizeUpStabilizationDelay", resizeUpStabilizationDelay)
+ .putIfNotNull("resizeDownStabilizationDelay", resizeDownStabilizationDelay)
+ .putIfNotNull("resizeOperator", resizeOperator)
+ .putIfNotNull("currentSizeOperator", currentSizeOperator)
+ .putIfNotNull("poolHotSensor", poolHotSensor)
+ .putIfNotNull("poolColdSensor", poolColdSensor)
+ .putIfNotNull("poolOkSensor", poolOkSensor)
+ .putIfNotNull("maxSizeReachedSensor", maxSizeReachedSensor)
+ .putIfNotNull("maxReachedNotificationDelay", maxReachedNotificationDelay)
+ .build();
+ }
+ }
+
+ // TODO Is there a nicer pattern for registering such type-coercions?
+ // Can't put it in the ResizeOperator interface, nor in core TypeCoercions class because interface is defined in policy/.
+ static {
+ TypeCoercions.registerAdapter(Closure.class, ResizeOperator.class, new Function<Closure,ResizeOperator>() {
+ @Override
+ public ResizeOperator apply(final Closure closure) {
+ return new ResizeOperator() {
+ @Override public Integer resize(Entity entity, Integer input) {
+ return (Integer) closure.call(entity, input);
+ }
+ };
+ }
+ });
+ }
+
+ // Pool workrate notifications.
+ public static BasicNotificationSensor<Map> DEFAULT_POOL_HOT_SENSOR = new BasicNotificationSensor<Map>(
+ Map.class, "resizablepool.hot", "Pool is over-utilized; it has insufficient resource for current workload");
+ public static BasicNotificationSensor<Map> DEFAULT_POOL_COLD_SENSOR = new BasicNotificationSensor<Map>(
+ Map.class, "resizablepool.cold", "Pool is under-utilized; it has too much resource for current workload");
+ public static BasicNotificationSensor<Map> DEFAULT_POOL_OK_SENSOR = new BasicNotificationSensor<Map>(
+ Map.class, "resizablepool.cold", "Pool utilization is ok; the available resources are fine for the current workload");
+
+ /**
+ * A convenience for policies that want to register a {@code builder.maxSizeReachedSensor(sensor)}.
+ * Note that this "default" is not set automatically; the default is for no sensor to be used (so
+ * no events emitted).
+ */
+ public static BasicNotificationSensor<MaxPoolSizeReachedEvent> DEFAULT_MAX_SIZE_REACHED_SENSOR = new BasicNotificationSensor<MaxPoolSizeReachedEvent>(
+ MaxPoolSizeReachedEvent.class, "resizablepool.maxSizeReached", "Consistently wanted to resize the pool above the max allowed size");
+
+ public static final String POOL_CURRENT_SIZE_KEY = "pool.current.size";
+ public static final String POOL_HIGH_THRESHOLD_KEY = "pool.high.threshold";
+ public static final String POOL_LOW_THRESHOLD_KEY = "pool.low.threshold";
+ public static final String POOL_CURRENT_WORKRATE_KEY = "pool.current.workrate";
+
+ @SuppressWarnings("serial")
+ @SetFromFlag("metric")
+ public static final ConfigKey<AttributeSensor<? extends Number>> METRIC = BasicConfigKey.builder(new TypeToken<AttributeSensor<? extends Number>>() {})
+ .name("autoscaler.metric")
+ .build();
+
+ @SetFromFlag("entityWithMetric")
+ public static final ConfigKey<Entity> ENTITY_WITH_METRIC = BasicConfigKey.builder(Entity.class)
+ .name("autoscaler.entityWithMetric")
+ .build();
+
+ @SetFromFlag("metricLowerBound")
+ public static final ConfigKey<Number> METRIC_LOWER_BOUND = BasicConfigKey.builder(Number.class)
+ .name("autoscaler.metricLowerBound")
+ .reconfigurable(true)
+ .build();
+
+ @SetFromFlag("metricUpperBound")
+ public static final ConfigKey<Number> METRIC_UPPER_BOUND = BasicConfigKey.builder(Number.class)
+ .name("autoscaler.metricUpperBound")
+ .reconfigurable(true)
+ .build();
+
+ @SetFromFlag("resizeUpIterationIncrement")
+ public static final ConfigKey<Integer> RESIZE_UP_ITERATION_INCREMENT = BasicConfigKey.builder(Integer.class)
+ .name("autoscaler.resizeUpIterationIncrement")
+ .description("Batch size for resizing up; the size will be increased by a multiple of this value")
+ .defaultValue(1)
+ .reconfigurable(true)
+ .build();
+ @SetFromFlag("resizeUpIterationMax")
+ public static final ConfigKey<Integer> RESIZE_UP_ITERATION_MAX = BasicConfigKey.builder(Integer.class)
+ .name("autoscaler.resizeUpIterationMax")
+ .defaultValue(Integer.MAX_VALUE)
+ .description("Maximum change to the size on a single iteration when scaling up")
+ .reconfigurable(true)
+ .build();
+ @SetFromFlag("resizeDownIterationIncrement")
+ public static final ConfigKey<Integer> RESIZE_DOWN_ITERATION_INCREMENT = BasicConfigKey.builder(Integer.class)
+ .name("autoscaler.resizeDownIterationIncrement")
+ .description("Batch size for resizing down; the size will be decreased by a multiple of this value")
+ .defaultValue(1)
+ .reconfigurable(true)
+ .build();
+ @SetFromFlag("resizeDownIterationMax")
+ public static final ConfigKey<Integer> RESIZE_DOWN_ITERATION_MAX = BasicConfigKey.builder(Integer.class)
+ .name("autoscaler.resizeDownIterationMax")
+ .defaultValue(Integer.MAX_VALUE)
+ .description("Maximum change to the size on a single iteration when scaling down")
+ .reconfigurable(true)
+ .build();
+
+ @SetFromFlag("minPeriodBetweenExecs")
+ public static final ConfigKey<Duration> MIN_PERIOD_BETWEEN_EXECS = BasicConfigKey.builder(Duration.class)
+ .name("autoscaler.minPeriodBetweenExecs")
+ .defaultValue(Duration.millis(100))
+ .build();
+
+ @SetFromFlag("resizeUpStabilizationDelay")
+ public static final ConfigKey<Duration> RESIZE_UP_STABILIZATION_DELAY = BasicConfigKey.builder(Duration.class)
+ .name("autoscaler.resizeUpStabilizationDelay")
+ .defaultValue(Duration.ZERO)
+ .reconfigurable(true)
+ .build();
+
+ @SetFromFlag("resizeDownStabilizationDelay")
+ public static final ConfigKey<Duration> RESIZE_DOWN_STABILIZATION_DELAY = BasicConfigKey.builder(Duration.class)
+ .name("autoscaler.resizeDownStabilizationDelay")
+ .defaultValue(Duration.ZERO)
+ .reconfigurable(true)
+ .build();
+
+ @SetFromFlag("minPoolSize")
+ public static final ConfigKey<Integer> MIN_POOL_SIZE = BasicConfigKey.builder(Integer.class)
+ .name("autoscaler.minPoolSize")
+ .defaultValue(1)
+ .reconfigurable(true)
+ .build();
+
+ @SetFromFlag("maxPoolSize")
+ public static final ConfigKey<Integer> MAX_POOL_SIZE = BasicConfigKey.builder(Integer.class)
+ .name("autoscaler.maxPoolSize")
+ .defaultValue(Integer.MAX_VALUE)
+ .reconfigurable(true)
+ .build();
+
+ @SetFromFlag("resizeOperator")
+ public static final ConfigKey<ResizeOperator> RESIZE_OPERATOR = BasicConfigKey.builder(ResizeOperator.class)
+ .name("autoscaler.resizeOperator")
+ .defaultValue(new ResizeOperator() {
+ public Integer resize(Entity entity, Integer desiredSize) {
+ return ((Resizable)entity).resize(desiredSize);
+ }})
+ .build();
+
+ @SuppressWarnings("serial")
+ @SetFromFlag("currentSizeOperator")
+ public static final ConfigKey<Function<Entity,Integer>> CURRENT_SIZE_OPERATOR = BasicConfigKey.builder(new TypeToken<Function<Entity,Integer>>() {})
+ .name("autoscaler.currentSizeOperator")
+ .defaultValue(new Function<Entity,Integer>() {
+ public Integer apply(Entity entity) {
+ return ((Resizable)entity).getCurrentSize();
+ }})
+ .build();
+
+ @SuppressWarnings("serial")
+ @SetFromFlag("poolHotSensor")
+ public static final ConfigKey<BasicNotificationSensor<? extends Map>> POOL_HOT_SENSOR = BasicConfigKey.builder(new TypeToken<BasicNotificationSensor<? extends Map>>() {})
+ .name("autoscaler.poolHotSensor")
+ .defaultValue(DEFAULT_POOL_HOT_SENSOR)
+ .build();
+
+ @SuppressWarnings("serial")
+ @SetFromFlag("poolColdSensor")
+ public static final ConfigKey<BasicNotificationSensor<? extends Map>> POOL_COLD_SENSOR = BasicConfigKey.builder(new TypeToken<BasicNotificationSensor<? extends Map>>() {})
+ .name("autoscaler.poolColdSensor")
+ .defaultValue(DEFAULT_POOL_COLD_SENSOR)
+ .build();
+
+ @SuppressWarnings("serial")
+ @SetFromFlag("poolOkSensor")
+ public static final ConfigKey<BasicNotificationSensor<? extends Map>> POOL_OK_SENSOR = BasicConfigKey.builder(new TypeToken<BasicNotificationSensor<? extends Map>>() {})
+ .name("autoscaler.poolOkSensor")
+ .defaultValue(DEFAULT_POOL_OK_SENSOR)
+ .build();
+
+ @SuppressWarnings("serial")
+ @SetFromFlag("maxSizeReachedSensor")
+ public static final ConfigKey<BasicNotificationSensor<? super MaxPoolSizeReachedEvent>> MAX_SIZE_REACHED_SENSOR = BasicConfigKey.builder(new TypeToken<BasicNotificationSensor<? super MaxPoolSizeReachedEvent>>() {})
+ .name("autoscaler.maxSizeReachedSensor")
+ .description("Sensor for which a notification will be emitted (on the associated entity) when " +
+ "we consistently wanted to resize the pool above the max allowed size, for " +
+ "maxReachedNotificationDelay milliseconds")
+ .build();
+
+ @SetFromFlag("maxReachedNotificationDelay")
+ public static final ConfigKey<Duration> MAX_REACHED_NOTIFICATION_DELAY = BasicConfigKey.builder(Duration.class)
+ .name("autoscaler.maxReachedNotificationDelay")
+ .description("Time that we consistently wanted to go above the maxPoolSize for, after which the " +
+ "maxSizeReachedSensor (if any) will be emitted")
+ .defaultValue(Duration.ZERO)
+ .build();
+
+ private Entity poolEntity;
+
+ private final AtomicBoolean executorQueued = new AtomicBoolean(false);
+ private volatile long executorTime = 0;
+ private volatile ScheduledExecutorService executor;
+
+ private SizeHistory recentUnboundedResizes;
+
+ private SizeHistory recentDesiredResizes;
+
+ private long maxReachedLastNotifiedTime;
+
+ private final SensorEventListener<Map> utilizationEventHandler = new SensorEventListener<Map>() {
+ public void onEvent(SensorEvent<Map> event) {
+ Map<String, ?> properties = (Map<String, ?>) event.getValue();
+ Sensor<?> sensor = event.getSensor();
+
+ if (sensor.equals(getPoolColdSensor())) {
+ onPoolCold(properties);
+ } else if (sensor.equals(getPoolHotSensor())) {
+ onPoolHot(properties);
+ } else if (sensor.equals(getPoolOkSensor())) {
+ onPoolOk(properties);
+ } else {
+ throw new IllegalStateException("Unexpected sensor type: "+sensor+"; event="+event);
+ }
+ }
+ };
+
+ private final SensorEventListener<Number> metricEventHandler = new SensorEventListener<Number>() {
+ public void onEvent(SensorEvent<Number> event) {
+ assert event.getSensor().equals(getMetric());
+ onMetricChanged(event.getValue());
+ }
+ };
+
+ public AutoScalerPolicy() {
+ this(MutableMap.<String,Object>of());
+ }
+
+ public AutoScalerPolicy(Map<String,?> props) {
+ super(props);
+ }
+
+ @Override
+ public void init() {
+ doInit();
+ }
+
+ @Override
+ public void rebind() {
+ doInit();
+ }
+
+ protected void doInit() {
+ long maxReachedNotificationDelay = getMaxReachedNotificationDelay().toMilliseconds();
+ recentUnboundedResizes = new SizeHistory(maxReachedNotificationDelay);
+
+ long maxResizeStabilizationDelay = Math.max(getResizeUpStabilizationDelay().toMilliseconds(), getResizeDownStabilizationDelay().toMilliseconds());
+ recentDesiredResizes = new SizeHistory(maxResizeStabilizationDelay);
+
+ // TODO Should re-use the execution manager's thread pool, somehow
+ executor = Executors.newSingleThreadScheduledExecutor(newThreadFactory());
+ }
+
+ public void setMetricLowerBound(Number val) {
+ if (LOG.isInfoEnabled()) LOG.info("{} changing metricLowerBound from {} to {}", new Object[] {this, getMetricLowerBound(), val});
+ config().set(METRIC_LOWER_BOUND, checkNotNull(val));
+ }
+
+ public void setMetricUpperBound(Number val) {
+ if (LOG.isInfoEnabled()) LOG.info("{} changing metricUpperBound from {} to {}", new Object[] {this, getMetricUpperBound(), val});
+ config().set(METRIC_UPPER_BOUND, checkNotNull(val));
+ }
+
+ private <T> void setOrDefault(ConfigKey<T> key, T val) {
+ if (val==null) val = key.getDefaultValue();
+ config().set(key, val);
+ }
+ public int getResizeUpIterationIncrement() { return getConfig(RESIZE_UP_ITERATION_INCREMENT); }
+ public void setResizeUpIterationIncrement(Integer val) {
+ if (LOG.isInfoEnabled()) LOG.info("{} changing resizeUpIterationIncrement from {} to {}", new Object[] {this, getResizeUpIterationIncrement(), val});
+ setOrDefault(RESIZE_UP_ITERATION_INCREMENT, val);
+ }
+ public int getResizeDownIterationIncrement() { return getConfig(RESIZE_DOWN_ITERATION_INCREMENT); }
+ public void setResizeDownIterationIncrement(Integer val) {
+ if (LOG.isInfoEnabled()) LOG.info("{} changing resizeDownIterationIncrement from {} to {}", new Object[] {this, getResizeDownIterationIncrement(), val});
+ setOrDefault(RESIZE_DOWN_ITERATION_INCREMENT, val);
+ }
+ public int getResizeUpIterationMax() { return getConfig(RESIZE_UP_ITERATION_MAX); }
+ public void setResizeUpIterationMax(Integer val) {
+ if (LOG.isInfoEnabled()) LOG.info("{} changing resizeUpIterationMax from {} to {}", new Object[] {this, getResizeUpIterationMax(), val});
+ setOrDefault(RESIZE_UP_ITERATION_MAX, val);
+ }
+ public int getResizeDownIterationMax() { return getConfig(RESIZE_DOWN_ITERATION_MAX); }
+ public void setResizeDownIterationMax(Integer val) {
+ if (LOG.isInfoEnabled()) LOG.info("{} changing resizeDownIterationMax from {} to {}", new Object[] {this, getResizeDownIterationMax(), val});
+ setOrDefault(RESIZE_DOWN_ITERATION_MAX, val);
+ }
+
+ public void setMinPeriodBetweenExecs(Duration val) {
+ if (LOG.isInfoEnabled()) LOG.info("{} changing minPeriodBetweenExecs from {} to {}", new Object[] {this, getMinPeriodBetweenExecs(), val});
+ config().set(MIN_PERIOD_BETWEEN_EXECS, val);
+ }
+
+ public void setResizeUpStabilizationDelay(Duration val) {
+ if (LOG.isInfoEnabled()) LOG.info("{} changing resizeUpStabilizationDelay from {} to {}", new Object[] {this, getResizeUpStabilizationDelay(), val});
+ config().set(RESIZE_UP_STABILIZATION_DELAY, val);
+ }
+
+ public void setResizeDownStabilizationDelay(Duration val) {
+ if (LOG.isInfoEnabled()) LOG.info("{} changing resizeDownStabilizationDelay from {} to {}", new Object[] {this, getResizeDownStabilizationDelay(), val});
+ config().set(RESIZE_DOWN_STABILIZATION_DELAY, val);
+ }
+
+ public void setMinPoolSize(int val) {
+ if (LOG.isInfoEnabled()) LOG.info("{} changing minPoolSize from {} to {}", new Object[] {this, getMinPoolSize(), val});
+ config().set(MIN_POOL_SIZE, val);
+ }
+
+ public void setMaxPoolSize(int val) {
+ if (LOG.isInfoEnabled()) LOG.info("{} changing maxPoolSize from {} to {}", new Object[] {this, getMaxPoolSize(), val});
+ config().set(MAX_POOL_SIZE, val);
+ }
+
+ private AttributeSensor<? extends Number> getMetric() {
+ return getConfig(METRIC);
+ }
+
+ private Entity getEntityWithMetric() {
+ return getConfig(ENTITY_WITH_METRIC);
+ }
+
+ private Number getMetricLowerBound() {
+ return getConfig(METRIC_LOWER_BOUND);
+ }
+
+ private Number getMetricUpperBound() {
+ return getConfig(METRIC_UPPER_BOUND);
+ }
+
+ private Duration getMinPeriodBetweenExecs() {
+ return getConfig(MIN_PERIOD_BETWEEN_EXECS);
+ }
+
+ private Duration getResizeUpStabilizationDelay() {
+ return getConfig(RESIZE_UP_STABILIZATION_DELAY);
+ }
+
+ private Duration getResizeDownStabilizationDelay() {
+ return getConfig(RESIZE_DOWN_STABILIZATION_DELAY);
+ }
+
+ private int getMinPoolSize() {
+ return getConfig(MIN_POOL_SIZE);
+ }
+
+ private int getMaxPoolSize() {
+ return getConfig(MAX_POOL_SIZE);
+ }
+
+ private ResizeOperator getResizeOperator() {
+ return getConfig(RESIZE_OPERATOR);
+ }
+
+ private Function<Entity,Integer> getCurrentSizeOperator() {
+ return getConfig(CURRENT_SIZE_OPERATOR);
+ }
+
+ private BasicNotificationSensor<? extends Map> getPoolHotSensor() {
+ return getConfig(POOL_HOT_SENSOR);
+ }
+
+ private BasicNotificationSensor<? extends Map> getPoolColdSensor() {
+ return getConfig(POOL_COLD_SENSOR);
+ }
+
+ private BasicNotificationSensor<? extends Map> getPoolOkSensor() {
+ return getConfig(POOL_OK_SENSOR);
+ }
+
+ private BasicNotificationSensor<? super MaxPoolSizeReachedEvent> getMaxSizeReachedSensor() {
+ return getConfig(MAX_SIZE_REACHED_SENSOR);
+ }
+
+ private Duration getMaxReachedNotificationDelay() {
+ return getConfig(MAX_REACHED_NOTIFICATION_DELAY);
+ }
+
+ @Override
+ protected <T> void doReconfigureConfig(ConfigKey<T> key, T val) {
+ if (key.equals(RESIZE_UP_STABILIZATION_DELAY)) {
+ Duration maxResizeStabilizationDelay = Duration.max((Duration)val, getResizeDownStabilizationDelay());
+ recentDesiredResizes.setWindowSize(maxResizeStabilizationDelay);
+ } else if (key.equals(RESIZE_DOWN_STABILIZATION_DELAY)) {
+ Duration maxResizeStabilizationDelay = Duration.max((Duration)val, getResizeUpStabilizationDelay());
+ recentDesiredResizes.setWindowSize(maxResizeStabilizationDelay);
+ } else if (key.equals(METRIC_LOWER_BOUND)) {
+ // TODO If recorded what last metric value was then we could recalculate immediately
+ // Rely on next metric-change to trigger recalculation;
+ // and same for those below...
+ } else if (key.equals(METRIC_UPPER_BOUND)) {
+ // see above
+ } else if (key.equals(RESIZE_UP_ITERATION_INCREMENT) || key.equals(RESIZE_UP_ITERATION_MAX) || key.equals(RESIZE_DOWN_ITERATION_INCREMENT) || key.equals(RESIZE_DOWN_ITERATION_MAX)) {
+ // no special actions needed
+ } else if (key.equals(MIN_POOL_SIZE)) {
+ int newMin = (Integer) val;
+ if (newMin > getConfig(MAX_POOL_SIZE)) {
+ throw new IllegalArgumentException("Min pool size "+val+" must not be greater than max pool size "+getConfig(MAX_POOL_SIZE));
+ }
+ onPoolSizeLimitsChanged(newMin, getConfig(MAX_POOL_SIZE));
+ } else if (key.equals(MAX_POOL_SIZE)) {
+ int newMax = (Integer) val;
+ if (newMax < getConfig(MIN_POOL_SIZE)) {
+ throw new IllegalArgumentException("Min pool size "+val+" must not be greater than max pool size "+getConfig(MAX_POOL_SIZE));
+ }
+ onPoolSizeLimitsChanged(getConfig(MIN_POOL_SIZE), newMax);
+ } else {
+ throw new UnsupportedOperationException("reconfiguring "+key+" unsupported for "+this);
+ }
+ }
+
+ @Override
+ public void suspend() {
+ super.suspend();
+ // TODO unsubscribe from everything? And resubscribe on resume?
+ if (executor != null) executor.shutdownNow();
+ }
+
+ @Override
+ public void resume() {
+ super.resume();
+ executor = Executors.newSingleThreadScheduledExecutor(newThreadFactory());
+ }
+
+ @Override
+ public void setEntity(EntityLocal entity) {
+ if (!config().getRaw(RESIZE_OPERATOR).isPresentAndNonNull()) {
+ Preconditions.checkArgument(entity instanceof Resizable, "Provided entity "+entity+" must be an instance of Resizable, because no custom-resizer operator supplied");
+ }
+ super.setEntity(entity);
+ this.poolEntity = entity;
+
+ if (getMetric() != null) {
+ Entity entityToSubscribeTo = (getEntityWithMetric() != null) ? getEntityWithMetric() : entity;
+ subscribe(entityToSubscribeTo, getMetric(), metricEventHandler);
+ }
+ subscribe(poolEntity, getPoolColdSensor(), utilizationEventHandler);
+ subscribe(poolEntity, getPoolHotSensor(), utilizationEventHandler);
+ subscribe(poolEntity, getPoolOkSensor(), utilizationEventHandler);
+ }
+
+ private ThreadFactory newThreadFactory() {
+ return new ThreadFactoryBuilder()
+ .setNameFormat("brooklyn-autoscalerpolicy-%d")
+ .build();
+ }
+
+ /**
+ * Forces an immediate resize (without waiting for stabilization etc) if the current size is
+ * not within the min and max limits. We schedule this so that all resize operations are done
+ * by the same thread.
+ */
+ private void onPoolSizeLimitsChanged(final int min, final int max) {
+ if (LOG.isTraceEnabled()) LOG.trace("{} checking pool size on limits changed for {} (between {} and {})", new Object[] {this, poolEntity, min, max});
+
+ if (isRunning() && isEntityUp()) {
+ executor.submit(new Runnable() {
+ @Override public void run() {
+ try {
+ int currentSize = getCurrentSizeOperator().apply(entity);
+ int desiredSize = Math.min(max, Math.max(min, currentSize));
+
+ if (currentSize != desiredSize) {
+ if (LOG.isInfoEnabled()) LOG.info("{} resizing pool {} immediateley from {} to {} (due to new pool size limits)", new Object[] {this, poolEntity, currentSize, desiredSize});
+ getResizeOperator().resize(poolEntity, desiredSize);
+ }
+
+ } catch (Exception e) {
+ if (isRunning()) {
+ LOG.error("Error resizing: "+e, e);
+ } else {
+ if (LOG.isDebugEnabled()) LOG.debug("Error resizing, but no longer running: "+e, e);
+ }
+ } catch (Throwable t) {
+ LOG.error("Error resizing: "+t, t);
+ throw Throwables.propagate(t);
+ }
+ }});
+ }
+ }
+
+ private enum ScalingType { HOT, COLD }
+ private static class ScalingData {
+ ScalingType scalingMode;
+ int currentSize;
+ double currentMetricValue;
+ Double metricUpperBound;
+ Double metricLowerBound;
+
+ public double getCurrentTotalActivity() {
+ return currentMetricValue * currentSize;
+ }
+
+ public boolean isHot() {
+ return ((scalingMode==null || scalingMode==ScalingType.HOT) && isValid(metricUpperBound) && currentMetricValue > metricUpperBound);
+ }
+ public boolean isCold() {
+ return ((scalingMode==null || scalingMode==ScalingType.COLD) && isValid(metricLowerBound) && currentMetricValue < metricLowerBound);
+ }
+ private boolean isValid(Double bound) {
+ return (bound!=null && bound>0);
+ }
+ }
+
+ private void onMetricChanged(Number val) {
+ if (LOG.isTraceEnabled()) LOG.trace("{} recording pool-metric for {}: {}", new Object[] {this, poolEntity, val});
+
+ if (val==null) {
+ // occurs e.g. if using an aggregating enricher who returns null when empty, the sensor has gone away
+ if (LOG.isTraceEnabled()) LOG.trace("{} not resizing pool {}, inbound metric is null", new Object[] {this, poolEntity});
+ return;
+ }
+
+ ScalingData data = new ScalingData();
+ data.currentMetricValue = val.doubleValue();
+ data.currentSize = getCurrentSizeOperator().apply(entity);
+ data.metricUpperBound = getMetricUpperBound().doubleValue();
+ data.metricLowerBound = getMetricLowerBound().doubleValue();
+
+ analyze(data, "pool");
+ }
+
+ private void onPoolCold(Map<String, ?> properties) {
+ if (LOG.isTraceEnabled()) LOG.trace("{} recording pool-cold for {}: {}", new Object[] {this, poolEntity, properties});
+ analyzeOnHotOrColdSensor(ScalingType.COLD, "cold pool", properties);
+ }
+
+ private void onPoolHot(Map<String, ?> properties) {
+ if (LOG.isTraceEnabled()) LOG.trace("{} recording pool-hot for {}: {}", new Object[] {this, poolEntity, properties});
+ analyzeOnHotOrColdSensor(ScalingType.HOT, "hot pool", properties);
+ }
+
+ private void analyzeOnHotOrColdSensor(ScalingType scalingMode, String description, Map<String, ?> properties) {
+ ScalingData data = new ScalingData();
+ data.scalingMode = scalingMode;
+ data.currentMetricValue = (Double) properties.get(POOL_CURRENT_WORKRATE_KEY);
+ data.currentSize = (Integer) properties.get(POOL_CURRENT_SIZE_KEY);
+ data.metricUpperBound = (Double) properties.get(POOL_HIGH_THRESHOLD_KEY);
+ data.metricLowerBound = (Double) properties.get(POOL_LOW_THRESHOLD_KEY);
+
+ analyze(data, description);
+ }
+
+ private void analyze(ScalingData data, String description) {
+ int desiredSizeUnconstrained;
+
+ /* We always scale out (modulo stabilization delay) if:
+ * currentTotalActivity > currentSize*metricUpperBound
+ * With newDesiredSize the smallest n such that n*metricUpperBound >= currentTotalActivity
+ * ie n >= currentTotalActiviy/metricUpperBound, thus n := Math.ceil(currentTotalActivity/metricUpperBound)
+ *
+ * Else consider scale back if:
+ * currentTotalActivity < currentSize*metricLowerBound
+ * With newDesiredSize normally the largest n such that:
+ * n*metricLowerBound <= currentTotalActivity
+ * BUT with an absolute requirement which trumps the above computation
+ * that the newDesiredSize doesn't cause immediate scale out:
+ * n*metricUpperBound >= currentTotalActivity
+ * thus n := Math.max ( floor(currentTotalActiviy/metricLowerBound), ceil(currentTotal/metricUpperBound) )
+ */
+ if (data.isHot()) {
+ // scale out
+ desiredSizeUnconstrained = (int)Math.ceil(data.getCurrentTotalActivity() / data.metricUpperBound);
+ data.scalingMode = ScalingType.HOT;
+
+ } else if (data.isCold()) {
+ // scale back
+ desiredSizeUnconstrained = (int)Math.floor(data.getCurrentTotalActivity() / data.metricLowerBound);
+ data.scalingMode = ScalingType.COLD;
+
+ } else {
+ if (LOG.isTraceEnabled()) LOG.trace("{} not resizing pool {} from {} ({} within range {}..{})", new Object[] {this, poolEntity, data.currentSize, data.currentMetricValue, data.metricLowerBound, data.metricUpperBound});
+ abortResize(data.currentSize);
+ return; // within the healthy range; no-op
+ }
+
+ if (LOG.isTraceEnabled()) LOG.debug("{} detected unconstrained desired size {}", new Object[] {this, desiredSizeUnconstrained});
+ int desiredSize = applyMinMaxConstraints(desiredSizeUnconstrained);
+
+ if ((data.scalingMode==ScalingType.COLD) && (desiredSize < data.currentSize)) {
+
+ int delta = data.currentSize - desiredSize;
+ int scaleIncrement = getResizeDownIterationIncrement();
+ int scaleMax = getResizeDownIterationMax();
+ if (delta>scaleMax) {
+ delta=scaleMax;
+ } else if (delta % scaleIncrement != 0) {
+ // keep scaling to the increment
+ delta += scaleIncrement - (delta % scaleIncrement);
+ }
+ desiredSize = data.currentSize - delta;
+
+ if (data.metricUpperBound!=null) {
+ // if upper bound supplied, check that this desired scale-back size
+ // is not going to cause scale-out on next run; i.e. anti-thrashing
+ while (desiredSize < data.currentSize && data.getCurrentTotalActivity() > data.metricUpperBound * desiredSize) {
+ if (LOG.isTraceEnabled()) LOG.trace("{} when resizing back pool {} from {}, tweaking from {} to prevent thrashing", new Object[] {this, poolEntity, data.currentSize, desiredSize });
+ desiredSize += scaleIncrement;
+ }
+ }
+ desiredSize = applyMinMaxConstraints(desiredSize);
+ if (desiredSize >= data.currentSize) data.scalingMode = null;
+
+ } else if ((data.scalingMode==ScalingType.HOT) && (desiredSize > data.currentSize)) {
+
+ int delta = desiredSize - data.currentSize;
+ int scaleIncrement = getResizeUpIterationIncrement();
+ int scaleMax = getResizeUpIterationMax();
+ if (delta>scaleMax) {
+ delta=scaleMax;
+ } else if (delta % scaleIncrement != 0) {
+ // keep scaling to the increment
+ delta += scaleIncrement - (delta % scaleIncrement);
+ }
+ desiredSize = data.currentSize + delta;
+ desiredSize = applyMinMaxConstraints(desiredSize);
+ if (desiredSize <= data.currentSize) data.scalingMode = null;
+
+ } else {
+ data.scalingMode = null;
+ }
+
+ if (data.scalingMode!=null) {
+ if (LOG.isDebugEnabled()) LOG.debug("{} provisionally resizing {} {} from {} to {} ({} < {}; ideal size {})", new Object[] {this, description, poolEntity, data.currentSize, desiredSize, data.currentMetricValue, data.metricLowerBound, desiredSizeUnconstrained});
+ scheduleResize(desiredSize);
+ } else {
+ if (LOG.isTraceEnabled()) LOG.trace("{} not resizing {} {} from {} to {}, {} out of healthy range {}..{} but unconstrained size {} blocked by bounds/check", new Object[] {this, description, poolEntity, data.currentSize, desiredSize, data.currentMetricValue, data.metricLowerBound, data.metricUpperBound, desiredSizeUnconstrained});
+ abortResize(data.currentSize);
+ // but add to the unbounded record for future consideration
+ }
+
+ onNewUnboundedPoolSize(desiredSizeUnconstrained);
+ }
+
+ private int applyMinMaxConstraints(int desiredSize) {
+ desiredSize = Math.max(getMinPoolSize(), desiredSize);
+ desiredSize = Math.min(getMaxPoolSize(), desiredSize);
+ return desiredSize;
+ }
+
+ private void onPoolOk(Map<String, ?> properties) {
+ if (LOG.isTraceEnabled()) LOG.trace("{} recording pool-ok for {}: {}", new Object[] {this, poolEntity, properties});
+
+ int poolCurrentSize = (Integer) properties.get(POOL_CURRENT_SIZE_KEY);
+
+ if (LOG.isTraceEnabled()) LOG.trace("{} not resizing ok pool {} from {}", new Object[] {this, poolEntity, poolCurrentSize});
+ abortResize(poolCurrentSize);
+ }
+
+ /**
+ * Schedules a resize, if there is not already a resize operation queued up. When that resize
+ * executes, it will resize to whatever the latest value is to be (rather than what it was told
+ * to do at the point the job was queued).
+ */
+ private void scheduleResize(final int newSize) {
+ recentDesiredResizes.add(newSize);
+
+ scheduleResize();
+ }
+
+ /**
+ * If a listener is registered to be notified of the max-pool-size cap being reached, then record
+ * what our unbounded size would be and schedule a check to see if this unbounded size is sustained.
+ *
+ * Piggy-backs off the existing scheduleResize execution, which now also checks if the listener
+ * needs to be called.
+ */
+ private void onNewUnboundedPoolSize(final int val) {
+ if (getMaxSizeReachedSensor() != null) {
+ recentUnboundedResizes.add(val);
+ scheduleResize();
+ }
+ }
+
+ private void abortResize(final int currentSize) {
+ recentDesiredResizes.add(currentSize);
+ recentUnboundedResizes.add(currentSize);
+ }
+
+ private boolean isEntityUp() {
+ if (entity == null) {
+ return false;
+ } else if (entity.getEntityType().getSensors().contains(Startable.SERVICE_UP)) {
+ return Boolean.TRUE.equals(entity.getAttribute(Startable.SERVICE_UP));
+ } else {
+ return true;
+ }
+ }
+
+ private void scheduleResize() {
+ // TODO Make scale-out calls concurrent, rather than waiting for first resize to entirely
+ // finish. On ec2 for example, this can cause us to grow very slowly if first request is for
+ // just one new VM to be provisioned.
+
+ if (isRunning() && isEntityUp() && executorQueued.compareAndSet(false, true)) {
+ long now = System.currentTimeMillis();
+ long delay = Math.max(0, (executorTime + getMinPeriodBetweenExecs().toMilliseconds()) - now);
+ if (LOG.isTraceEnabled()) LOG.trace("{} scheduling resize in {}ms", this, delay);
+
+ executor.schedule(new Runnable() {
+ @Override public void run() {
+ try {
+ executorTime = System.currentTimeMillis();
+ executorQueued.set(false);
+
+ resizeNow();
+ notifyMaxReachedIfRequiredNow();
+
+ } catch (Exception e) {
+ if (isRunning()) {
+ LOG.error("Error resizing: "+e, e);
+ } else {
+ if (LOG.isDebugEnabled()) LOG.debug("Error resizing, but no longer running: "+e, e);
+ }
+ } catch (Throwable t) {
+ LOG.error("Error resizing: "+t, t);
+ throw Throwables.propagate(t);
+ }
+ }},
+ delay,
+ TimeUnit.MILLISECONDS);
+ }
+ }
+
+ /**
+ * Looks at the values for "unbounded pool size" (i.e. if we ignore caps of minSize and maxSize) to report what
+ * those values have been within a time window. The time window used is the "maxReachedNotificationDelay",
+ * which determines how many milliseconds after being consistently above the max-size will it take before
+ * we emit the sensor event (if any).
+ */
+ private void notifyMaxReachedIfRequiredNow() {
+ BasicNotificationSensor<? super MaxPoolSizeReachedEvent> maxSizeReachedSensor = getMaxSizeReachedSensor();
+ if (maxSizeReachedSensor == null) {
+ return;
+ }
+
+ WindowSummary valsSummary = recentUnboundedResizes.summarizeWindow(getMaxReachedNotificationDelay());
+ long timeWindowSize = getMaxReachedNotificationDelay().toMilliseconds();
+ long currentPoolSize = getCurrentSizeOperator().apply(poolEntity);
+ int maxAllowedPoolSize = getMaxPoolSize();
+ long unboundedSustainedMaxPoolSize = valsSummary.min; // The sustained maximum (i.e. the smallest it's dropped down to)
+ long unboundedCurrentPoolSize = valsSummary.latest;
+
+ if (maxReachedLastNotifiedTime > 0) {
+ // already notified the listener; don't do it again
+ // TODO Could have max period for notifications, or a step increment to warn when exceeded by ever bigger amounts
+
+ } else if (unboundedSustainedMaxPoolSize > maxAllowedPoolSize) {
+ // We have consistently wanted to be bigger than the max allowed; tell the listener
+ if (LOG.isDebugEnabled()) LOG.debug("{} notifying listener of max pool size reached; current {}, max {}, unbounded current {}, unbounded max {}",
+ new Object[] {this, currentPoolSize, maxAllowedPoolSize, unboundedCurrentPoolSize, unboundedSustainedMaxPoolSize});
+
+ maxReachedLastNotifiedTime = System.currentTimeMillis();
+ MaxPoolSizeReachedEvent event = MaxPoolSizeReachedEvent.builder()
+ .currentPoolSize(currentPoolSize)
+ .maxAllowed(maxAllowedPoolSize)
+ .currentUnbounded(unboundedCurrentPoolSize)
+ .maxUnbounded(unboundedSustainedMaxPoolSize)
+ .timeWindow(timeWindowSize)
+ .build();
+ entity.emit(maxSizeReachedSensor, event);
+
+ } else if (valsSummary.max > maxAllowedPoolSize) {
+ // We temporarily wanted to be bigger than the max allowed; check back later to see if consistent
+ // TODO Could check if there has been anything bigger than "min" since min happened (would be more efficient)
+ if (LOG.isTraceEnabled()) LOG.trace("{} re-scheduling max-reached check for {}, as unbounded size not stable (min {}, max {}, latest {})",
+ new Object[] {this, poolEntity, valsSummary.min, valsSummary.max, valsSummary.latest});
+ scheduleResize();
+
+ } else {
+ // nothing to write home about; continually below maxAllowed
+ }
+ }
+
+ private void resizeNow() {
+ long currentPoolSize = getCurrentSizeOperator().apply(poolEntity);
+ CalculatedDesiredPoolSize calculatedDesiredPoolSize = calculateDesiredPoolSize(currentPoolSize);
+ final long desiredPoolSize = calculatedDesiredPoolSize.size;
+ boolean stable = calculatedDesiredPoolSize.stable;
+
+ if (!stable) {
+ // the desired size fluctuations are not stable; ensure we check again later (due to time-window)
+ // even if no additional events have been received
+ // (note we continue now with as "good" a resize as we can given the instability)
+ if (LOG.isTraceEnabled()) LOG.trace("{} re-scheduling resize check for {}, as desired size not stable (current {}, desired {}); continuing with resize...",
+ new Object[] {this, poolEntity, currentPoolSize, desiredPoolSize});
+ scheduleResize();
+ }
+ if (currentPoolSize == desiredPoolSize) {
+ if (LOG.isTraceEnabled()) LOG.trace("{} not resizing pool {} from {} to {}",
+ new Object[] {this, poolEntity, currentPoolSize, desiredPoolSize});
+ return;
+ }
+
+ if (LOG.isDebugEnabled()) LOG.debug("{} requesting resize to {}; current {}, min {}, max {}",
+ new Object[] {this, desiredPoolSize, currentPoolSize, getMinPoolSize(), getMaxPoolSize()});
+
+ Entities.submit(entity, Tasks.<Void>builder().name("Auto-scaler")
+ .description("Auto-scaler recommending resize from "+currentPoolSize+" to "+desiredPoolSize)
+ .tag(BrooklynTaskTags.NON_TRANSIENT_TASK_TAG)
+ .body(new Callable<Void>() {
+ @Override
+ public Void call() throws Exception {
+ // TODO Should we use int throughout, rather than casting here?
+ getResizeOperator().resize(poolEntity, (int) desiredPoolSize);
+ return null;
+ }
+ }).build())
+ .blockUntilEnded();
+ }
+
+ /**
+ * Complicated logic for stabilization-delay...
+ * Only grow if we have consistently been asked to grow for the resizeUpStabilizationDelay period;
+ * Only shrink if we have consistently been asked to shrink for the resizeDownStabilizationDelay period.
+ *
+ * @return tuple of desired pool size, and whether this is "stable" (i.e. if we receive no more events
+ * will this continue to be the desired pool size)
+ */
+ private CalculatedDesiredPoolSize calculateDesiredPoolSize(long currentPoolSize) {
+ long now = System.currentTimeMillis();
+ WindowSummary downsizeSummary = recentDesiredResizes.summarizeWindow(getResizeDownStabilizationDelay());
+ WindowSummary upsizeSummary = recentDesiredResizes.summarizeWindow(getResizeUpStabilizationDelay());
+
+ // this is the _sustained_ growth value; the smallest size that has been requested in the "stable-for-growing" period
+ long maxDesiredPoolSize = upsizeSummary.min;
+ boolean stableForGrowing = upsizeSummary.stableForGrowth;
+
+ // this is the _sustained_ shrink value; largest size that has been requested in the "stable-for-shrinking" period:
+ long minDesiredPoolSize = downsizeSummary.max;
+ boolean stableForShrinking = downsizeSummary.stableForShrinking;
+
+ // (it is a logical consequence of the above that minDesired >= maxDesired -- this is correct, if confusing:
+ // think of minDesired as the minimum size we are allowed to resize to, and similarly for maxDesired;
+ // if min > max we can scale to max if current < max, or scale to min if current > min)
+
+ long desiredPoolSize;
+
+ boolean stable;
+
+ if (currentPoolSize < maxDesiredPoolSize) {
+ // we have valid request to grow
+ // (we'll never have a valid request to grow and a valid to shrink simultaneously, btw)
+ desiredPoolSize = maxDesiredPoolSize;
+ stable = stableForGrowing;
+ } else if (currentPoolSize > minDesiredPoolSize) {
+ // we have valid request to shrink
+ desiredPoolSize = minDesiredPoolSize;
+ stable = stableForShrinking;
+ } else {
+ desiredPoolSize = currentPoolSize;
+ stable = stableForGrowing && stableForShrinking;
+ }
+
+ if (LOG.isTraceEnabled()) LOG.trace("{} calculated desired pool size: from {} to {}; minDesired {}, maxDesired {}; " +
+ "stable {}; now {}; downsizeHistory {}; upsizeHistory {}",
+ new Object[] {this, currentPoolSize, desiredPoolSize, minDesiredPoolSize, maxDesiredPoolSize, stable, now, downsizeSummary, upsizeSummary});
+
+ return new CalculatedDesiredPoolSize(desiredPoolSize, stable);
+ }
+
+ private static class CalculatedDesiredPoolSize {
+ final long size;
+ final boolean stable;
+
+ CalculatedDesiredPoolSize(long size, boolean stable) {
+ this.size = size;
+ this.stable = stable;
+ }
+ }
+
+ @Override
+ public String toString() {
+ return getClass().getSimpleName() + (groovyTruth(name) ? "("+name+")" : "");
+ }
+}
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/d30ff597/policy/src/main/java/org/apache/brooklyn/policy/autoscaling/MaxPoolSizeReachedEvent.java
----------------------------------------------------------------------
diff --git a/policy/src/main/java/org/apache/brooklyn/policy/autoscaling/MaxPoolSizeReachedEvent.java b/policy/src/main/java/org/apache/brooklyn/policy/autoscaling/MaxPoolSizeReachedEvent.java
new file mode 100644
index 0000000..6e97771
--- /dev/null
+++ b/policy/src/main/java/org/apache/brooklyn/policy/autoscaling/MaxPoolSizeReachedEvent.java
@@ -0,0 +1,103 @@
+/*
+ * 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.policy.autoscaling;
+
+import java.io.Serializable;
+
+import com.google.common.base.Objects;
+
+public class MaxPoolSizeReachedEvent implements Serializable {
+ private static final long serialVersionUID = 1602627701360505190L;
+
+ public static Builder builder() {
+ return new Builder();
+ }
+
+ public static class Builder {
+ protected long maxAllowed;
+ protected long currentPoolSize;
+ protected long currentUnbounded;
+ protected long maxUnbounded;
+ protected long timeWindow;
+
+ public Builder maxAllowed(long val) {
+ this.maxAllowed = val; return this;
+ }
+
+ public Builder currentPoolSize(long val) {
+ this.currentPoolSize = val; return this;
+ }
+
+ public Builder currentUnbounded(long val) {
+ this.currentUnbounded = val; return this;
+ }
+
+ public Builder maxUnbounded(long val) {
+ this.maxUnbounded = val; return this;
+ }
+
+ public Builder timeWindow(long val) {
+ this.timeWindow = val; return this;
+ }
+ public MaxPoolSizeReachedEvent build() {
+ return new MaxPoolSizeReachedEvent(this);
+ }
+ }
+
+ private final long maxAllowed;
+ private final long currentPoolSize;
+ private final long currentUnbounded;
+ private final long maxUnbounded;
+ private final long timeWindow;
+
+ protected MaxPoolSizeReachedEvent(Builder builder) {
+ maxAllowed = builder.maxAllowed;
+ currentPoolSize = builder.currentPoolSize;
+ currentUnbounded = builder.currentUnbounded;
+ maxUnbounded = builder.maxUnbounded;
+ timeWindow = builder.timeWindow;
+ }
+
+ public long getMaxAllowed() {
+ return maxAllowed;
+ }
+
+ public long getCurrentPoolSize() {
+ return currentPoolSize;
+ }
+
+ public long getCurrentUnbounded() {
+ return currentUnbounded;
+ }
+
+ public long getMaxUnbounded() {
+ return maxUnbounded;
+ }
+
+ public long getTimeWindow() {
+ return timeWindow;
+ }
+
+ @Override
+ public String toString() {
+ return Objects.toStringHelper(this).add("maxAllowed", maxAllowed).add("currentPoolSize", currentPoolSize)
+ .add("currentUnbounded", currentUnbounded).add("maxUnbounded", maxUnbounded)
+ .add("timeWindow", timeWindow).toString();
+ }
+}
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/d30ff597/policy/src/main/java/org/apache/brooklyn/policy/autoscaling/ResizeOperator.java
----------------------------------------------------------------------
diff --git a/policy/src/main/java/org/apache/brooklyn/policy/autoscaling/ResizeOperator.java b/policy/src/main/java/org/apache/brooklyn/policy/autoscaling/ResizeOperator.java
new file mode 100644
index 0000000..4f4fbb0
--- /dev/null
+++ b/policy/src/main/java/org/apache/brooklyn/policy/autoscaling/ResizeOperator.java
@@ -0,0 +1,31 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.brooklyn.policy.autoscaling;
+
+import org.apache.brooklyn.api.entity.Entity;
+
+public interface ResizeOperator {
+
+ /**
+ * Resizes the given entity to the desired size, if possible.
+ *
+ * @return the new size of the entity
+ */
+ public Integer resize(Entity entity, Integer desiredSize);
+}
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/d30ff597/policy/src/main/java/org/apache/brooklyn/policy/autoscaling/SizeHistory.java
----------------------------------------------------------------------
diff --git a/policy/src/main/java/org/apache/brooklyn/policy/autoscaling/SizeHistory.java b/policy/src/main/java/org/apache/brooklyn/policy/autoscaling/SizeHistory.java
new file mode 100644
index 0000000..0aa8801
--- /dev/null
+++ b/policy/src/main/java/org/apache/brooklyn/policy/autoscaling/SizeHistory.java
@@ -0,0 +1,166 @@
+/*
+ * 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.policy.autoscaling;
+
+import java.util.List;
+
+import brooklyn.util.collections.MutableMap;
+import brooklyn.util.collections.TimeWindowedList;
+import brooklyn.util.collections.TimestampedValue;
+import brooklyn.util.time.Duration;
+
+import com.google.common.base.Objects;
+
+/**
+ * Using a {@link TimeWindowedList}, tracks the recent history of values to allow a summary of
+ * those values to be obtained.
+ *
+ * @author aled
+ */
+public class SizeHistory {
+
+ public static class WindowSummary {
+ /** The most recent value (or -1 if there has been no value) */
+ public final long latest;
+
+ /** The minimum vaule within the given time period */
+ public final long min;
+
+ /** The maximum vaule within the given time period */
+ public final long max;
+
+ /** true if, since that max value, there have not been any higher values */
+ public final boolean stableForGrowth;
+
+ /** true if, since that low value, there have not been any lower values */
+ public final boolean stableForShrinking;
+
+ public WindowSummary(long latest, long min, long max, boolean stableForGrowth, boolean stableForShrinking) {
+ this.latest = latest;
+ this.min = min;
+ this.max = max;
+ this.stableForGrowth = stableForGrowth;
+ this.stableForShrinking = stableForShrinking;
+ }
+
+ @Override
+ public String toString() {
+ return Objects.toStringHelper(this).add("latest", latest).add("min", min).add("max", max)
+ .add("stableForGrowth", stableForGrowth).add("stableForShrinking", stableForShrinking).toString();
+ }
+ }
+
+ private final TimeWindowedList<Number> recentDesiredResizes;
+
+ public SizeHistory(long windowSize) {
+ recentDesiredResizes = new TimeWindowedList<Number>(MutableMap.of("timePeriod", windowSize, "minExpiredVals", 1));
+ }
+
+ public void add(final int val) {
+ recentDesiredResizes.add(val);
+ }
+
+ public void setWindowSize(Duration newWindowSize) {
+ recentDesiredResizes.setTimePeriod(newWindowSize);
+ }
+
+ /**
+ * Summarises the history of values in this time window, with a few special things:
+ * <ul>
+ * <li>If entire time-window is not covered by the given values, then min is Integer.MIN_VALUE and max is Integer.MAX_VALUE
+ * <li>If no values, then latest is -1
+ * <li>If no recent values, then keeps last-seen value (no matter how old), to use that
+ * <li>"stable for growth" means that since that max value, there have not been any higher values
+ * <li>"stable for shrinking" means that since that low value, there have not been any lower values
+ * </ul>
+ */
+ public WindowSummary summarizeWindow(Duration windowSize) {
+ long now = System.currentTimeMillis();
+ List<TimestampedValue<Number>> windowVals = recentDesiredResizes.getValuesInWindow(now, windowSize);
+
+ Number latestObj = latestInWindow(windowVals);
+ long latest = (latestObj == null) ? -1: latestObj.longValue();
+ long max = maxInWindow(windowVals, windowSize).longValue();
+ long min = minInWindow(windowVals, windowSize).longValue();
+
+ // TODO Could do more sophisticated "stable" check; this is the easiest code - correct but not most efficient
+ // in terms of the caller having to schedule additional stability checks.
+ boolean stable = (min == max);
+
+ return new WindowSummary(latest, min, max, stable, stable);
+ }
+
+ /**
+ * If the entire time-window is not covered by the given values, then returns Integer.MAX_VALUE.
+ */
+ private <T extends Number> T maxInWindow(List<TimestampedValue<T>> vals, Duration timeWindow) {
+ // TODO bad casting from Integer default result to T
+ long now = System.currentTimeMillis();
+ long epoch = now - timeWindow.toMilliseconds();
+ T result = null;
+ double resultAsDouble = Integer.MAX_VALUE;
+ for (TimestampedValue<T> val : vals) {
+ T valAsNum = val.getValue();
+ double valAsDouble = (valAsNum != null) ? valAsNum.doubleValue() : 0;
+ if (result == null && val.getTimestamp() > epoch) {
+ result = withDefault(null, Integer.MAX_VALUE);
+ resultAsDouble = result.doubleValue();
+ }
+ if (result == null || (valAsNum != null && valAsDouble > resultAsDouble)) {
+ result = valAsNum;
+ resultAsDouble = valAsDouble;
+ }
+ }
+ return withDefault(result, Integer.MAX_VALUE);
+ }
+
+ /**
+ * If the entire time-window is not covered by the given values, then returns Integer.MIN_VALUE
+ */
+ private <T extends Number> T minInWindow(List<TimestampedValue<T>> vals, Duration timeWindow) {
+ long now = System.currentTimeMillis();
+ long epoch = now - timeWindow.toMilliseconds();
+ T result = null;
+ double resultAsDouble = Integer.MIN_VALUE;
+ for (TimestampedValue<T> val : vals) {
+ T valAsNum = val.getValue();
+ double valAsDouble = (valAsNum != null) ? valAsNum.doubleValue() : 0;
+ if (result == null && val.getTimestamp() > epoch) {
+ result = withDefault(null, Integer.MIN_VALUE);
+ resultAsDouble = result.doubleValue();
+ }
+ if (result == null || (val.getValue() != null && valAsDouble < resultAsDouble)) {
+ result = valAsNum;
+ resultAsDouble = valAsDouble;
+ }
+ }
+ return withDefault(result, Integer.MIN_VALUE);
+ }
+
+ @SuppressWarnings("unchecked")
+ private <T> T withDefault(T result, Integer defaultValue) {
+ return result!=null ? result : (T) defaultValue;
+ }
+ /**
+ * @return null if empty, or the most recent value
+ */
+ private <T extends Number> T latestInWindow(List<TimestampedValue<T>> vals) {
+ return vals.isEmpty() ? null : vals.get(vals.size()-1).getValue();
+ }
+}
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/d30ff597/policy/src/main/java/org/apache/brooklyn/policy/followthesun/DefaultFollowTheSunModel.java
----------------------------------------------------------------------
diff --git a/policy/src/main/java/org/apache/brooklyn/policy/followthesun/DefaultFollowTheSunModel.java b/policy/src/main/java/org/apache/brooklyn/policy/followthesun/DefaultFollowTheSunModel.java
new file mode 100644
index 0000000..fef3d7f
--- /dev/null
+++ b/policy/src/main/java/org/apache/brooklyn/policy/followthesun/DefaultFollowTheSunModel.java
@@ -0,0 +1,328 @@
+/*
+ * 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.policy.followthesun;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import java.io.ByteArrayOutputStream;
+import java.io.PrintStream;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.apache.brooklyn.api.location.Location;
+import org.apache.brooklyn.location.basic.AbstractLocation;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Maps;
+
+public class DefaultFollowTheSunModel<ContainerType, ItemType> implements FollowTheSunModel<ContainerType, ItemType> {
+
+ private static final Logger LOG = LoggerFactory.getLogger(DefaultFollowTheSunModel.class);
+
+ // Concurrent maps cannot have null value; use this to represent when no container is supplied for an item
+ private static final String NULL = "null-val";
+ private static final Location NULL_LOCATION = new AbstractLocation(newHashMap("name","null-location")) {};
+
+ private final String name;
+ private final Set<ContainerType> containers = Collections.newSetFromMap(new ConcurrentHashMap<ContainerType,Boolean>());
+ private final Map<ItemType, ContainerType> itemToContainer = new ConcurrentHashMap<ItemType, ContainerType>();
+ private final Map<ContainerType, Location> containerToLocation = new ConcurrentHashMap<ContainerType, Location>();
+ private final Map<ItemType, Location> itemToLocation = new ConcurrentHashMap<ItemType, Location>();
+ private final Map<ItemType, Map<? extends ItemType, Double>> itemUsage = new ConcurrentHashMap<ItemType, Map<? extends ItemType,Double>>();
+ private final Set<ItemType> immovableItems = Collections.newSetFromMap(new ConcurrentHashMap<ItemType, Boolean>());
+
+ public DefaultFollowTheSunModel(String name) {
+ this.name = name;
+ }
+
+ @Override
+ public Set<ItemType> getItems() {
+ return itemToContainer.keySet();
+ }
+
+ @Override
+ public ContainerType getItemContainer(ItemType item) {
+ ContainerType result = itemToContainer.get(item);
+ return (isNull(result) ? null : result);
+ }
+
+ @Override
+ public Location getItemLocation(ItemType item) {
+ Location result = itemToLocation.get(item);
+ return (isNull(result) ? null : result);
+ }
+
+ @Override
+ public Location getContainerLocation(ContainerType container) {
+ Location result = containerToLocation.get(container);
+ return (isNull(result) ? null : result);
+ }
+
+ // Provider methods.
+
+ @Override public String getName() {
+ return name;
+ }
+
+ // TODO: delete?
+ @Override public String getName(ItemType item) {
+ return item.toString();
+ }
+
+ @Override public boolean isItemMoveable(ItemType item) {
+ // If don't know about item, then assume not movable; otherwise has this item been explicitly flagged as immovable?
+ return hasItem(item) && !immovableItems.contains(item);
+ }
+
+ @Override public boolean isItemAllowedIn(ItemType item, Location location) {
+ return true; // TODO?
+ }
+
+ @Override public boolean hasActiveMigration(ItemType item) {
+ return false; // TODO?
+ }
+
+ @Override
+ // FIXME Too expensive to compute; store in a different data structure?
+ public Map<ItemType, Map<Location, Double>> getDirectSendsToItemByLocation() {
+ Map<ItemType, Map<Location, Double>> result = new LinkedHashMap<ItemType, Map<Location,Double>>(getNumItems());
+
+ for (Map.Entry<ItemType, Map<? extends ItemType, Double>> entry : itemUsage.entrySet()) {
+ ItemType targetItem = entry.getKey();
+ Map<? extends ItemType, Double> sources = entry.getValue();
+ if (sources.isEmpty()) continue; // no-one talking to us
+
+ Map<Location, Double> targetUsageByLocation = new LinkedHashMap<Location, Double>();
+ result.put(targetItem, targetUsageByLocation);
+
+ for (Map.Entry<? extends ItemType, Double> entry2 : sources.entrySet()) {
+ ItemType sourceItem = entry2.getKey();
+ Location sourceLocation = getItemLocation(sourceItem);
+ double usageVal = (entry.getValue() != null) ? entry2.getValue() : 0d;
+ if (sourceLocation == null) continue; // don't know where to attribute this load; e.g. item may have just terminated
+ if (sourceItem.equals(targetItem)) continue; // ignore msgs to self
+
+ Double usageValTotal = targetUsageByLocation.get(sourceLocation);
+ double newUsageValTotal = (usageValTotal != null ? usageValTotal : 0d) + usageVal;
+ targetUsageByLocation.put(sourceLocation, newUsageValTotal);
+ }
+ }
+
+ return result;
+ }
+
+ @Override
+ public Set<ContainerType> getAvailableContainersFor(ItemType item, Location location) {
+ checkNotNull(location);
+ return getContainersInLocation(location);
+ }
+
+
+ // Mutators.
+
+ @Override
+ public void onItemMoved(ItemType item, ContainerType newContainer) {
+ // idempotent, as may be called multiple times
+ Location newLocation = (newContainer != null) ? containerToLocation.get(newContainer) : null;
+ ContainerType newContainerNonNull = toNonNullContainer(newContainer);
+ Location newLocationNonNull = toNonNullLocation(newLocation);
+ ContainerType oldContainer = itemToContainer.put(item, newContainerNonNull);
+ Location oldLocation = itemToLocation.put(item, newLocationNonNull);
+ }
+
+ @Override
+ public void onContainerAdded(ContainerType container, Location location) {
+ Location locationNonNull = toNonNullLocation(location);
+ containers.add(container);
+ containerToLocation.put(container, locationNonNull);
+ for (ItemType item : getItemsOnContainer(container)) {
+ itemToLocation.put(item, locationNonNull);
+ }
+ }
+
+ @Override
+ public void onContainerRemoved(ContainerType container) {
+ containers.remove(container);
+ containerToLocation.remove(container);
+ }
+
+ public void onContainerLocationUpdated(ContainerType container, Location location) {
+ if (!containers.contains(container)) {
+ // unknown container; probably just stopped?
+ // If this overtook onContainerAdded, then assume we'll lookup the location and get it right in onContainerAdded
+ if (LOG.isDebugEnabled()) LOG.debug("Ignoring setting of location for unknown container {}, to {}", container, location);
+ return;
+ }
+ Location locationNonNull = toNonNullLocation(location);
+ containerToLocation.put(container, locationNonNull);
+ for (ItemType item : getItemsOnContainer(container)) {
+ itemToLocation.put(item, locationNonNull);
+ }
+ }
+
+ @Override
+ public void onItemAdded(ItemType item, ContainerType container, boolean immovable) {
+ // idempotent, as may be called multiple times
+
+ if (immovable) {
+ immovableItems.add(item);
+ }
+ Location location = (container != null) ? containerToLocation.get(container) : null;
+ ContainerType containerNonNull = toNonNullContainer(container);
+ Location locationNonNull = toNonNullLocation(location);
+ ContainerType oldContainer = itemToContainer.put(item, containerNonNull);
+ Location oldLocation = itemToLocation.put(item, locationNonNull);
+ }
+
+ @Override
+ public void onItemRemoved(ItemType item) {
+ itemToContainer.remove(item);
+ itemToLocation.remove(item);
+ itemUsage.remove(item);
+ immovableItems.remove(item);
+ }
+
+ @Override
+ public void onItemUsageUpdated(ItemType item, Map<? extends ItemType, Double> newValue) {
+ if (hasItem(item)) {
+ itemUsage.put(item, newValue);
+ } else {
+ // Can happen when item removed - get notification of removal and workrate from group and item
+ // respectively, so can overtake each other
+ if (LOG.isDebugEnabled()) LOG.debug("Ignoring setting of usage for unknown item {}, to {}", item, newValue);
+ }
+ }
+
+
+ // Additional methods for tests.
+
+ /**
+ * Warning: this can be an expensive (time and memory) operation if there are a lot of items/containers.
+ */
+ @VisibleForTesting
+ public String itemDistributionToString() {
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ dumpItemDistribution(new PrintStream(baos));
+ return new String(baos.toByteArray());
+ }
+
+ @VisibleForTesting
+ public void dumpItemDistribution() {
+ dumpItemDistribution(System.out);
+ }
+
+ @VisibleForTesting
+ public void dumpItemDistribution(PrintStream out) {
+ Map<ItemType, Map<Location, Double>> directSendsToItemByLocation = getDirectSendsToItemByLocation();
+
+ out.println("Follow-The-Sun dump: ");
+ for (Location location: getLocations()) {
+ out.println("\t"+"Location "+location);
+ for (ContainerType container : getContainersInLocation(location)) {
+ out.println("\t\t"+"Container "+container);
+ for (ItemType item : getItemsOnContainer(container)) {
+ Map<Location, Double> inboundUsage = directSendsToItemByLocation.get(item);
+ Map<? extends ItemType, Double> outboundUsage = itemUsage.get(item);
+ double totalInboundByLocation = (inboundUsage != null) ? sum(inboundUsage.values()) : 0d;
+ double totalInboundByActor = (outboundUsage != null) ? sum(outboundUsage.values()) : 0d;
+ out.println("\t\t\t"+"Item "+item);
+ out.println("\t\t\t\t"+"Inbound-by-location: "+totalInboundByLocation+": "+inboundUsage);
+ out.println("\t\t\t\t"+"Inbound-by-actor: "+totalInboundByActor+": "+outboundUsage);
+ }
+ }
+ }
+ out.flush();
+ }
+
+ private boolean hasItem(ItemType item) {
+ return itemToContainer.containsKey(item);
+ }
+
+ private Set<Location> getLocations() {
+ return ImmutableSet.copyOf(containerToLocation.values());
+ }
+
+ private Set<ContainerType> getContainersInLocation(Location location) {
+ Set<ContainerType> result = new LinkedHashSet<ContainerType>();
+ for (Map.Entry<ContainerType, Location> entry : containerToLocation.entrySet()) {
+ if (location.equals(entry.getValue())) {
+ result.add(entry.getKey());
+ }
+ }
+ return result;
+ }
+
+ private Set<ItemType> getItemsOnContainer(ContainerType container) {
+ Set<ItemType> result = new LinkedHashSet<ItemType>();
+ for (Map.Entry<ItemType, ContainerType> entry : itemToContainer.entrySet()) {
+ if (container.equals(entry.getValue())) {
+ result.add(entry.getKey());
+ }
+ }
+ return result;
+ }
+
+ private int getNumItems() {
+ return itemToContainer.size();
+ }
+
+ @SuppressWarnings("unchecked")
+ private ContainerType nullContainer() {
+ return (ContainerType) NULL; // relies on erasure
+ }
+
+ private Location nullLocation() {
+ return NULL_LOCATION;
+ }
+
+ private ContainerType toNonNullContainer(ContainerType val) {
+ return (val != null) ? val : nullContainer();
+ }
+
+ private Location toNonNullLocation(Location val) {
+ return (val != null) ? val : nullLocation();
+ }
+
+ private boolean isNull(Object val) {
+ return val == NULL || val == NULL_LOCATION;
+ }
+
+ // TODO Move to utils; or stop AbstractLocation from removing things from the map!
+ public static <K,V> Map<K,V> newHashMap(K k, V v) {
+ Map<K,V> result = Maps.newLinkedHashMap();
+ result.put(k, v);
+ return result;
+ }
+
+ public static double sum(Collection<? extends Number> values) {
+ double total = 0;
+ for (Number d : values) {
+ total += d.doubleValue();
+ }
+ return total;
+ }
+}
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/d30ff597/policy/src/main/java/org/apache/brooklyn/policy/followthesun/FollowTheSunModel.java
----------------------------------------------------------------------
diff --git a/policy/src/main/java/org/apache/brooklyn/policy/followthesun/FollowTheSunModel.java b/policy/src/main/java/org/apache/brooklyn/policy/followthesun/FollowTheSunModel.java
new file mode 100644
index 0000000..07c6ed0
--- /dev/null
+++ b/policy/src/main/java/org/apache/brooklyn/policy/followthesun/FollowTheSunModel.java
@@ -0,0 +1,56 @@
+/*
+ * 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.policy.followthesun;
+
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.brooklyn.api.location.Location;
+
+/**
+ * Captures the state of items, containers and locations for the purpose of moving items around
+ * to minimise latency. For consumption by a {@link FollowTheSunStrategy}.
+ */
+public interface FollowTheSunModel<ContainerType, ItemType> {
+
+ // Attributes of the pool.
+ public String getName();
+
+ // Attributes of containers and items.
+ public String getName(ItemType item);
+ public Set<ItemType> getItems();
+ public Map<ItemType, Map<Location, Double>> getDirectSendsToItemByLocation();
+ public Location getItemLocation(ItemType item);
+ public ContainerType getItemContainer(ItemType item);
+ public Location getContainerLocation(ContainerType container);
+ public boolean hasActiveMigration(ItemType item);
+ public Set<ContainerType> getAvailableContainersFor(ItemType item, Location location);
+ public boolean isItemMoveable(ItemType item);
+ public boolean isItemAllowedIn(ItemType item, Location location);
+
+ // Mutators for keeping the model in-sync with the observed world
+ public void onContainerAdded(ContainerType container, Location location);
+ public void onContainerRemoved(ContainerType container);
+ public void onContainerLocationUpdated(ContainerType container, Location location);
+
+ public void onItemAdded(ItemType item, ContainerType parentContainer, boolean immovable);
+ public void onItemRemoved(ItemType item);
+ public void onItemUsageUpdated(ItemType item, Map<? extends ItemType, Double> newValues);
+ public void onItemMoved(ItemType item, ContainerType newContainer);
+}
[12/24] incubator-brooklyn git commit: [BROOKLYN-162] Renaming
package policy
Posted by he...@apache.org.
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/d30ff597/policy/src/test/java/brooklyn/policy/autoscaling/AutoScalerPolicyMetricTest.java
----------------------------------------------------------------------
diff --git a/policy/src/test/java/brooklyn/policy/autoscaling/AutoScalerPolicyMetricTest.java b/policy/src/test/java/brooklyn/policy/autoscaling/AutoScalerPolicyMetricTest.java
deleted file mode 100644
index 174b982..0000000
--- a/policy/src/test/java/brooklyn/policy/autoscaling/AutoScalerPolicyMetricTest.java
+++ /dev/null
@@ -1,274 +0,0 @@
-/*
- * 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 brooklyn.policy.autoscaling;
-
-import static brooklyn.policy.autoscaling.AutoScalerPolicyTest.currentSizeAsserter;
-import static org.testng.Assert.assertEquals;
-import static org.testng.Assert.assertFalse;
-import static org.testng.Assert.assertTrue;
-
-import java.util.List;
-
-import org.apache.brooklyn.api.entity.proxying.EntitySpec;
-import org.apache.brooklyn.api.event.AttributeSensor;
-import org.apache.brooklyn.api.event.SensorEvent;
-import org.apache.brooklyn.api.event.SensorEventListener;
-import org.apache.brooklyn.test.entity.TestApplication;
-import org.apache.brooklyn.test.entity.TestCluster;
-import org.apache.brooklyn.test.entity.TestEntity;
-import org.testng.annotations.AfterMethod;
-import org.testng.annotations.BeforeMethod;
-import org.testng.annotations.Test;
-
-import brooklyn.entity.basic.Entities;
-import brooklyn.event.basic.BasicNotificationSensor;
-import brooklyn.event.basic.Sensors;
-import brooklyn.test.Asserts;
-
-import com.google.common.collect.ImmutableMap;
-import com.google.common.collect.Lists;
-
-public class AutoScalerPolicyMetricTest {
-
- private static long TIMEOUT_MS = 10000;
- private static long SHORT_WAIT_MS = 50;
-
- private static final AttributeSensor<Integer> MY_ATTRIBUTE = Sensors.newIntegerSensor("autoscaler.test.intAttrib");
- TestApplication app;
- TestCluster tc;
-
- @BeforeMethod(alwaysRun=true)
- public void before() {
- app = TestApplication.Factory.newManagedInstanceForTests();
- tc = app.createAndManageChild(EntitySpec.create(TestCluster.class)
- .configure("initialSize", 1));
- }
-
- @AfterMethod(alwaysRun=true)
- public void tearDown() throws Exception {
- if (app != null) Entities.destroyAll(app.getManagementContext());
- }
-
- @Test
- public void testIncrementsSizeIffUpperBoundExceeded() {
- tc.resize(1);
-
- AutoScalerPolicy policy = new AutoScalerPolicy.Builder().metric(MY_ATTRIBUTE).metricLowerBound(50).metricUpperBound(100).build();
- tc.addPolicy(policy);
-
- tc.setAttribute(MY_ATTRIBUTE, 100);
- Asserts.succeedsContinually(ImmutableMap.of("timeout", SHORT_WAIT_MS), currentSizeAsserter(tc, 1));
-
- tc.setAttribute(MY_ATTRIBUTE, 101);
- Asserts.succeedsEventually(ImmutableMap.of("timeout", TIMEOUT_MS), currentSizeAsserter(tc, 2));
- }
-
- @Test
- public void testDecrementsSizeIffLowerBoundExceeded() {
- tc.resize(2);
-
- AutoScalerPolicy policy = new AutoScalerPolicy.Builder().metric(MY_ATTRIBUTE).metricLowerBound(50).metricUpperBound(100).build();
- tc.addPolicy(policy);
-
- tc.setAttribute(MY_ATTRIBUTE, 50);
- Asserts.succeedsContinually(ImmutableMap.of("timeout", SHORT_WAIT_MS), currentSizeAsserter(tc, 2));
-
- tc.setAttribute(MY_ATTRIBUTE, 49);
- Asserts.succeedsEventually(ImmutableMap.of("timeout", TIMEOUT_MS), currentSizeAsserter(tc, 1));
- }
-
- @Test(groups="Integration")
- public void testIncrementsSizeInProportionToMetric() {
- tc.resize(5);
-
- AutoScalerPolicy policy = new AutoScalerPolicy.Builder().metric(MY_ATTRIBUTE).metricLowerBound(50).metricUpperBound(100).build();
- tc.addPolicy(policy);
-
- // workload 200 so requires doubling size to 10 to handle: (200*5)/100 = 10
- tc.setAttribute(MY_ATTRIBUTE, 200);
- Asserts.succeedsEventually(ImmutableMap.of("timeout", TIMEOUT_MS), currentSizeAsserter(tc, 10));
-
- // workload 5, requires 1 entity: (10*110)/100 = 11
- tc.setAttribute(MY_ATTRIBUTE, 110);
- Asserts.succeedsEventually(ImmutableMap.of("timeout", TIMEOUT_MS), currentSizeAsserter(tc, 11));
- }
-
- @Test(groups="Integration")
- public void testDecrementsSizeInProportionToMetric() {
- tc.resize(5);
-
- AutoScalerPolicy policy = new AutoScalerPolicy.Builder().metric(MY_ATTRIBUTE).metricLowerBound(50).metricUpperBound(100).build();
- tc.addPolicy(policy);
-
- // workload can be handled by 4 servers, within its valid range: (49*5)/50 = 4.9
- tc.setAttribute(MY_ATTRIBUTE, 49);
- Asserts.succeedsEventually(ImmutableMap.of("timeout", TIMEOUT_MS), currentSizeAsserter(tc, 4));
-
- // workload can be handled by 4 servers, within its valid range: (25*4)/50 = 2
- tc.setAttribute(MY_ATTRIBUTE, 25);
- Asserts.succeedsEventually(ImmutableMap.of("timeout", TIMEOUT_MS), currentSizeAsserter(tc, 2));
-
- tc.setAttribute(MY_ATTRIBUTE, 0);
- Asserts.succeedsEventually(ImmutableMap.of("timeout", TIMEOUT_MS), currentSizeAsserter(tc, 1));
- }
-
- @Test(groups="Integration")
- public void testObeysMinAndMaxSize() {
- tc.resize(4);
-
- AutoScalerPolicy policy = new AutoScalerPolicy.Builder().metric(MY_ATTRIBUTE)
- .metricLowerBound(50).metricUpperBound(100)
- .minPoolSize(2).maxPoolSize(6)
- .build();
- tc.addPolicy(policy);
-
- // Decreases to min-size only
- tc.setAttribute(MY_ATTRIBUTE, 0);
- Asserts.succeedsEventually(ImmutableMap.of("timeout", TIMEOUT_MS), currentSizeAsserter(tc, 2));
-
- // Increases to max-size only
- tc.setAttribute(MY_ATTRIBUTE, 100000);
- Asserts.succeedsEventually(ImmutableMap.of("timeout", TIMEOUT_MS), currentSizeAsserter(tc, 6));
- }
-
- @Test(groups="Integration",invocationCount=20)
- public void testWarnsWhenMaxCapReached() {
- final List<MaxPoolSizeReachedEvent> maxReachedEvents = Lists.newCopyOnWriteArrayList();
- tc.resize(1);
-
- BasicNotificationSensor<MaxPoolSizeReachedEvent> maxSizeReachedSensor = AutoScalerPolicy.DEFAULT_MAX_SIZE_REACHED_SENSOR;
-
- app.subscribe(tc, maxSizeReachedSensor, new SensorEventListener<MaxPoolSizeReachedEvent>() {
- @Override public void onEvent(SensorEvent<MaxPoolSizeReachedEvent> event) {
- maxReachedEvents.add(event.getValue());
- }});
-
- AutoScalerPolicy policy = new AutoScalerPolicy.Builder().metric(MY_ATTRIBUTE)
- .metricLowerBound(50).metricUpperBound(100)
- .maxPoolSize(6)
- .maxSizeReachedSensor(maxSizeReachedSensor)
- .build();
- tc.addPolicy(policy);
-
- // workload can be handled by 6 servers, so no need to notify: 6 <= (100*6)/50
- tc.setAttribute(MY_ATTRIBUTE, 600);
- Asserts.succeedsEventually(ImmutableMap.of("timeout", TIMEOUT_MS), currentSizeAsserter(tc, 6));
- assertTrue(maxReachedEvents.isEmpty());
-
- // Increases to above max capacity: would require (100000*6)/100 = 6000
- tc.setAttribute(MY_ATTRIBUTE, 100000);
-
- // Assert our listener gets notified (once)
- Asserts.succeedsEventually(ImmutableMap.of("timeout", TIMEOUT_MS), new Runnable() {
- public void run() {
- assertEquals(maxReachedEvents.size(), 1);
- assertEquals(maxReachedEvents.get(0).getMaxAllowed(), 6);
- assertEquals(maxReachedEvents.get(0).getCurrentPoolSize(), 6);
- assertEquals(maxReachedEvents.get(0).getCurrentUnbounded(), 6000);
- assertEquals(maxReachedEvents.get(0).getMaxUnbounded(), 6000);
- assertEquals(maxReachedEvents.get(0).getTimeWindow(), 0);
- }});
- Asserts.succeedsContinually(new Runnable() {
- @Override public void run() {
- assertEquals(maxReachedEvents.size(), 1);
- }});
- currentSizeAsserter(tc, 6).run();
- }
-
- @Test
- public void testDestructionState() {
- tc.resize(1);
-
- AutoScalerPolicy policy = new AutoScalerPolicy.Builder().metric(MY_ATTRIBUTE).metricLowerBound(50).metricUpperBound(100).build();
- tc.addPolicy(policy);
-
- policy.destroy();
- assertTrue(policy.isDestroyed());
- assertFalse(policy.isRunning());
-
- tc.setAttribute(MY_ATTRIBUTE, 100000);
- Asserts.succeedsContinually(ImmutableMap.of("timeout", SHORT_WAIT_MS), currentSizeAsserter(tc, 1));
-
- // TODO Could assert all subscriptions have been de-registered as well,
- // but that requires exposing more things just for testing...
- }
-
- @Test
- public void testSuspendState() {
- AutoScalerPolicy policy = new AutoScalerPolicy.Builder().metric(MY_ATTRIBUTE).metricLowerBound(50).metricUpperBound(100).build();
- tc.addPolicy(policy);
-
- policy.suspend();
- assertFalse(policy.isRunning());
- assertFalse(policy.isDestroyed());
-
- policy.resume();
- assertTrue(policy.isRunning());
- assertFalse(policy.isDestroyed());
- }
-
- @Test
- public void testPostSuspendActions() {
- tc.resize(1);
-
- AutoScalerPolicy policy = new AutoScalerPolicy.Builder().metric(MY_ATTRIBUTE).metricLowerBound(50).metricUpperBound(100).build();
- tc.addPolicy(policy);
-
- policy.suspend();
-
- tc.setAttribute(MY_ATTRIBUTE, 100000);
- Asserts.succeedsContinually(ImmutableMap.of("timeout", SHORT_WAIT_MS), currentSizeAsserter(tc, 1));
- }
-
- @Test
- public void testPostResumeActions() {
- tc.resize(1);
-
- AutoScalerPolicy policy = new AutoScalerPolicy.Builder().metric(MY_ATTRIBUTE).metricLowerBound(50).metricUpperBound(100).build();
- tc.addPolicy(policy);
-
- policy.suspend();
- policy.resume();
- tc.setAttribute(MY_ATTRIBUTE, 101);
- Asserts.succeedsEventually(ImmutableMap.of("timeout", TIMEOUT_MS), currentSizeAsserter(tc, 2));
- }
-
- @Test
- public void testSubscribesToMetricOnSpecifiedEntity() {
- TestEntity entityWithMetric = app.createAndManageChild(EntitySpec.create(TestEntity.class));
-
- tc.resize(1);
-
- AutoScalerPolicy policy = new AutoScalerPolicy.Builder()
- .metric(TestEntity.SEQUENCE)
- .entityWithMetric(entityWithMetric)
- .metricLowerBound(50)
- .metricUpperBound(100)
- .build();
- tc.addPolicy(policy);
-
- // First confirm that tc is not being listened to for this entity
- tc.setAttribute(TestEntity.SEQUENCE, 101);
- Asserts.succeedsContinually(ImmutableMap.of("timeout", SHORT_WAIT_MS), currentSizeAsserter(tc, 1));
-
- // Then confirm we listen to the correct "entityWithMetric"
- entityWithMetric.setAttribute(TestEntity.SEQUENCE, 101);
- Asserts.succeedsEventually(ImmutableMap.of("timeout", TIMEOUT_MS), currentSizeAsserter(tc, 2));
- }
-}
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/d30ff597/policy/src/test/java/brooklyn/policy/autoscaling/AutoScalerPolicyRebindTest.java
----------------------------------------------------------------------
diff --git a/policy/src/test/java/brooklyn/policy/autoscaling/AutoScalerPolicyRebindTest.java b/policy/src/test/java/brooklyn/policy/autoscaling/AutoScalerPolicyRebindTest.java
deleted file mode 100644
index 03f3f1d..0000000
--- a/policy/src/test/java/brooklyn/policy/autoscaling/AutoScalerPolicyRebindTest.java
+++ /dev/null
@@ -1,137 +0,0 @@
-/*
- * 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 brooklyn.policy.autoscaling;
-
-import static org.testng.Assert.assertEquals;
-
-import java.util.Map;
-import java.util.concurrent.TimeUnit;
-
-import org.apache.brooklyn.api.entity.proxying.EntitySpec;
-import org.apache.brooklyn.api.event.AttributeSensor;
-import org.apache.brooklyn.api.location.LocationSpec;
-import org.apache.brooklyn.test.EntityTestUtils;
-import org.apache.brooklyn.test.entity.TestApplication;
-import org.apache.brooklyn.test.entity.TestEntity;
-import org.testng.annotations.BeforeMethod;
-import org.testng.annotations.Test;
-
-import brooklyn.entity.basic.EntityInternal;
-import brooklyn.entity.group.DynamicCluster;
-import brooklyn.entity.rebind.RebindTestFixtureWithApp;
-import brooklyn.event.basic.BasicNotificationSensor;
-import brooklyn.event.basic.Sensors;
-
-import org.apache.brooklyn.location.basic.SimulatedLocation;
-
-import brooklyn.util.time.Duration;
-
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.Iterables;
-
-public class AutoScalerPolicyRebindTest extends RebindTestFixtureWithApp {
-
- public static BasicNotificationSensor<Map> POOL_HOT_SENSOR = new BasicNotificationSensor<Map>(
- Map.class, "AutoScalerPolicyRebindTest.resizablepool.hot", "Pool is over-utilized; it has insufficient resource for current workload");
- public static BasicNotificationSensor<Map> POOL_COLD_SENSOR = new BasicNotificationSensor<Map>(
- Map.class, "AutoScalerPolicyRebindTest.resizablepool.cold", "Pool is under-utilized; it has too much resource for current workload");
- public static BasicNotificationSensor<Map> POOL_OK_SENSOR = new BasicNotificationSensor<Map>(
- Map.class, "AutoScalerPolicyRebindTest.resizablepool.cold", "Pool utilization is ok; the available resources are fine for the current workload");
- public static BasicNotificationSensor<MaxPoolSizeReachedEvent> MAX_SIZE_REACHED_SENSOR = new BasicNotificationSensor<MaxPoolSizeReachedEvent>(
- MaxPoolSizeReachedEvent.class, "AutoScalerPolicyRebindTest.maxSizeReached");
- public static AttributeSensor<Integer> METRIC_SENSOR = Sensors.newIntegerSensor("AutoScalerPolicyRebindTest.metric");
-
- private DynamicCluster origCluster;
- private SimulatedLocation origLoc;
-
- @BeforeMethod(alwaysRun=true)
- @Override
- public void setUp() throws Exception {
- super.setUp();
- origLoc = origManagementContext.getLocationManager().createLocation(LocationSpec.create(SimulatedLocation.class));
- origCluster = origApp.createAndManageChild(EntitySpec.create(DynamicCluster.class)
- .configure("memberSpec", EntitySpec.create(TestEntity.class)));
- }
-
- @Test
- public void testRestoresAutoScalerConfig() throws Exception {
- origCluster.addPolicy(AutoScalerPolicy.builder()
- .name("myname")
- .metric(METRIC_SENSOR)
- .entityWithMetric(origCluster)
- .metricUpperBound(1)
- .metricLowerBound(2)
- .minPoolSize(0)
- .maxPoolSize(3)
- .minPeriodBetweenExecs(Duration.of(4, TimeUnit.MILLISECONDS))
- .resizeUpStabilizationDelay(Duration.of(5, TimeUnit.MILLISECONDS))
- .resizeDownStabilizationDelay(Duration.of(6, TimeUnit.MILLISECONDS))
- .poolHotSensor(POOL_HOT_SENSOR)
- .poolColdSensor(POOL_COLD_SENSOR)
- .poolOkSensor(POOL_OK_SENSOR)
- .maxSizeReachedSensor(MAX_SIZE_REACHED_SENSOR)
- .maxReachedNotificationDelay(Duration.of(7, TimeUnit.MILLISECONDS))
- .buildSpec());
-
- TestApplication newApp = rebind();
- DynamicCluster newCluster = (DynamicCluster) Iterables.getOnlyElement(newApp.getChildren());
- AutoScalerPolicy newPolicy = (AutoScalerPolicy) Iterables.getOnlyElement(newCluster.getPolicies());
-
- assertEquals(newPolicy.getDisplayName(), "myname");
- assertEquals(newPolicy.getConfig(AutoScalerPolicy.METRIC), METRIC_SENSOR);
- assertEquals(newPolicy.getConfig(AutoScalerPolicy.ENTITY_WITH_METRIC), newCluster);
- assertEquals(newPolicy.getConfig(AutoScalerPolicy.METRIC_UPPER_BOUND), 1);
- assertEquals(newPolicy.getConfig(AutoScalerPolicy.METRIC_LOWER_BOUND), 2);
- assertEquals(newPolicy.getConfig(AutoScalerPolicy.MIN_POOL_SIZE), (Integer)0);
- assertEquals(newPolicy.getConfig(AutoScalerPolicy.MAX_POOL_SIZE), (Integer)3);
- assertEquals(newPolicy.getConfig(AutoScalerPolicy.MIN_PERIOD_BETWEEN_EXECS), Duration.of(4, TimeUnit.MILLISECONDS));
- assertEquals(newPolicy.getConfig(AutoScalerPolicy.RESIZE_UP_STABILIZATION_DELAY), Duration.of(5, TimeUnit.MILLISECONDS));
- assertEquals(newPolicy.getConfig(AutoScalerPolicy.RESIZE_DOWN_STABILIZATION_DELAY), Duration.of(6, TimeUnit.MILLISECONDS));
- assertEquals(newPolicy.getConfig(AutoScalerPolicy.POOL_HOT_SENSOR), POOL_HOT_SENSOR);
- assertEquals(newPolicy.getConfig(AutoScalerPolicy.POOL_COLD_SENSOR), POOL_COLD_SENSOR);
- assertEquals(newPolicy.getConfig(AutoScalerPolicy.POOL_OK_SENSOR), POOL_OK_SENSOR);
- assertEquals(newPolicy.getConfig(AutoScalerPolicy.MAX_SIZE_REACHED_SENSOR), MAX_SIZE_REACHED_SENSOR);
- assertEquals(newPolicy.getConfig(AutoScalerPolicy.MAX_REACHED_NOTIFICATION_DELAY), Duration.of(7, TimeUnit.MILLISECONDS));
- }
-
- @Test
- public void testAutoScalerResizesAfterRebind() throws Exception {
- origCluster.start(ImmutableList.of(origLoc));
- origCluster.addPolicy(AutoScalerPolicy.builder()
- .name("myname")
- .metric(METRIC_SENSOR)
- .entityWithMetric(origCluster)
- .metricUpperBound(10)
- .metricLowerBound(100)
- .minPoolSize(1)
- .maxPoolSize(3)
- .buildSpec());
-
- TestApplication newApp = rebind();
- DynamicCluster newCluster = (DynamicCluster) Iterables.getOnlyElement(newApp.getChildren());
-
- assertEquals(newCluster.getCurrentSize(), (Integer)1);
-
- ((EntityInternal)newCluster).setAttribute(METRIC_SENSOR, 1000);
- EntityTestUtils.assertGroupSizeEqualsEventually(newCluster, 3);
-
- ((EntityInternal)newCluster).setAttribute(METRIC_SENSOR, 1);
- EntityTestUtils.assertGroupSizeEqualsEventually(newCluster, 1);
- }
-}
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/d30ff597/policy/src/test/java/brooklyn/policy/autoscaling/AutoScalerPolicyReconfigurationTest.java
----------------------------------------------------------------------
diff --git a/policy/src/test/java/brooklyn/policy/autoscaling/AutoScalerPolicyReconfigurationTest.java b/policy/src/test/java/brooklyn/policy/autoscaling/AutoScalerPolicyReconfigurationTest.java
deleted file mode 100644
index e685735..0000000
--- a/policy/src/test/java/brooklyn/policy/autoscaling/AutoScalerPolicyReconfigurationTest.java
+++ /dev/null
@@ -1,190 +0,0 @@
-/*
- * 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 brooklyn.policy.autoscaling;
-
-import static brooklyn.policy.autoscaling.AutoScalerPolicyTest.currentSizeAsserter;
-
-import org.apache.brooklyn.api.entity.proxying.EntitySpec;
-import org.apache.brooklyn.api.event.AttributeSensor;
-import org.apache.brooklyn.test.entity.TestApplication;
-import org.apache.brooklyn.test.entity.TestCluster;
-import org.testng.annotations.AfterMethod;
-import org.testng.annotations.BeforeMethod;
-import org.testng.annotations.Test;
-
-import brooklyn.entity.basic.Entities;
-import brooklyn.event.basic.Sensors;
-import brooklyn.test.Asserts;
-import brooklyn.util.time.Duration;
-
-import com.google.common.collect.ImmutableMap;
-
-public class AutoScalerPolicyReconfigurationTest {
-
- private static long TIMEOUT_MS = 10000;
-
- private static final AttributeSensor<Integer> MY_ATTRIBUTE = Sensors.newIntegerSensor("autoscaler.test.intAttrib");
- TestApplication app;
- TestCluster tc;
-
- @BeforeMethod(alwaysRun=true)
- public void before() throws Exception {
- app = TestApplication.Factory.newManagedInstanceForTests();
- tc = app.createAndManageChild(EntitySpec.create(TestCluster.class)
- .configure("initialSize", 1));
- }
-
- @AfterMethod(alwaysRun=true)
- public void tearDown() throws Exception {
- if (app != null) Entities.destroyAll(app.getManagementContext());
- }
-
- @Test
- public void testIncreaseMinPoolSizeCausesImmediateGrowth() {
- tc.resize(2);
-
- AutoScalerPolicy policy = new AutoScalerPolicy.Builder().metric(MY_ATTRIBUTE)
- .metricLowerBound(50).metricUpperBound(100)
- .minPoolSize(2)
- .build();
- tc.addPolicy(policy);
-
- policy.config().set(AutoScalerPolicy.MIN_POOL_SIZE, 3);
-
- Asserts.succeedsEventually(ImmutableMap.of("timeout", TIMEOUT_MS), currentSizeAsserter(tc, 3));
- }
-
- @Test
- public void testDecreaseMinPoolSizeAllowsSubsequentShrink() {
- tc.resize(4);
-
- AutoScalerPolicy policy = new AutoScalerPolicy.Builder().metric(MY_ATTRIBUTE)
- .metricLowerBound(50).metricUpperBound(100)
- .minPoolSize(2)
- .build();
- tc.addPolicy(policy);
-
- // 25*4 = 100 -> 2 nodes at 50 each
- tc.setAttribute(MY_ATTRIBUTE, 25);
- Asserts.succeedsEventually(ImmutableMap.of("timeout", TIMEOUT_MS), currentSizeAsserter(tc, 2));
-
- // Decreases to new min-size
- policy.config().set(AutoScalerPolicy.MIN_POOL_SIZE, 1);
- tc.setAttribute(MY_ATTRIBUTE, 0);
- Asserts.succeedsEventually(ImmutableMap.of("timeout", TIMEOUT_MS), currentSizeAsserter(tc, 1));
- }
-
- @Test
- public void testDecreaseMaxPoolSizeCausesImmediateShrink() {
- tc.resize(6);
-
- AutoScalerPolicy policy = new AutoScalerPolicy.Builder().metric(MY_ATTRIBUTE)
- .metricLowerBound(50).metricUpperBound(100)
- .maxPoolSize(6)
- .build();
- tc.addPolicy(policy);
-
- policy.config().set(AutoScalerPolicy.MAX_POOL_SIZE, 4);
-
- Asserts.succeedsEventually(ImmutableMap.of("timeout", TIMEOUT_MS), currentSizeAsserter(tc, 4));
- }
-
- @Test
- public void testIncreaseMaxPoolSizeAllowsSubsequentGrowth() {
- tc.resize(3);
-
- AutoScalerPolicy policy = new AutoScalerPolicy.Builder().metric(MY_ATTRIBUTE)
- .metricLowerBound(50).metricUpperBound(100)
- .maxPoolSize(6)
- .build();
- tc.addPolicy(policy);
-
- // 200*3 = 600 -> 6 nodes at 100 each
- tc.setAttribute(MY_ATTRIBUTE, 200);
- Asserts.succeedsEventually(ImmutableMap.of("timeout", TIMEOUT_MS), currentSizeAsserter(tc, 6));
-
- policy.config().set(AutoScalerPolicy.MAX_POOL_SIZE, 8);
-
- // Increases to max-size only
- tc.setAttribute(MY_ATTRIBUTE, 100000);
- Asserts.succeedsEventually(ImmutableMap.of("timeout", TIMEOUT_MS), currentSizeAsserter(tc, 8));
- }
-
- @Test
- public void testReconfigureMetricLowerBound() {
- tc.resize(2);
-
- AutoScalerPolicy policy = new AutoScalerPolicy.Builder().metric(MY_ATTRIBUTE)
- .metricLowerBound(50).metricUpperBound(100)
- .build();
- tc.addPolicy(policy);
-
- policy.config().set(AutoScalerPolicy.METRIC_LOWER_BOUND, 51);
-
- tc.setAttribute(MY_ATTRIBUTE, 50);
- Asserts.succeedsEventually(ImmutableMap.of("timeout", TIMEOUT_MS), currentSizeAsserter(tc, 1));
- }
-
- @Test
- public void testReconfigureMetricUpperBound() {
- tc.resize(1);
-
- AutoScalerPolicy policy = new AutoScalerPolicy.Builder().metric(MY_ATTRIBUTE)
- .metricLowerBound(50).metricUpperBound(100)
- .build();
- tc.addPolicy(policy);
-
- policy.config().set(AutoScalerPolicy.METRIC_UPPER_BOUND, 99);
-
- tc.setAttribute(MY_ATTRIBUTE, 100);
- Asserts.succeedsEventually(ImmutableMap.of("timeout", TIMEOUT_MS), currentSizeAsserter(tc, 2));
- }
-
- @Test
- public void testReconfigureResizeUpStabilizationDelay() {
- tc.resize(1);
-
- AutoScalerPolicy policy = new AutoScalerPolicy.Builder().metric(MY_ATTRIBUTE)
- .metricLowerBound(50).metricUpperBound(100)
- .resizeUpStabilizationDelay(Duration.TWO_MINUTES)
- .build();
- tc.addPolicy(policy);
-
- policy.config().set(AutoScalerPolicy.RESIZE_UP_STABILIZATION_DELAY, Duration.ZERO);
-
- tc.setAttribute(MY_ATTRIBUTE, 101);
- Asserts.succeedsEventually(ImmutableMap.of("timeout", TIMEOUT_MS), currentSizeAsserter(tc, 2));
- }
-
- @Test
- public void testReconfigureResizeDownStabilizationDelay() {
- tc.resize(2);
-
- AutoScalerPolicy policy = new AutoScalerPolicy.Builder().metric(MY_ATTRIBUTE)
- .metricLowerBound(50).metricUpperBound(100)
- .resizeDownStabilizationDelay(Duration.TWO_MINUTES)
- .build();
- tc.addPolicy(policy);
-
- policy.config().set(AutoScalerPolicy.RESIZE_DOWN_STABILIZATION_DELAY, Duration.ZERO);
-
- tc.setAttribute(MY_ATTRIBUTE, 1);
- Asserts.succeedsEventually(ImmutableMap.of("timeout", TIMEOUT_MS), currentSizeAsserter(tc, 1));
- }
-}
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/d30ff597/policy/src/test/java/brooklyn/policy/autoscaling/AutoScalerPolicyTest.java
----------------------------------------------------------------------
diff --git a/policy/src/test/java/brooklyn/policy/autoscaling/AutoScalerPolicyTest.java b/policy/src/test/java/brooklyn/policy/autoscaling/AutoScalerPolicyTest.java
deleted file mode 100644
index a4724f1..0000000
--- a/policy/src/test/java/brooklyn/policy/autoscaling/AutoScalerPolicyTest.java
+++ /dev/null
@@ -1,649 +0,0 @@
-/*
- * 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 brooklyn.policy.autoscaling;
-
-import static org.testng.Assert.assertEquals;
-import static org.testng.Assert.assertTrue;
-
-import java.lang.management.ManagementFactory;
-import java.lang.management.MemoryMXBean;
-import java.lang.management.MemoryUsage;
-import java.lang.management.OperatingSystemMXBean;
-import java.lang.management.ThreadInfo;
-import java.lang.management.ThreadMXBean;
-import java.util.List;
-import java.util.Map;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.atomic.AtomicInteger;
-
-import org.apache.brooklyn.api.entity.Entity;
-import org.apache.brooklyn.api.entity.proxying.EntitySpec;
-import org.apache.brooklyn.api.policy.PolicySpec;
-import org.apache.brooklyn.test.entity.TestApplication;
-import org.apache.brooklyn.test.entity.TestCluster;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.testng.Assert;
-import org.testng.annotations.AfterMethod;
-import org.testng.annotations.BeforeMethod;
-import org.testng.annotations.Test;
-
-import brooklyn.entity.basic.Entities;
-import brooklyn.entity.trait.Resizable;
-import brooklyn.event.basic.BasicNotificationSensor;
-import brooklyn.test.Asserts;
-import brooklyn.util.collections.MutableList;
-import brooklyn.util.collections.MutableMap;
-import brooklyn.util.time.Duration;
-
-import com.google.common.base.Stopwatch;
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.ImmutableMap;
-
-public class AutoScalerPolicyTest {
-
- private static final Logger log = LoggerFactory.getLogger(AutoScalerPolicyTest.class);
-
- private static long TIMEOUT_MS = 10*1000;
- private static long SHORT_WAIT_MS = 250;
- private static long OVERHEAD_DURATION_MS = 500;
- private static long EARLY_RETURN_MS = 10;
-
- private static final int MANY_TIMES_INVOCATION_COUNT = 10;
-
- AutoScalerPolicy policy;
- TestCluster cluster;
- LocallyResizableEntity resizable;
- TestApplication app;
- List<Integer> policyResizes = MutableList.of();
-
- @BeforeMethod(alwaysRun=true)
- public void setUp() throws Exception {
- log.info("resetting "+getClass().getSimpleName());
- app = TestApplication.Factory.newManagedInstanceForTests();
- cluster = app.createAndManageChild(EntitySpec.create(TestCluster.class).configure(TestCluster.INITIAL_SIZE, 1));
- resizable = new LocallyResizableEntity(cluster, cluster);
- Entities.manage(resizable);
- PolicySpec<AutoScalerPolicy> policySpec = PolicySpec.create(AutoScalerPolicy.class).configure(AutoScalerPolicy.RESIZE_OPERATOR, new ResizeOperator() {
- @Override
- public Integer resize(Entity entity, Integer desiredSize) {
- log.info("resizing to "+desiredSize);
- policyResizes.add(desiredSize);
- return ((Resizable)entity).resize(desiredSize);
- }
- });
- policy = resizable.addPolicy(policySpec);
- policyResizes.clear();
- }
-
- @AfterMethod(alwaysRun=true)
- public void tearDown() throws Exception {
- if (policy != null) policy.destroy();
- if (app != null) Entities.destroyAll(app.getManagementContext());
- cluster = null;
- resizable = null;
- policy = null;
- }
-
- public void assertSizeEventually(Integer targetSize) {
- Asserts.succeedsEventually(ImmutableMap.of("timeout", TIMEOUT_MS), currentSizeAsserter(resizable, targetSize));
- assertEquals(policyResizes.get(policyResizes.size()-1), targetSize);
- }
-
- @Test
- public void testShrinkColdPool() throws Exception {
- resizable.resize(4);
- // all metrics as per-node here
- resizable.emit(AutoScalerPolicy.DEFAULT_POOL_COLD_SENSOR, message(30d/4, 10, 20));
-
- // expect pool to shrink to 3 (i.e. maximum to have >= 10 per container)
- assertSizeEventually(3);
- }
-
- @Test
- public void testShrinkColdPoolTotals() throws Exception {
- resizable.resize(4);
- // all metrics as totals here
- resizable.emit(AutoScalerPolicy.DEFAULT_POOL_COLD_SENSOR, message(30L, 4*10L, 4*20L));
-
- // expect pool to shrink to 3 (i.e. maximum to have >= 10 per container)
- assertSizeEventually(3);
- }
-
- @Test
- public void testShrinkColdPoolRoundsUpDesiredNumberOfContainers() throws Exception {
- resizable.resize(4);
- resizable.emit(AutoScalerPolicy.DEFAULT_POOL_COLD_SENSOR, message(1L, 4*10L, 4*20L));
-
- Asserts.succeedsEventually(ImmutableMap.of("timeout", TIMEOUT_MS), currentSizeAsserter(resizable, 1));
- }
-
- @Test
- public void testGrowHotPool() throws Exception {
- resizable.resize(2);
- // all metrics as per-node here
- resizable.emit(AutoScalerPolicy.DEFAULT_POOL_HOT_SENSOR, message(21L, 10L, 20L));
-
- // expect pool to grow to 3 (i.e. minimum to have <= 20 per container)
- assertSizeEventually(3);
- }
-
- @Test
- public void testGrowHotPoolTotals() throws Exception {
- resizable.resize(2);
- // all metrics as totals here
- resizable.emit(AutoScalerPolicy.DEFAULT_POOL_HOT_SENSOR, message(41L, 2*10L, 2*20L));
-
- // expect pool to grow to 3 (i.e. minimum to have <= 20 per container)
- assertSizeEventually(3);
- }
-
- @Test
- public void testGrowShrinkRespectsResizeIterationIncrementAndResizeIterationMax() throws Exception {
- resizable.resize(2);
- policy.config().set(AutoScalerPolicy.RESIZE_UP_ITERATION_INCREMENT, 2);
- policy.config().set(AutoScalerPolicy.RESIZE_UP_ITERATION_MAX, 4);
- policy.config().set(AutoScalerPolicy.RESIZE_DOWN_ITERATION_INCREMENT, 3);
- policy.config().set(AutoScalerPolicy.RESIZE_DOWN_ITERATION_MAX, 3);
-
- resizable.emit(AutoScalerPolicy.DEFAULT_POOL_HOT_SENSOR, message(42/2, 10, 20));
- // expect pool to grow to 4 (i.e. to have <= 20 per container we need 3, but increment is 2)
- assertSizeEventually(4);
-
- resizable.emit(AutoScalerPolicy.DEFAULT_POOL_HOT_SENSOR, message(200/4, 10, 20));
- // a single hot message can only make it go to 8
- assertSizeEventually(8);
- assertEquals(policyResizes, MutableList.of(4, 8));
- resizable.emit(AutoScalerPolicy.DEFAULT_POOL_HOT_SENSOR, message(200/8, 10, 20));
- assertSizeEventually(10);
- assertEquals(policyResizes, MutableList.of(4, 8, 10));
-
- // now shrink
- policyResizes.clear();
- policy.config().set(AutoScalerPolicy.MIN_POOL_SIZE, 2);
- resizable.emit(AutoScalerPolicy.DEFAULT_POOL_COLD_SENSOR, message(1, 10, 20));
- assertSizeEventually(7);
- resizable.emit(AutoScalerPolicy.DEFAULT_POOL_COLD_SENSOR, message(1, 10, 20));
- assertSizeEventually(4);
- resizable.emit(AutoScalerPolicy.DEFAULT_POOL_COLD_SENSOR, message(1, 10, 20));
- assertSizeEventually(2);
- assertEquals(policyResizes, MutableList.of(7, 4, 2));
- }
-
- @Test
- public void testHasId() throws Exception {
- resizable.removePolicy(policy);
- policy = AutoScalerPolicy.builder()
- .minPoolSize(2)
- .build();
- resizable.addPolicy(policy);
- Assert.assertTrue(policy.getId()!=null);
- }
-
- @Test
- public void testNeverShrinkBelowMinimum() throws Exception {
- resizable.removePolicy(policy);
- policy = AutoScalerPolicy.builder()
- .minPoolSize(2)
- .build();
- resizable.addPolicy(policy);
-
- resizable.resize(4);
- resizable.emit(AutoScalerPolicy.DEFAULT_POOL_COLD_SENSOR, message(4, 0L, 4*10L, 4*20L));
-
- // expect pool to shrink only to the minimum
- Asserts.succeedsEventually(ImmutableMap.of("timeout", TIMEOUT_MS), currentSizeAsserter(resizable, 2));
- }
-
- @Test
- public void testNeverGrowAboveMaximmum() throws Exception {
- resizable.removePolicy(policy);
- policy = AutoScalerPolicy.builder()
- .maxPoolSize(5)
- .build();
- resizable.addPolicy(policy);
-
- resizable.resize(4);
- resizable.emit(AutoScalerPolicy.DEFAULT_POOL_HOT_SENSOR, message(4, 1000000L, 4*10L, 4*20L));
-
- // expect pool to grow only to the maximum
- Asserts.succeedsEventually(ImmutableMap.of("timeout", TIMEOUT_MS), currentSizeAsserter(resizable, 5));
- }
-
- @Test
- public void testNeverGrowColdPool() throws Exception {
- resizable.resize(2);
- resizable.emit(AutoScalerPolicy.DEFAULT_POOL_COLD_SENSOR, message(2, 1000L, 2*10L, 2*20L));
-
- Thread.sleep(SHORT_WAIT_MS);
- assertEquals(resizable.getCurrentSize(), (Integer)2);
- }
-
- @Test
- public void testNeverShrinkHotPool() throws Exception {
- resizable.resizeSleepTime = 0;
- resizable.resize(2);
- resizable.emit(AutoScalerPolicy.DEFAULT_POOL_HOT_SENSOR, message(2, 0L, 2*10L, 2*20L));
-
- // if had been a POOL_COLD, would have shrunk to 3
- Thread.sleep(SHORT_WAIT_MS);
- assertEquals(resizable.getCurrentSize(), (Integer)2);
- }
-
- @Test(groups="Integration")
- public void testConcurrentShrinkShrink() throws Exception {
- resizable.resizeSleepTime = 250;
- resizable.resize(4);
- resizable.emit(AutoScalerPolicy.DEFAULT_POOL_COLD_SENSOR, message(4, 30L, 4*10L, 4*20L));
- // would cause pool to shrink to 3
-
- resizable.emit(AutoScalerPolicy.DEFAULT_POOL_COLD_SENSOR, message(4, 1L, 4*10L, 4*20L));
- // now expect pool to shrink to 1
-
- Asserts.succeedsEventually(ImmutableMap.of("timeout", TIMEOUT_MS), currentSizeAsserter(resizable, 1));
- }
-
- @Test(groups="Integration")
- public void testConcurrentGrowGrow() throws Exception {
- resizable.resizeSleepTime = 250;
- resizable.resize(2);
- resizable.emit(AutoScalerPolicy.DEFAULT_POOL_HOT_SENSOR, message(2, 41L, 2*10L, 2*20L));
- // would cause pool to grow to 3
-
- resizable.emit(AutoScalerPolicy.DEFAULT_POOL_HOT_SENSOR, message(2, 81L, 2*10L, 2*20L));
- // now expect pool to grow to 5
-
- Asserts.succeedsEventually(ImmutableMap.of("timeout", TIMEOUT_MS), currentSizeAsserter(resizable, 5));
- }
-
- @Test(groups="Integration")
- public void testConcurrentGrowShrink() throws Exception {
- resizable.resizeSleepTime = 250;
- resizable.resize(2);
- resizable.emit(AutoScalerPolicy.DEFAULT_POOL_HOT_SENSOR, message(2, 81L, 2*10L, 2*20L));
- // would cause pool to grow to 5
-
- resizable.emit(AutoScalerPolicy.DEFAULT_POOL_COLD_SENSOR, message(2, 1L, 2*10L, 2*20L));
- // now expect pool to shrink to 1
-
- Asserts.succeedsEventually(ImmutableMap.of("timeout", TIMEOUT_MS), currentSizeAsserter(resizable, 1));
- }
-
- @Test(groups="Integration")
- public void testConcurrentShrinkGrow() throws Exception {
- resizable.resizeSleepTime = 250;
- resizable.resize(4);
- resizable.emit(AutoScalerPolicy.DEFAULT_POOL_COLD_SENSOR, message(4, 1L, 4*10L, 4*20L));
- // would cause pool to shrink to 1
-
- resizable.emit(AutoScalerPolicy.DEFAULT_POOL_HOT_SENSOR, message(4, 81L, 4*10L, 4*20L));
- // now expect pool to grow to 5
-
- Asserts.succeedsEventually(ImmutableMap.of("timeout", TIMEOUT_MS), currentSizeAsserter(resizable, 5));
- }
-
- // FIXME failed in jenkins (e.g. #1035); with "lists don't have the same size expected:<3> but was:<2>"
- // Is it just too time sensitive? But I'd have expected > 3 rather than less
- @Test(groups="WIP")
- public void testRepeatedQueuedResizeTakesLatestValueRatherThanIntermediateValues() throws Exception {
- // TODO is this too time sensitive? the resize takes only 250ms so if it finishes before the next emit we'd also see size=2
- resizable.resizeSleepTime = 500;
- resizable.resize(4);
- resizable.emit(AutoScalerPolicy.DEFAULT_POOL_COLD_SENSOR, message(4, 30L, 4*10L, 4*20L)); // shrink to 3
- resizable.emit(AutoScalerPolicy.DEFAULT_POOL_COLD_SENSOR, message(4, 20L, 4*10L, 4*20L)); // shrink to 2
- resizable.emit(AutoScalerPolicy.DEFAULT_POOL_COLD_SENSOR, message(4, 10L, 4*10L, 4*20L)); // shrink to 1
-
- Asserts.succeedsEventually(ImmutableMap.of("timeout", TIMEOUT_MS), currentSizeAsserter(resizable, 1));
- assertEquals(resizable.sizes, ImmutableList.of(4, 3, 1));
- }
-
-
- @Test
- public void testUsesResizeOperatorOverride() throws Exception {
- resizable.removePolicy(policy);
-
- final AtomicInteger counter = new AtomicInteger();
- policy = AutoScalerPolicy.builder()
- .resizeOperator(new ResizeOperator() {
- @Override public Integer resize(Entity entity, Integer desiredSize) {
- counter.incrementAndGet();
- return desiredSize;
- }})
- .build();
- resizable.addPolicy(policy);
-
- resizable.emit(AutoScalerPolicy.DEFAULT_POOL_HOT_SENSOR, message(1, 21L, 1*10L, 1*20L)); // grow to 2
-
- Asserts.succeedsEventually(MutableMap.of("timeout",TIMEOUT_MS), new Runnable() {
- public void run() {
- assertTrue(counter.get() >= 1, "cccounter="+counter);
- }});
- }
-
- @Test
- public void testUsesCustomSensorOverride() throws Exception {
- resizable.removePolicy(policy);
-
- @SuppressWarnings("rawtypes")
- BasicNotificationSensor<Map> customPoolHotSensor = new BasicNotificationSensor<Map>(Map.class, "custom.hot", "");
- @SuppressWarnings("rawtypes")
- BasicNotificationSensor<Map> customPoolColdSensor = new BasicNotificationSensor<Map>(Map.class, "custom.cold", "");
- @SuppressWarnings("rawtypes")
- BasicNotificationSensor<Map> customPoolOkSensor = new BasicNotificationSensor<Map>(Map.class, "custom.ok", "");
- policy = AutoScalerPolicy.builder()
- .poolHotSensor(customPoolHotSensor)
- .poolColdSensor(customPoolColdSensor)
- .poolOkSensor(customPoolOkSensor)
- .build();
- resizable.addPolicy(policy);
-
- resizable.emit(customPoolHotSensor, message(1, 21L, 1*10L, 1*20L)); // grow to 2
- Asserts.succeedsEventually(ImmutableMap.of("timeout", TIMEOUT_MS), currentSizeAsserter(resizable, 2));
-
- resizable.emit(customPoolColdSensor, message(2, 1L, 1*10L, 1*20L)); // shrink to 1
- Asserts.succeedsEventually(ImmutableMap.of("timeout", TIMEOUT_MS), currentSizeAsserter(resizable, 1));
- }
-
- @Test(groups="Integration")
- public void testResizeUpStabilizationDelayIgnoresBlip() throws Exception {
- long resizeUpStabilizationDelay = 1000L;
- Duration minPeriodBetweenExecs = Duration.ZERO;
- resizable.removePolicy(policy);
-
- policy = AutoScalerPolicy.builder()
- .resizeUpStabilizationDelay(Duration.of(resizeUpStabilizationDelay, TimeUnit.MILLISECONDS))
- .minPeriodBetweenExecs(minPeriodBetweenExecs)
- .build();
- resizable.addPolicy(policy);
- resizable.resize(1);
-
- // Ignores temporary blip
- resizable.emit(AutoScalerPolicy.DEFAULT_POOL_HOT_SENSOR, message(1, 61L, 1*10L, 1*20L)); // would grow to 4
- Thread.sleep(resizeUpStabilizationDelay-OVERHEAD_DURATION_MS);
- resizable.emit(AutoScalerPolicy.DEFAULT_POOL_OK_SENSOR, message(1, 11L, 4*10L, 4*20L)); // but 1 is still adequate
-
- assertEquals(resizable.getCurrentSize(), (Integer)1);
- Asserts.succeedsContinually(MutableMap.of("duration", 2000L), new Runnable() {
- @Override public void run() {
- assertEquals(resizable.sizes, ImmutableList.of(1));
- }});
- }
-
- // FIXME failing in jenkins occassionally - have put it in the "Acceptance" group for now
- //
- // Error was things like it taking a couple of seconds too long to scale-up. This is *not*
- // just caused by a slow GC (running with -verbose:gc shows during a failure several
- // incremental GCs that usually don't amount to more than 0.2 of a second at most, often less).
- // Doing a thread-dump etc immediately after the too-long delay shows no strange thread usage,
- // and shows releng3 system load averages of numbers like 1.73, 2.87 and 1.22.
- //
- // Is healthy on normal machines.
- @Test(groups={"Integration", "Acceptance"}, invocationCount=MANY_TIMES_INVOCATION_COUNT)
- public void testRepeatedResizeUpStabilizationDelayTakesMaxSustainedDesired() throws Throwable {
- try {
- testResizeUpStabilizationDelayTakesMaxSustainedDesired();
- } catch (Throwable t) {
- dumpThreadsEtc();
- throw t;
- }
- }
-
- @Test(groups="Integration")
- public void testResizeUpStabilizationDelayTakesMaxSustainedDesired() throws Exception {
- long resizeUpStabilizationDelay = 1100L;
- Duration minPeriodBetweenExecs = Duration.ZERO;
- resizable.removePolicy(policy);
-
- policy = AutoScalerPolicy.builder()
- .resizeUpStabilizationDelay(Duration.of(resizeUpStabilizationDelay, TimeUnit.MILLISECONDS))
- .minPeriodBetweenExecs(minPeriodBetweenExecs)
- .build();
- resizable.addPolicy(policy);
- resizable.resize(1);
-
- // Will grow to only the max sustained in this time window
- // (i.e. to 2 within the first $resizeUpStabilizationDelay milliseconds)
- Stopwatch stopwatch = Stopwatch.createStarted();
-
- resizable.emit(AutoScalerPolicy.DEFAULT_POOL_HOT_SENSOR, message(1, 61L, 1*10L, 1*20L)); // would grow to 4
- resizable.emit(AutoScalerPolicy.DEFAULT_POOL_HOT_SENSOR, message(1, 21L, 1*10L, 1*20L)); // would grow to 2
- Thread.sleep(resizeUpStabilizationDelay-OVERHEAD_DURATION_MS);
-
- long postSleepTime = stopwatch.elapsed(TimeUnit.MILLISECONDS);
-
- resizable.emit(AutoScalerPolicy.DEFAULT_POOL_HOT_SENSOR, message(1, 61L, 1*10L, 1*20L)); // would grow to 4
-
- // Wait for it to reach size 2, and confirm take expected time
- // TODO This is time sensitive, and sometimes fails in CI with size=4 if we wait for currentSize==2 (presumably GC kicking in?)
- // Therefore do strong assertion of currentSize==2 later, so can write out times if it goes wrong.
- Asserts.succeedsEventually(MutableMap.of("period", 1, "timeout", TIMEOUT_MS), new Runnable() {
- public void run() {
- assertTrue(resizable.getCurrentSize() >= 2, "currentSize="+resizable.getCurrentSize());
- }});
- assertEquals(resizable.getCurrentSize(), (Integer)2,
- stopwatch.elapsed(TimeUnit.MILLISECONDS)+"ms after first emission; "+(stopwatch.elapsed(TimeUnit.MILLISECONDS)-postSleepTime)+"ms after last");
-
- long timeToResizeTo2 = stopwatch.elapsed(TimeUnit.MILLISECONDS);
- assertTrue(timeToResizeTo2 >= resizeUpStabilizationDelay-EARLY_RETURN_MS &&
- timeToResizeTo2 <= resizeUpStabilizationDelay+OVERHEAD_DURATION_MS,
- "Resizing to 2: time="+timeToResizeTo2+"; resizeUpStabilizationDelay="+resizeUpStabilizationDelay);
-
- // Will then grow to 4 $resizeUpStabilizationDelay milliseconds after that emission
- Asserts.succeedsEventually(MutableMap.of("period", 1, "timeout", TIMEOUT_MS),
- currentSizeAsserter(resizable, 4));
- long timeToResizeTo4 = stopwatch.elapsed(TimeUnit.MILLISECONDS) - postSleepTime;
-
- assertTrue(timeToResizeTo4 >= resizeUpStabilizationDelay-EARLY_RETURN_MS &&
- timeToResizeTo4 <= resizeUpStabilizationDelay+OVERHEAD_DURATION_MS,
- "Resizing to 4: timeToResizeTo4="+timeToResizeTo4+"; timeToResizeTo2="+timeToResizeTo2+"; resizeUpStabilizationDelay="+resizeUpStabilizationDelay);
- }
-
- @Test(groups="Integration")
- public void testResizeUpStabilizationDelayResizesAfterDelay() {
- final long resizeUpStabilizationDelay = 1000L;
- Duration minPeriodBetweenExecs = Duration.ZERO;
- resizable.removePolicy(policy);
-
- policy = resizable.addPolicy(AutoScalerPolicy.builder()
- .resizeUpStabilizationDelay(Duration.of(resizeUpStabilizationDelay, TimeUnit.MILLISECONDS))
- .minPeriodBetweenExecs(minPeriodBetweenExecs)
- .buildSpec());
- resizable.resize(1);
-
- // After suitable delay, grows to desired
- final long emitTime = System.currentTimeMillis();
- final Map<String, Object> need4 = message(1, 61L, 1*10L, 1*20L);
- resizable.emit(AutoScalerPolicy.DEFAULT_POOL_HOT_SENSOR, need4); // would grow to 4
- final AtomicInteger emitCount = new AtomicInteger(0);
-
- Asserts.succeedsEventually(MutableMap.of("timeout", TIMEOUT_MS), new Runnable() {
- public void run() {
- if (System.currentTimeMillis() - emitTime > (2+emitCount.get())*resizeUpStabilizationDelay) {
- //first one may not have been received, in a registration race
- resizable.emit(AutoScalerPolicy.DEFAULT_POOL_HOT_SENSOR, need4);
- emitCount.incrementAndGet();
- }
- assertEquals(resizable.getCurrentSize(), (Integer)4);
- }});
-
- long resizeDelay = System.currentTimeMillis() - emitTime;
- assertTrue(resizeDelay >= (resizeUpStabilizationDelay-EARLY_RETURN_MS), "resizeDelay="+resizeDelay);
- }
-
- @Test(groups="Integration")
- public void testResizeDownStabilizationDelayIgnoresBlip() throws Exception {
- long resizeStabilizationDelay = 1000L;
- Duration minPeriodBetweenExecs = Duration.ZERO;
- resizable.removePolicy(policy);
-
- policy = AutoScalerPolicy.builder()
- .resizeDownStabilizationDelay(Duration.of(resizeStabilizationDelay, TimeUnit.MILLISECONDS))
- .minPeriodBetweenExecs(minPeriodBetweenExecs)
- .build();
- resizable.addPolicy(policy);
- resizable.resize(2);
-
- // Ignores temporary blip
- resizable.emit(AutoScalerPolicy.DEFAULT_POOL_COLD_SENSOR, message(2, 1L, 2*10L, 2*20L)); // would shrink to 1
- Thread.sleep(resizeStabilizationDelay-OVERHEAD_DURATION_MS);
- resizable.emit(AutoScalerPolicy.DEFAULT_POOL_OK_SENSOR, message(2, 20L, 1*10L, 1*20L)); // but 2 is still adequate
-
- assertEquals(resizable.getCurrentSize(), (Integer)2);
- Asserts.succeedsContinually(MutableMap.of("duration", 2000L), new Runnable() {
- public void run() {
- assertEquals(resizable.sizes, ImmutableList.of(2));
- }});
- }
-
- // FIXME Acceptance -- see comment against testRepeatedResizeUpStabilizationDelayTakesMaxSustainedDesired
- @Test(groups={"Integration", "Acceptance"}, invocationCount=MANY_TIMES_INVOCATION_COUNT)
- public void testRepeatedResizeDownStabilizationDelayTakesMinSustainedDesired() throws Throwable {
- try {
- testResizeDownStabilizationDelayTakesMinSustainedDesired();
- } catch (Throwable t) {
- dumpThreadsEtc();
- throw t;
- }
- }
-
- @Test(groups="Integration")
- public void testResizeDownStabilizationDelayTakesMinSustainedDesired() throws Exception {
- long resizeDownStabilizationDelay = 1100L;
- Duration minPeriodBetweenExecs = Duration.ZERO;
- policy.suspend();
- resizable.removePolicy(policy);
-
- policy = AutoScalerPolicy.builder()
- .resizeDownStabilizationDelay(Duration.of(resizeDownStabilizationDelay, TimeUnit.MILLISECONDS))
- .minPeriodBetweenExecs(minPeriodBetweenExecs)
- .build();
- resizable.addPolicy(policy);
- resizable.resize(3);
-
- // Will shrink to only the min sustained in this time window
- // (i.e. to 2 within the first $resizeUpStabilizationDelay milliseconds)
- Stopwatch stopwatch = Stopwatch.createStarted();
-
- resizable.emit(AutoScalerPolicy.DEFAULT_POOL_COLD_SENSOR, message(3, 1L, 3*10L, 3*20L)); // would shrink to 1
- resizable.emit(AutoScalerPolicy.DEFAULT_POOL_COLD_SENSOR, message(3, 20L, 3*10L, 3*20L)); // would shrink to 2
- Thread.sleep(resizeDownStabilizationDelay-OVERHEAD_DURATION_MS);
-
- long postSleepTime = stopwatch.elapsed(TimeUnit.MILLISECONDS);
-
- resizable.emit(AutoScalerPolicy.DEFAULT_POOL_COLD_SENSOR, message(3, 1L, 3*10L, 3*20L)); // would shrink to 1
-
- // Wait for it to reach size 2, and confirm take expected time
- // TODO This is time sensitive, and sometimes fails in CI with size=1 if we wait for currentSize==2 (presumably GC kicking in?)
- // Therefore do strong assertion of currentSize==2 later, so can write out times if it goes wrong.
- Asserts.succeedsEventually(MutableMap.of("period", 1, "timeout", TIMEOUT_MS), new Runnable() {
- public void run() {
- assertTrue(resizable.getCurrentSize() <= 2, "currentSize="+resizable.getCurrentSize());
- }});
- assertEquals(resizable.getCurrentSize(), (Integer)2,
- stopwatch.elapsed(TimeUnit.MILLISECONDS)+"ms after first emission; "+(stopwatch.elapsed(TimeUnit.MILLISECONDS)-postSleepTime)+"ms after last");
-
- long timeToResizeTo2 = stopwatch.elapsed(TimeUnit.MILLISECONDS);
- assertTrue(timeToResizeTo2 >= resizeDownStabilizationDelay-EARLY_RETURN_MS &&
- timeToResizeTo2 <= resizeDownStabilizationDelay+OVERHEAD_DURATION_MS,
- "Resizing to 2: time="+timeToResizeTo2+"; resizeDownStabilizationDelay="+resizeDownStabilizationDelay);
-
- // Will then shrink to 1 $resizeUpStabilizationDelay milliseconds after that emission
- Asserts.succeedsEventually(MutableMap.of("period", 1, "timeout", TIMEOUT_MS),
- currentSizeAsserter(resizable, 1));
- long timeToResizeTo1 = stopwatch.elapsed(TimeUnit.MILLISECONDS) - postSleepTime;
-
- assertTrue(timeToResizeTo1 >= resizeDownStabilizationDelay-EARLY_RETURN_MS &&
- timeToResizeTo1 <= resizeDownStabilizationDelay+OVERHEAD_DURATION_MS,
- "Resizing to 1: timeToResizeTo1="+timeToResizeTo1+"; timeToResizeTo2="+timeToResizeTo2+"; resizeDownStabilizationDelay="+resizeDownStabilizationDelay);
- }
-
- @Test(groups="Integration")
- public void testResizeDownStabilizationDelayResizesAfterDelay() throws Exception {
- final long resizeDownStabilizationDelay = 1000L;
- Duration minPeriodBetweenExecs = Duration.ZERO;
- resizable.removePolicy(policy);
-
- policy = AutoScalerPolicy.builder()
- .resizeDownStabilizationDelay(Duration.of(resizeDownStabilizationDelay, TimeUnit.MILLISECONDS))
- .minPeriodBetweenExecs(minPeriodBetweenExecs)
- .build();
- resizable.addPolicy(policy);
- resizable.resize(2);
-
- // After suitable delay, grows to desired
- final long emitTime = System.currentTimeMillis();
- final Map<String, Object> needJust1 = message(2, 1L, 2*10L, 2*20L);
- resizable.emit(AutoScalerPolicy.DEFAULT_POOL_COLD_SENSOR, needJust1); // would shrink to 1
- final AtomicInteger emitCount = new AtomicInteger(0);
-
- Asserts.succeedsEventually(MutableMap.of("timeout", TIMEOUT_MS), new Runnable() {
- public void run() {
- if (System.currentTimeMillis() - emitTime > (2+emitCount.get())*resizeDownStabilizationDelay) {
- //first one may not have been received, in a registration race
- resizable.emit(AutoScalerPolicy.DEFAULT_POOL_COLD_SENSOR, needJust1); // would shrink to 1
- emitCount.incrementAndGet();
- }
- assertEquals(resizable.getCurrentSize(), (Integer)1);
- }});
-
- long resizeDelay = System.currentTimeMillis() - emitTime;
- assertTrue(resizeDelay >= (resizeDownStabilizationDelay-EARLY_RETURN_MS), "resizeDelay="+resizeDelay);
- }
-
- Map<String, Object> message(double currentWorkrate, double lowThreshold, double highThreshold) {
- return message(resizable.getCurrentSize(), currentWorkrate, lowThreshold, highThreshold);
- }
- static Map<String, Object> message(int currentSize, double currentWorkrate, double lowThreshold, double highThreshold) {
- return ImmutableMap.<String,Object>of(
- AutoScalerPolicy.POOL_CURRENT_SIZE_KEY, currentSize,
- AutoScalerPolicy.POOL_CURRENT_WORKRATE_KEY, currentWorkrate,
- AutoScalerPolicy.POOL_LOW_THRESHOLD_KEY, lowThreshold,
- AutoScalerPolicy.POOL_HIGH_THRESHOLD_KEY, highThreshold);
- }
-
- public static Runnable currentSizeAsserter(final Resizable resizable, final Integer desired) {
- return new Runnable() {
- public void run() {
- assertEquals(resizable.getCurrentSize(), desired);
- }
- };
- }
-
- public static void dumpThreadsEtc() {
- ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean();
- ThreadInfo[] threads = threadMXBean.dumpAllThreads(true, true);
- for (ThreadInfo thread : threads) {
- System.out.println(thread.getThreadName()+" ("+thread.getThreadState()+")");
- for (StackTraceElement stackTraceElement : thread.getStackTrace()) {
- System.out.println("\t"+stackTraceElement);
- }
- }
-
- MemoryMXBean memoryMXBean = ManagementFactory.getMemoryMXBean();
- MemoryUsage heapMemoryUsage = memoryMXBean.getHeapMemoryUsage();
- MemoryUsage nonHeapMemoryUsage = memoryMXBean.getNonHeapMemoryUsage();
- System.out.println("Memory:");
- System.out.println("\tHeap: used="+heapMemoryUsage.getUsed()+"; max="+heapMemoryUsage.getMax()+"; init="+heapMemoryUsage.getInit()+"; committed="+heapMemoryUsage.getCommitted());
- System.out.println("\tNon-heap: used="+nonHeapMemoryUsage.getUsed()+"; max="+nonHeapMemoryUsage.getMax()+"; init="+nonHeapMemoryUsage.getInit()+"; committed="+nonHeapMemoryUsage.getCommitted());
-
- OperatingSystemMXBean operatingSystemMXBean = ManagementFactory.getOperatingSystemMXBean();
- System.out.println("OS:");
- System.out.println("\tsysLoadAvg="+operatingSystemMXBean.getSystemLoadAverage()+"; availableProcessors="+operatingSystemMXBean.getAvailableProcessors()+"; arch="+operatingSystemMXBean.getArch());
- }
-}
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/d30ff597/policy/src/test/java/brooklyn/policy/autoscaling/LocallyResizableEntity.java
----------------------------------------------------------------------
diff --git a/policy/src/test/java/brooklyn/policy/autoscaling/LocallyResizableEntity.java b/policy/src/test/java/brooklyn/policy/autoscaling/LocallyResizableEntity.java
deleted file mode 100644
index c6b3c09..0000000
--- a/policy/src/test/java/brooklyn/policy/autoscaling/LocallyResizableEntity.java
+++ /dev/null
@@ -1,73 +0,0 @@
-/*
- * 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 brooklyn.policy.autoscaling;
-
-import java.util.List;
-
-import org.apache.brooklyn.api.entity.Entity;
-import org.apache.brooklyn.test.entity.TestCluster;
-
-import brooklyn.entity.basic.AbstractEntity;
-import brooklyn.entity.trait.Resizable;
-import brooklyn.entity.trait.Startable;
-
-import com.google.common.base.Throwables;
-import com.google.common.collect.Lists;
-
-/**
- * Test class for providing a Resizable LocallyManagedEntity for policy testing
- * It is hooked up to a TestCluster that can be used to make assertions against
- */
-public class LocallyResizableEntity extends AbstractEntity implements Resizable {
- List<Integer> sizes = Lists.newArrayList();
- TestCluster cluster;
- long resizeSleepTime = 0;
-
- public LocallyResizableEntity (TestCluster tc) {
- this(null, tc);
- }
- @SuppressWarnings("deprecation")
- public LocallyResizableEntity (Entity parent, TestCluster tc) {
- super(parent);
- this.cluster = tc;
- setAttribute(Startable.SERVICE_UP, true);
- }
-
- @Override
- public Integer resize(Integer newSize) {
- try {
- Thread.sleep(resizeSleepTime);
- sizes.add(newSize);
- return cluster.resize(newSize);
- } catch (InterruptedException e) {
- Thread.currentThread().interrupt();
- throw Throwables.propagate(e);
- }
- }
-
- @Override
- public Integer getCurrentSize() {
- return cluster.getCurrentSize();
- }
-
- @Override
- public String toString() {
- return getDisplayName();
- }
-}
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/d30ff597/policy/src/test/java/brooklyn/policy/followthesun/AbstractFollowTheSunPolicyTest.java
----------------------------------------------------------------------
diff --git a/policy/src/test/java/brooklyn/policy/followthesun/AbstractFollowTheSunPolicyTest.java b/policy/src/test/java/brooklyn/policy/followthesun/AbstractFollowTheSunPolicyTest.java
deleted file mode 100644
index c8855f2..0000000
--- a/policy/src/test/java/brooklyn/policy/followthesun/AbstractFollowTheSunPolicyTest.java
+++ /dev/null
@@ -1,239 +0,0 @@
-/*
- * 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 brooklyn.policy.followthesun;
-
-import static org.testng.Assert.assertEquals;
-
-import java.util.Collection;
-import java.util.Map;
-import java.util.Random;
-import java.util.Set;
-
-import org.apache.brooklyn.api.entity.Entity;
-import org.apache.brooklyn.api.entity.Group;
-import org.apache.brooklyn.api.entity.basic.EntityLocal;
-import org.apache.brooklyn.api.entity.proxying.EntitySpec;
-import org.apache.brooklyn.api.location.Location;
-import org.apache.brooklyn.api.location.LocationSpec;
-import org.apache.brooklyn.api.management.ManagementContext;
-import org.apache.brooklyn.test.entity.LocalManagementContextForTests;
-import org.apache.brooklyn.test.entity.TestApplication;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.testng.annotations.AfterMethod;
-import org.testng.annotations.BeforeMethod;
-
-import brooklyn.entity.basic.ApplicationBuilder;
-import brooklyn.entity.basic.DynamicGroup;
-import brooklyn.entity.basic.Entities;
-
-import org.apache.brooklyn.location.basic.SimulatedLocation;
-
-import brooklyn.policy.loadbalancing.BalanceableContainer;
-import brooklyn.policy.loadbalancing.MockContainerEntity;
-import brooklyn.policy.loadbalancing.MockItemEntity;
-import brooklyn.policy.loadbalancing.MockItemEntityImpl;
-import brooklyn.policy.loadbalancing.Movable;
-import brooklyn.test.Asserts;
-import brooklyn.util.collections.MutableMap;
-import brooklyn.util.time.Time;
-
-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;
-import com.google.common.collect.Maps;
-
-public class AbstractFollowTheSunPolicyTest {
-
- private static final Logger LOG = LoggerFactory.getLogger(AbstractFollowTheSunPolicyTest.class);
-
- protected static final long TIMEOUT_MS = 10*1000;
- protected static final long SHORT_WAIT_MS = 250;
-
- protected static final long CONTAINER_STARTUP_DELAY_MS = 100;
-
- protected TestApplication app;
- protected ManagementContext managementContext;
- protected SimulatedLocation loc1;
- protected SimulatedLocation loc2;
- protected FollowTheSunPool pool;
- protected DefaultFollowTheSunModel<Entity, Movable> model;
- protected FollowTheSunPolicy policy;
- protected Group containerGroup;
- protected Group itemGroup;
- protected Random random = new Random();
-
- @BeforeMethod(alwaysRun=true)
- public void setUp() throws Exception {
- LOG.debug("In AbstractFollowTheSunPolicyTest.setUp()");
-
- MockItemEntityImpl.totalMoveCount.set(0);
- MockItemEntityImpl.lastMoveTime.set(0);
-
- managementContext = LocalManagementContextForTests.newInstance();
- app = ApplicationBuilder.newManagedApp(TestApplication.class, managementContext);
-
- loc1 = managementContext.getLocationManager().createLocation(LocationSpec.create(SimulatedLocation.class).configure("name", "loc1"));
- loc2 = managementContext.getLocationManager().createLocation(LocationSpec.create(SimulatedLocation.class).configure("name", "loc2"));
-
- containerGroup = app.createAndManageChild(EntitySpec.create(DynamicGroup.class)
- .displayName("containerGroup")
- .configure(DynamicGroup.ENTITY_FILTER, Predicates.instanceOf(MockContainerEntity.class)));
-
- itemGroup = app.createAndManageChild(EntitySpec.create(DynamicGroup.class)
- .displayName("itemGroup")
- .configure(DynamicGroup.ENTITY_FILTER, Predicates.instanceOf(MockItemEntity.class)));
- model = new DefaultFollowTheSunModel<Entity, Movable>("pool-model");
- pool = app.createAndManageChild(EntitySpec.create(FollowTheSunPool.class));
- pool.setContents(containerGroup, itemGroup);
- policy = new FollowTheSunPolicy(MockItemEntity.ITEM_USAGE_METRIC, model, FollowTheSunParameters.newDefault());
- pool.addPolicy(policy);
- app.start(ImmutableList.of(loc1, loc2));
- }
-
- @AfterMethod(alwaysRun=true)
- public void tearDown() {
- if (pool != null && policy != null) pool.removePolicy(policy);
- if (app != null) Entities.destroyAll(app.getManagementContext());
- MockItemEntityImpl.totalMoveCount.set(0);
- MockItemEntityImpl.lastMoveTime.set(0);
- }
-
- /**
- * Asserts that the given container have the given expected workrates (by querying the containers directly).
- * Accepts an accuracy of "precision" for each container's workrate.
- */
- protected void assertItemDistributionEventually(final Map<MockContainerEntity, ? extends Collection<MockItemEntity>> expected) {
- try {
- Asserts.succeedsEventually(MutableMap.of("timeout", TIMEOUT_MS), new Runnable() {
- public void run() {
- assertItemDistribution(expected);
- }});
- } catch (AssertionError e) {
- String errMsg = e.getMessage()+"; "+verboseDumpToString();
- throw new RuntimeException(errMsg, e);
- }
- }
-
- protected void assertItemDistributionContinually(final Map<MockContainerEntity, Collection<MockItemEntity>> expected) {
- try {
- Asserts.succeedsContinually(MutableMap.of("timeout", SHORT_WAIT_MS), new Runnable() {
- public void run() {
- assertItemDistribution(expected);
- }});
- } catch (AssertionError e) {
- String errMsg = e.getMessage()+"; "+verboseDumpToString();
- throw new RuntimeException(errMsg, e);
- }
- }
-
- protected void assertItemDistribution(Map<MockContainerEntity, ? extends Collection<MockItemEntity>> expected) {
- String errMsg = verboseDumpToString();
- for (Map.Entry<MockContainerEntity, ? extends Collection<MockItemEntity>> entry : expected.entrySet()) {
- MockContainerEntity container = entry.getKey();
- Collection<MockItemEntity> expectedItems = entry.getValue();
-
- assertEquals(ImmutableSet.copyOf(container.getBalanceableItems()), ImmutableSet.copyOf(expectedItems), errMsg);
- }
- }
-
- protected String verboseDumpToString() {
- Iterable<MockContainerEntity> containers = Iterables.filter(app.getManagementContext().getEntityManager().getEntities(), MockContainerEntity.class);
- Iterable<MockItemEntity> items = Iterables.filter(app.getManagementContext().getEntityManager().getEntities(), MockItemEntity.class);
-
- Iterable<Double> containerRates = Iterables.transform(containers, new Function<MockContainerEntity, Double>() {
- @Override public Double apply(MockContainerEntity input) {
- return (double) input.getWorkrate();
- }});
-
- Iterable<Map<Entity, Double>> containerItemUsages = Iterables.transform(containers, new Function<MockContainerEntity, Map<Entity, Double>>() {
- @Override public Map<Entity, Double> apply(MockContainerEntity input) {
- return input.getItemUsage();
- }});
-
- Map<MockContainerEntity, Set<Movable>> itemDistributionByContainer = Maps.newLinkedHashMap();
- for (MockContainerEntity container : containers) {
- itemDistributionByContainer.put(container, container.getBalanceableItems());
- }
-
- Map<Movable, BalanceableContainer<?>> itemDistributionByItem = Maps.newLinkedHashMap();
- for (Movable item : items) {
- itemDistributionByItem.put(item, item.getAttribute(Movable.CONTAINER));
- }
-
- String modelItemDistribution = model.itemDistributionToString();
-
- return "containers="+containers+"; containerRates="+containerRates
- +"; containerItemUsages="+containerItemUsages
- +"; itemDistributionByContainer="+itemDistributionByContainer
- +"; itemDistributionByItem="+itemDistributionByItem
- +"; model="+modelItemDistribution
- +"; totalMoves="+MockItemEntityImpl.totalMoveCount
- +"; lastMoveTime="+Time.makeDateString(MockItemEntityImpl.lastMoveTime.get());
- }
-
- protected MockContainerEntity newContainer(TestApplication app, Location loc, String name) {
- return newAsyncContainer(app, loc, name, 0);
- }
-
- /**
- * Creates a new container that will take "delay" millis to complete its start-up.
- */
- protected MockContainerEntity newAsyncContainer(TestApplication app, Location loc, String name, long delay) {
- // FIXME Is this comment true?
- // Annoyingly, can't set parent until after the threshold config has been defined.
- MockContainerEntity container = app.createAndManageChild(EntitySpec.create(MockContainerEntity.class)
- .displayName(name)
- .configure(MockContainerEntity.DELAY, delay));
- LOG.debug("Managed new container {}", container);
- container.start(ImmutableList.of(loc));
- return container;
- }
-
- protected static MockItemEntity newLockedItem(TestApplication app, MockContainerEntity container, String name) {
- MockItemEntity item = app.createAndManageChild(EntitySpec.create(MockItemEntity.class)
- .displayName(name)
- .configure(MockItemEntity.IMMOVABLE, true));
- LOG.debug("Managed new locked item {}", container);
- if (container != null) {
- item.move(container);
- }
- return item;
- }
-
- protected static MockItemEntity newItem(TestApplication app, MockContainerEntity container, String name) {
- MockItemEntity item = app.createAndManageChild(EntitySpec.create(MockItemEntity.class)
- .displayName(name));
- LOG.debug("Managed new item {} at {}", item, container);
- if (container != null) {
- item.move(container);
- }
- return item;
- }
-
- protected static MockItemEntity newItem(TestApplication app, MockContainerEntity container, String name, Map<? extends Entity, Double> workpattern) {
- MockItemEntity item = newItem(app, container, name);
- if (workpattern != null) {
- ((EntityLocal)item).setAttribute(MockItemEntity.ITEM_USAGE_METRIC, (Map) workpattern);
- }
- return item;
- }
-}
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/d30ff597/policy/src/test/java/brooklyn/policy/followthesun/FollowTheSunModelTest.java
----------------------------------------------------------------------
diff --git a/policy/src/test/java/brooklyn/policy/followthesun/FollowTheSunModelTest.java b/policy/src/test/java/brooklyn/policy/followthesun/FollowTheSunModelTest.java
deleted file mode 100644
index 9b16a2a..0000000
--- a/policy/src/test/java/brooklyn/policy/followthesun/FollowTheSunModelTest.java
+++ /dev/null
@@ -1,195 +0,0 @@
-/*
- * 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 brooklyn.policy.followthesun;
-
-import static org.testng.Assert.assertEquals;
-import static org.testng.Assert.assertFalse;
-import static org.testng.Assert.assertTrue;
-
-import org.testng.annotations.AfterMethod;
-import org.testng.annotations.BeforeMethod;
-import org.testng.annotations.Test;
-import org.apache.brooklyn.api.location.Location;
-import org.apache.brooklyn.location.basic.SimulatedLocation;
-
-import brooklyn.policy.loadbalancing.MockContainerEntity;
-import brooklyn.policy.loadbalancing.MockContainerEntityImpl;
-import brooklyn.policy.loadbalancing.MockItemEntity;
-import brooklyn.policy.loadbalancing.MockItemEntityImpl;
-
-import com.google.common.collect.ImmutableMap;
-import com.google.common.collect.ImmutableSet;
-
-public class FollowTheSunModelTest {
-
- private Location loc1 = new SimulatedLocation(DefaultFollowTheSunModel.newHashMap("name","loc1"));
- private Location loc2 = new SimulatedLocation(DefaultFollowTheSunModel.newHashMap("name","loc2"));
- private MockContainerEntity container1 = new MockContainerEntityImpl();
- private MockContainerEntity container2 = new MockContainerEntityImpl();
- private MockItemEntity item1 = new MockItemEntityImpl();
- private MockItemEntity item2 = new MockItemEntityImpl();
- private MockItemEntity item3 = new MockItemEntityImpl();
-
- private DefaultFollowTheSunModel<MockContainerEntity, MockItemEntity> model;
-
- @BeforeMethod(alwaysRun=true)
- public void setUp() throws Exception {
- model = new DefaultFollowTheSunModel<MockContainerEntity, MockItemEntity>("myname");
- }
-
- @AfterMethod(alwaysRun=true)
- public void tearDown() throws Exception {
- // noting to tear down; no management context created
- }
-
- @Test
- public void testSimpleAddAndRemove() throws Exception {
- model.onContainerAdded(container1, loc1);
- model.onContainerAdded(container2, loc2);
- model.onItemAdded(item1, container1, true);
- model.onItemAdded(item2, container2, true);
-
- assertEquals(model.getContainerLocation(container1), loc1);
- assertEquals(model.getContainerLocation(container2), loc2);
- assertEquals(model.getItems(), ImmutableSet.of(item1, item2));
- assertEquals(model.getItemLocation(item1), loc1);
- assertEquals(model.getItemLocation(item2), loc2);
- assertEquals(model.getItemContainer(item1), container1);
- assertEquals(model.getItemContainer(item2), container2);
-
- model.onContainerRemoved(container2);
- model.onItemRemoved(item2);
-
- assertEquals(model.getContainerLocation(container1), loc1);
- assertEquals(model.getContainerLocation(container2), null);
- assertEquals(model.getItems(), ImmutableSet.of(item1));
- assertEquals(model.getItemLocation(item1), loc1);
- assertEquals(model.getItemLocation(item2), null);
- assertEquals(model.getItemContainer(item1), container1);
- assertEquals(model.getItemContainer(item2), null);
- }
-
- @Test
- public void testItemUsageMetrics() throws Exception {
- model.onContainerAdded(container1, loc1);
- model.onContainerAdded(container2, loc2);
- model.onItemAdded(item1, container1, true);
- model.onItemAdded(item2, container2, true);
-
- model.onItemUsageUpdated(item1, ImmutableMap.of(item2, 12d));
- model.onItemUsageUpdated(item2, ImmutableMap.of(item1, 11d));
-
- assertEquals(model.getDirectSendsToItemByLocation(),
- ImmutableMap.of(item1, ImmutableMap.of(loc2, 12d), item2, ImmutableMap.of(loc1, 11d)));
- }
-
- @Test
- public void testItemUsageReportedIfLocationSetAfterUsageUpdate() throws Exception {
- model.onContainerAdded(container1, null);
- model.onContainerAdded(container2, null);
- model.onItemAdded(item1, container1, true);
- model.onItemAdded(item2, container2, true);
- model.onItemUsageUpdated(item1, ImmutableMap.of(item2, 12d));
- model.onContainerLocationUpdated(container1, loc1);
- model.onContainerLocationUpdated(container2, loc2);
-
- assertEquals(model.getDirectSendsToItemByLocation(),
- ImmutableMap.of(item1, ImmutableMap.of(loc2, 12d)));
- }
-
- @Test
- public void testItemUsageMetricsSummedForActorsInSameLocation() throws Exception {
- model.onContainerAdded(container1, loc1);
- model.onContainerAdded(container2, loc2);
- model.onItemAdded(item1, container1, true);
- model.onItemAdded(item2, container2, true);
- model.onItemAdded(item3, container2, true);
-
- model.onItemUsageUpdated(item1, ImmutableMap.of(item2, 12d, item3, 13d));
-
- assertEquals(model.getDirectSendsToItemByLocation(),
- ImmutableMap.of(item1, ImmutableMap.of(loc2, 12d+13d)));
- }
-
- @Test
- public void testItemMovedWillUpdateLocationUsage() throws Exception {
- model.onContainerAdded(container1, loc1);
- model.onContainerAdded(container2, loc2);
- model.onItemAdded(item1, container1, false);
- model.onItemAdded(item2, container2, false);
- model.onItemUsageUpdated(item2, ImmutableMap.of(item1, 12d));
-
- model.onItemMoved(item1, container2);
-
- assertEquals(model.getDirectSendsToItemByLocation(),
- ImmutableMap.of(item2, ImmutableMap.of(loc2, 12d)));
- assertEquals(model.getItemContainer(item1), container2);
- assertEquals(model.getItemLocation(item1), loc2);
- }
-
- @Test
- public void testItemAddedWithNoContainer() throws Exception {
- model.onItemAdded(item1, null, true);
-
- assertEquals(model.getItems(), ImmutableSet.of(item1));
- assertEquals(model.getItemContainer(item1), null);
- assertEquals(model.getItemLocation(item1), null);
- }
-
- @Test
- public void testItemAddedBeforeContainer() throws Exception {
- model.onItemAdded(item1, container1, true);
- model.onContainerAdded(container1, loc1);
-
- assertEquals(model.getItems(), ImmutableSet.of(item1));
- assertEquals(model.getItemContainer(item1), container1);
- assertEquals(model.getItemLocation(item1), loc1);
- }
-
- @Test
- public void testItemMovedBeforeContainerAdded() throws Exception {
- model.onContainerAdded(container1, loc1);
- model.onItemAdded(item1, container1, true);
- model.onItemMoved(item1, container2);
- model.onContainerAdded(container2, loc2);
-
- assertEquals(model.getItems(), ImmutableSet.of(item1));
- assertEquals(model.getItemContainer(item1), container2);
- assertEquals(model.getItemLocation(item1), loc2);
- }
-
- @Test
- public void testItemAddedAnswersMovability() throws Exception {
- model.onItemAdded(item1, container1, false);
- model.onItemAdded(item2, container1, true);
- assertTrue(model.isItemMoveable(item1));
- assertFalse(model.isItemMoveable(item2));
- }
-
- @Test
- public void testWorkrateUpdateAfterItemRemovalIsNotRecorded() throws Exception {
- model.onContainerAdded(container1, loc1);
- model.onItemAdded(item1, container1, true);
- model.onItemAdded(item2, container1, true);
- model.onItemRemoved(item1);
- model.onItemUsageUpdated(item1, ImmutableMap.of(item2, 123d));
-
- assertFalse(model.getDirectSendsToItemByLocation().containsKey(item1));
- }
-}
[04/24] incubator-brooklyn git commit: Merge branch 'master' of
github.com:apache/incubator-brooklyn into add-postgres-user
Posted by he...@apache.org.
Merge branch 'master' of github.com:apache/incubator-brooklyn into add-postgres-user
Conflicts:
software/database/src/main/java/brooklyn/entity/database/postgresql/PostgreSqlNode.java
Project: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/commit/8cba4d3c
Tree: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/tree/8cba4d3c
Diff: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/diff/8cba4d3c
Branch: refs/heads/master
Commit: 8cba4d3cd437f5e59be1bc24bbd875eaee10ff20
Parents: ac82d23 5dfe944
Author: Robert Moss <ro...@gmail.com>
Authored: Tue Aug 18 10:26:54 2015 +0100
Committer: Robert Moss <ro...@gmail.com>
Committed: Tue Aug 18 10:26:54 2015 +0100
----------------------------------------------------------------------
LICENSE | 275 ++-
.../src/main/java/brooklyn/BrooklynVersion.java | 6 +-
.../brooklyn/basic/AbstractBrooklynObject.java | 249 ---
.../brooklyn/basic/BasicConfigurableObject.java | 120 --
.../brooklyn/basic/BrooklynDynamicType.java | 284 ---
.../brooklyn/basic/BrooklynObjectInternal.java | 104 -
.../brooklyn/basic/BrooklynTypeSnapshot.java | 102 -
.../main/java/brooklyn/basic/BrooklynTypes.java | 132 --
.../basic/internal/ApiObjectsFactoryImpl.java | 42 -
.../brooklyn/config/BrooklynProperties.java | 6 +-
.../brooklyn/config/BrooklynServerPaths.java | 2 +-
.../config/internal/AbstractConfigMapImpl.java | 4 +-
.../enricher/CustomAggregatingEnricher.java | 2 +-
.../main/java/brooklyn/enricher/Enrichers.java | 2 +-
.../enricher/basic/AbstractEnricher.java | 4 +-
.../basic/AbstractMultipleSensorAggregator.java | 2 +-
.../basic/AbstractTypeTransformingEnricher.java | 2 +-
.../brooklyn/enricher/basic/Aggregator.java | 2 +-
.../enricher/basic/EnricherDynamicType.java | 2 +-
.../enricher/basic/EnricherTypeSnapshot.java | 2 +-
.../java/brooklyn/enricher/basic/Joiner.java | 2 +-
.../brooklyn/enricher/basic/Propagator.java | 6 +-
.../brooklyn/enricher/basic/Transformer.java | 4 +-
.../brooklyn/enricher/basic/UpdatingMap.java | 2 +-
.../basic/YamlTimeWeightedDeltaEnricher.java | 2 +-
.../brooklyn/entity/basic/AbstractEffector.java | 4 +-
.../brooklyn/entity/basic/AbstractEntity.java | 16 +-
.../java/brooklyn/entity/basic/BasicGroup.java | 2 +-
.../entity/basic/BrooklynConfigKeys.java | 4 +-
.../entity/basic/BrooklynShutdownHooks.java | 2 +-
.../brooklyn/entity/basic/BrooklynTaskTags.java | 6 +-
.../java/brooklyn/entity/basic/ConfigKeys.java | 2 +-
.../java/brooklyn/entity/basic/DataEntity.java | 2 +-
.../brooklyn/entity/basic/DynamicGroup.java | 2 +-
.../brooklyn/entity/basic/DynamicGroupImpl.java | 2 +-
.../entity/basic/EffectorStartableImpl.java | 3 +-
.../java/brooklyn/entity/basic/Entities.java | 20 +-
.../brooklyn/entity/basic/EntityConfigMap.java | 10 +-
.../entity/basic/EntityDynamicType.java | 2 +-
.../brooklyn/entity/basic/EntityFunctions.java | 2 +-
.../brooklyn/entity/basic/EntityInternal.java | 4 +-
.../basic/EntityTransientCopyInternal.java | 2 +-
.../entity/basic/EntityTypeSnapshot.java | 2 +-
.../java/brooklyn/entity/basic/EntityTypes.java | 2 +-
.../java/brooklyn/entity/basic/Lifecycle.java | 2 +-
.../brooklyn/entity/basic/MethodEffector.java | 2 +-
.../java/brooklyn/entity/basic/Sanitizer.java | 2 +-
.../entity/basic/ServiceStateLogic.java | 2 +-
.../entity/effector/AddChildrenEffector.java | 2 +-
.../brooklyn/entity/effector/AddEffector.java | 2 +-
.../brooklyn/entity/effector/AddSensor.java | 2 +-
.../brooklyn/entity/effector/EffectorBody.java | 10 +-
.../brooklyn/entity/effector/EffectorTasks.java | 10 +-
.../brooklyn/entity/effector/Effectors.java | 4 +-
.../group/AbstractMembershipTrackingPolicy.java | 2 +-
.../java/brooklyn/entity/group/Cluster.java | 2 +-
.../brooklyn/entity/group/DynamicCluster.java | 2 +-
.../entity/group/DynamicClusterImpl.java | 8 +-
.../brooklyn/entity/group/DynamicFabric.java | 2 +-
.../entity/group/DynamicMultiGroup.java | 2 +-
.../entity/group/QuarantineGroupImpl.java | 4 +-
.../entity/proxying/EntityProxyImpl.java | 6 +-
.../entity/proxying/InternalEntityFactory.java | 6 +-
.../proxying/InternalLocationFactory.java | 4 +-
.../entity/proxying/InternalPolicyFactory.java | 4 +-
.../AbstractBrooklynObjectRebindSupport.java | 4 +-
.../rebind/ActivePartialRebindIteration.java | 2 +-
.../rebind/BasicCatalogItemRebindSupport.java | 2 +-
.../rebind/BasicEnricherRebindSupport.java | 4 +-
.../entity/rebind/BasicEntityRebindSupport.java | 2 +-
.../entity/rebind/BasicFeedRebindSupport.java | 4 +-
.../rebind/BasicLocationRebindSupport.java | 4 +-
.../entity/rebind/BasicPolicyRebindSupport.java | 7 +-
.../rebind/ImmediateDeltaChangeListener.java | 2 +-
.../rebind/PeriodicDeltaChangeListener.java | 6 +-
.../brooklyn/entity/rebind/RebindIteration.java | 8 +-
.../entity/rebind/RebindManagerImpl.java | 6 +-
.../entity/rebind/dto/BasicEntityMemento.java | 3 +-
.../entity/rebind/dto/BasicLocationMemento.java | 2 +-
.../entity/rebind/dto/MementosGenerators.java | 8 +-
.../BrooklynMementoPersisterToObjectStore.java | 2 +-
.../persister/BrooklynPersistenceUtils.java | 4 +-
.../rebind/persister/FileBasedObjectStore.java | 2 +-
.../rebind/persister/XmlMementoSerializer.java | 2 +-
.../rebind/transformer/CompoundTransformer.java | 4 +-
.../transformer/CompoundTransformerLoader.java | 4 +-
.../java/brooklyn/entity/trait/Startable.java | 4 +-
.../brooklyn/entity/trait/StartableMethods.java | 6 +-
.../java/brooklyn/event/basic/AttributeMap.java | 2 +-
.../basic/AttributeSensorAndConfigKey.java | 2 +-
.../brooklyn/event/basic/BasicConfigKey.java | 4 +-
.../event/basic/DependentConfiguration.java | 16 +-
.../basic/PortAttributeSensorAndConfigKey.java | 2 +-
...platedStringAttributeSensorAndConfigKey.java | 2 +-
.../java/brooklyn/event/feed/AbstractFeed.java | 2 +-
.../event/feed/AttributePollHandler.java | 4 +-
.../main/java/brooklyn/event/feed/Poller.java | 6 +-
.../java/brooklyn/event/feed/http/HttpFeed.java | 6 +-
.../event/feed/http/HttpPollConfig.java | 4 +-
.../brooklyn/event/feed/http/HttpPollValue.java | 2 +-
.../brooklyn/event/feed/http/HttpPolls.java | 5 +-
.../event/feed/http/HttpValueFunctions.java | 3 +-
.../brooklyn/event/feed/shell/ShellFeed.java | 6 +-
.../java/brooklyn/event/feed/ssh/SshFeed.java | 6 +-
.../windows/WindowsPerformanceCounterFeed.java | 4 +-
.../policy/basic/AbstractEntityAdjunct.java | 510 -----
.../brooklyn/policy/basic/AbstractPolicy.java | 119 --
.../java/brooklyn/policy/basic/AdjunctType.java | 174 --
.../brooklyn/policy/basic/ConfigMapImpl.java | 140 --
.../policy/basic/GeneralPurposePolicy.java | 36 -
.../java/brooklyn/policy/basic/Policies.java | 73 -
.../policy/basic/PolicyDynamicType.java | 44 -
.../policy/basic/PolicyTypeSnapshot.java | 40 -
.../util/BrooklynLanguageExtensions.java | 48 -
.../brooklyn/util/BrooklynMavenArtifacts.java | 58 -
.../brooklyn/util/BrooklynNetworkUtils.java | 41 -
.../main/java/brooklyn/util/ResourceUtils.java | 639 ------
.../java/brooklyn/util/config/ConfigBag.java | 588 ------
.../brooklyn/util/crypto/FluentKeySigner.java | 192 --
.../java/brooklyn/util/crypto/SecureKeys.java | 184 --
.../java/brooklyn/util/file/ArchiveBuilder.java | 423 ----
.../java/brooklyn/util/file/ArchiveTasks.java | 58 -
.../java/brooklyn/util/file/ArchiveUtils.java | 351 ----
.../util/flags/ClassCoercionException.java | 39 -
.../java/brooklyn/util/flags/FlagUtils.java | 587 ------
.../brooklyn/util/flags/MethodCoercions.java | 183 --
.../java/brooklyn/util/flags/SetFromFlag.java | 71 -
.../java/brooklyn/util/flags/TypeCoercions.java | 879 --------
.../main/java/brooklyn/util/http/HttpTool.java | 387 ----
.../brooklyn/util/http/HttpToolResponse.java | 185 --
.../util/internal/ConfigKeySelfExtracting.java | 41 -
.../java/brooklyn/util/internal/Repeater.java | 369 ----
.../ssh/BackoffLimitedRetryHandler.java | 74 -
.../util/internal/ssh/ShellAbstractTool.java | 442 ----
.../brooklyn/util/internal/ssh/ShellTool.java | 113 -
.../util/internal/ssh/SshAbstractTool.java | 172 --
.../util/internal/ssh/SshException.java | 32 -
.../brooklyn/util/internal/ssh/SshTool.java | 174 --
.../util/internal/ssh/cli/SshCliTool.java | 316 ---
.../util/internal/ssh/process/ProcessTool.java | 214 --
.../internal/ssh/sshj/SshjClientConnection.java | 282 ---
.../util/internal/ssh/sshj/SshjTool.java | 1091 ----------
.../util/javalang/ReflectionScanner.java | 135 --
.../brooklyn/util/javalang/UrlClassLoader.java | 69 -
.../java/brooklyn/util/mutex/MutexSupport.java | 120 --
.../brooklyn/util/mutex/SemaphoreForTasks.java | 112 -
.../util/mutex/SemaphoreWithOwners.java | 231 --
.../java/brooklyn/util/mutex/WithMutexes.java | 45 -
.../src/main/java/brooklyn/util/osgi/Osgis.java | 719 -------
.../util/task/AbstractExecutionContext.java | 75 -
.../util/task/BasicExecutionContext.java | 221 --
.../util/task/BasicExecutionManager.java | 755 -------
.../main/java/brooklyn/util/task/BasicTask.java | 892 --------
.../java/brooklyn/util/task/CanSetName.java | 25 -
.../java/brooklyn/util/task/CompoundTask.java | 131 --
.../brooklyn/util/task/DeferredSupplier.java | 38 -
.../util/task/DynamicSequentialTask.java | 480 -----
.../java/brooklyn/util/task/DynamicTasks.java | 337 ---
.../brooklyn/util/task/ExecutionListener.java | 31 -
.../java/brooklyn/util/task/ExecutionUtils.java | 49 -
.../java/brooklyn/util/task/ForwardingTask.java | 325 ---
.../util/task/ListenableForwardingFuture.java | 50 -
.../java/brooklyn/util/task/ParallelTask.java | 85 -
.../java/brooklyn/util/task/ScheduledTask.java | 185 --
.../java/brooklyn/util/task/SequentialTask.java | 58 -
.../util/task/SingleThreadedScheduler.java | 216 --
.../java/brooklyn/util/task/TaskBuilder.java | 184 --
.../java/brooklyn/util/task/TaskInternal.java | 125 --
.../java/brooklyn/util/task/TaskScheduler.java | 41 -
.../main/java/brooklyn/util/task/TaskTags.java | 71 -
.../src/main/java/brooklyn/util/task/Tasks.java | 488 -----
.../java/brooklyn/util/task/ValueResolver.java | 426 ----
.../util/task/ssh/SshFetchTaskFactory.java | 89 -
.../util/task/ssh/SshFetchTaskWrapper.java | 135 --
.../util/task/ssh/SshPutTaskFactory.java | 123 --
.../brooklyn/util/task/ssh/SshPutTaskStub.java | 69 -
.../util/task/ssh/SshPutTaskWrapper.java | 190 --
.../java/brooklyn/util/task/ssh/SshTasks.java | 236 ---
.../internal/AbstractSshExecTaskFactory.java | 58 -
.../ssh/internal/PlainSshExecTaskFactory.java | 71 -
.../util/task/system/ProcessTaskFactory.java | 65 -
.../util/task/system/ProcessTaskStub.java | 101 -
.../util/task/system/ProcessTaskWrapper.java | 187 --
.../brooklyn/util/task/system/SystemTasks.java | 29 -
.../internal/AbstractProcessTaskFactory.java | 214 --
.../system/internal/ExecWithLoggingHelpers.java | 200 --
.../internal/SystemProcessTaskFactory.java | 131 --
.../brooklyn/util/text/DataUriSchemeParser.java | 267 ---
.../brooklyn/util/text/TemplateProcessor.java | 397 ----
...ompilerIndependentOuterClassFieldMapper.java | 166 --
.../xstream/EnumCaseForgivingConverter.java | 60 -
.../EnumCaseForgivingSingleValueConverter.java | 35 -
.../util/xstream/ImmutableListConverter.java | 54 -
.../util/xstream/ImmutableMapConverter.java | 56 -
.../util/xstream/ImmutableSetConverter.java | 54 -
.../util/xstream/Inet4AddressConverter.java | 65 -
.../brooklyn/util/xstream/MapConverter.java | 104 -
.../util/xstream/MutableSetConverter.java | 44 -
.../util/xstream/StringKeyMapConverter.java | 134 --
.../brooklyn/util/xstream/XmlSerializer.java | 97 -
.../java/brooklyn/util/xstream/XmlUtil.java | 59 -
.../brooklyn/basic/AbstractBrooklynObject.java | 249 +++
.../brooklyn/basic/BasicConfigurableObject.java | 120 ++
.../brooklyn/basic/BrooklynDynamicType.java | 284 +++
.../brooklyn/basic/BrooklynObjectInternal.java | 104 +
.../brooklyn/basic/BrooklynTypeSnapshot.java | 102 +
.../apache/brooklyn/basic/BrooklynTypes.java | 132 ++
.../basic/internal/ApiObjectsFactoryImpl.java | 42 +
.../catalog/internal/BasicBrooklynCatalog.java | 4 +-
.../catalog/internal/CatalogClasspathDo.java | 6 +-
.../core/catalog/internal/CatalogDto.java | 2 +-
.../core/catalog/internal/CatalogDtoUtils.java | 2 +-
.../catalog/internal/CatalogInitialization.java | 4 +-
.../core/catalog/internal/CatalogItemDo.java | 2 +-
.../internal/CatalogItemDtoAbstract.java | 6 +-
.../core/catalog/internal/CatalogUtils.java | 2 +-
.../catalog/internal/CatalogXmlSerializer.java | 8 +-
.../internal/BrooklynFeatureEnablement.java | 2 +-
.../core/internal/BrooklynInitialization.java | 9 +-
.../management/entitlement/Entitlements.java | 2 +-
.../ha/HighAvailabilityManagerImpl.java | 4 +-
.../core/management/ha/OsgiManager.java | 4 +-
.../internal/AbstractManagementContext.java | 8 +-
.../internal/AsyncCollectionChangeAdapter.java | 4 +-
.../internal/BrooklynGarbageCollector.java | 6 +-
.../core/management/internal/EffectorUtils.java | 4 +-
.../internal/EntityManagementUtils.java | 4 +-
.../management/internal/LocalEntityManager.java | 2 +-
.../internal/LocalLocationManager.java | 4 +-
.../internal/LocalManagementContext.java | 10 +-
.../internal/LocalSubscriptionManager.java | 4 +-
.../management/internal/LocalUsageManager.java | 2 +-
.../internal/ManagementContextInternal.java | 2 +-
.../policy/basic/AbstractEntityAdjunct.java | 510 +++++
.../core/policy/basic/AbstractPolicy.java | 119 ++
.../brooklyn/core/policy/basic/AdjunctType.java | 174 ++
.../core/policy/basic/ConfigMapImpl.java | 140 ++
.../core/policy/basic/GeneralPurposePolicy.java | 36 +
.../brooklyn/core/policy/basic/Policies.java | 73 +
.../core/policy/basic/PolicyDynamicType.java | 44 +
.../core/policy/basic/PolicyTypeSnapshot.java | 40 +
.../core/util/BrooklynLanguageExtensions.java | 48 +
.../core/util/BrooklynMavenArtifacts.java | 58 +
.../core/util/BrooklynNetworkUtils.java | 44 +
.../brooklyn/core/util/ResourceUtils.java | 639 ++++++
.../brooklyn/core/util/config/ConfigBag.java | 589 ++++++
.../core/util/crypto/FluentKeySigner.java | 192 ++
.../brooklyn/core/util/crypto/SecureKeys.java | 186 ++
.../brooklyn/core/util/file/ArchiveBuilder.java | 424 ++++
.../brooklyn/core/util/file/ArchiveTasks.java | 58 +
.../brooklyn/core/util/file/ArchiveUtils.java | 351 ++++
.../core/util/flags/ClassCoercionException.java | 39 +
.../brooklyn/core/util/flags/FlagUtils.java | 587 ++++++
.../core/util/flags/MethodCoercions.java | 183 ++
.../brooklyn/core/util/flags/SetFromFlag.java | 71 +
.../brooklyn/core/util/flags/TypeCoercions.java | 879 ++++++++
.../brooklyn/core/util/http/HttpTool.java | 387 ++++
.../core/util/http/HttpToolResponse.java | 185 ++
.../util/internal/ConfigKeySelfExtracting.java | 41 +
.../brooklyn/core/util/internal/Repeater.java | 370 ++++
.../ssh/BackoffLimitedRetryHandler.java | 74 +
.../util/internal/ssh/ShellAbstractTool.java | 442 ++++
.../core/util/internal/ssh/ShellTool.java | 113 +
.../core/util/internal/ssh/SshAbstractTool.java | 172 ++
.../core/util/internal/ssh/SshException.java | 32 +
.../core/util/internal/ssh/SshTool.java | 174 ++
.../core/util/internal/ssh/cli/SshCliTool.java | 317 +++
.../util/internal/ssh/process/ProcessTool.java | 215 ++
.../internal/ssh/sshj/SshjClientConnection.java | 282 +++
.../core/util/internal/ssh/sshj/SshjTool.java | 1091 ++++++++++
.../core/util/javalang/ReflectionScanner.java | 135 ++
.../core/util/javalang/UrlClassLoader.java | 70 +
.../brooklyn/core/util/mutex/MutexSupport.java | 119 ++
.../core/util/mutex/SemaphoreForTasks.java | 112 +
.../core/util/mutex/SemaphoreWithOwners.java | 231 ++
.../brooklyn/core/util/mutex/WithMutexes.java | 45 +
.../apache/brooklyn/core/util/osgi/Osgis.java | 720 +++++++
.../util/task/AbstractExecutionContext.java | 75 +
.../core/util/task/BasicExecutionContext.java | 221 ++
.../core/util/task/BasicExecutionManager.java | 755 +++++++
.../brooklyn/core/util/task/BasicTask.java | 892 ++++++++
.../brooklyn/core/util/task/CanSetName.java | 25 +
.../brooklyn/core/util/task/CompoundTask.java | 131 ++
.../core/util/task/DeferredSupplier.java | 38 +
.../core/util/task/DynamicSequentialTask.java | 480 +++++
.../brooklyn/core/util/task/DynamicTasks.java | 337 +++
.../core/util/task/ExecutionListener.java | 31 +
.../brooklyn/core/util/task/ExecutionUtils.java | 49 +
.../brooklyn/core/util/task/ForwardingTask.java | 325 +++
.../util/task/ListenableForwardingFuture.java | 50 +
.../brooklyn/core/util/task/ParallelTask.java | 85 +
.../brooklyn/core/util/task/ScheduledTask.java | 185 ++
.../brooklyn/core/util/task/SequentialTask.java | 58 +
.../core/util/task/SingleThreadedScheduler.java | 216 ++
.../brooklyn/core/util/task/TaskBuilder.java | 184 ++
.../brooklyn/core/util/task/TaskInternal.java | 125 ++
.../brooklyn/core/util/task/TaskScheduler.java | 41 +
.../brooklyn/core/util/task/TaskTags.java | 71 +
.../apache/brooklyn/core/util/task/Tasks.java | 488 +++++
.../brooklyn/core/util/task/ValueResolver.java | 426 ++++
.../core/util/task/ssh/SshFetchTaskFactory.java | 88 +
.../core/util/task/ssh/SshFetchTaskWrapper.java | 135 ++
.../core/util/task/ssh/SshPutTaskFactory.java | 123 ++
.../core/util/task/ssh/SshPutTaskStub.java | 69 +
.../core/util/task/ssh/SshPutTaskWrapper.java | 189 ++
.../brooklyn/core/util/task/ssh/SshTasks.java | 236 +++
.../internal/AbstractSshExecTaskFactory.java | 58 +
.../ssh/internal/PlainSshExecTaskFactory.java | 71 +
.../util/task/system/ProcessTaskFactory.java | 66 +
.../core/util/task/system/ProcessTaskStub.java | 102 +
.../util/task/system/ProcessTaskWrapper.java | 187 ++
.../core/util/task/system/SystemTasks.java | 29 +
.../internal/AbstractProcessTaskFactory.java | 216 ++
.../system/internal/ExecWithLoggingHelpers.java | 202 ++
.../internal/SystemProcessTaskFactory.java | 131 ++
.../core/util/text/DataUriSchemeParser.java | 267 +++
.../core/util/text/TemplateProcessor.java | 398 ++++
...ompilerIndependentOuterClassFieldMapper.java | 166 ++
.../xstream/EnumCaseForgivingConverter.java | 60 +
.../EnumCaseForgivingSingleValueConverter.java | 35 +
.../util/xstream/ImmutableListConverter.java | 54 +
.../util/xstream/ImmutableMapConverter.java | 56 +
.../util/xstream/ImmutableSetConverter.java | 54 +
.../util/xstream/Inet4AddressConverter.java | 65 +
.../core/util/xstream/MapConverter.java | 104 +
.../core/util/xstream/MutableSetConverter.java | 44 +
.../util/xstream/StringKeyMapConverter.java | 134 ++
.../core/util/xstream/XmlSerializer.java | 97 +
.../brooklyn/core/util/xstream/XmlUtil.java | 59 +
.../location/access/BrooklynAccessUtils.java | 8 +-
.../PortForwardManagerLocationResolver.java | 3 +-
.../location/basic/AbstractLocation.java | 8 +-
.../basic/AbstractLocationResolver.java | 2 +-
.../AggregatingMachineProvisioningLocation.java | 2 +-
.../location/basic/BasicLocationRegistry.java | 2 +-
.../location/basic/BasicMachineDetails.java | 10 +-
.../location/basic/ByonLocationResolver.java | 4 +-
.../FixedListMachineProvisioningLocation.java | 4 +-
.../location/basic/HostLocationResolver.java | 2 +-
.../basic/LocalhostLocationResolver.java | 3 +-
.../LocalhostMachineProvisioningLocation.java | 10 +-
...calhostPropertiesFromBrooklynProperties.java | 3 +-
.../location/basic/LocationConfigUtils.java | 8 +-
.../location/basic/LocationDynamicType.java | 2 +-
.../location/basic/LocationInternal.java | 4 +-
...ocationPropertiesFromBrooklynProperties.java | 4 +-
.../location/basic/LocationTypeSnapshot.java | 2 +-
.../brooklyn/location/basic/MultiLocation.java | 2 +-
.../location/basic/NamedLocationResolver.java | 2 +-
.../brooklyn/location/basic/PortRanges.java | 3 +-
.../basic/SingleMachineLocationResolver.java | 2 +-
.../SingleMachineProvisioningLocation.java | 3 +-
.../location/basic/SshMachineLocation.java | 32 +-
...bstractCloudMachineProvisioningLocation.java | 4 +-
.../location/cloud/CloudLocationConfig.java | 3 +-
.../cloud/names/AbstractCloudMachineNamer.java | 4 +-
.../cloud/names/BasicCloudMachineNamer.java | 4 +-
.../location/cloud/names/CloudMachineNamer.java | 3 +-
.../cloud/names/CustomMachineNamer.java | 6 +-
.../location/dynamic/DynamicLocation.java | 2 +-
.../location/dynamic/LocationOwner.java | 2 +-
.../brooklyn/location/geo/HostGeoInfo.java | 2 +-
.../location/geo/LocalhostExternalIpLoader.java | 4 +-
...pi.basic.internal.ApiObjectsFactoryInterface | 2 +-
.../brooklyn/camp/lite/CampYamlLiteTest.java | 4 +-
.../camp/lite/TestAppAssemblyInstantiator.java | 2 +-
.../enricher/basic/BasicEnricherTest.java | 2 +-
.../java/brooklyn/entity/EffectorSayHiTest.java | 2 +-
.../entity/EntityPreManagementTest.java | 2 +-
.../java/brooklyn/entity/SetFromFlagTest.java | 2 +-
.../brooklyn/entity/basic/ConfigMapTest.java | 4 +-
.../basic/DependentConfigurationTest.java | 2 +-
.../brooklyn/entity/basic/EntityConfigTest.java | 2 +-
.../brooklyn/entity/basic/EntitySpecTest.java | 4 +-
...apListAndOtherStructuredConfigKeyTest.groovy | 2 +-
.../entity/basic/PolicyRegistrationTest.java | 2 +-
.../brooklyn/entity/basic/SanitizerTest.java | 3 +-
.../entity/effector/EffectorBasicTest.java | 2 +-
.../effector/EffectorConcatenateTest.java | 4 +-
.../entity/effector/EffectorTaskTest.java | 10 +-
.../entity/group/GroupPickUpEntitiesTest.java | 2 +-
.../java/brooklyn/entity/rebind/Dumpers.java | 2 +-
.../entity/rebind/RebindCatalogEntityTest.java | 3 +-
.../entity/rebind/RebindCatalogItemTest.java | 3 +-
.../entity/rebind/RebindEnricherTest.java | 2 +-
.../rebind/RebindEntityDynamicTypeInfoTest.java | 2 +-
.../entity/rebind/RebindEntityTest.java | 2 +-
.../entity/rebind/RebindFailuresTest.java | 4 +-
.../brooklyn/entity/rebind/RebindFeedTest.java | 4 +-
.../entity/rebind/RebindFeedWithHaTest.java | 4 +-
.../entity/rebind/RebindLocationTest.java | 2 +-
.../entity/rebind/RebindManagerTest.java | 5 +-
.../entity/rebind/RebindPolicyTest.java | 4 +-
.../entity/rebind/RebindTestFixture.java | 2 +-
.../transformer/impl/XsltTransformerTest.java | 4 +-
.../brooklyn/entity/trait/FailingEntity.java | 2 +-
.../entity/trait/FailingEntityImpl.java | 2 +-
.../entity/trait/StartableMethodsTest.java | 3 +-
.../java/brooklyn/event/feed/PollerTest.java | 2 +-
.../brooklyn/event/feed/http/HttpFeedTest.java | 4 +-
.../event/feed/http/HttpValueFunctionsTest.java | 3 +-
.../brooklyn/policy/basic/BasicPolicyTest.java | 89 -
.../brooklyn/policy/basic/EnricherTypeTest.java | 59 -
.../brooklyn/policy/basic/PolicyConfigTest.java | 202 --
.../policy/basic/PolicySubscriptionTest.java | 125 --
.../brooklyn/policy/basic/PolicyTypeTest.java | 58 -
.../EntityCleanupLongevityTestFixture.java | 4 +-
.../FilePersistencePerformanceTest.java | 2 +-
.../qa/performance/TaskPerformanceTest.java | 4 +-
.../test/java/brooklyn/test/HttpService.java | 4 +-
.../java/brooklyn/test/policy/TestEnricher.java | 2 +-
.../java/brooklyn/test/policy/TestPolicy.java | 4 +-
.../util/BrooklynMavenArtifactsTest.java | 96 -
.../brooklyn/util/ResourceUtilsHttpTest.java | 196 --
.../java/brooklyn/util/ResourceUtilsTest.java | 189 --
.../brooklyn/util/config/ConfigBagTest.java | 191 --
.../util/crypto/SecureKeysAndSignerTest.java | 166 --
.../brooklyn/util/file/ArchiveBuilderTest.java | 193 --
.../brooklyn/util/file/ArchiveUtilsTest.java | 135 --
.../util/flags/MethodCoercionsTest.java | 146 --
.../brooklyn/util/http/BetterMockWebServer.java | 138 --
.../util/http/HttpToolIntegrationTest.java | 98 -
.../brooklyn/util/internal/FlagUtilsTest.java | 314 ---
.../brooklyn/util/internal/RepeaterTest.groovy | 255 ---
.../util/internal/TypeCoercionsTest.java | 360 ----
.../util/internal/ssh/RecordingSshTool.java | 95 -
.../internal/ssh/ShellToolAbstractTest.java | 439 ----
.../ssh/SshToolAbstractIntegrationTest.java | 301 ---
.../ssh/SshToolAbstractPerformanceTest.java | 137 --
.../ssh/cli/SshCliToolIntegrationTest.java | 119 --
.../ssh/cli/SshCliToolPerformanceTest.java | 44 -
.../ssh/process/ProcessToolIntegrationTest.java | 69 -
.../ssh/process/ProcessToolStaticsTest.java | 79 -
.../sshj/SshjToolAsyncStubIntegrationTest.java | 177 --
.../ssh/sshj/SshjToolIntegrationTest.java | 313 ---
.../ssh/sshj/SshjToolPerformanceTest.java | 44 -
.../brooklyn/util/mutex/WithMutexesTest.java | 126 --
.../test/java/brooklyn/util/osgi/OsgisTest.java | 41 -
.../util/ssh/BashCommandsIntegrationTest.java | 501 -----
.../task/BasicTaskExecutionPerformanceTest.java | 206 --
.../util/task/BasicTaskExecutionTest.java | 460 ----
.../util/task/BasicTasksFutureTest.java | 224 --
.../util/task/CompoundTaskExecutionTest.java | 252 ---
.../util/task/DynamicSequentialTaskTest.java | 365 ----
.../util/task/NonBasicTaskExecutionTest.java | 126 --
.../util/task/ScheduledExecutionTest.java | 287 ---
.../util/task/SingleThreadedSchedulerTest.java | 192 --
.../util/task/TaskFinalizationTest.java | 62 -
.../test/java/brooklyn/util/task/TasksTest.java | 181 --
.../brooklyn/util/task/ValueResolverTest.java | 132 --
.../brooklyn/util/task/ssh/SshTasksTest.java | 207 --
.../util/task/system/SystemTasksTest.java | 134 --
.../util/text/DataUriSchemeParserTest.java | 52 -
.../util/text/TemplateProcessorTest.java | 179 --
.../util/xstream/CompilerCompatibilityTest.java | 154 --
.../util/xstream/ConverterTestFixture.java | 40 -
.../xstream/EnumCaseForgivingConverterTest.java | 52 -
.../xstream/ImmutableListConverterTest.java | 59 -
.../util/xstream/InetAddressConverterTest.java | 41 -
.../util/xstream/StringKeyMapConverterTest.java | 77 -
.../java/brooklyn/util/xstream/XmlUtilTest.java | 33 -
.../core/catalog/internal/CatalogDtoTest.java | 2 +-
.../core/catalog/internal/CatalogLoadTest.java | 3 +-
.../core/catalog/internal/CatalogScanTest.java | 2 +-
.../AcmeEntitlementManagerTestFixture.java | 2 +-
.../entitlement/EntityEntitlementTest.java | 2 +-
.../internal/EntityExecutionManagerTest.java | 8 +-
.../management/osgi/OsgiStandaloneTest.java | 6 +-
.../osgi/OsgiVersionMoreEntityTest.java | 2 +-
.../core/policy/basic/BasicPolicyTest.java | 90 +
.../core/policy/basic/EnricherTypeTest.java | 59 +
.../core/policy/basic/PolicyConfigTest.java | 202 ++
.../policy/basic/PolicySubscriptionTest.java | 128 ++
.../core/policy/basic/PolicyTypeTest.java | 59 +
.../core/util/BrooklynMavenArtifactsTest.java | 98 +
.../core/util/ResourceUtilsHttpTest.java | 197 ++
.../brooklyn/core/util/ResourceUtilsTest.java | 190 ++
.../core/util/config/ConfigBagTest.java | 193 ++
.../util/crypto/SecureKeysAndSignerTest.java | 169 ++
.../core/util/file/ArchiveBuilderTest.java | 194 ++
.../core/util/file/ArchiveUtilsTest.java | 139 ++
.../core/util/flags/MethodCoercionsTest.java | 149 ++
.../core/util/http/BetterMockWebServer.java | 138 ++
.../core/util/http/HttpToolIntegrationTest.java | 100 +
.../core/util/internal/FlagUtilsTest.java | 314 +++
.../core/util/internal/RepeaterTest.groovy | 257 +++
.../core/util/internal/TypeCoercionsTest.java | 360 ++++
.../util/internal/ssh/RecordingSshTool.java | 97 +
.../internal/ssh/ShellToolAbstractTest.java | 441 ++++
.../ssh/SshToolAbstractIntegrationTest.java | 304 +++
.../ssh/SshToolAbstractPerformanceTest.java | 138 ++
.../ssh/cli/SshCliToolIntegrationTest.java | 119 ++
.../ssh/cli/SshCliToolPerformanceTest.java | 44 +
.../ssh/process/ProcessToolIntegrationTest.java | 69 +
.../ssh/process/ProcessToolStaticsTest.java | 80 +
.../sshj/SshjToolAsyncStubIntegrationTest.java | 178 ++
.../ssh/sshj/SshjToolIntegrationTest.java | 314 +++
.../ssh/sshj/SshjToolPerformanceTest.java | 44 +
.../core/util/mutex/WithMutexesTest.java | 129 ++
.../brooklyn/core/util/osgi/OsgisTest.java | 41 +
.../util/ssh/BashCommandsIntegrationTest.java | 504 +++++
.../task/BasicTaskExecutionPerformanceTest.java | 209 ++
.../core/util/task/BasicTaskExecutionTest.java | 462 ++++
.../core/util/task/BasicTasksFutureTest.java | 227 ++
.../util/task/CompoundTaskExecutionTest.java | 258 +++
.../util/task/DynamicSequentialTaskTest.java | 371 ++++
.../util/task/NonBasicTaskExecutionTest.java | 130 ++
.../core/util/task/ScheduledExecutionTest.java | 291 +++
.../util/task/SingleThreadedSchedulerTest.java | 195 ++
.../core/util/task/TaskFinalizationTest.java | 63 +
.../brooklyn/core/util/task/TasksTest.java | 184 ++
.../core/util/task/ValueResolverTest.java | 134 ++
.../core/util/task/ssh/SshTasksTest.java | 213 ++
.../core/util/task/system/SystemTasksTest.java | 137 ++
.../core/util/text/DataUriSchemeParserTest.java | 53 +
.../core/util/text/TemplateProcessorTest.java | 180 ++
.../util/xstream/CompilerCompatibilityTest.java | 158 ++
.../core/util/xstream/ConverterTestFixture.java | 40 +
.../xstream/EnumCaseForgivingConverterTest.java | 53 +
.../xstream/ImmutableListConverterTest.java | 60 +
.../util/xstream/InetAddressConverterTest.java | 42 +
.../util/xstream/StringKeyMapConverterTest.java | 78 +
.../brooklyn/core/util/xstream/XmlUtilTest.java | 34 +
.../location/basic/AbstractLocationTest.java | 2 +-
.../basic/LegacyAbstractLocationTest.java | 2 +-
.../location/basic/LocationConfigTest.java | 2 +-
.../location/basic/LocationConfigUtilsTest.java | 3 +-
.../brooklyn/location/basic/PortRangesTest.java | 3 +-
.../SshMachineLocationIntegrationTest.java | 9 +-
.../SshMachineLocationPerformanceTest.java | 2 +-
.../SshMachineLocationReuseIntegrationTest.java | 4 +-
.../location/basic/SshMachineLocationTest.java | 14 +-
.../location/cloud/CloudMachineNamerTest.java | 4 +-
.../location/cloud/CustomMachineNamerTest.java | 3 +-
.../brooklyn/test/entity/BlockingEntity.java | 2 +-
.../apache/brooklyn/test/entity/TestEntity.java | 2 +-
.../rebind/compiler_compatibility_eclipse.xml | 20 +-
.../rebind/compiler_compatibility_oracle.xml | 18 +-
docs/guide/dev/code/licensing.md | 6 +
.../brooklyn/demo/GlobalWebFabricExample.java | 6 +-
.../brooklyn/demo/CumulusRDFApplication.java | 8 +-
.../demo/WebClusterDatabaseExampleApp.java | 6 +-
.../demo/WebClusterDatabaseExampleGroovy.groovy | 6 +-
.../policy/os/AdvertiseWinrmLoginPolicy.java | 3 +-
.../brooklyn/policy/os/CreateUserPolicy.java | 6 +-
.../location/jclouds/BrooklynMachinePool.java | 2 +-
.../jclouds/ComputeServiceRegistry.java | 3 +-
.../jclouds/ComputeServiceRegistryImpl.java | 2 +-
.../jclouds/JcloudsByonLocationResolver.java | 2 +-
.../location/jclouds/JcloudsLocation.java | 18 +-
.../location/jclouds/JcloudsLocationConfig.java | 3 +-
.../jclouds/JcloudsLocationCustomizer.java | 3 +-
.../location/jclouds/JcloudsMachineNamer.java | 2 +-
...JcloudsPropertiesFromBrooklynProperties.java | 4 +-
.../jclouds/JcloudsSshMachineLocation.java | 2 +-
.../brooklyn/location/jclouds/JcloudsUtil.java | 2 +-
.../jclouds/JcloudsWinRmMachineLocation.java | 2 +-
.../jclouds/SudoTtyFixingCustomizer.java | 7 +-
.../JcloudsLocationSecurityGroupCustomizer.java | 4 +-
.../persister/jclouds/BlobStoreExpiryTest.java | 6 +-
.../policy/os/CreateUserPolicyLiveTest.java | 2 +-
.../policy/os/CreateUserPolicyTest.java | 3 +-
.../jclouds/AbstractJcloudsStubbedLiveTest.java | 3 +-
.../jclouds/BailOutJcloudsLocation.java | 2 +-
...ationTemplateOptionsCustomisersLiveTest.java | 4 +-
.../location/jclouds/JcloudsLocationTest.java | 2 +-
.../jclouds/JcloudsMachineNamerTest.java | 2 +-
.../jclouds/RebindJcloudsLocationLiveTest.java | 2 +-
.../jclouds/RebindJcloudsLocationTest.java | 3 +-
.../java/brooklyn/enricher/DeltaEnricher.java | 2 +-
.../brooklyn/enricher/HttpLatencyDetector.java | 2 +-
.../brooklyn/enricher/RollingMeanEnricher.java | 2 +-
.../enricher/RollingTimeWindowMeanEnricher.java | 2 +-
.../enricher/TimeFractionDeltaEnricher.java | 2 +-
.../enricher/TimeWeightedDeltaEnricher.java | 2 +-
.../entity/brooklyn/BrooklynMetrics.java | 2 +-
.../entity/brooklyn/BrooklynMetricsImpl.java | 2 +-
.../policy/autoscaling/AutoScalerPolicy.java | 8 +-
.../policy/followthesun/FollowTheSunPolicy.java | 4 +-
.../policy/ha/AbstractFailureDetector.java | 8 +-
.../policy/ha/ConditionalSuspendPolicy.java | 4 +-
.../policy/ha/ConnectionFailureDetector.java | 2 +-
.../policy/ha/ServiceFailureDetector.java | 8 +-
.../brooklyn/policy/ha/ServiceReplacer.java | 6 +-
.../brooklyn/policy/ha/ServiceRestarter.java | 6 +-
.../policy/ha/SshMachineFailureDetector.java | 4 +-
.../loadbalancing/ItemsInContainersGroup.java | 2 +-
.../loadbalancing/LoadBalancingPolicy.java | 4 +-
.../brooklyn/policy/loadbalancing/Movable.java | 2 +-
.../enricher/HttpLatencyDetectorTest.java | 4 +-
.../brooklyn/enricher/RebindEnricherTest.java | 2 +-
.../brooklyn/policy/ha/ServiceReplacerTest.java | 2 +-
.../policy/ha/ServiceRestarterTest.java | 2 +-
.../loadbalancing/MockContainerEntity.java | 2 +-
.../entity/database/derby/DerbyDatabase.java | 4 +-
.../database/derby/DerbyDatabaseSshDriver.java | 4 +-
.../entity/database/derby/DerbySchema.java | 8 +-
.../postgresql/PostgreSqlNodeSaltImpl.java | 12 +-
.../apache/brooklyn/entity/salt/SaltConfig.java | 11 +-
.../brooklyn/entity/salt/SaltConfigs.java | 4 +-
.../entity/salt/SaltLifecycleEffectorTasks.java | 8 +-
.../brooklyn/entity/salt/SaltStackMaster.java | 9 +-
.../entity/salt/SaltStackMasterSshDriver.java | 5 +-
.../apache/brooklyn/entity/salt/SaltTasks.java | 13 +-
.../postgresql/PostgreSqlSaltLiveTest.java | 11 +-
.../brooklyn/entity/salt/SaltConfigsTest.java | 2 +-
.../entity/salt/SaltLiveTestSupport.java | 8 +-
.../entity/monitoring/zabbix/ZabbixFeed.java | 4 +-
.../monitoring/zabbix/ZabbixMonitored.java | 2 +-
.../monitoring/zabbix/ZabbixPollConfig.java | 2 +-
.../entity/monitoring/zabbix/ZabbixServer.java | 2 +-
.../nosql/hazelcast/HazelcastCluster.java | 9 +-
.../nosql/hazelcast/HazelcastClusterImpl.java | 8 +-
.../entity/nosql/hazelcast/HazelcastNode.java | 9 +-
.../nosql/hazelcast/HazelcastNodeImpl.java | 2 +-
.../nosql/hazelcast/HazelcastNodeSshDriver.java | 6 +-
.../nosql/infinispan/Infinispan5Server.java | 4 +-
.../nosql/infinispan/Infinispan5SshDriver.java | 4 +-
.../hazelcast/HazelcastClusterEc2LiveTest.java | 4 +-
.../HazelcastClusterSoftlayerLiveTest.java | 4 +-
.../Infinispan5ServerIntegrationTest.groovy | 6 +-
.../basic/AbstractSoftwareProcessDriver.java | 8 +-
.../basic/AbstractSoftwareProcessSshDriver.java | 12 +-
.../SameServerDriverLifecycleEffectorTasks.java | 4 +-
.../brooklyn/entity/basic/SameServerEntity.java | 2 +-
.../entity/basic/SameServerEntityImpl.java | 6 +-
.../brooklyn/entity/basic/SoftwareProcess.java | 2 +-
...wareProcessDriverLifecycleEffectorTasks.java | 4 +-
.../entity/basic/SoftwareProcessImpl.java | 8 +-
.../basic/VanillaSoftwareProcessSshDriver.java | 4 +-
.../basic/lifecycle/NaiveScriptRunner.java | 2 +-
.../entity/basic/lifecycle/ScriptHelper.java | 12 +-
.../brooklynnode/BrooklynEntityMirrorImpl.java | 8 +-
.../entity/brooklynnode/BrooklynNode.java | 2 +-
.../entity/brooklynnode/BrooklynNodeImpl.java | 12 +-
.../brooklynnode/BrooklynNodeSshDriver.java | 10 +-
.../entity/brooklynnode/EntityHttpClient.java | 4 +-
.../brooklynnode/EntityHttpClientImpl.java | 6 +-
.../brooklynnode/RemoteEffectorBuilder.java | 2 +-
.../BrooklynClusterUpgradeEffectorBody.java | 6 +-
.../BrooklynNodeUpgradeEffectorBody.java | 6 +-
.../effector/SelectMasterEffectorBody.java | 4 +-
.../SetHighAvailabilityModeEffectorBody.java | 4 +-
...SetHighAvailabilityPriorityEffectorBody.java | 4 +-
.../brooklyn/entity/chef/ChefAttributeFeed.java | 4 +-
.../java/brooklyn/entity/chef/ChefConfig.java | 3 +-
.../entity/chef/ChefLifecycleEffectorTasks.java | 10 +-
.../brooklyn/entity/chef/ChefServerTasks.java | 2 +-
.../brooklyn/entity/chef/ChefSoloDriver.java | 3 +-
.../java/brooklyn/entity/chef/ChefTasks.java | 10 +-
.../entity/chef/KnifeConvergeTaskFactory.java | 4 +-
.../brooklyn/entity/chef/KnifeTaskFactory.java | 10 +-
.../java/JavaSoftwareProcessSshDriver.java | 16 +-
.../java/brooklyn/entity/java/JmxSupport.java | 8 +-
.../brooklyn/entity/java/JmxmpSslSupport.java | 7 +-
.../java/brooklyn/entity/java/UsesJava.java | 3 +-
.../brooklyn/entity/java/UsesJavaMXBeans.java | 2 +-
.../main/java/brooklyn/entity/java/UsesJmx.java | 3 +-
.../brooklyn/entity/java/VanillaJavaApp.java | 2 +-
.../entity/java/VanillaJavaAppImpl.java | 2 +-
.../entity/java/VanillaJavaAppSshDriver.java | 10 +-
.../entity/machine/MachineEntityImpl.java | 6 +-
.../brooklyn/entity/pool/ServerPoolImpl.java | 2 +-
.../entity/pool/ServerPoolLocation.java | 3 +-
.../entity/service/EntityLaunchListener.java | 2 +-
.../entity/service/InitdServiceInstaller.java | 12 +-
.../entity/service/SystemServiceEnricher.java | 14 +-
.../entity/software/MachineInitTasks.java | 8 +-
.../software/MachineLifecycleEffectorTasks.java | 8 +-
.../software/ProvidesProvisioningFlags.java | 3 +-
.../entity/software/SshEffectorTasks.java | 24 +-
.../brooklyn/entity/software/StaticSensor.java | 6 +-
.../entity/software/http/HttpRequestSensor.java | 2 +-
.../software/java/JmxAttributeSensor.java | 6 +-
.../entity/software/ssh/SshCommandEffector.java | 2 +-
.../entity/software/ssh/SshCommandSensor.java | 4 +-
.../winrm/WindowsPerformanceCounterSensors.java | 2 +-
.../java/brooklyn/event/feed/jmx/JmxHelper.java | 2 +-
.../basic/SoftwareProcessEntityLatchTest.java | 2 +-
.../basic/SoftwareProcessEntityRebindTest.java | 3 +-
.../entity/basic/SoftwareProcessEntityTest.java | 6 +-
...SoftwareProcessSshDriverIntegrationTest.java | 2 +-
...ftwareProcessAndChildrenIntegrationTest.java | 2 +-
.../entity/basic/lifecycle/MyEntityImpl.java | 6 +-
.../basic/lifecycle/NaiveScriptRunnerTest.java | 6 +-
.../basic/lifecycle/StartStopSshDriverTest.java | 8 +-
.../BrooklynNodeIntegrationTest.java | 8 +-
.../brooklynnode/CallbackEntityHttpClient.java | 4 +-
.../entity/chef/ChefLiveTestSupport.java | 2 +-
.../chef/ChefServerTasksIntegrationTest.java | 2 +-
.../ChefSoloDriverMySqlEntityLiveTest.java | 2 +-
.../java/brooklyn/entity/java/JavaOptsTest.java | 6 +-
.../brooklyn/entity/java/JmxSupportTest.java | 4 +-
.../brooklyn/entity/java/SslKeyConfigTest.java | 5 +-
.../entity/java/VanillaJavaAppRebindTest.java | 2 +-
.../entity/java/VanillaJavaAppTest.java | 8 +-
.../MachineLifecycleEffectorTasksTest.java | 4 +-
.../entity/software/SoftwareEffectorTest.java | 5 +-
.../entity/software/SshEffectorTasksTest.java | 6 +-
.../entity/software/StaticSensorTest.java | 2 +-
.../software/http/HttpRequestSensorTest.java | 2 +-
.../mysql/AbstractToyMySqlEntityTest.java | 2 +-
.../mysql/DynamicToyMySqlEntityBuilder.java | 8 +-
.../software/ssh/SshCommandIntegrationTest.java | 2 +-
software/database/pom.xml | 4 -
.../entity/database/DatastoreMixins.java | 4 +-
.../entity/database/crate/CrateNode.java | 3 +-
.../entity/database/mariadb/MariaDbDriver.java | 3 +-
.../entity/database/mariadb/MariaDbNode.java | 3 +-
.../database/mariadb/MariaDbNodeImpl.java | 4 +-
.../database/mariadb/MariaDbSshDriver.java | 4 +-
.../entity/database/mysql/MySqlClusterImpl.java | 4 +-
.../entity/database/mysql/MySqlDriver.java | 3 +-
.../entity/database/mysql/MySqlNode.java | 3 +-
.../entity/database/mysql/MySqlNodeImpl.java | 4 +-
.../entity/database/mysql/MySqlSshDriver.java | 4 +-
.../database/postgresql/PostgreSqlDriver.java | 3 +-
.../database/postgresql/PostgreSqlNode.java | 4 +-
.../PostgreSqlNodeChefImplFromScratch.java | 8 +-
.../database/postgresql/PostgreSqlNodeImpl.java | 2 +-
.../postgresql/PostgreSqlSshDriver.java | 8 +-
.../entity/database/rubyrep/RubyRepNode.java | 2 +-
.../database/postgresql/PostgreSqlChefTest.java | 2 +-
.../messaging/activemq/ActiveMQBroker.java | 2 +-
.../entity/messaging/amqp/AmqpExchange.java | 2 +-
.../brooklyn/entity/messaging/kafka/Kafka.java | 3 +-
.../entity/messaging/kafka/KafkaBroker.java | 4 +-
.../entity/messaging/kafka/KafkaCluster.java | 2 +-
.../entity/messaging/kafka/KafkaZooKeeper.java | 2 +-
.../entity/messaging/qpid/QpidBroker.java | 2 +-
.../messaging/qpid/QpidDestinationImpl.java | 2 +-
.../entity/messaging/rabbit/RabbitBroker.java | 2 +-
.../brooklyn/entity/messaging/storm/Storm.java | 2 +-
.../entity/messaging/storm/StormDeployment.java | 2 +-
.../messaging/storm/StormDeploymentImpl.java | 2 +-
.../entity/zookeeper/ZooKeeperEnsemble.java | 2 +-
.../entity/zookeeper/ZooKeeperNode.java | 2 +-
.../storm/StormAbstractCloudLiveTest.java | 4 +-
.../entity/monitoring/monit/MonitNode.java | 2 +-
.../entity/network/bind/BindDnsServer.java | 4 +-
.../nosql/cassandra/CassandraDatacenter.java | 2 +-
.../cassandra/CassandraDatacenterImpl.java | 4 +-
.../entity/nosql/cassandra/CassandraNode.java | 4 +-
.../nosql/cassandra/CassandraNodeDriver.java | 3 +-
.../nosql/cassandra/CassandraNodeImpl.java | 4 +-
.../nosql/cassandra/CassandraNodeSshDriver.java | 8 +-
.../nosql/couchbase/CouchbaseCluster.java | 2 +-
.../nosql/couchbase/CouchbaseClusterImpl.java | 10 +-
.../entity/nosql/couchbase/CouchbaseNode.java | 2 +-
.../nosql/couchbase/CouchbaseNodeImpl.java | 8 +-
.../nosql/couchbase/CouchbaseNodeSshDriver.java | 12 +-
.../nosql/couchbase/CouchbaseSyncGateway.java | 2 +-
.../entity/nosql/couchdb/CouchDBCluster.java | 2 +-
.../entity/nosql/couchdb/CouchDBNode.java | 2 +-
.../entity/nosql/couchdb/CouchDBNodeImpl.java | 2 +-
.../elasticsearch/ElasticSearchCluster.java | 2 +-
.../nosql/elasticsearch/ElasticSearchNode.java | 3 +-
.../elasticsearch/ElasticSearchNodeImpl.java | 4 +-
.../nosql/mongodb/AbstractMongoDBServer.java | 2 +-
.../entity/nosql/mongodb/MongoDBClient.java | 3 +-
.../entity/nosql/mongodb/MongoDBReplicaSet.java | 2 +-
.../entity/nosql/mongodb/MongoDBServer.java | 2 +-
.../sharding/CoLocatedMongoDBRouter.java | 2 +-
.../sharding/MongoDBShardedDeployment.java | 2 +-
.../brooklyn/entity/nosql/redis/RedisSlave.java | 2 +-
.../brooklyn/entity/nosql/redis/RedisStore.java | 2 +-
.../brooklyn/entity/nosql/riak/RiakCluster.java | 2 +-
.../entity/nosql/riak/RiakClusterImpl.java | 2 +-
.../brooklyn/entity/nosql/riak/RiakNode.java | 2 +-
.../entity/nosql/riak/RiakNodeImpl.java | 2 +-
.../entity/nosql/riak/RiakNodeSshDriver.java | 4 +-
.../brooklyn/entity/nosql/solr/SolrServer.java | 4 +-
.../entity/nosql/solr/SolrServerSshDriver.java | 2 +-
.../cassandra/CassandraDatacenterTest.java | 4 +-
.../ElasticSearchClusterIntegrationTest.java | 4 +-
.../ElasticSearchNodeIntegrationTest.java | 5 +-
.../entity/osgi/karaf/KarafContainer.java | 2 +-
.../entity/dns/AbstractGeoDnsServiceImpl.java | 2 +-
.../dns/geoscaling/GeoscalingDnsService.java | 2 +-
.../geoscaling/GeoscalingDnsServiceImpl.java | 4 +-
.../geoscaling/GeoscalingScriptGenerator.java | 3 +-
.../dns/geoscaling/GeoscalingWebClient.java | 2 +-
.../entity/proxy/AbstractController.java | 2 +-
.../entity/proxy/AbstractControllerImpl.java | 2 +-
.../brooklyn/entity/proxy/LoadBalancer.java | 2 +-
.../brooklyn/entity/proxy/ProxySslConfig.java | 5 +-
.../entity/proxy/nginx/NginxController.java | 2 +-
.../entity/proxy/nginx/NginxControllerImpl.java | 8 +-
.../entity/proxy/nginx/NginxSshDriver.java | 8 +-
.../nginx/NginxTemplateConfigGenerator.java | 4 +-
.../brooklyn/entity/proxy/nginx/UrlMapping.java | 2 +-
.../webapp/ControlledDynamicWebAppCluster.java | 2 +-
.../entity/webapp/DynamicWebAppClusterImpl.java | 8 +-
.../entity/webapp/JavaWebAppService.java | 2 +-
.../entity/webapp/JavaWebAppSshDriver.java | 8 +-
.../entity/webapp/WebAppServiceConstants.java | 2 +-
.../entity/webapp/jboss/JBoss6Server.java | 2 +-
.../entity/webapp/jboss/JBoss7Server.java | 2 +-
.../entity/webapp/jetty/Jetty6Server.java | 2 +-
.../webapp/nodejs/NodeJsWebAppService.java | 3 +-
.../webapp/nodejs/NodeJsWebAppSshDriver.java | 4 +-
.../entity/webapp/tomcat/Tomcat8Server.java | 2 +-
.../entity/webapp/tomcat/TomcatServer.java | 4 +-
.../GeoscalingScriptGeneratorTest.java | 3 +-
.../dns/geoscaling/GeoscalingWebClientTest.java | 2 +-
.../entity/proxy/AbstractControllerTest.java | 2 +-
.../entity/proxy/ProxySslConfigTest.java | 2 +-
.../nginx/NginxRebindWithHaIntegrationTest.java | 4 +-
.../AbstractWebAppFixtureIntegrationTest.java | 4 +-
.../entity/webapp/HttpsSslConfigTest.java | 2 +-
.../webapp/WebAppConcurrentDeployTest.java | 4 +-
.../test/entity/TestJavaWebAppEntity.java | 2 +-
.../test/entity/TestJavaWebAppEntityImpl.java | 2 +-
.../app/SampleLocalhostIntegrationTest.java | 2 +-
.../camp/brooklyn/YamlLauncherAbstract.java | 2 +-
.../BrooklynAssemblyTemplateInstantiator.java | 4 +-
.../BrooklynComponentTemplateResolver.java | 10 +-
.../BrooklynEntityDecorationResolver.java | 2 +-
.../creation/BrooklynYamlTypeInstantiator.java | 2 +-
.../spi/dsl/BrooklynDslDeferredSupplier.java | 2 +-
.../camp/brooklyn/spi/dsl/DslUtils.java | 2 +-
.../spi/dsl/methods/BrooklynDslCommon.java | 10 +-
.../brooklyn/spi/dsl/methods/DslComponent.java | 4 +-
.../camp/brooklyn/AbstractYamlRebindTest.java | 4 +-
.../camp/brooklyn/AbstractYamlTest.java | 4 +-
.../camp/brooklyn/DslAndRebindYamlTest.java | 2 +-
.../camp/brooklyn/EntitiesYamlTest.java | 2 +-
...aWebAppWithDslYamlRebindIntegrationTest.java | 2 +-
.../brooklyn/JavaWebAppsIntegrationTest.java | 2 +-
.../camp/brooklyn/JavaWebAppsMatchingTest.java | 4 +-
.../camp/brooklyn/MapReferenceYamlTest.java | 2 +-
.../brooklyn/camp/brooklyn/ObjectsYamlTest.java | 6 +-
.../brooklyn/ReloadBrooklynPropertiesTest.java | 2 +-
.../camp/brooklyn/TestReferencingPolicy.java | 2 +-
.../TestSensorAndEffectorInitializer.java | 2 +-
.../catalog/AbstractCatalogXmlTest.java | 2 +-
.../CatalogOsgiVersionMoreEntityTest.java | 4 +-
.../brooklyn/catalog/CatalogYamlEntityTest.java | 2 +-
usage/cli/pom.xml | 4 +-
.../org/apache/brooklyn/cli/ItemLister.java | 6 +-
.../main/java/org/apache/brooklyn/cli/Main.java | 15 +-
.../apache/brooklyn/cli/lister/ClassFinder.java | 6 +-
.../brooklyn/cli/lister/ItemDescriptors.java | 4 +-
usage/cli/src/main/license/DISCLAIMER | 8 -
usage/cli/src/main/license/LICENSE | 208 --
usage/cli/src/main/license/NOTICE | 5 -
usage/cli/src/main/license/README.md | 7 +
usage/cli/src/main/license/files/DISCLAIMER | 8 +
usage/cli/src/main/license/files/LICENSE | 239 +++
usage/cli/src/main/license/files/NOTICE | 5 +
.../cli/src/main/license/source-inclusions.yaml | 24 +
.../statics/brooklyn-object-list.html | 2 +-
.../statics/style/js/catalog/bloodhound.js | 727 -------
.../statics/style/js/catalog/typeahead.js | 727 +++++++
.../java/org/apache/brooklyn/cli/CliTest.java | 2 +-
usage/cli/src/test/license/DISCLAIMER | 8 -
usage/cli/src/test/license/LICENSE | 175 --
usage/cli/src/test/license/NOTICE | 5 -
usage/cli/src/test/license/files/DISCLAIMER | 8 +
usage/cli/src/test/license/files/LICENSE | 175 ++
usage/cli/src/test/license/files/NOTICE | 5 +
usage/dist/licensing/.gitignore | 2 +
usage/dist/licensing/MAIN_LICENSE_ASL2 | 176 ++
usage/dist/licensing/README.md | 77 +
usage/dist/licensing/extras-files | 1 +
usage/dist/licensing/licenses/binary/ASL2 | 177 ++
.../dist/licensing/licenses/binary/BSD-2-Clause | 23 +
.../dist/licensing/licenses/binary/BSD-3-Clause | 27 +
usage/dist/licensing/licenses/binary/CDDL1 | 381 ++++
usage/dist/licensing/licenses/binary/CDDL1.1 | 304 +++
usage/dist/licensing/licenses/binary/EPL1 | 212 ++
usage/dist/licensing/licenses/binary/MIT | 20 +
usage/dist/licensing/licenses/binary/WTFPL | 15 +
.../dist/licensing/licenses/binary/bouncycastle | 23 +
usage/dist/licensing/licenses/binary/jtidy | 53 +
usage/dist/licensing/licenses/binary/jython | 27 +
.../licenses/binary/metastuff-bsd-style | 43 +
.../licenses/binary/xpp3_indiana_university | 45 +
usage/dist/licensing/licenses/cli/MIT | 20 +
.../dist/licensing/licenses/jsgui/BSD-3-Clause | 27 +
usage/dist/licensing/licenses/jsgui/MIT | 20 +
.../dist/licensing/licenses/source/BSD-3-Clause | 27 +
usage/dist/licensing/licenses/source/MIT | 20 +
usage/dist/licensing/make-all-licenses.sh | 61 +
usage/dist/licensing/make-one-license.sh | 77 +
usage/dist/licensing/overrides.yaml | 220 ++
.../licensing/projects-with-custom-licenses | 2 +
usage/dist/pom.xml | 6 +
.../dist/src/main/config/build-distribution.xml | 2 +-
usage/dist/src/main/license/DISCLAIMER | 8 -
usage/dist/src/main/license/LICENSE | 1691 ---------------
usage/dist/src/main/license/NOTICE | 5 -
usage/dist/src/main/license/README.md | 2 +
usage/dist/src/main/license/files/DISCLAIMER | 8 +
usage/dist/src/main/license/files/LICENSE | 1987 ++++++++++++++++++
usage/dist/src/main/license/files/NOTICE | 5 +
usage/jsgui/pom.xml | 4 +-
usage/jsgui/src/main/license/DISCLAIMER | 8 -
usage/jsgui/src/main/license/LICENSE | 292 ---
usage/jsgui/src/main/license/NOTICE | 5 -
usage/jsgui/src/main/license/README.md | 7 +
usage/jsgui/src/main/license/files/DISCLAIMER | 8 +
usage/jsgui/src/main/license/files/LICENSE | 354 ++++
usage/jsgui/src/main/license/files/NOTICE | 5 +
.../src/main/license/source-inclusions.yaml | 39 +
.../brooklyn/launcher/BrooklynWebServer.java | 14 +-
.../launcher/config/CustomResourceLocator.java | 2 +-
.../entity/basic/VanillaSoftwareYamlTest.java | 2 +-
.../brooklynnode/BrooklynNodeRestTest.java | 6 +-
.../database/mssql/MssqlBlueprintLiveTest.java | 5 +-
.../BrooklynLauncherRebindCatalogTest.java | 2 +-
.../launcher/BrooklynWebServerTest.java | 4 +-
.../blueprints/AbstractBlueprintTest.java | 2 +-
.../qa/load/SimulatedMySqlNodeImpl.java | 6 +-
.../brooklyn/qa/longevity/MonitorUtils.java | 4 +-
.../SoftlayerObtainPrivateLiveTest.java | 4 +-
.../resources/AbstractBrooklynRestResource.java | 2 +-
.../rest/resources/ApplicationResource.java | 2 +-
.../rest/resources/CatalogResource.java | 2 +-
.../rest/resources/EntityConfigResource.java | 4 +-
.../brooklyn/rest/resources/EntityResource.java | 2 +-
.../rest/resources/PolicyConfigResource.java | 5 +-
.../brooklyn/rest/resources/PolicyResource.java | 4 +-
.../brooklyn/rest/resources/SensorResource.java | 2 +-
.../brooklyn/rest/resources/ServerResource.java | 6 +-
.../rest/transform/CatalogTransformer.java | 2 +-
.../rest/transform/EffectorTransformer.java | 4 +-
.../rest/transform/LocationTransformer.java | 2 +-
.../rest/transform/PolicyTransformer.java | 2 +-
.../rest/transform/TaskTransformer.java | 2 +-
.../rest/util/BrooklynRestResourceUtils.java | 6 +-
.../rest/util/DefaultExceptionMapper.java | 2 +-
.../BrooklynPropertiesSecurityFilterTest.java | 6 +-
.../brooklyn/rest/HaMasterCheckFilterTest.java | 4 +-
.../rest/resources/CatalogResetTest.java | 2 +-
.../SensorResourceIntegrationTest.java | 4 +-
.../ServerResourceIntegrationTest.java | 7 +-
.../rest/testing/mocks/CapitalizePolicy.java | 3 +-
.../testing/mocks/RestMockSimpleEntity.java | 3 +-
.../testing/mocks/RestMockSimplePolicy.java | 4 +-
.../util/BrooklynRestResourceUtilsTest.java | 2 +-
.../json/BrooklynJacksonSerializerTest.java | 2 +-
.../util/jmx/jmxmp/JmxmpAgentSslTest.java | 5 +-
.../brooklyn/util/jmx/jmxmp/JmxmpClient.java | 3 +-
.../brooklyn/osgi/tests/SimpleLocation.java | 3 +-
.../java/brooklyn/osgi/tests/SimplePolicy.java | 5 +-
.../osgi/tests/more/MoreEntityImpl.java | 3 +-
.../brooklyn/osgi/tests/more/MorePolicy.java | 3 +-
.../osgi/tests/more/MoreEntityImpl.java | 2 +-
.../osgi/tests/more/MoreEntityImpl.java | 2 +-
.../brooklyn/osgi/tests/more/MorePolicy.java | 2 +-
951 files changed, 36820 insertions(+), 34031 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/8cba4d3c/software/database/src/main/java/brooklyn/entity/database/postgresql/PostgreSqlNode.java
----------------------------------------------------------------------
diff --cc software/database/src/main/java/brooklyn/entity/database/postgresql/PostgreSqlNode.java
index e8496f3,20f4b76..b90d19e
--- a/software/database/src/main/java/brooklyn/entity/database/postgresql/PostgreSqlNode.java
+++ b/software/database/src/main/java/brooklyn/entity/database/postgresql/PostgreSqlNode.java
@@@ -31,9 -31,9 +31,9 @@@ import brooklyn.entity.database.Databas
import brooklyn.entity.database.DatastoreMixins;
import brooklyn.entity.database.DatastoreMixins.DatastoreCommon;
import brooklyn.entity.effector.Effectors;
+import brooklyn.event.basic.BasicAttributeSensorAndConfigKey;
import brooklyn.event.basic.PortAttributeSensorAndConfigKey;
- import brooklyn.util.flags.SetFromFlag;
-
+ import org.apache.brooklyn.location.basic.PortRanges;
/**
* PostgreSQL database node entity.
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/8cba4d3c/software/database/src/main/java/brooklyn/entity/database/postgresql/PostgreSqlSshDriver.java
----------------------------------------------------------------------
diff --cc software/database/src/main/java/brooklyn/entity/database/postgresql/PostgreSqlSshDriver.java
index 0242af1,5ff0175..b7b5722
--- a/software/database/src/main/java/brooklyn/entity/database/postgresql/PostgreSqlSshDriver.java
+++ b/software/database/src/main/java/brooklyn/entity/database/postgresql/PostgreSqlSshDriver.java
@@@ -47,8 -47,11 +47,12 @@@ import brooklyn.entity.basic.SoftwarePr
import brooklyn.entity.database.DatastoreMixins;
import brooklyn.entity.software.SshEffectorTasks;
+import org.apache.brooklyn.api.entity.basic.EntityLocal;
import org.apache.brooklyn.api.location.OsDetails;
+ import org.apache.brooklyn.core.util.task.DynamicTasks;
+ import org.apache.brooklyn.core.util.task.ssh.SshTasks;
+ import org.apache.brooklyn.core.util.task.ssh.SshTasks.OnFailingTask;
+ import org.apache.brooklyn.core.util.task.system.ProcessTaskWrapper;
import org.apache.brooklyn.location.basic.SshMachineLocation;
import brooklyn.util.collections.MutableList;
@@@ -57,12 -60,7 +61,8 @@@ import brooklyn.util.exceptions.Excepti
import brooklyn.util.net.Urls;
import brooklyn.util.os.Os;
import brooklyn.util.stream.Streams;
- import brooklyn.util.task.DynamicTasks;
- import brooklyn.util.task.ssh.SshTasks;
- import brooklyn.util.task.ssh.SshTasks.OnFailingTask;
- import brooklyn.util.task.system.ProcessTaskWrapper;
import brooklyn.util.text.Identifiers;
+import brooklyn.util.text.StringEscapes;
import brooklyn.util.text.StringFunctions;
import brooklyn.util.text.Strings;
[13/24] incubator-brooklyn git commit: [BROOKLYN-162] Renaming
package policy
Posted by he...@apache.org.
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/d30ff597/policy/src/main/java/org/apache/brooklyn/policy/loadbalancing/BalancingStrategy.java
----------------------------------------------------------------------
diff --git a/policy/src/main/java/org/apache/brooklyn/policy/loadbalancing/BalancingStrategy.java b/policy/src/main/java/org/apache/brooklyn/policy/loadbalancing/BalancingStrategy.java
new file mode 100644
index 0000000..9d3968b
--- /dev/null
+++ b/policy/src/main/java/org/apache/brooklyn/policy/loadbalancing/BalancingStrategy.java
@@ -0,0 +1,622 @@
+/*
+ * 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.policy.loadbalancing;
+
+import java.text.MessageFormat;
+import java.util.Collection;
+import java.util.LinkedHashSet;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+
+import org.apache.brooklyn.api.entity.Entity;
+import org.apache.brooklyn.api.location.Location;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Represents an abstract algorithm for optimally balancing worker "items" among several "containers" based on the workloads
+ * of the items, and corresponding high- and low-thresholds on the containers.
+ *
+ * TODO: extract interface, provide default implementation
+ * TODO: remove legacy code comments
+ */
+public class BalancingStrategy<NodeType extends Entity, ItemType extends Movable> {
+
+ // FIXME Bad idea to use MessageFormat.format in this way; if toString of entity contains
+ // special characters interpreted by MessageFormat, then it will all break!
+
+ // This is a modified version of the watermark elasticity policy from Monterey v3.
+
+ private static final Logger LOG = LoggerFactory.getLogger(BalancingStrategy.class);
+
+ private static final int MAX_MIGRATIONS_PER_BALANCING_NODE = 20; // arbitrary (Splodge)
+ private static final boolean BALANCE_COLD_PULLS_IN_SAME_RUN_AS_HOT_PUSHES = false;
+
+ private final String name;
+ private final BalanceablePoolModel<NodeType, ItemType> model;
+ private final PolicyUtilForPool<NodeType, ItemType> helper;
+// private boolean loggedColdestTooHigh = false;
+// private boolean loggedHottestTooLow = false;
+
+
+ public BalancingStrategy(String name, BalanceablePoolModel<NodeType, ItemType> model) {
+ this.name = name;
+ this.model = model;
+ this.helper = new PolicyUtilForPool<NodeType, ItemType>(model);
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void rebalance() {
+ checkAndApplyOn(model.getPoolContents());
+ }
+
+ public int getMaxMigrationsPerBalancingNode() {
+ return MAX_MIGRATIONS_PER_BALANCING_NODE;
+ }
+
+ public BalanceablePoolModel<NodeType, ItemType> getDataProvider() {
+ return model;
+ }
+
+ // This was the entry point for the legacy policy.
+ private void checkAndApplyOn(final Collection<NodeType> dirtyNodesSupplied) {
+ Collection<NodeType> dirtyNodes = dirtyNodesSupplied;
+
+// if (startTime + FORCE_ALL_NODES_IF_DELAYED_FOR_MILLIS < System.currentTimeMillis()) {
+// Set<NodeType> allNodes = new LinkedHashSet<NodeType>();
+// allNodes.addAll(dirtyNodes);
+// dirtyNodes = allNodes;
+// allNodes.addAll(getDataProvider().getPoolContents());
+// if (LOG.isDebugEnabled())
+// LOG.debug("policy "+getDataProvider().getAbbr()+" running after delay ("+
+// TimeUtils.makeTimeString(System.currentTimeMillis() - startTime)+", analysing all nodes: "+
+// dirtyNodes);
+// }
+
+// nodeFinder.optionalCachedNodesWithBacklogDetected.clear();
+// boolean gonnaGrow = growPool(dirtyNodes);
+// getDataProvider().waitForAllTransitionsComplete();
+ boolean gonnaGrow = false;
+
+ Set<NodeType> nonFrozenDirtyNodes = new LinkedHashSet<NodeType>(dirtyNodes);
+// boolean gonnaShrink = false;
+// if (!gonnaGrow && !DONT_SHRINK_UNLESS_BALANCED) {
+// gonnaShrink = shrinkPool(nonFrozenDirtyNodes);
+// getDataProvider().waitForAllTransitionsComplete();
+// }
+
+ if (getDataProvider().getPoolSize() >= 2) {
+ boolean didBalancing = false;
+ for (NodeType a : nonFrozenDirtyNodes) {
+ didBalancing |= balanceItemsOnNodesInQuestion(a, gonnaGrow);
+// getMutator().waitForAllTransitionsComplete();
+ }
+ if (didBalancing) {
+ return;
+ }
+ }
+
+// if (!gonnaGrow && DONT_SHRINK_UNLESS_BALANCED) {
+// gonnaShrink = shrinkPool(nonFrozenDirtyNodes);
+// getDataProvider().waitForAllTransitionsComplete();
+// }
+
+// if (gonnaGrow || gonnaShrink)
+// //don't log 'doing nothing' message
+// return;
+
+// if (LOG.isDebugEnabled()) {
+// double poolTotal = getDataProvider().getPoolPredictedWorkrateTotal();
+// int poolSize = getDataProvider().getPoolPredictedSize();
+// LOG.debug(MessageFormat.format("policy "+getDataProvider().getAbbr()+" did nothing; pool workrate is {0,number,#.##} x {1} nodes",
+// 1.0*poolTotal/poolSize, poolSize));
+// }
+ }
+
+ protected boolean balanceItemsOnNodesInQuestion(NodeType questionedNode, boolean gonnaGrow) {
+ double questionedNodeTotalWorkrate = getDataProvider().getTotalWorkrate(questionedNode);
+
+ boolean balanced = balanceItemsOnHotNode(questionedNode, questionedNodeTotalWorkrate, gonnaGrow);
+// getMutator().waitForAllTransitionsComplete();
+
+ if (!balanced || BALANCE_COLD_PULLS_IN_SAME_RUN_AS_HOT_PUSHES) {
+ balanced |= balanceItemsOnColdNode(questionedNode, questionedNodeTotalWorkrate, gonnaGrow);
+// getMutator().waitForAllTransitionsComplete();
+ }
+ if (balanced)
+ return true;
+
+ if (LOG.isDebugEnabled()) {
+ LOG.debug( MessageFormat.format(
+ "policy "+getDataProvider().getName()+" not balancing "+questionedNode+"; " +
+ "its workrate {0,number,#.##} is acceptable (or cannot be balanced)", questionedNodeTotalWorkrate) );
+ }
+ return false;
+ }
+
+ protected boolean balanceItemsOnHotNode(NodeType node, double nodeWorkrate, boolean gonnaGrow) {
+ double originalNodeWorkrate = nodeWorkrate;
+ int migrationCount = 0;
+ int iterationCount = 0;
+
+ Set<ItemType> itemsMoved = new LinkedHashSet<ItemType>();
+ Set<NodeType> nodesChecked = new LinkedHashSet<NodeType>();
+
+// if (nodeFinder.config.COUNT_BACKLOG_AS_EXTRA_WORKRATE) {
+// int backlog = nodeFinder.getBacklogQueueLength(questionedNode);
+// if (backlog>0) {
+// Level l = backlog>1000 ? Level.INFO : backlog>10 ? Level.FINE : Level.FINER;
+// if (LOG.isLoggable(l)) {
+// LOG.log( l, MessageFormat.format(
+// "policy "+getDataProvider().getAbbr()+" detected queue at node "+questionedNode+", " +
+// "inflating workrate {0,number,#.##} + "+backlog, questionedNodeTotalWorkrate) );
+// }
+// questionedNodeTotalWorkrate += backlog;
+// }
+// }
+
+ Double highThreshold = model.getHighThreshold(node);
+ if (highThreshold == -1) {
+ // node presumably has been removed; TODO log
+ return false;
+ }
+
+ while (nodeWorkrate > highThreshold && migrationCount < getMaxMigrationsPerBalancingNode()) {
+ iterationCount++;
+
+ if (LOG.isDebugEnabled()) {
+ LOG.debug(MessageFormat.format(
+ "policy "+getDataProvider().getName()+" considering balancing hot node "+node+" " +
+ "(workrate {0,number,#.##}); iteration "+iterationCount, nodeWorkrate) );
+ }
+
+ // move from hot node, to coldest
+
+ NodeType coldNode = helper.findColdestContainer(nodesChecked);
+
+ if (coldNode == null) {
+ if (LOG.isDebugEnabled()) {
+ LOG.debug( MessageFormat.format(
+ "policy "+getDataProvider().getName()+" not balancing hot node "+node+" " +
+ "(workrate {0,number,#.##}); no coldest node available", nodeWorkrate) );
+ }
+ break;
+ }
+
+ if (coldNode.equals(node)) {
+ if (LOG.isDebugEnabled()) {
+ LOG.debug( MessageFormat.format(
+ "policy "+getDataProvider().getName()+" not balancing hot node "+node+" " +
+ "(workrate {0,number,#.##}); it is also the coldest modifiable node", nodeWorkrate) );
+ }
+ break;
+ }
+
+ double coldNodeWorkrate = getDataProvider().getTotalWorkrate(coldNode);
+ boolean emergencyLoadBalancing = coldNodeWorkrate < nodeWorkrate*2/3;
+ double coldNodeHighThreshold = model.getHighThreshold(coldNode);
+ if (coldNodeWorkrate >= coldNodeHighThreshold && !emergencyLoadBalancing) {
+ //don't balance if all nodes are approx equally hot (and very hot)
+
+ //for now, stop warning if it is a recurring theme!
+// Level level = loggedColdestTooHigh ? Level.FINER : Level.INFO;
+// LOG.log(level, MessageFormat.format(
+// "policy "+getDataProvider().getAbbr()+" not balancing hot node "+questionedNode+" " +
+// "(workrate {0,number,#.##}); coldest node "+coldNode+" has workrate {1,number,#.##} also too high"+
+// (loggedColdestTooHigh ? "" : " (future cases will be logged at finer)"),
+// questionedNodeTotalWorkrate, coldNodeWorkrate) );
+// loggedColdestTooHigh = true;
+ break;
+ }
+ double poolLowWatermark = Double.MAX_VALUE; // TODO
+ if (gonnaGrow && (coldNodeWorkrate >= poolLowWatermark && !emergencyLoadBalancing)) {
+ //if we're growing the pool, refuse to balance unless the cold node is indeed very cold, or hot node very hot
+
+ //for now, stop warning if it is a recurring theme!
+// Level level = loggedColdestTooHigh ? Level.FINER : Level.INFO;
+// LOG.log(level, MessageFormat.format(
+// "policy "+getDataProvider().getAbbr()+" not balancing hot node "+questionedNode+" " +
+// "(workrate {0,number,#.##}); coldest node "+coldNode+" has workrate {1,number,#.##} also too high to accept while pool is growing" +
+// (loggedColdestTooHigh ? "" : " (future cases will be logged at finer)"),
+// questionedNodeTotalWorkrate, coldNodeWorkrate) );
+// loggedColdestTooHigh = true;
+ break;
+ }
+
+ String questionedNodeName = getDataProvider().getName(node);
+ String coldNodeName = getDataProvider().getName(coldNode);
+ Location coldNodeLocation = getDataProvider().getLocation(coldNode);
+
+ if (LOG.isDebugEnabled()) {
+ LOG.debug( MessageFormat.format(
+ "policy "+getDataProvider().getName()+" balancing hot node "+questionedNodeName+" " +
+ "("+node+", workrate {0,number,#.##}), " +
+ "considering target "+coldNodeName+" ("+coldNode+", workrate {1,number,#.##})",
+ nodeWorkrate, coldNodeWorkrate) );
+ }
+
+ double idealSizeToMove = (nodeWorkrate - coldNodeWorkrate) / 2;
+ //if the 'ideal' amount to move would cause cold to be too hot, then reduce ideal amount
+
+ if (idealSizeToMove + coldNodeWorkrate > coldNodeHighThreshold)
+ idealSizeToMove = coldNodeHighThreshold - coldNodeWorkrate;
+
+
+ double maxSizeToMoveIdeally = Math.min(
+ nodeWorkrate/2 + 0.00001,
+ //permit it to exceed node high if there is no alternative (this is 'max' not 'ideal'),
+ //so long as it still gives a significant benefit
+ // getConfiguration().nodeHighWaterMark - coldNodeWorkrate,
+ (nodeWorkrate - coldNodeWorkrate)*0.9);
+ double maxSizeToMoveIfNoSmallButLarger = nodeWorkrate*3/4;
+
+ Map<ItemType, Double> questionedNodeItems = getDataProvider().getItemWorkrates(node);
+ if (questionedNodeItems == null) {
+ if (LOG.isDebugEnabled())
+ LOG.debug(MessageFormat.format(
+ "policy "+getDataProvider().getName()+" balancing hot node "+questionedNodeName+" " +
+ "("+node+", workrate {0,number,#.##}), abandoned; " +
+ "item report for " + questionedNodeName + " unavailable",
+ nodeWorkrate));
+ break;
+ }
+ ItemType itemToMove = findBestItemToMove(questionedNodeItems, idealSizeToMove, maxSizeToMoveIdeally,
+ maxSizeToMoveIfNoSmallButLarger, itemsMoved, coldNodeLocation);
+
+ if (itemToMove == null) {
+ if (LOG.isDebugEnabled())
+ LOG.debug(MessageFormat.format(
+ "policy "+getDataProvider().getName()+" balancing hot node "+questionedNodeName+" " +
+ "("+node+", workrate {0,number,#.##}), ending; " +
+ "no suitable segment found " +
+ "(ideal transition item size {1,number,#.##}, max {2,number,#.##}, " +
+ "moving to coldest node "+coldNodeName+" ("+coldNode+", workrate {3,number,#.##}); available items: {4}",
+ nodeWorkrate, idealSizeToMove, maxSizeToMoveIdeally, coldNodeWorkrate, questionedNodeItems) );
+ break;
+ }
+
+ itemsMoved.add(itemToMove);
+ double itemWorkrate = questionedNodeItems.get(itemToMove);
+
+// if (LOG.isLoggable(Level.FINE))
+// LOG.fine( MessageFormat.format(
+// "policy "+getDataProvider().getAbbr()+" balancing hot node "+questionedNodeName+" " +
+// "(workrate {0,number,#.##}, too high), transitioning " + itemToMove +
+// " to "+coldNodeName+" (workrate {1,number,#.##}, now += {2,number,#.##})",
+// questionedNodeTotalWorkrate, coldNodeWorkrate, segmentRate) );
+
+ nodeWorkrate -= itemWorkrate;
+ coldNodeWorkrate += itemWorkrate;
+
+ moveItem(itemToMove, node, coldNode);
+ ++migrationCount;
+ }
+
+ if (LOG.isDebugEnabled()) {
+ if (iterationCount == 0) {
+ if (LOG.isTraceEnabled())
+ LOG.trace( MessageFormat.format(
+ "policy "+getDataProvider().getName()+" balancing if hot finished at node "+node+"; " +
+ "workrate {0,number,#.##} not hot",
+ originalNodeWorkrate) );
+ }
+ else if (itemsMoved.isEmpty()) {
+ if (LOG.isTraceEnabled())
+ LOG.trace( MessageFormat.format(
+ "policy "+getDataProvider().getName()+" balancing finished at hot node "+node+" " +
+ "(workrate {0,number,#.##}); no way to improve it",
+ originalNodeWorkrate) );
+ } else {
+ LOG.debug( MessageFormat.format(
+ "policy "+getDataProvider().getName()+" balancing finished at hot node "+node+"; " +
+ "workrate from {0,number,#.##} to {1,number,#.##} (report now says {2,number,#.##}) " +
+ "by moving off {3}",
+ originalNodeWorkrate,
+ nodeWorkrate,
+ getDataProvider().getTotalWorkrate(node),
+ itemsMoved
+ ) );
+ }
+ }
+ return !itemsMoved.isEmpty();
+ }
+
+ protected boolean balanceItemsOnColdNode(NodeType questionedNode, double questionedNodeTotalWorkrate, boolean gonnaGrow) {
+ // Abort if the node has pending adjustments.
+ Map<ItemType, Double> items = getDataProvider().getItemWorkrates(questionedNode);
+ if (items == null) {
+ if (LOG.isDebugEnabled()) {
+ LOG.debug( MessageFormat.format(
+ "policy "+getDataProvider().getName()+" not balancing cold node "+questionedNode+" " +
+ "(workrate {0,number,#.##}); workrate breakdown unavailable (probably reverting)",
+ questionedNodeTotalWorkrate) );
+ }
+ return false;
+ }
+ for (ItemType item : items.keySet()) {
+ if (!model.isItemMoveable(item)) {
+ if (LOG.isDebugEnabled()) {
+ LOG.debug( MessageFormat.format(
+ "policy "+getDataProvider().getName()+" not balancing cold node "+questionedNode+" " +
+ "(workrate {0,number,#.##}); at least one item ("+item+") is in flux",
+ questionedNodeTotalWorkrate) );
+ }
+ return false;
+ }
+ }
+
+ double originalQuestionedNodeTotalWorkrate = questionedNodeTotalWorkrate;
+ int numMigrations = 0;
+
+ Set<ItemType> itemsMoved = new LinkedHashSet<ItemType>();
+ Set<NodeType> nodesChecked = new LinkedHashSet<NodeType>();
+
+ int iters = 0;
+ Location questionedLocation = getDataProvider().getLocation(questionedNode);
+
+ double lowThreshold = model.getLowThreshold(questionedNode);
+ while (questionedNodeTotalWorkrate < lowThreshold) {
+ iters++;
+
+ if (LOG.isDebugEnabled()) {
+ LOG.debug( MessageFormat.format(
+ "policy "+getDataProvider().getName()+" considering balancing cold node "+questionedNode+" " +
+ "(workrate {0,number,#.##}); iteration "+iters, questionedNodeTotalWorkrate));
+ }
+
+ // move from cold node, to hottest
+
+ NodeType hotNode = helper.findHottestContainer(nodesChecked);
+
+ if (hotNode == null) {
+ if (LOG.isDebugEnabled()) {
+ LOG.debug( MessageFormat.format(
+ "policy "+getDataProvider().getName()+" not balancing cold node "+questionedNode+" " +
+ "(workrate {0,number,#.##}); no hottest node available", questionedNodeTotalWorkrate) );
+ }
+
+ break;
+ }
+ if (hotNode.equals(questionedNode)) {
+ if (LOG.isDebugEnabled()) {
+ LOG.debug( MessageFormat.format(
+ "policy "+getDataProvider().getName()+" not balancing cold node "+questionedNode+" " +
+ "(workrate {0,number,#.##}); it is also the hottest modfiable node", questionedNodeTotalWorkrate) );
+ }
+ break;
+ }
+
+
+ double hotNodeWorkrate = getDataProvider().getTotalWorkrate(hotNode);
+ double hotNodeLowThreshold = model.getLowThreshold(hotNode);
+ double hotNodeHighThreshold = model.getHighThreshold(hotNode);
+ boolean emergencyLoadBalancing = false; //doesn't apply to cold
+ if (hotNodeWorkrate == -1 || hotNodeLowThreshold == -1 || hotNodeHighThreshold == -1) {
+ // hotNode presumably has been removed; TODO log
+ break;
+ }
+ if (hotNodeWorkrate <= hotNodeLowThreshold && !emergencyLoadBalancing) {
+ //don't balance if all nodes are too low
+
+ //for now, stop warning if it is a recurring theme!
+// Level level = loggedHottestTooLow ? Level.FINER : Level.INFO;
+// LOG.log(level, MessageFormat.format(
+// "policy "+getDataProvider().getAbbr()+" not balancing cold node "+questionedNode+" " +
+// "(workrate {0,number,#.##}); hottest node "+hotNode+" has workrate {1,number,#.##} also too low" +
+// (loggedHottestTooLow ? "" : " (future cases will be logged at finer)"),
+// questionedNodeTotalWorkrate, hotNodeWorkrate) );
+// loggedHottestTooLow = true;
+ break;
+ }
+ if (gonnaGrow && (hotNodeWorkrate <= hotNodeHighThreshold && !emergencyLoadBalancing)) {
+ //if we're growing the pool, refuse to balance unless the hot node is quite hot
+
+ //again, stop warning if it is a recurring theme!
+// Level level = loggedHottestTooLow ? Level.FINER : Level.INFO;
+// LOG.log(level, MessageFormat.format(
+// "policy "+getDataProvider().getAbbr()+" not balancing cold node "+questionedNode+" " +
+// "(workrate {0,number,#.##}); hottest node "+hotNode+" has workrate {1,number,#.##} also too low to accept while pool is growing"+
+// (loggedHottestTooLow ? "" : " (future cases will be logged at finer)"),
+// questionedNodeTotalWorkrate, hotNodeWorkrate) );
+// loggedHottestTooLow = true;
+ break;
+ }
+
+ String questionedNodeName = getDataProvider().getName(questionedNode);
+ String hotNodeName = getDataProvider().getName(hotNode);
+
+ if (LOG.isDebugEnabled()) {
+ LOG.debug( MessageFormat.format(
+ "policy "+getDataProvider().getName()+" balancing cold node "+questionedNodeName+" " +
+ "("+questionedNode+", workrate {0,number,#.##}), " +
+ "considering source "+hotNodeName+" ("+hotNode+", workrate {1,number,#.##})",
+ questionedNodeTotalWorkrate, hotNodeWorkrate) );
+ }
+
+ double idealSizeToMove = (hotNodeWorkrate - questionedNodeTotalWorkrate) / 2;
+ //if the 'ideal' amount to move would cause cold to be too hot, then reduce ideal amount
+ double targetNodeHighThreshold = model.getHighThreshold(questionedNode);
+ if (idealSizeToMove + questionedNodeTotalWorkrate > targetNodeHighThreshold)
+ idealSizeToMove = targetNodeHighThreshold - questionedNodeTotalWorkrate;
+ double maxSizeToMoveIdeally = Math.min(
+ hotNodeWorkrate/2,
+ //allow to swap order, but not very much (0.9 was allowed when balancing high)
+ (hotNodeWorkrate - questionedNodeTotalWorkrate)*0.6);
+ double maxSizeToMoveIfNoSmallButLarger = questionedNodeTotalWorkrate*3/4;
+
+ Map<ItemType, Double> hotNodeItems = getDataProvider().getItemWorkrates(hotNode);
+ if (hotNodeItems == null) {
+ if (LOG.isDebugEnabled())
+ LOG.debug(MessageFormat.format(
+ "policy "+getDataProvider().getName()+" balancing cold node "+questionedNodeName+" " +
+ "("+questionedNode+", workrate {0,number,#.##}), " +
+ "excluding hot node "+hotNodeName+" because its item report unavailable",
+ questionedNodeTotalWorkrate));
+ nodesChecked.add(hotNode);
+ continue;
+ }
+
+ ItemType itemToMove = findBestItemToMove(hotNodeItems, idealSizeToMove, maxSizeToMoveIdeally,
+ maxSizeToMoveIfNoSmallButLarger, itemsMoved, questionedLocation);
+ if (itemToMove == null) {
+ if (LOG.isDebugEnabled())
+ LOG.debug(MessageFormat.format(
+ "policy "+getDataProvider().getName()+" balancing cold node "+questionedNodeName+" " +
+ "("+questionedNode+", workrate {0,number,#.##}), " +
+ "excluding hot node "+hotNodeName+" because it has no appilcable items " +
+ "(ideal transition item size {1,number,#.##}, max {2,number,#.##}, " +
+ "moving from hot node "+hotNodeName+" ("+hotNode+", workrate {3,number,#.##}); available items: {4}",
+ questionedNodeTotalWorkrate, idealSizeToMove, maxSizeToMoveIdeally, hotNodeWorkrate, hotNodeItems) );
+
+ nodesChecked.add(hotNode);
+ continue;
+ }
+
+ itemsMoved.add(itemToMove);
+ double segmentRate = hotNodeItems.get(itemToMove);
+
+// if (LOG.isLoggable(Level.FINE))
+// LOG.fine( MessageFormat.format(
+// "policy "+getDataProvider().getAbbr()+" balancing cold node "+questionedNodeName+" " +
+// "(workrate {0,number,#.##}, too low), transitioning " + itemToMove +
+// " from "+hotNodeName+" (workrate {1,number,#.##}, now -= {2,number,#.##})",
+// questionedNodeTotalWorkrate, hotNodeWorkrate, segmentRate) );
+
+ questionedNodeTotalWorkrate += segmentRate;
+ hotNodeWorkrate -= segmentRate;
+
+ moveItem(itemToMove, hotNode, questionedNode);
+
+ if (++numMigrations >= getMaxMigrationsPerBalancingNode()) {
+ break;
+ }
+ }
+
+ if (LOG.isDebugEnabled()) {
+ if (iters == 0) {
+ if (LOG.isTraceEnabled())
+ LOG.trace( MessageFormat.format(
+ "policy "+getDataProvider().getName()+" balancing if cold finished at node "+questionedNode+"; " +
+ "workrate {0,number,#.##} not cold",
+ originalQuestionedNodeTotalWorkrate) );
+ }
+ else if (itemsMoved.isEmpty()) {
+ if (LOG.isTraceEnabled())
+ LOG.trace( MessageFormat.format(
+ "policy "+getDataProvider().getName()+" balancing finished at cold node "+questionedNode+" " +
+ "(workrate {0,number,#.##}); no way to improve it",
+ originalQuestionedNodeTotalWorkrate) );
+ } else {
+ LOG.debug( MessageFormat.format(
+ "policy "+getDataProvider().getName()+" balancing finished at cold node "+questionedNode+"; " +
+ "workrate from {0,number,#.##} to {1,number,#.##} (report now says {2,number,#.##}) " +
+ "by moving in {3}",
+ originalQuestionedNodeTotalWorkrate,
+ questionedNodeTotalWorkrate,
+ getDataProvider().getTotalWorkrate(questionedNode),
+ itemsMoved) );
+ }
+ }
+ return !itemsMoved.isEmpty();
+ }
+
+ protected void moveItem(ItemType item, NodeType oldNode, NodeType newNode) {
+ item.move(newNode);
+ model.onItemMoved(item, newNode);
+ }
+
+ /**
+ * "Best" is defined as nearest to the targetCost, without exceeding maxCost, unless maxCostIfNothingSmallerButLarger > 0
+ * which does just that (useful if the ideal and target are estimates and aren't quite right, typically it will take
+ * something larger than maxRate but less than half the total rate, which is only possible when the estimates don't agree)
+ */
+ protected ItemType findBestItemToMove(Map<ItemType, Double> costsPerItem, double targetCost, double maxCost,
+ double maxCostIfNothingSmallerButLarger, Set<ItemType> excludedItems, Location locationIfKnown) {
+
+ ItemType closestMatch = null;
+ ItemType smallestMoveable = null, largest = null;
+ double minDiff = Double.MAX_VALUE, smallestC = Double.MAX_VALUE, largestC = Double.MIN_VALUE;
+ boolean exclusions = false;
+
+ for (Entry<ItemType, Double> entry : costsPerItem.entrySet()) {
+ ItemType item = entry.getKey();
+ Double cost = entry.getValue();
+
+ if (cost == null) {
+ if (LOG.isDebugEnabled()) LOG.debug(MessageFormat.format("Item ''{0}'' has null workrate: skipping", item));
+ continue;
+ }
+
+ if (!model.isItemMoveable(item)) {
+ if (LOG.isDebugEnabled()) LOG.debug(MessageFormat.format("Item ''{0}'' cannot be moved: skipping", item));
+ continue;
+ }
+ if (cost < 0) {
+ if (LOG.isDebugEnabled()) LOG.debug(MessageFormat.format("Item ''{0}'' subject to recent adjustment: skipping", item));
+ continue;
+ }
+ if (excludedItems.contains(item)) {
+ exclusions = true;
+ continue;
+ }
+ if (cost < 0) { // FIXME: already tested above
+ exclusions = true;
+ continue;
+ }
+ if (cost <= 0) { // FIXME: overlaps previous condition
+ continue;
+ }
+ if (largest == null || cost > largestC) {
+ largest = item;
+ largestC = cost;
+ }
+ if (!model.isItemMoveable(item)) { // FIXME: already tested above
+ continue;
+ }
+ if (locationIfKnown != null && !model.isItemAllowedIn(item, locationIfKnown)) {
+ continue;
+ }
+ if (smallestMoveable == null || cost < smallestC) {
+ smallestMoveable = item;
+ smallestC = cost;
+ }
+ if (cost > maxCost) {
+ continue;
+ }
+ double diff = Math.abs(targetCost - cost);
+ if (closestMatch == null || diff < minDiff) {
+ closestMatch = item;
+ minDiff = diff;
+ }
+ }
+
+ if (closestMatch != null)
+ return closestMatch;
+
+ if (smallestC < maxCostIfNothingSmallerButLarger && smallestC < largestC && !exclusions)
+ return smallestMoveable;
+
+ return null;
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/d30ff597/policy/src/main/java/org/apache/brooklyn/policy/loadbalancing/DefaultBalanceablePoolModel.java
----------------------------------------------------------------------
diff --git a/policy/src/main/java/org/apache/brooklyn/policy/loadbalancing/DefaultBalanceablePoolModel.java b/policy/src/main/java/org/apache/brooklyn/policy/loadbalancing/DefaultBalanceablePoolModel.java
new file mode 100644
index 0000000..5f6cabc
--- /dev/null
+++ b/policy/src/main/java/org/apache/brooklyn/policy/loadbalancing/DefaultBalanceablePoolModel.java
@@ -0,0 +1,280 @@
+/*
+ * 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.policy.loadbalancing;
+
+import java.io.ByteArrayOutputStream;
+import java.io.PrintStream;
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.apache.brooklyn.api.location.Location;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.collect.HashMultimap;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Multimaps;
+import com.google.common.collect.SetMultimap;
+
+/**
+ * Standard implementation of {@link BalanceablePoolModel}, providing essential arithmetic for item and container
+ * workrates and thresholds. See subclasses for specific requirements for migrating items.
+ */
+public class DefaultBalanceablePoolModel<ContainerType, ItemType> implements BalanceablePoolModel<ContainerType, ItemType> {
+
+ private static final Logger LOG = LoggerFactory.getLogger(DefaultBalanceablePoolModel.class);
+
+ /*
+ * Performance comments.
+ * - Used hprof with LoadBalancingPolicySoakTest.testLoadBalancingManyManyItemsTest (1000 items)
+ * - Prior to adding containerToItems, it created a new set by iterating over all items.
+ * This was the biggest percentage of any brooklyn code.
+ * Hence it's worth duplicating the values, keyed by item and keyed by container.
+ * - Unfortunately changing threading model (so have a "rebalancer" thread, and a thread that
+ * processes events to update the model), get ConcurrentModificationException if don't take
+ * copy of containerToItems.get(node)...
+ */
+
+ // Concurrent maps cannot have null value; use this to represent when no container is supplied for an item
+ private static final String NULL_CONTAINER = "null-container";
+
+ private final String name;
+ private final Set<ContainerType> containers = Collections.newSetFromMap(new ConcurrentHashMap<ContainerType,Boolean>());
+ private final Map<ContainerType, Double> containerToLowThreshold = new ConcurrentHashMap<ContainerType, Double>();
+ private final Map<ContainerType, Double> containerToHighThreshold = new ConcurrentHashMap<ContainerType, Double>();
+ private final Map<ItemType, ContainerType> itemToContainer = new ConcurrentHashMap<ItemType, ContainerType>();
+ private final SetMultimap<ContainerType, ItemType> containerToItems = Multimaps.synchronizedSetMultimap(HashMultimap.<ContainerType, ItemType>create());
+ private final Map<ItemType, Double> itemToWorkrate = new ConcurrentHashMap<ItemType, Double>();
+ private final Set<ItemType> immovableItems = Collections.newSetFromMap(new ConcurrentHashMap<ItemType, Boolean>());
+
+ private volatile double poolLowThreshold = 0;
+ private volatile double poolHighThreshold = 0;
+ private volatile double currentPoolWorkrate = 0;
+
+ public DefaultBalanceablePoolModel(String name) {
+ this.name = name;
+ }
+
+ public ContainerType getParentContainer(ItemType item) {
+ ContainerType result = itemToContainer.get(item);
+ return (result != NULL_CONTAINER) ? result : null;
+ }
+
+ public Set<ItemType> getItemsForContainer(ContainerType node) {
+ Set<ItemType> result = containerToItems.get(node);
+ synchronized (containerToItems) {
+ return (result != null) ? ImmutableSet.copyOf(result) : Collections.<ItemType>emptySet();
+ }
+ }
+
+ public Double getItemWorkrate(ItemType item) {
+ return itemToWorkrate.get(item);
+ }
+
+ @Override public double getPoolLowThreshold() { return poolLowThreshold; }
+ @Override public double getPoolHighThreshold() { return poolHighThreshold; }
+ @Override public double getCurrentPoolWorkrate() { return currentPoolWorkrate; }
+ @Override public boolean isHot() { return !containers.isEmpty() && currentPoolWorkrate > poolHighThreshold; }
+ @Override public boolean isCold() { return !containers.isEmpty() && currentPoolWorkrate < poolLowThreshold; }
+
+
+ // Provider methods.
+
+ @Override public String getName() { return name; }
+ @Override public int getPoolSize() { return containers.size(); }
+ @Override public Set<ContainerType> getPoolContents() { return containers; }
+ @Override public String getName(ContainerType container) { return container.toString(); } // TODO: delete?
+ @Override public Location getLocation(ContainerType container) { return null; } // TODO?
+
+ @Override public double getLowThreshold(ContainerType container) {
+ Double result = containerToLowThreshold.get(container);
+ return (result != null) ? result : -1;
+ }
+
+ @Override public double getHighThreshold(ContainerType container) {
+ Double result = containerToHighThreshold.get(container);
+ return (result != null) ? result : -1;
+ }
+
+ @Override public double getTotalWorkrate(ContainerType container) {
+ double totalWorkrate = 0;
+ for (ItemType item : getItemsForContainer(container)) {
+ Double workrate = itemToWorkrate.get(item);
+ if (workrate != null)
+ totalWorkrate += Math.abs(workrate);
+ }
+ return totalWorkrate;
+ }
+
+ @Override public Map<ContainerType, Double> getContainerWorkrates() {
+ Map<ContainerType, Double> result = new LinkedHashMap<ContainerType, Double>();
+ for (ContainerType node : containers)
+ result.put(node, getTotalWorkrate(node));
+ return result;
+ }
+
+ @Override public Map<ItemType, Double> getItemWorkrates(ContainerType node) {
+ Map<ItemType, Double> result = new LinkedHashMap<ItemType, Double>();
+ for (ItemType item : getItemsForContainer(node))
+ result.put(item, itemToWorkrate.get(item));
+ return result;
+ }
+
+ @Override public boolean isItemMoveable(ItemType item) {
+ // If don't know about item, then assume not movable; otherwise has this item been explicitly flagged as immovable?
+ return itemToContainer.containsKey(item) && !immovableItems.contains(item);
+ }
+
+ @Override public boolean isItemAllowedIn(ItemType item, Location location) {
+ return true; // TODO?
+ }
+
+
+ // Mutators.
+
+ @Override
+ public void onItemMoved(ItemType item, ContainerType newNode) {
+ if (!itemToContainer.containsKey(item)) {
+ // Item may have been deleted; order of events received from different sources
+ // (i.e. item itself and for itemGroup membership) is non-deterministic.
+ LOG.info("Balanceable pool model ignoring onItemMoved for unknown item {} to container {}; " +
+ "if onItemAdded subsequently received will get new container then", item, newNode);
+ return;
+ }
+ ContainerType newNodeNonNull = toNonNullContainer(newNode);
+ ContainerType oldNode = itemToContainer.put(item, newNodeNonNull);
+ if (oldNode != null && oldNode != NULL_CONTAINER) containerToItems.remove(oldNode, item);
+ if (newNode != null) containerToItems.put(newNode, item);
+ }
+
+ @Override
+ public void onContainerAdded(ContainerType newContainer, double lowThreshold, double highThreshold) {
+ boolean added = containers.add(newContainer);
+ if (!added) {
+ // See LoadBalancingPolicy.onContainerAdded for possible explanation of why can get duplicate calls
+ LOG.debug("Duplicate container-added event for {}; ignoring", newContainer);
+ return;
+ }
+ containerToLowThreshold.put(newContainer, lowThreshold);
+ containerToHighThreshold.put(newContainer, highThreshold);
+ poolLowThreshold += lowThreshold;
+ poolHighThreshold += highThreshold;
+ }
+
+ @Override
+ public void onContainerRemoved(ContainerType oldContainer) {
+ containers.remove(oldContainer);
+ Double containerLowThreshold = containerToLowThreshold.remove(oldContainer);
+ Double containerHighThresold = containerToHighThreshold.remove(oldContainer);
+ poolLowThreshold -= (containerLowThreshold != null ? containerLowThreshold : 0);
+ poolHighThreshold -= (containerHighThresold != null ? containerHighThresold : 0);
+
+ // TODO: assert no orphaned items
+ }
+
+ @Override
+ public void onItemAdded(ItemType item, ContainerType parentContainer) {
+ onItemAdded(item, parentContainer, false);
+ }
+
+ @Override
+ public void onItemAdded(ItemType item, ContainerType parentContainer, boolean immovable) {
+ // Duplicate calls to onItemAdded do no harm, as long as most recent is most accurate!
+ // Important that it stays that way for now - See LoadBalancingPolicy.onContainerAdded for explanation.
+
+ if (immovable)
+ immovableItems.add(item);
+
+ ContainerType parentContainerNonNull = toNonNullContainer(parentContainer);
+ ContainerType oldNode = itemToContainer.put(item, parentContainerNonNull);
+ if (oldNode != null && oldNode != NULL_CONTAINER) containerToItems.remove(oldNode, item);
+ if (parentContainer != null) containerToItems.put(parentContainer, item);
+ }
+
+ @Override
+ public void onItemRemoved(ItemType item) {
+ ContainerType oldNode = itemToContainer.remove(item);
+ if (oldNode != null && oldNode != NULL_CONTAINER) containerToItems.remove(oldNode, item);
+ Double workrate = itemToWorkrate.remove(item);
+ if (workrate != null)
+ currentPoolWorkrate -= workrate;
+ immovableItems.remove(item);
+ }
+
+ @Override
+ public void onItemWorkrateUpdated(ItemType item, double newValue) {
+ if (hasItem(item)) {
+ Double oldValue = itemToWorkrate.put(item, newValue);
+ double delta = ( newValue - (oldValue != null ? oldValue : 0) );
+ currentPoolWorkrate += delta;
+ } else {
+ // Can happen when item removed - get notification of removal and workrate from group and item
+ // respectively, so can overtake each other
+ if (LOG.isDebugEnabled()) LOG.debug("Ignoring setting of workrate for unknown item {}, to {}", item, newValue);
+ }
+ }
+
+ private boolean hasItem(ItemType item) {
+ return itemToContainer.containsKey(item);
+ }
+
+
+ // Additional methods for tests.
+
+ /**
+ * Warning: this can be an expensive (time and memory) operation if there are a lot of items/containers.
+ */
+ @VisibleForTesting
+ public String itemDistributionToString() {
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ dumpItemDistribution(new PrintStream(baos));
+ return new String(baos.toByteArray());
+ }
+
+ @VisibleForTesting
+ public void dumpItemDistribution() {
+ dumpItemDistribution(System.out);
+ }
+
+ @VisibleForTesting
+ public void dumpItemDistribution(PrintStream out) {
+ for (ContainerType container : getPoolContents()) {
+ out.println("Container '"+container+"': ");
+ for (ItemType item : getItemsForContainer(container)) {
+ Double workrate = getItemWorkrate(item);
+ out.println("\t"+"Item '"+item+"' ("+workrate+")");
+ }
+ }
+ out.flush();
+ }
+
+ @SuppressWarnings("unchecked")
+ private ContainerType nullContainer() {
+ return (ContainerType) NULL_CONTAINER; // relies on erasure
+ }
+
+ private ContainerType toNonNullContainer(ContainerType container) {
+ return (container != null) ? container : nullContainer();
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/d30ff597/policy/src/main/java/org/apache/brooklyn/policy/loadbalancing/ItemsInContainersGroup.java
----------------------------------------------------------------------
diff --git a/policy/src/main/java/org/apache/brooklyn/policy/loadbalancing/ItemsInContainersGroup.java b/policy/src/main/java/org/apache/brooklyn/policy/loadbalancing/ItemsInContainersGroup.java
new file mode 100644
index 0000000..2538bc6
--- /dev/null
+++ b/policy/src/main/java/org/apache/brooklyn/policy/loadbalancing/ItemsInContainersGroup.java
@@ -0,0 +1,52 @@
+/*
+ * 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.policy.loadbalancing;
+
+import org.apache.brooklyn.api.entity.Entity;
+import org.apache.brooklyn.api.entity.Group;
+import org.apache.brooklyn.api.entity.proxying.ImplementedBy;
+import org.apache.brooklyn.core.util.flags.SetFromFlag;
+
+import brooklyn.config.ConfigKey;
+import brooklyn.entity.basic.DynamicGroup;
+import brooklyn.event.basic.BasicConfigKey;
+
+import com.google.common.base.Predicate;
+import com.google.common.base.Predicates;
+
+/**
+ * A group of items that are contained within a given (dynamically changing) set of containers.
+ *
+ * The {@link setContainers(Group)} sets the group of containers. The membership of that group
+ * is dynamically tracked.
+ *
+ * When containers are added/removed, or when an items is added/removed, or when an {@link Moveable} item
+ * is moved then the membership of this group of items is automatically updated accordingly.
+ *
+ * For example: in Monterey, this could be used to track the actors that are within a given cluster of venues.
+ */
+@ImplementedBy(ItemsInContainersGroupImpl.class)
+public interface ItemsInContainersGroup extends DynamicGroup {
+
+ @SetFromFlag("itemFilter")
+ public static final ConfigKey<Predicate<? super Entity>> ITEM_FILTER = new BasicConfigKey(
+ Predicate.class, "itemsInContainerGroup.itemFilter", "Filter for which items within the containers will automatically be in group", Predicates.alwaysTrue());
+
+ public void setContainers(Group containerGroup);
+}
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/d30ff597/policy/src/main/java/org/apache/brooklyn/policy/loadbalancing/ItemsInContainersGroupImpl.java
----------------------------------------------------------------------
diff --git a/policy/src/main/java/org/apache/brooklyn/policy/loadbalancing/ItemsInContainersGroupImpl.java b/policy/src/main/java/org/apache/brooklyn/policy/loadbalancing/ItemsInContainersGroupImpl.java
new file mode 100644
index 0000000..225a2b6
--- /dev/null
+++ b/policy/src/main/java/org/apache/brooklyn/policy/loadbalancing/ItemsInContainersGroupImpl.java
@@ -0,0 +1,148 @@
+/*
+ * 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.policy.loadbalancing;
+
+import org.apache.brooklyn.api.entity.Entity;
+import org.apache.brooklyn.api.entity.Group;
+import org.apache.brooklyn.api.event.Sensor;
+import org.apache.brooklyn.api.event.SensorEvent;
+import org.apache.brooklyn.api.event.SensorEventListener;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import brooklyn.entity.basic.AbstractGroup;
+import brooklyn.entity.basic.DynamicGroupImpl;
+
+import com.google.common.base.Predicate;
+
+/**
+ * A group of items that are contained within a given (dynamically changing) set of containers.
+ *
+ * The {@link setContainers(Group)} sets the group of containers. The membership of that group
+ * is dynamically tracked.
+ *
+ * When containers are added/removed, or when an items is added/removed, or when an {@link Moveable} item
+ * is moved then the membership of this group of items is automatically updated accordingly.
+ *
+ * For example: in Monterey, this could be used to track the actors that are within a given cluster of venues.
+ */
+public class ItemsInContainersGroupImpl extends DynamicGroupImpl implements ItemsInContainersGroup {
+
+ // TODO Inefficient: will not scale to many 1000s of items
+
+ private static final Logger LOG = LoggerFactory.getLogger(ItemsInContainersGroup.class);
+
+ private Group containerGroup;
+
+ private final SensorEventListener<Object> eventHandler = new SensorEventListener<Object>() {
+ @Override
+ public void onEvent(SensorEvent<Object> event) {
+ Entity source = event.getSource();
+ Object value = event.getValue();
+ Sensor sensor = event.getSensor();
+
+ if (sensor.equals(AbstractGroup.MEMBER_ADDED)) {
+ onContainerAdded((Entity) value);
+ } else if (sensor.equals(AbstractGroup.MEMBER_REMOVED)) {
+ onContainerRemoved((Entity) value);
+ } else if (sensor.equals(Movable.CONTAINER)) {
+ onItemMoved((Movable)source, (BalanceableContainer<?>) value);
+ } else {
+ throw new IllegalStateException("Unhandled event type "+sensor+": "+event);
+ }
+ }
+ };
+
+ public ItemsInContainersGroupImpl() {
+ }
+
+ @Override
+ public void init() {
+ super.init();
+ setEntityFilter(new Predicate<Entity>() {
+ @Override public boolean apply(Entity e) {
+ return acceptsEntity(e);
+ }});
+ }
+
+ protected Predicate<? super Entity> getItemFilter() {
+ return getConfig(ITEM_FILTER);
+ }
+
+ @Override
+ protected boolean acceptsEntity(Entity e) {
+ if (e instanceof Movable) {
+ return acceptsItem((Movable)e, ((Movable)e).getAttribute(Movable.CONTAINER));
+ }
+ return false;
+ }
+
+ boolean acceptsItem(Movable e, BalanceableContainer c) {
+ return (containerGroup != null && c != null) ? getItemFilter().apply(e) && containerGroup.hasMember(c) : false;
+ }
+
+ @Override
+ public void setContainers(Group containerGroup) {
+ this.containerGroup = containerGroup;
+ subscribe(containerGroup, AbstractGroup.MEMBER_ADDED, eventHandler);
+ subscribe(containerGroup, AbstractGroup.MEMBER_REMOVED, eventHandler);
+ subscribe(null, Movable.CONTAINER, eventHandler);
+
+ if (LOG.isTraceEnabled()) LOG.trace("{} scanning entities on container group set", this);
+ rescanEntities();
+ }
+
+ private void onContainerAdded(Entity newContainer) {
+ if (LOG.isTraceEnabled()) LOG.trace("{} rescanning entities on container {} added", this, newContainer);
+ rescanEntities();
+ }
+
+ private void onContainerRemoved(Entity oldContainer) {
+ if (LOG.isTraceEnabled()) LOG.trace("{} rescanning entities on container {} removed", this, oldContainer);
+ rescanEntities();
+ }
+
+ protected void onEntityAdded(Entity item) {
+ if (acceptsEntity(item)) {
+ if (LOG.isDebugEnabled()) LOG.debug("{} adding new item {}", this, item);
+ addMember(item);
+ }
+ }
+
+ protected void onEntityRemoved(Entity item) {
+ if (removeMember(item)) {
+ if (LOG.isDebugEnabled()) LOG.debug("{} removing deleted item {}", this, item);
+ }
+ }
+
+ private void onItemMoved(Movable item, BalanceableContainer container) {
+ if (LOG.isTraceEnabled()) LOG.trace("{} processing moved item {}, to container {}", new Object[] {this, item, container});
+ if (hasMember(item)) {
+ if (!acceptsItem(item, container)) {
+ if (LOG.isDebugEnabled()) LOG.debug("{} removing moved item {} from group, as new container {} is not a member", new Object[] {this, item, container});
+ removeMember(item);
+ }
+ } else {
+ if (acceptsItem(item, container)) {
+ if (LOG.isDebugEnabled()) LOG.debug("{} adding moved item {} to group, as new container {} is a member", new Object[] {this, item, container});
+ addMember(item);
+ }
+ }
+ }
+}
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/d30ff597/policy/src/main/java/org/apache/brooklyn/policy/loadbalancing/LoadBalancingPolicy.java
----------------------------------------------------------------------
diff --git a/policy/src/main/java/org/apache/brooklyn/policy/loadbalancing/LoadBalancingPolicy.java b/policy/src/main/java/org/apache/brooklyn/policy/loadbalancing/LoadBalancingPolicy.java
new file mode 100644
index 0000000..f437fc8
--- /dev/null
+++ b/policy/src/main/java/org/apache/brooklyn/policy/loadbalancing/LoadBalancingPolicy.java
@@ -0,0 +1,344 @@
+/*
+ * 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.policy.loadbalancing;
+
+import static brooklyn.util.JavaGroovyEquivalents.elvis;
+import static brooklyn.util.JavaGroovyEquivalents.groovyTruth;
+
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.ThreadFactory;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import org.apache.brooklyn.api.entity.Entity;
+import org.apache.brooklyn.api.entity.basic.EntityLocal;
+import org.apache.brooklyn.api.event.AttributeSensor;
+import org.apache.brooklyn.api.event.Sensor;
+import org.apache.brooklyn.api.event.SensorEvent;
+import org.apache.brooklyn.api.event.SensorEventListener;
+import org.apache.brooklyn.core.policy.basic.AbstractPolicy;
+import org.apache.brooklyn.core.util.flags.SetFromFlag;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import brooklyn.config.ConfigKey;
+import brooklyn.entity.basic.EntityInternal;
+
+import org.apache.brooklyn.policy.autoscaling.AutoScalerPolicy;
+
+import brooklyn.util.collections.MutableMap;
+
+import com.google.common.base.Preconditions;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.util.concurrent.ThreadFactoryBuilder;
+
+
+/**
+ * <p>Policy that is attached to a pool of "containers", each of which can host one or more migratable "items".
+ * The policy monitors the workrates of the items and effects migrations in an attempt to ensure that the containers
+ * are all sufficiently utilized without any of them being overloaded.
+ *
+ * <p>The particular sensor that defines the items' workrates is specified when the policy is constructed. High- and
+ * low-thresholds are defined as <strong>configuration keys</strong> on each of the container entities in the pool:
+ * for an item sensor named {@code foo.bar.sensorName}, the corresponding container config keys would be named
+ * {@code foo.bar.sensorName.threshold.low} and {@code foo.bar.sensorName.threshold.high}.
+ *
+ * <p>In addition to balancing items among the available containers, this policy causes the pool Entity to emit
+ * {@code POOL_COLD} and {@code POOL_HOT} events when it is determined that there is a surplus or shortfall
+ * of container resource in the pool respectively. These events may be consumed by a separate policy that is capable
+ * of resizing the container pool.
+ */
+ // removed from catalog because it cannot currently be configured via catalog mechanisms
+ // PolicySpec.create fails due to no no-arg constructor
+ // TODO make metric and model things which can be initialized from config then reinstate in catalog
+//@Catalog(name="Load Balancer", description="Policy that is attached to a pool of \"containers\", each of which "
+// + "can host one or more migratable \"items\". The policy monitors the workrates of the items and effects "
+// + "migrations in an attempt to ensure that the containers are all sufficiently utilized without any of "
+// + "them being overloaded.")
+public class LoadBalancingPolicy<NodeType extends Entity, ItemType extends Movable> extends AbstractPolicy {
+
+ private static final Logger LOG = LoggerFactory.getLogger(LoadBalancingPolicy.class);
+
+ @SetFromFlag(defaultVal="100")
+ private long minPeriodBetweenExecs;
+
+ private final AttributeSensor<? extends Number> metric;
+ private final String lowThresholdConfigKeyName;
+ private final String highThresholdConfigKeyName;
+ private final BalanceablePoolModel<NodeType, ItemType> model;
+ private final BalancingStrategy<NodeType, ItemType> strategy;
+ private BalanceableWorkerPool poolEntity;
+
+ private volatile ScheduledExecutorService executor;
+ private final AtomicBoolean executorQueued = new AtomicBoolean(false);
+ private volatile long executorTime = 0;
+
+ private int lastEmittedDesiredPoolSize = 0;
+ private static enum TemperatureStates { COLD, HOT }
+ private TemperatureStates lastEmittedPoolTemperature = null; // "cold" or "hot"
+
+ private final SensorEventListener<Object> eventHandler = new SensorEventListener<Object>() {
+ @SuppressWarnings({ "rawtypes", "unchecked" })
+ public void onEvent(SensorEvent<Object> event) {
+ if (LOG.isTraceEnabled()) LOG.trace("{} received event {}", LoadBalancingPolicy.this, event);
+ Entity source = event.getSource();
+ Object value = event.getValue();
+ Sensor sensor = event.getSensor();
+
+ if (sensor.equals(metric)) {
+ onItemMetricUpdate((ItemType)source, ((Number) value).doubleValue(), true);
+ } else if (sensor.equals(BalanceableWorkerPool.CONTAINER_ADDED)) {
+ onContainerAdded((NodeType) value, true);
+ } else if (sensor.equals(BalanceableWorkerPool.CONTAINER_REMOVED)) {
+ onContainerRemoved((NodeType) value, true);
+ } else if (sensor.equals(BalanceableWorkerPool.ITEM_ADDED)) {
+ BalanceableWorkerPool.ContainerItemPair pair = (BalanceableWorkerPool.ContainerItemPair) value;
+ onItemAdded((ItemType)pair.item, (NodeType)pair.container, true);
+ } else if (sensor.equals(BalanceableWorkerPool.ITEM_REMOVED)) {
+ BalanceableWorkerPool.ContainerItemPair pair = (BalanceableWorkerPool.ContainerItemPair) value;
+ onItemRemoved((ItemType)pair.item, (NodeType)pair.container, true);
+ } else if (sensor.equals(BalanceableWorkerPool.ITEM_MOVED)) {
+ BalanceableWorkerPool.ContainerItemPair pair = (BalanceableWorkerPool.ContainerItemPair) value;
+ onItemMoved((ItemType)pair.item, (NodeType)pair.container, true);
+ }
+ }
+ };
+
+ public LoadBalancingPolicy() {
+ this(null, null);
+ }
+
+ public LoadBalancingPolicy(AttributeSensor<? extends Number> metric,
+ BalanceablePoolModel<NodeType, ItemType> model) {
+ this(MutableMap.of(), metric, model);
+ }
+ @SuppressWarnings({ "unchecked", "rawtypes" })
+ public LoadBalancingPolicy(Map props, AttributeSensor<? extends Number> metric,
+ BalanceablePoolModel<NodeType, ItemType> model) {
+
+ super(props);
+ this.metric = metric;
+ this.lowThresholdConfigKeyName = metric.getName()+".threshold.low";
+ this.highThresholdConfigKeyName = metric.getName()+".threshold.high";
+ this.model = model;
+ this.strategy = new BalancingStrategy(getDisplayName(), model); // TODO: extract interface, inject impl
+
+ // TODO Should re-use the execution manager's thread pool, somehow
+ executor = Executors.newSingleThreadScheduledExecutor(newThreadFactory());
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ public void setEntity(EntityLocal entity) {
+ Preconditions.checkArgument(entity instanceof BalanceableWorkerPool, "Provided entity must be a BalanceableWorkerPool");
+ super.setEntity(entity);
+ this.poolEntity = (BalanceableWorkerPool) entity;
+
+ // Detect when containers are added to or removed from the pool.
+ subscribe(poolEntity, BalanceableWorkerPool.CONTAINER_ADDED, eventHandler);
+ subscribe(poolEntity, BalanceableWorkerPool.CONTAINER_REMOVED, eventHandler);
+ subscribe(poolEntity, BalanceableWorkerPool.ITEM_ADDED, eventHandler);
+ subscribe(poolEntity, BalanceableWorkerPool.ITEM_REMOVED, eventHandler);
+ subscribe(poolEntity, BalanceableWorkerPool.ITEM_MOVED, eventHandler);
+
+ // Take heed of any extant containers.
+ for (Entity container : poolEntity.getContainerGroup().getMembers()) {
+ onContainerAdded((NodeType)container, false);
+ }
+ for (Entity item : poolEntity.getItemGroup().getMembers()) {
+ onItemAdded((ItemType)item, (NodeType)item.getAttribute(Movable.CONTAINER), false);
+ }
+
+ scheduleRebalance();
+ }
+
+ @Override
+ public void suspend() {
+ // TODO unsubscribe from everything? And resubscribe on resume?
+ super.suspend();
+ if (executor != null) executor.shutdownNow();;
+ executorQueued.set(false);
+ }
+
+ @Override
+ public void resume() {
+ super.resume();
+ executor = Executors.newSingleThreadScheduledExecutor(newThreadFactory());
+ executorTime = 0;
+ executorQueued.set(false);
+ }
+
+ private ThreadFactory newThreadFactory() {
+ return new ThreadFactoryBuilder()
+ .setNameFormat("brooklyn-followthesunpolicy-%d")
+ .build();
+ }
+
+ private void scheduleRebalance() {
+ if (isRunning() && executorQueued.compareAndSet(false, true)) {
+ long now = System.currentTimeMillis();
+ long delay = Math.max(0, (executorTime + minPeriodBetweenExecs) - now);
+
+ executor.schedule(new Runnable() {
+ @SuppressWarnings("rawtypes")
+ public void run() {
+ try {
+ executorTime = System.currentTimeMillis();
+ executorQueued.set(false);
+ strategy.rebalance();
+
+ if (LOG.isDebugEnabled()) LOG.debug("{} post-rebalance: poolSize={}; workrate={}; lowThreshold={}; " +
+ "highThreshold={}", new Object[] {this, model.getPoolSize(), model.getCurrentPoolWorkrate(),
+ model.getPoolLowThreshold(), model.getPoolHighThreshold()});
+
+ if (model.isCold()) {
+ Map eventVal = ImmutableMap.of(
+ AutoScalerPolicy.POOL_CURRENT_SIZE_KEY, model.getPoolSize(),
+ AutoScalerPolicy.POOL_CURRENT_WORKRATE_KEY, model.getCurrentPoolWorkrate(),
+ AutoScalerPolicy.POOL_LOW_THRESHOLD_KEY, model.getPoolLowThreshold(),
+ AutoScalerPolicy.POOL_HIGH_THRESHOLD_KEY, model.getPoolHighThreshold());
+
+ ((EntityLocal)poolEntity).emit(AutoScalerPolicy.DEFAULT_POOL_COLD_SENSOR, eventVal);
+
+ if (LOG.isInfoEnabled()) {
+ int desiredPoolSize = (int) Math.ceil(model.getCurrentPoolWorkrate() / (model.getPoolLowThreshold()/model.getPoolSize()));
+ if (desiredPoolSize != lastEmittedDesiredPoolSize || lastEmittedPoolTemperature != TemperatureStates.COLD) {
+ LOG.info("{} emitted COLD (suggesting {}): {}", new Object[] {this, desiredPoolSize, eventVal});
+ lastEmittedDesiredPoolSize = desiredPoolSize;
+ lastEmittedPoolTemperature = TemperatureStates.COLD;
+ }
+ }
+
+ } else if (model.isHot()) {
+ Map eventVal = ImmutableMap.of(
+ AutoScalerPolicy.POOL_CURRENT_SIZE_KEY, model.getPoolSize(),
+ AutoScalerPolicy.POOL_CURRENT_WORKRATE_KEY, model.getCurrentPoolWorkrate(),
+ AutoScalerPolicy.POOL_LOW_THRESHOLD_KEY, model.getPoolLowThreshold(),
+ AutoScalerPolicy.POOL_HIGH_THRESHOLD_KEY, model.getPoolHighThreshold());
+
+ ((EntityLocal)poolEntity).emit(AutoScalerPolicy.DEFAULT_POOL_HOT_SENSOR, eventVal);
+
+ if (LOG.isInfoEnabled()) {
+ int desiredPoolSize = (int) Math.ceil(model.getCurrentPoolWorkrate() / (model.getPoolHighThreshold()/model.getPoolSize()));
+ if (desiredPoolSize != lastEmittedDesiredPoolSize || lastEmittedPoolTemperature != TemperatureStates.HOT) {
+ LOG.info("{} emitted HOT (suggesting {}): {}", new Object[] {this, desiredPoolSize, eventVal});
+ lastEmittedDesiredPoolSize = desiredPoolSize;
+ lastEmittedPoolTemperature = TemperatureStates.HOT;
+ }
+ }
+ }
+
+ } catch (Exception e) {
+ if (isRunning()) {
+ LOG.error("Error rebalancing", e);
+ } else {
+ LOG.debug("Error rebalancing, but no longer running", e);
+ }
+ }
+ }},
+ delay,
+ TimeUnit.MILLISECONDS);
+ }
+ }
+
+ // TODO Can get duplicate onContainerAdded events.
+ // I presume it's because we subscribe and then iterate over the extant containers.
+ // Solution would be for subscription to give you events for existing / current value(s).
+ // Also current impl messes up single-threaded updates model: the setEntity is a different thread than for subscription events.
+ private void onContainerAdded(NodeType newContainer, boolean rebalanceNow) {
+ Preconditions.checkArgument(newContainer instanceof BalanceableContainer, "Added container must be a BalanceableContainer");
+ if (LOG.isTraceEnabled()) LOG.trace("{} recording addition of container {}", this, newContainer);
+ // Low and high thresholds for the metric we're interested in are assumed to be present
+ // in the container's configuration.
+ Number lowThreshold = (Number) findConfigValue(newContainer, lowThresholdConfigKeyName);
+ Number highThreshold = (Number) findConfigValue(newContainer, highThresholdConfigKeyName);
+ if (lowThreshold == null || highThreshold == null) {
+ LOG.warn(
+ "Balanceable container '"+newContainer+"' does not define low- and high- threshold configuration keys: '"+
+ lowThresholdConfigKeyName+"' and '"+highThresholdConfigKeyName+"', skipping");
+ return;
+ }
+
+ model.onContainerAdded(newContainer, lowThreshold.doubleValue(), highThreshold.doubleValue());
+
+ // Note: no need to scan the container for items; they will appear via the ITEM_ADDED events.
+ // Also, must abide by any item-filters etc defined in the pool.
+
+ if (rebalanceNow) scheduleRebalance();
+ }
+
+ private static Object findConfigValue(Entity entity, String configKeyName) {
+ Map<ConfigKey<?>, Object> config = ((EntityInternal)entity).getAllConfig();
+ for (Entry<ConfigKey<?>, Object> entry : config.entrySet()) {
+ if (configKeyName.equals(entry.getKey().getName()))
+ return entry.getValue();
+ }
+ return null;
+ }
+
+ // TODO Receiving duplicates of onContainerRemoved (e.g. when running LoadBalancingInmemorySoakTest)
+ private void onContainerRemoved(NodeType oldContainer, boolean rebalanceNow) {
+ if (LOG.isTraceEnabled()) LOG.trace("{} recording removal of container {}", this, oldContainer);
+ model.onContainerRemoved(oldContainer);
+ if (rebalanceNow) scheduleRebalance();
+ }
+
+ private void onItemAdded(ItemType item, NodeType parentContainer, boolean rebalanceNow) {
+ Preconditions.checkArgument(item instanceof Movable, "Added item "+item+" must implement Movable");
+ if (LOG.isTraceEnabled()) LOG.trace("{} recording addition of item {} in container {}", new Object[] {this, item, parentContainer});
+
+ subscribe(item, metric, eventHandler);
+
+ // Update the model, including the current metric value (if any).
+ boolean immovable = (Boolean)elvis(item.getConfig(Movable.IMMOVABLE), false);
+ Number currentValue = item.getAttribute(metric);
+ model.onItemAdded(item, parentContainer, immovable);
+ if (currentValue != null)
+ model.onItemWorkrateUpdated(item, currentValue.doubleValue());
+
+ if (rebalanceNow) scheduleRebalance();
+ }
+
+ private void onItemRemoved(ItemType item, NodeType parentContainer, boolean rebalanceNow) {
+ if (LOG.isTraceEnabled()) LOG.trace("{} recording removal of item {}", this, item);
+ unsubscribe(item);
+ model.onItemRemoved(item);
+ if (rebalanceNow) scheduleRebalance();
+ }
+
+ private void onItemMoved(ItemType item, NodeType parentContainer, boolean rebalanceNow) {
+ if (LOG.isTraceEnabled()) LOG.trace("{} recording moving of item {} to {}", new Object[] {this, item, parentContainer});
+ model.onItemMoved(item, parentContainer);
+ if (rebalanceNow) scheduleRebalance();
+ }
+
+ private void onItemMetricUpdate(ItemType item, double newValue, boolean rebalanceNow) {
+ if (LOG.isTraceEnabled()) LOG.trace("{} recording metric update for item {}, new value {}", new Object[] {this, item, newValue});
+ model.onItemWorkrateUpdated(item, newValue);
+ if (rebalanceNow) scheduleRebalance();
+ }
+
+ @Override
+ public String toString() {
+ return getClass().getSimpleName() + (groovyTruth(name) ? "("+name+")" : "");
+ }
+}
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/d30ff597/policy/src/main/java/org/apache/brooklyn/policy/loadbalancing/LocationConstraint.java
----------------------------------------------------------------------
diff --git a/policy/src/main/java/org/apache/brooklyn/policy/loadbalancing/LocationConstraint.java b/policy/src/main/java/org/apache/brooklyn/policy/loadbalancing/LocationConstraint.java
new file mode 100644
index 0000000..b58b8d2
--- /dev/null
+++ b/policy/src/main/java/org/apache/brooklyn/policy/loadbalancing/LocationConstraint.java
@@ -0,0 +1,28 @@
+/*
+ * 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.policy.loadbalancing;
+
+import org.apache.brooklyn.api.location.Location;
+
+/**
+ * Temporary stub to resolve dependencies in ported LoadBalancingPolicy.
+ */
+public class LocationConstraint {
+ public boolean isPermitted(Location l) { return true; } // TODO
+}
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/d30ff597/policy/src/main/java/org/apache/brooklyn/policy/loadbalancing/Movable.java
----------------------------------------------------------------------
diff --git a/policy/src/main/java/org/apache/brooklyn/policy/loadbalancing/Movable.java b/policy/src/main/java/org/apache/brooklyn/policy/loadbalancing/Movable.java
new file mode 100644
index 0000000..68ba43d
--- /dev/null
+++ b/policy/src/main/java/org/apache/brooklyn/policy/loadbalancing/Movable.java
@@ -0,0 +1,51 @@
+/*
+ * 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.policy.loadbalancing;
+
+import org.apache.brooklyn.api.entity.Entity;
+import org.apache.brooklyn.core.util.flags.SetFromFlag;
+
+import brooklyn.config.ConfigKey;
+import brooklyn.entity.annotation.Effector;
+import brooklyn.entity.annotation.EffectorParam;
+import brooklyn.entity.basic.MethodEffector;
+import brooklyn.event.basic.BasicAttributeSensor;
+import brooklyn.event.basic.BasicConfigKey;
+
+
+/**
+ * Represents an item that can be migrated between balanceable containers.
+ */
+public interface Movable extends Entity {
+
+ @SetFromFlag("immovable")
+ public static ConfigKey<Boolean> IMMOVABLE = new BasicConfigKey<Boolean>(
+ Boolean.class, "movable.item.immovable", "Indicates whether this item instance is immovable, so cannot be moved by policies", false);
+
+ public static BasicAttributeSensor<BalanceableContainer> CONTAINER = new BasicAttributeSensor<BalanceableContainer>(
+ BalanceableContainer.class, "movable.item.container", "The container that this item is on");
+
+ public static final MethodEffector<Void> MOVE = new MethodEffector<Void>(Movable.class, "move");
+
+ public String getContainerId();
+
+ @Effector(description="Moves this entity to the given container")
+ public void move(@EffectorParam(name="destination") Entity destination);
+
+}
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/d30ff597/policy/src/main/java/org/apache/brooklyn/policy/loadbalancing/PolicyUtilForPool.java
----------------------------------------------------------------------
diff --git a/policy/src/main/java/org/apache/brooklyn/policy/loadbalancing/PolicyUtilForPool.java b/policy/src/main/java/org/apache/brooklyn/policy/loadbalancing/PolicyUtilForPool.java
new file mode 100644
index 0000000..b5c6a28
--- /dev/null
+++ b/policy/src/main/java/org/apache/brooklyn/policy/loadbalancing/PolicyUtilForPool.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.policy.loadbalancing;
+
+import java.util.Set;
+
+/**
+ * Provides conveniences for searching for hot/cold containers in a provided pool model.
+ * Ported from Monterey v3, with irrelevant bits removed.
+ */
+public class PolicyUtilForPool<ContainerType, ItemType> {
+
+ private final BalanceablePoolModel<ContainerType, ItemType> model;
+
+
+ public PolicyUtilForPool (BalanceablePoolModel<ContainerType, ItemType> model) {
+ this.model = model;
+ }
+
+ public ContainerType findColdestContainer(Set<ContainerType> excludedContainers) {
+ return findColdestContainer(excludedContainers, null);
+ }
+
+ /**
+ * Identifies the container with the maximum spare capacity (highThreshold - currentWorkrate),
+ * returns null if none of the model's nodes has spare capacity.
+ */
+ public ContainerType findColdestContainer(Set<ContainerType> excludedContainers, LocationConstraint locationConstraint) {
+ double maxSpareCapacity = 0;
+ ContainerType coldest = null;
+
+ for (ContainerType c : model.getPoolContents()) {
+ if (excludedContainers.contains(c))
+ continue;
+ if (locationConstraint != null && !locationConstraint.isPermitted(model.getLocation(c)))
+ continue;
+
+ double highThreshold = model.getHighThreshold(c);
+ double totalWorkrate = model.getTotalWorkrate(c);
+ double spareCapacity = highThreshold - totalWorkrate;
+
+ if (highThreshold == -1 || totalWorkrate == -1) {
+ continue; // container presumably has been removed
+ }
+ if (spareCapacity > maxSpareCapacity) {
+ maxSpareCapacity = spareCapacity;
+ coldest = c;
+ }
+ }
+ return coldest;
+ }
+
+ /**
+ * Identifies the container with the maximum overshoot (currentWorkrate - highThreshold),
+ * returns null if none of the model's nodes has an overshoot.
+ */
+ public ContainerType findHottestContainer(Set<ContainerType> excludedContainers) {
+ double maxOvershoot = 0;
+ ContainerType hottest = null;
+
+ for (ContainerType c : model.getPoolContents()) {
+ if (excludedContainers.contains(c))
+ continue;
+
+ double totalWorkrate = model.getTotalWorkrate(c);
+ double highThreshold = model.getHighThreshold(c);
+ double overshoot = totalWorkrate - highThreshold;
+
+ if (highThreshold == -1 || totalWorkrate == -1) {
+ continue; // container presumably has been removed
+ }
+ if (overshoot > maxOvershoot) {
+ maxOvershoot = overshoot;
+ hottest = c;
+ }
+ }
+ return hottest;
+ }
+
+}
\ No newline at end of file
[08/24] incubator-brooklyn git commit: [BROOKLYN-162] Renaming
package policy
Posted by he...@apache.org.
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/d30ff597/policy/src/test/java/org/apache/brooklyn/policy/autoscaling/AutoScalerPolicyTest.java
----------------------------------------------------------------------
diff --git a/policy/src/test/java/org/apache/brooklyn/policy/autoscaling/AutoScalerPolicyTest.java b/policy/src/test/java/org/apache/brooklyn/policy/autoscaling/AutoScalerPolicyTest.java
new file mode 100644
index 0000000..27441be
--- /dev/null
+++ b/policy/src/test/java/org/apache/brooklyn/policy/autoscaling/AutoScalerPolicyTest.java
@@ -0,0 +1,649 @@
+/*
+ * 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.policy.autoscaling;
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertTrue;
+
+import java.lang.management.ManagementFactory;
+import java.lang.management.MemoryMXBean;
+import java.lang.management.MemoryUsage;
+import java.lang.management.OperatingSystemMXBean;
+import java.lang.management.ThreadInfo;
+import java.lang.management.ThreadMXBean;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import org.apache.brooklyn.api.entity.Entity;
+import org.apache.brooklyn.api.entity.proxying.EntitySpec;
+import org.apache.brooklyn.api.policy.PolicySpec;
+import org.apache.brooklyn.test.entity.TestApplication;
+import org.apache.brooklyn.test.entity.TestCluster;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.testng.Assert;
+import org.testng.annotations.AfterMethod;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import brooklyn.entity.basic.Entities;
+import brooklyn.entity.trait.Resizable;
+import brooklyn.event.basic.BasicNotificationSensor;
+import brooklyn.test.Asserts;
+import brooklyn.util.collections.MutableList;
+import brooklyn.util.collections.MutableMap;
+import brooklyn.util.time.Duration;
+
+import com.google.common.base.Stopwatch;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+
+public class AutoScalerPolicyTest {
+
+ private static final Logger log = LoggerFactory.getLogger(AutoScalerPolicyTest.class);
+
+ private static long TIMEOUT_MS = 10*1000;
+ private static long SHORT_WAIT_MS = 250;
+ private static long OVERHEAD_DURATION_MS = 500;
+ private static long EARLY_RETURN_MS = 10;
+
+ private static final int MANY_TIMES_INVOCATION_COUNT = 10;
+
+ AutoScalerPolicy policy;
+ TestCluster cluster;
+ LocallyResizableEntity resizable;
+ TestApplication app;
+ List<Integer> policyResizes = MutableList.of();
+
+ @BeforeMethod(alwaysRun=true)
+ public void setUp() throws Exception {
+ log.info("resetting "+getClass().getSimpleName());
+ app = TestApplication.Factory.newManagedInstanceForTests();
+ cluster = app.createAndManageChild(EntitySpec.create(TestCluster.class).configure(TestCluster.INITIAL_SIZE, 1));
+ resizable = new LocallyResizableEntity(cluster, cluster);
+ Entities.manage(resizable);
+ PolicySpec<AutoScalerPolicy> policySpec = PolicySpec.create(AutoScalerPolicy.class).configure(AutoScalerPolicy.RESIZE_OPERATOR, new ResizeOperator() {
+ @Override
+ public Integer resize(Entity entity, Integer desiredSize) {
+ log.info("resizing to "+desiredSize);
+ policyResizes.add(desiredSize);
+ return ((Resizable)entity).resize(desiredSize);
+ }
+ });
+ policy = resizable.addPolicy(policySpec);
+ policyResizes.clear();
+ }
+
+ @AfterMethod(alwaysRun=true)
+ public void tearDown() throws Exception {
+ if (policy != null) policy.destroy();
+ if (app != null) Entities.destroyAll(app.getManagementContext());
+ cluster = null;
+ resizable = null;
+ policy = null;
+ }
+
+ public void assertSizeEventually(Integer targetSize) {
+ Asserts.succeedsEventually(ImmutableMap.of("timeout", TIMEOUT_MS), currentSizeAsserter(resizable, targetSize));
+ assertEquals(policyResizes.get(policyResizes.size()-1), targetSize);
+ }
+
+ @Test
+ public void testShrinkColdPool() throws Exception {
+ resizable.resize(4);
+ // all metrics as per-node here
+ resizable.emit(AutoScalerPolicy.DEFAULT_POOL_COLD_SENSOR, message(30d/4, 10, 20));
+
+ // expect pool to shrink to 3 (i.e. maximum to have >= 10 per container)
+ assertSizeEventually(3);
+ }
+
+ @Test
+ public void testShrinkColdPoolTotals() throws Exception {
+ resizable.resize(4);
+ // all metrics as totals here
+ resizable.emit(AutoScalerPolicy.DEFAULT_POOL_COLD_SENSOR, message(30L, 4*10L, 4*20L));
+
+ // expect pool to shrink to 3 (i.e. maximum to have >= 10 per container)
+ assertSizeEventually(3);
+ }
+
+ @Test
+ public void testShrinkColdPoolRoundsUpDesiredNumberOfContainers() throws Exception {
+ resizable.resize(4);
+ resizable.emit(AutoScalerPolicy.DEFAULT_POOL_COLD_SENSOR, message(1L, 4*10L, 4*20L));
+
+ Asserts.succeedsEventually(ImmutableMap.of("timeout", TIMEOUT_MS), currentSizeAsserter(resizable, 1));
+ }
+
+ @Test
+ public void testGrowHotPool() throws Exception {
+ resizable.resize(2);
+ // all metrics as per-node here
+ resizable.emit(AutoScalerPolicy.DEFAULT_POOL_HOT_SENSOR, message(21L, 10L, 20L));
+
+ // expect pool to grow to 3 (i.e. minimum to have <= 20 per container)
+ assertSizeEventually(3);
+ }
+
+ @Test
+ public void testGrowHotPoolTotals() throws Exception {
+ resizable.resize(2);
+ // all metrics as totals here
+ resizable.emit(AutoScalerPolicy.DEFAULT_POOL_HOT_SENSOR, message(41L, 2*10L, 2*20L));
+
+ // expect pool to grow to 3 (i.e. minimum to have <= 20 per container)
+ assertSizeEventually(3);
+ }
+
+ @Test
+ public void testGrowShrinkRespectsResizeIterationIncrementAndResizeIterationMax() throws Exception {
+ resizable.resize(2);
+ policy.config().set(AutoScalerPolicy.RESIZE_UP_ITERATION_INCREMENT, 2);
+ policy.config().set(AutoScalerPolicy.RESIZE_UP_ITERATION_MAX, 4);
+ policy.config().set(AutoScalerPolicy.RESIZE_DOWN_ITERATION_INCREMENT, 3);
+ policy.config().set(AutoScalerPolicy.RESIZE_DOWN_ITERATION_MAX, 3);
+
+ resizable.emit(AutoScalerPolicy.DEFAULT_POOL_HOT_SENSOR, message(42/2, 10, 20));
+ // expect pool to grow to 4 (i.e. to have <= 20 per container we need 3, but increment is 2)
+ assertSizeEventually(4);
+
+ resizable.emit(AutoScalerPolicy.DEFAULT_POOL_HOT_SENSOR, message(200/4, 10, 20));
+ // a single hot message can only make it go to 8
+ assertSizeEventually(8);
+ assertEquals(policyResizes, MutableList.of(4, 8));
+ resizable.emit(AutoScalerPolicy.DEFAULT_POOL_HOT_SENSOR, message(200/8, 10, 20));
+ assertSizeEventually(10);
+ assertEquals(policyResizes, MutableList.of(4, 8, 10));
+
+ // now shrink
+ policyResizes.clear();
+ policy.config().set(AutoScalerPolicy.MIN_POOL_SIZE, 2);
+ resizable.emit(AutoScalerPolicy.DEFAULT_POOL_COLD_SENSOR, message(1, 10, 20));
+ assertSizeEventually(7);
+ resizable.emit(AutoScalerPolicy.DEFAULT_POOL_COLD_SENSOR, message(1, 10, 20));
+ assertSizeEventually(4);
+ resizable.emit(AutoScalerPolicy.DEFAULT_POOL_COLD_SENSOR, message(1, 10, 20));
+ assertSizeEventually(2);
+ assertEquals(policyResizes, MutableList.of(7, 4, 2));
+ }
+
+ @Test
+ public void testHasId() throws Exception {
+ resizable.removePolicy(policy);
+ policy = AutoScalerPolicy.builder()
+ .minPoolSize(2)
+ .build();
+ resizable.addPolicy(policy);
+ Assert.assertTrue(policy.getId()!=null);
+ }
+
+ @Test
+ public void testNeverShrinkBelowMinimum() throws Exception {
+ resizable.removePolicy(policy);
+ policy = AutoScalerPolicy.builder()
+ .minPoolSize(2)
+ .build();
+ resizable.addPolicy(policy);
+
+ resizable.resize(4);
+ resizable.emit(AutoScalerPolicy.DEFAULT_POOL_COLD_SENSOR, message(4, 0L, 4*10L, 4*20L));
+
+ // expect pool to shrink only to the minimum
+ Asserts.succeedsEventually(ImmutableMap.of("timeout", TIMEOUT_MS), currentSizeAsserter(resizable, 2));
+ }
+
+ @Test
+ public void testNeverGrowAboveMaximmum() throws Exception {
+ resizable.removePolicy(policy);
+ policy = AutoScalerPolicy.builder()
+ .maxPoolSize(5)
+ .build();
+ resizable.addPolicy(policy);
+
+ resizable.resize(4);
+ resizable.emit(AutoScalerPolicy.DEFAULT_POOL_HOT_SENSOR, message(4, 1000000L, 4*10L, 4*20L));
+
+ // expect pool to grow only to the maximum
+ Asserts.succeedsEventually(ImmutableMap.of("timeout", TIMEOUT_MS), currentSizeAsserter(resizable, 5));
+ }
+
+ @Test
+ public void testNeverGrowColdPool() throws Exception {
+ resizable.resize(2);
+ resizable.emit(AutoScalerPolicy.DEFAULT_POOL_COLD_SENSOR, message(2, 1000L, 2*10L, 2*20L));
+
+ Thread.sleep(SHORT_WAIT_MS);
+ assertEquals(resizable.getCurrentSize(), (Integer)2);
+ }
+
+ @Test
+ public void testNeverShrinkHotPool() throws Exception {
+ resizable.resizeSleepTime = 0;
+ resizable.resize(2);
+ resizable.emit(AutoScalerPolicy.DEFAULT_POOL_HOT_SENSOR, message(2, 0L, 2*10L, 2*20L));
+
+ // if had been a POOL_COLD, would have shrunk to 3
+ Thread.sleep(SHORT_WAIT_MS);
+ assertEquals(resizable.getCurrentSize(), (Integer)2);
+ }
+
+ @Test(groups="Integration")
+ public void testConcurrentShrinkShrink() throws Exception {
+ resizable.resizeSleepTime = 250;
+ resizable.resize(4);
+ resizable.emit(AutoScalerPolicy.DEFAULT_POOL_COLD_SENSOR, message(4, 30L, 4*10L, 4*20L));
+ // would cause pool to shrink to 3
+
+ resizable.emit(AutoScalerPolicy.DEFAULT_POOL_COLD_SENSOR, message(4, 1L, 4*10L, 4*20L));
+ // now expect pool to shrink to 1
+
+ Asserts.succeedsEventually(ImmutableMap.of("timeout", TIMEOUT_MS), currentSizeAsserter(resizable, 1));
+ }
+
+ @Test(groups="Integration")
+ public void testConcurrentGrowGrow() throws Exception {
+ resizable.resizeSleepTime = 250;
+ resizable.resize(2);
+ resizable.emit(AutoScalerPolicy.DEFAULT_POOL_HOT_SENSOR, message(2, 41L, 2*10L, 2*20L));
+ // would cause pool to grow to 3
+
+ resizable.emit(AutoScalerPolicy.DEFAULT_POOL_HOT_SENSOR, message(2, 81L, 2*10L, 2*20L));
+ // now expect pool to grow to 5
+
+ Asserts.succeedsEventually(ImmutableMap.of("timeout", TIMEOUT_MS), currentSizeAsserter(resizable, 5));
+ }
+
+ @Test(groups="Integration")
+ public void testConcurrentGrowShrink() throws Exception {
+ resizable.resizeSleepTime = 250;
+ resizable.resize(2);
+ resizable.emit(AutoScalerPolicy.DEFAULT_POOL_HOT_SENSOR, message(2, 81L, 2*10L, 2*20L));
+ // would cause pool to grow to 5
+
+ resizable.emit(AutoScalerPolicy.DEFAULT_POOL_COLD_SENSOR, message(2, 1L, 2*10L, 2*20L));
+ // now expect pool to shrink to 1
+
+ Asserts.succeedsEventually(ImmutableMap.of("timeout", TIMEOUT_MS), currentSizeAsserter(resizable, 1));
+ }
+
+ @Test(groups="Integration")
+ public void testConcurrentShrinkGrow() throws Exception {
+ resizable.resizeSleepTime = 250;
+ resizable.resize(4);
+ resizable.emit(AutoScalerPolicy.DEFAULT_POOL_COLD_SENSOR, message(4, 1L, 4*10L, 4*20L));
+ // would cause pool to shrink to 1
+
+ resizable.emit(AutoScalerPolicy.DEFAULT_POOL_HOT_SENSOR, message(4, 81L, 4*10L, 4*20L));
+ // now expect pool to grow to 5
+
+ Asserts.succeedsEventually(ImmutableMap.of("timeout", TIMEOUT_MS), currentSizeAsserter(resizable, 5));
+ }
+
+ // FIXME failed in jenkins (e.g. #1035); with "lists don't have the same size expected:<3> but was:<2>"
+ // Is it just too time sensitive? But I'd have expected > 3 rather than less
+ @Test(groups="WIP")
+ public void testRepeatedQueuedResizeTakesLatestValueRatherThanIntermediateValues() throws Exception {
+ // TODO is this too time sensitive? the resize takes only 250ms so if it finishes before the next emit we'd also see size=2
+ resizable.resizeSleepTime = 500;
+ resizable.resize(4);
+ resizable.emit(AutoScalerPolicy.DEFAULT_POOL_COLD_SENSOR, message(4, 30L, 4*10L, 4*20L)); // shrink to 3
+ resizable.emit(AutoScalerPolicy.DEFAULT_POOL_COLD_SENSOR, message(4, 20L, 4*10L, 4*20L)); // shrink to 2
+ resizable.emit(AutoScalerPolicy.DEFAULT_POOL_COLD_SENSOR, message(4, 10L, 4*10L, 4*20L)); // shrink to 1
+
+ Asserts.succeedsEventually(ImmutableMap.of("timeout", TIMEOUT_MS), currentSizeAsserter(resizable, 1));
+ assertEquals(resizable.sizes, ImmutableList.of(4, 3, 1));
+ }
+
+
+ @Test
+ public void testUsesResizeOperatorOverride() throws Exception {
+ resizable.removePolicy(policy);
+
+ final AtomicInteger counter = new AtomicInteger();
+ policy = AutoScalerPolicy.builder()
+ .resizeOperator(new ResizeOperator() {
+ @Override public Integer resize(Entity entity, Integer desiredSize) {
+ counter.incrementAndGet();
+ return desiredSize;
+ }})
+ .build();
+ resizable.addPolicy(policy);
+
+ resizable.emit(AutoScalerPolicy.DEFAULT_POOL_HOT_SENSOR, message(1, 21L, 1*10L, 1*20L)); // grow to 2
+
+ Asserts.succeedsEventually(MutableMap.of("timeout",TIMEOUT_MS), new Runnable() {
+ public void run() {
+ assertTrue(counter.get() >= 1, "cccounter="+counter);
+ }});
+ }
+
+ @Test
+ public void testUsesCustomSensorOverride() throws Exception {
+ resizable.removePolicy(policy);
+
+ @SuppressWarnings("rawtypes")
+ BasicNotificationSensor<Map> customPoolHotSensor = new BasicNotificationSensor<Map>(Map.class, "custom.hot", "");
+ @SuppressWarnings("rawtypes")
+ BasicNotificationSensor<Map> customPoolColdSensor = new BasicNotificationSensor<Map>(Map.class, "custom.cold", "");
+ @SuppressWarnings("rawtypes")
+ BasicNotificationSensor<Map> customPoolOkSensor = new BasicNotificationSensor<Map>(Map.class, "custom.ok", "");
+ policy = AutoScalerPolicy.builder()
+ .poolHotSensor(customPoolHotSensor)
+ .poolColdSensor(customPoolColdSensor)
+ .poolOkSensor(customPoolOkSensor)
+ .build();
+ resizable.addPolicy(policy);
+
+ resizable.emit(customPoolHotSensor, message(1, 21L, 1*10L, 1*20L)); // grow to 2
+ Asserts.succeedsEventually(ImmutableMap.of("timeout", TIMEOUT_MS), currentSizeAsserter(resizable, 2));
+
+ resizable.emit(customPoolColdSensor, message(2, 1L, 1*10L, 1*20L)); // shrink to 1
+ Asserts.succeedsEventually(ImmutableMap.of("timeout", TIMEOUT_MS), currentSizeAsserter(resizable, 1));
+ }
+
+ @Test(groups="Integration")
+ public void testResizeUpStabilizationDelayIgnoresBlip() throws Exception {
+ long resizeUpStabilizationDelay = 1000L;
+ Duration minPeriodBetweenExecs = Duration.ZERO;
+ resizable.removePolicy(policy);
+
+ policy = AutoScalerPolicy.builder()
+ .resizeUpStabilizationDelay(Duration.of(resizeUpStabilizationDelay, TimeUnit.MILLISECONDS))
+ .minPeriodBetweenExecs(minPeriodBetweenExecs)
+ .build();
+ resizable.addPolicy(policy);
+ resizable.resize(1);
+
+ // Ignores temporary blip
+ resizable.emit(AutoScalerPolicy.DEFAULT_POOL_HOT_SENSOR, message(1, 61L, 1*10L, 1*20L)); // would grow to 4
+ Thread.sleep(resizeUpStabilizationDelay-OVERHEAD_DURATION_MS);
+ resizable.emit(AutoScalerPolicy.DEFAULT_POOL_OK_SENSOR, message(1, 11L, 4*10L, 4*20L)); // but 1 is still adequate
+
+ assertEquals(resizable.getCurrentSize(), (Integer)1);
+ Asserts.succeedsContinually(MutableMap.of("duration", 2000L), new Runnable() {
+ @Override public void run() {
+ assertEquals(resizable.sizes, ImmutableList.of(1));
+ }});
+ }
+
+ // FIXME failing in jenkins occassionally - have put it in the "Acceptance" group for now
+ //
+ // Error was things like it taking a couple of seconds too long to scale-up. This is *not*
+ // just caused by a slow GC (running with -verbose:gc shows during a failure several
+ // incremental GCs that usually don't amount to more than 0.2 of a second at most, often less).
+ // Doing a thread-dump etc immediately after the too-long delay shows no strange thread usage,
+ // and shows releng3 system load averages of numbers like 1.73, 2.87 and 1.22.
+ //
+ // Is healthy on normal machines.
+ @Test(groups={"Integration", "Acceptance"}, invocationCount=MANY_TIMES_INVOCATION_COUNT)
+ public void testRepeatedResizeUpStabilizationDelayTakesMaxSustainedDesired() throws Throwable {
+ try {
+ testResizeUpStabilizationDelayTakesMaxSustainedDesired();
+ } catch (Throwable t) {
+ dumpThreadsEtc();
+ throw t;
+ }
+ }
+
+ @Test(groups="Integration")
+ public void testResizeUpStabilizationDelayTakesMaxSustainedDesired() throws Exception {
+ long resizeUpStabilizationDelay = 1100L;
+ Duration minPeriodBetweenExecs = Duration.ZERO;
+ resizable.removePolicy(policy);
+
+ policy = AutoScalerPolicy.builder()
+ .resizeUpStabilizationDelay(Duration.of(resizeUpStabilizationDelay, TimeUnit.MILLISECONDS))
+ .minPeriodBetweenExecs(minPeriodBetweenExecs)
+ .build();
+ resizable.addPolicy(policy);
+ resizable.resize(1);
+
+ // Will grow to only the max sustained in this time window
+ // (i.e. to 2 within the first $resizeUpStabilizationDelay milliseconds)
+ Stopwatch stopwatch = Stopwatch.createStarted();
+
+ resizable.emit(AutoScalerPolicy.DEFAULT_POOL_HOT_SENSOR, message(1, 61L, 1*10L, 1*20L)); // would grow to 4
+ resizable.emit(AutoScalerPolicy.DEFAULT_POOL_HOT_SENSOR, message(1, 21L, 1*10L, 1*20L)); // would grow to 2
+ Thread.sleep(resizeUpStabilizationDelay-OVERHEAD_DURATION_MS);
+
+ long postSleepTime = stopwatch.elapsed(TimeUnit.MILLISECONDS);
+
+ resizable.emit(AutoScalerPolicy.DEFAULT_POOL_HOT_SENSOR, message(1, 61L, 1*10L, 1*20L)); // would grow to 4
+
+ // Wait for it to reach size 2, and confirm take expected time
+ // TODO This is time sensitive, and sometimes fails in CI with size=4 if we wait for currentSize==2 (presumably GC kicking in?)
+ // Therefore do strong assertion of currentSize==2 later, so can write out times if it goes wrong.
+ Asserts.succeedsEventually(MutableMap.of("period", 1, "timeout", TIMEOUT_MS), new Runnable() {
+ public void run() {
+ assertTrue(resizable.getCurrentSize() >= 2, "currentSize="+resizable.getCurrentSize());
+ }});
+ assertEquals(resizable.getCurrentSize(), (Integer)2,
+ stopwatch.elapsed(TimeUnit.MILLISECONDS)+"ms after first emission; "+(stopwatch.elapsed(TimeUnit.MILLISECONDS)-postSleepTime)+"ms after last");
+
+ long timeToResizeTo2 = stopwatch.elapsed(TimeUnit.MILLISECONDS);
+ assertTrue(timeToResizeTo2 >= resizeUpStabilizationDelay-EARLY_RETURN_MS &&
+ timeToResizeTo2 <= resizeUpStabilizationDelay+OVERHEAD_DURATION_MS,
+ "Resizing to 2: time="+timeToResizeTo2+"; resizeUpStabilizationDelay="+resizeUpStabilizationDelay);
+
+ // Will then grow to 4 $resizeUpStabilizationDelay milliseconds after that emission
+ Asserts.succeedsEventually(MutableMap.of("period", 1, "timeout", TIMEOUT_MS),
+ currentSizeAsserter(resizable, 4));
+ long timeToResizeTo4 = stopwatch.elapsed(TimeUnit.MILLISECONDS) - postSleepTime;
+
+ assertTrue(timeToResizeTo4 >= resizeUpStabilizationDelay-EARLY_RETURN_MS &&
+ timeToResizeTo4 <= resizeUpStabilizationDelay+OVERHEAD_DURATION_MS,
+ "Resizing to 4: timeToResizeTo4="+timeToResizeTo4+"; timeToResizeTo2="+timeToResizeTo2+"; resizeUpStabilizationDelay="+resizeUpStabilizationDelay);
+ }
+
+ @Test(groups="Integration")
+ public void testResizeUpStabilizationDelayResizesAfterDelay() {
+ final long resizeUpStabilizationDelay = 1000L;
+ Duration minPeriodBetweenExecs = Duration.ZERO;
+ resizable.removePolicy(policy);
+
+ policy = resizable.addPolicy(AutoScalerPolicy.builder()
+ .resizeUpStabilizationDelay(Duration.of(resizeUpStabilizationDelay, TimeUnit.MILLISECONDS))
+ .minPeriodBetweenExecs(minPeriodBetweenExecs)
+ .buildSpec());
+ resizable.resize(1);
+
+ // After suitable delay, grows to desired
+ final long emitTime = System.currentTimeMillis();
+ final Map<String, Object> need4 = message(1, 61L, 1*10L, 1*20L);
+ resizable.emit(AutoScalerPolicy.DEFAULT_POOL_HOT_SENSOR, need4); // would grow to 4
+ final AtomicInteger emitCount = new AtomicInteger(0);
+
+ Asserts.succeedsEventually(MutableMap.of("timeout", TIMEOUT_MS), new Runnable() {
+ public void run() {
+ if (System.currentTimeMillis() - emitTime > (2+emitCount.get())*resizeUpStabilizationDelay) {
+ //first one may not have been received, in a registration race
+ resizable.emit(AutoScalerPolicy.DEFAULT_POOL_HOT_SENSOR, need4);
+ emitCount.incrementAndGet();
+ }
+ assertEquals(resizable.getCurrentSize(), (Integer)4);
+ }});
+
+ long resizeDelay = System.currentTimeMillis() - emitTime;
+ assertTrue(resizeDelay >= (resizeUpStabilizationDelay-EARLY_RETURN_MS), "resizeDelay="+resizeDelay);
+ }
+
+ @Test(groups="Integration")
+ public void testResizeDownStabilizationDelayIgnoresBlip() throws Exception {
+ long resizeStabilizationDelay = 1000L;
+ Duration minPeriodBetweenExecs = Duration.ZERO;
+ resizable.removePolicy(policy);
+
+ policy = AutoScalerPolicy.builder()
+ .resizeDownStabilizationDelay(Duration.of(resizeStabilizationDelay, TimeUnit.MILLISECONDS))
+ .minPeriodBetweenExecs(minPeriodBetweenExecs)
+ .build();
+ resizable.addPolicy(policy);
+ resizable.resize(2);
+
+ // Ignores temporary blip
+ resizable.emit(AutoScalerPolicy.DEFAULT_POOL_COLD_SENSOR, message(2, 1L, 2*10L, 2*20L)); // would shrink to 1
+ Thread.sleep(resizeStabilizationDelay-OVERHEAD_DURATION_MS);
+ resizable.emit(AutoScalerPolicy.DEFAULT_POOL_OK_SENSOR, message(2, 20L, 1*10L, 1*20L)); // but 2 is still adequate
+
+ assertEquals(resizable.getCurrentSize(), (Integer)2);
+ Asserts.succeedsContinually(MutableMap.of("duration", 2000L), new Runnable() {
+ public void run() {
+ assertEquals(resizable.sizes, ImmutableList.of(2));
+ }});
+ }
+
+ // FIXME Acceptance -- see comment against testRepeatedResizeUpStabilizationDelayTakesMaxSustainedDesired
+ @Test(groups={"Integration", "Acceptance"}, invocationCount=MANY_TIMES_INVOCATION_COUNT)
+ public void testRepeatedResizeDownStabilizationDelayTakesMinSustainedDesired() throws Throwable {
+ try {
+ testResizeDownStabilizationDelayTakesMinSustainedDesired();
+ } catch (Throwable t) {
+ dumpThreadsEtc();
+ throw t;
+ }
+ }
+
+ @Test(groups="Integration")
+ public void testResizeDownStabilizationDelayTakesMinSustainedDesired() throws Exception {
+ long resizeDownStabilizationDelay = 1100L;
+ Duration minPeriodBetweenExecs = Duration.ZERO;
+ policy.suspend();
+ resizable.removePolicy(policy);
+
+ policy = AutoScalerPolicy.builder()
+ .resizeDownStabilizationDelay(Duration.of(resizeDownStabilizationDelay, TimeUnit.MILLISECONDS))
+ .minPeriodBetweenExecs(minPeriodBetweenExecs)
+ .build();
+ resizable.addPolicy(policy);
+ resizable.resize(3);
+
+ // Will shrink to only the min sustained in this time window
+ // (i.e. to 2 within the first $resizeUpStabilizationDelay milliseconds)
+ Stopwatch stopwatch = Stopwatch.createStarted();
+
+ resizable.emit(AutoScalerPolicy.DEFAULT_POOL_COLD_SENSOR, message(3, 1L, 3*10L, 3*20L)); // would shrink to 1
+ resizable.emit(AutoScalerPolicy.DEFAULT_POOL_COLD_SENSOR, message(3, 20L, 3*10L, 3*20L)); // would shrink to 2
+ Thread.sleep(resizeDownStabilizationDelay-OVERHEAD_DURATION_MS);
+
+ long postSleepTime = stopwatch.elapsed(TimeUnit.MILLISECONDS);
+
+ resizable.emit(AutoScalerPolicy.DEFAULT_POOL_COLD_SENSOR, message(3, 1L, 3*10L, 3*20L)); // would shrink to 1
+
+ // Wait for it to reach size 2, and confirm take expected time
+ // TODO This is time sensitive, and sometimes fails in CI with size=1 if we wait for currentSize==2 (presumably GC kicking in?)
+ // Therefore do strong assertion of currentSize==2 later, so can write out times if it goes wrong.
+ Asserts.succeedsEventually(MutableMap.of("period", 1, "timeout", TIMEOUT_MS), new Runnable() {
+ public void run() {
+ assertTrue(resizable.getCurrentSize() <= 2, "currentSize="+resizable.getCurrentSize());
+ }});
+ assertEquals(resizable.getCurrentSize(), (Integer)2,
+ stopwatch.elapsed(TimeUnit.MILLISECONDS)+"ms after first emission; "+(stopwatch.elapsed(TimeUnit.MILLISECONDS)-postSleepTime)+"ms after last");
+
+ long timeToResizeTo2 = stopwatch.elapsed(TimeUnit.MILLISECONDS);
+ assertTrue(timeToResizeTo2 >= resizeDownStabilizationDelay-EARLY_RETURN_MS &&
+ timeToResizeTo2 <= resizeDownStabilizationDelay+OVERHEAD_DURATION_MS,
+ "Resizing to 2: time="+timeToResizeTo2+"; resizeDownStabilizationDelay="+resizeDownStabilizationDelay);
+
+ // Will then shrink to 1 $resizeUpStabilizationDelay milliseconds after that emission
+ Asserts.succeedsEventually(MutableMap.of("period", 1, "timeout", TIMEOUT_MS),
+ currentSizeAsserter(resizable, 1));
+ long timeToResizeTo1 = stopwatch.elapsed(TimeUnit.MILLISECONDS) - postSleepTime;
+
+ assertTrue(timeToResizeTo1 >= resizeDownStabilizationDelay-EARLY_RETURN_MS &&
+ timeToResizeTo1 <= resizeDownStabilizationDelay+OVERHEAD_DURATION_MS,
+ "Resizing to 1: timeToResizeTo1="+timeToResizeTo1+"; timeToResizeTo2="+timeToResizeTo2+"; resizeDownStabilizationDelay="+resizeDownStabilizationDelay);
+ }
+
+ @Test(groups="Integration")
+ public void testResizeDownStabilizationDelayResizesAfterDelay() throws Exception {
+ final long resizeDownStabilizationDelay = 1000L;
+ Duration minPeriodBetweenExecs = Duration.ZERO;
+ resizable.removePolicy(policy);
+
+ policy = AutoScalerPolicy.builder()
+ .resizeDownStabilizationDelay(Duration.of(resizeDownStabilizationDelay, TimeUnit.MILLISECONDS))
+ .minPeriodBetweenExecs(minPeriodBetweenExecs)
+ .build();
+ resizable.addPolicy(policy);
+ resizable.resize(2);
+
+ // After suitable delay, grows to desired
+ final long emitTime = System.currentTimeMillis();
+ final Map<String, Object> needJust1 = message(2, 1L, 2*10L, 2*20L);
+ resizable.emit(AutoScalerPolicy.DEFAULT_POOL_COLD_SENSOR, needJust1); // would shrink to 1
+ final AtomicInteger emitCount = new AtomicInteger(0);
+
+ Asserts.succeedsEventually(MutableMap.of("timeout", TIMEOUT_MS), new Runnable() {
+ public void run() {
+ if (System.currentTimeMillis() - emitTime > (2+emitCount.get())*resizeDownStabilizationDelay) {
+ //first one may not have been received, in a registration race
+ resizable.emit(AutoScalerPolicy.DEFAULT_POOL_COLD_SENSOR, needJust1); // would shrink to 1
+ emitCount.incrementAndGet();
+ }
+ assertEquals(resizable.getCurrentSize(), (Integer)1);
+ }});
+
+ long resizeDelay = System.currentTimeMillis() - emitTime;
+ assertTrue(resizeDelay >= (resizeDownStabilizationDelay-EARLY_RETURN_MS), "resizeDelay="+resizeDelay);
+ }
+
+ Map<String, Object> message(double currentWorkrate, double lowThreshold, double highThreshold) {
+ return message(resizable.getCurrentSize(), currentWorkrate, lowThreshold, highThreshold);
+ }
+ static Map<String, Object> message(int currentSize, double currentWorkrate, double lowThreshold, double highThreshold) {
+ return ImmutableMap.<String,Object>of(
+ AutoScalerPolicy.POOL_CURRENT_SIZE_KEY, currentSize,
+ AutoScalerPolicy.POOL_CURRENT_WORKRATE_KEY, currentWorkrate,
+ AutoScalerPolicy.POOL_LOW_THRESHOLD_KEY, lowThreshold,
+ AutoScalerPolicy.POOL_HIGH_THRESHOLD_KEY, highThreshold);
+ }
+
+ public static Runnable currentSizeAsserter(final Resizable resizable, final Integer desired) {
+ return new Runnable() {
+ public void run() {
+ assertEquals(resizable.getCurrentSize(), desired);
+ }
+ };
+ }
+
+ public static void dumpThreadsEtc() {
+ ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean();
+ ThreadInfo[] threads = threadMXBean.dumpAllThreads(true, true);
+ for (ThreadInfo thread : threads) {
+ System.out.println(thread.getThreadName()+" ("+thread.getThreadState()+")");
+ for (StackTraceElement stackTraceElement : thread.getStackTrace()) {
+ System.out.println("\t"+stackTraceElement);
+ }
+ }
+
+ MemoryMXBean memoryMXBean = ManagementFactory.getMemoryMXBean();
+ MemoryUsage heapMemoryUsage = memoryMXBean.getHeapMemoryUsage();
+ MemoryUsage nonHeapMemoryUsage = memoryMXBean.getNonHeapMemoryUsage();
+ System.out.println("Memory:");
+ System.out.println("\tHeap: used="+heapMemoryUsage.getUsed()+"; max="+heapMemoryUsage.getMax()+"; init="+heapMemoryUsage.getInit()+"; committed="+heapMemoryUsage.getCommitted());
+ System.out.println("\tNon-heap: used="+nonHeapMemoryUsage.getUsed()+"; max="+nonHeapMemoryUsage.getMax()+"; init="+nonHeapMemoryUsage.getInit()+"; committed="+nonHeapMemoryUsage.getCommitted());
+
+ OperatingSystemMXBean operatingSystemMXBean = ManagementFactory.getOperatingSystemMXBean();
+ System.out.println("OS:");
+ System.out.println("\tsysLoadAvg="+operatingSystemMXBean.getSystemLoadAverage()+"; availableProcessors="+operatingSystemMXBean.getAvailableProcessors()+"; arch="+operatingSystemMXBean.getArch());
+ }
+}
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/d30ff597/policy/src/test/java/org/apache/brooklyn/policy/autoscaling/LocallyResizableEntity.java
----------------------------------------------------------------------
diff --git a/policy/src/test/java/org/apache/brooklyn/policy/autoscaling/LocallyResizableEntity.java b/policy/src/test/java/org/apache/brooklyn/policy/autoscaling/LocallyResizableEntity.java
new file mode 100644
index 0000000..0b3ef9c
--- /dev/null
+++ b/policy/src/test/java/org/apache/brooklyn/policy/autoscaling/LocallyResizableEntity.java
@@ -0,0 +1,73 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.brooklyn.policy.autoscaling;
+
+import java.util.List;
+
+import org.apache.brooklyn.api.entity.Entity;
+import org.apache.brooklyn.test.entity.TestCluster;
+
+import brooklyn.entity.basic.AbstractEntity;
+import brooklyn.entity.trait.Resizable;
+import brooklyn.entity.trait.Startable;
+
+import com.google.common.base.Throwables;
+import com.google.common.collect.Lists;
+
+/**
+ * Test class for providing a Resizable LocallyManagedEntity for policy testing
+ * It is hooked up to a TestCluster that can be used to make assertions against
+ */
+public class LocallyResizableEntity extends AbstractEntity implements Resizable {
+ List<Integer> sizes = Lists.newArrayList();
+ TestCluster cluster;
+ long resizeSleepTime = 0;
+
+ public LocallyResizableEntity (TestCluster tc) {
+ this(null, tc);
+ }
+ @SuppressWarnings("deprecation")
+ public LocallyResizableEntity (Entity parent, TestCluster tc) {
+ super(parent);
+ this.cluster = tc;
+ setAttribute(Startable.SERVICE_UP, true);
+ }
+
+ @Override
+ public Integer resize(Integer newSize) {
+ try {
+ Thread.sleep(resizeSleepTime);
+ sizes.add(newSize);
+ return cluster.resize(newSize);
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ throw Throwables.propagate(e);
+ }
+ }
+
+ @Override
+ public Integer getCurrentSize() {
+ return cluster.getCurrentSize();
+ }
+
+ @Override
+ public String toString() {
+ return getDisplayName();
+ }
+}
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/d30ff597/policy/src/test/java/org/apache/brooklyn/policy/followthesun/AbstractFollowTheSunPolicyTest.java
----------------------------------------------------------------------
diff --git a/policy/src/test/java/org/apache/brooklyn/policy/followthesun/AbstractFollowTheSunPolicyTest.java b/policy/src/test/java/org/apache/brooklyn/policy/followthesun/AbstractFollowTheSunPolicyTest.java
new file mode 100644
index 0000000..1928e3c
--- /dev/null
+++ b/policy/src/test/java/org/apache/brooklyn/policy/followthesun/AbstractFollowTheSunPolicyTest.java
@@ -0,0 +1,239 @@
+/*
+ * 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.policy.followthesun;
+
+import static org.testng.Assert.assertEquals;
+
+import java.util.Collection;
+import java.util.Map;
+import java.util.Random;
+import java.util.Set;
+
+import org.apache.brooklyn.api.entity.Entity;
+import org.apache.brooklyn.api.entity.Group;
+import org.apache.brooklyn.api.entity.basic.EntityLocal;
+import org.apache.brooklyn.api.entity.proxying.EntitySpec;
+import org.apache.brooklyn.api.location.Location;
+import org.apache.brooklyn.api.location.LocationSpec;
+import org.apache.brooklyn.api.management.ManagementContext;
+import org.apache.brooklyn.test.entity.LocalManagementContextForTests;
+import org.apache.brooklyn.test.entity.TestApplication;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.testng.annotations.AfterMethod;
+import org.testng.annotations.BeforeMethod;
+
+import brooklyn.entity.basic.ApplicationBuilder;
+import brooklyn.entity.basic.DynamicGroup;
+import brooklyn.entity.basic.Entities;
+
+import org.apache.brooklyn.location.basic.SimulatedLocation;
+
+import org.apache.brooklyn.policy.loadbalancing.BalanceableContainer;
+import org.apache.brooklyn.policy.loadbalancing.MockContainerEntity;
+import org.apache.brooklyn.policy.loadbalancing.MockItemEntity;
+import org.apache.brooklyn.policy.loadbalancing.MockItemEntityImpl;
+import org.apache.brooklyn.policy.loadbalancing.Movable;
+import brooklyn.test.Asserts;
+import brooklyn.util.collections.MutableMap;
+import brooklyn.util.time.Time;
+
+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;
+import com.google.common.collect.Maps;
+
+public class AbstractFollowTheSunPolicyTest {
+
+ private static final Logger LOG = LoggerFactory.getLogger(AbstractFollowTheSunPolicyTest.class);
+
+ protected static final long TIMEOUT_MS = 10*1000;
+ protected static final long SHORT_WAIT_MS = 250;
+
+ protected static final long CONTAINER_STARTUP_DELAY_MS = 100;
+
+ protected TestApplication app;
+ protected ManagementContext managementContext;
+ protected SimulatedLocation loc1;
+ protected SimulatedLocation loc2;
+ protected FollowTheSunPool pool;
+ protected DefaultFollowTheSunModel<Entity, Movable> model;
+ protected FollowTheSunPolicy policy;
+ protected Group containerGroup;
+ protected Group itemGroup;
+ protected Random random = new Random();
+
+ @BeforeMethod(alwaysRun=true)
+ public void setUp() throws Exception {
+ LOG.debug("In AbstractFollowTheSunPolicyTest.setUp()");
+
+ MockItemEntityImpl.totalMoveCount.set(0);
+ MockItemEntityImpl.lastMoveTime.set(0);
+
+ managementContext = LocalManagementContextForTests.newInstance();
+ app = ApplicationBuilder.newManagedApp(TestApplication.class, managementContext);
+
+ loc1 = managementContext.getLocationManager().createLocation(LocationSpec.create(SimulatedLocation.class).configure("name", "loc1"));
+ loc2 = managementContext.getLocationManager().createLocation(LocationSpec.create(SimulatedLocation.class).configure("name", "loc2"));
+
+ containerGroup = app.createAndManageChild(EntitySpec.create(DynamicGroup.class)
+ .displayName("containerGroup")
+ .configure(DynamicGroup.ENTITY_FILTER, Predicates.instanceOf(MockContainerEntity.class)));
+
+ itemGroup = app.createAndManageChild(EntitySpec.create(DynamicGroup.class)
+ .displayName("itemGroup")
+ .configure(DynamicGroup.ENTITY_FILTER, Predicates.instanceOf(MockItemEntity.class)));
+ model = new DefaultFollowTheSunModel<Entity, Movable>("pool-model");
+ pool = app.createAndManageChild(EntitySpec.create(FollowTheSunPool.class));
+ pool.setContents(containerGroup, itemGroup);
+ policy = new FollowTheSunPolicy(MockItemEntity.ITEM_USAGE_METRIC, model, FollowTheSunParameters.newDefault());
+ pool.addPolicy(policy);
+ app.start(ImmutableList.of(loc1, loc2));
+ }
+
+ @AfterMethod(alwaysRun=true)
+ public void tearDown() {
+ if (pool != null && policy != null) pool.removePolicy(policy);
+ if (app != null) Entities.destroyAll(app.getManagementContext());
+ MockItemEntityImpl.totalMoveCount.set(0);
+ MockItemEntityImpl.lastMoveTime.set(0);
+ }
+
+ /**
+ * Asserts that the given container have the given expected workrates (by querying the containers directly).
+ * Accepts an accuracy of "precision" for each container's workrate.
+ */
+ protected void assertItemDistributionEventually(final Map<MockContainerEntity, ? extends Collection<MockItemEntity>> expected) {
+ try {
+ Asserts.succeedsEventually(MutableMap.of("timeout", TIMEOUT_MS), new Runnable() {
+ public void run() {
+ assertItemDistribution(expected);
+ }});
+ } catch (AssertionError e) {
+ String errMsg = e.getMessage()+"; "+verboseDumpToString();
+ throw new RuntimeException(errMsg, e);
+ }
+ }
+
+ protected void assertItemDistributionContinually(final Map<MockContainerEntity, Collection<MockItemEntity>> expected) {
+ try {
+ Asserts.succeedsContinually(MutableMap.of("timeout", SHORT_WAIT_MS), new Runnable() {
+ public void run() {
+ assertItemDistribution(expected);
+ }});
+ } catch (AssertionError e) {
+ String errMsg = e.getMessage()+"; "+verboseDumpToString();
+ throw new RuntimeException(errMsg, e);
+ }
+ }
+
+ protected void assertItemDistribution(Map<MockContainerEntity, ? extends Collection<MockItemEntity>> expected) {
+ String errMsg = verboseDumpToString();
+ for (Map.Entry<MockContainerEntity, ? extends Collection<MockItemEntity>> entry : expected.entrySet()) {
+ MockContainerEntity container = entry.getKey();
+ Collection<MockItemEntity> expectedItems = entry.getValue();
+
+ assertEquals(ImmutableSet.copyOf(container.getBalanceableItems()), ImmutableSet.copyOf(expectedItems), errMsg);
+ }
+ }
+
+ protected String verboseDumpToString() {
+ Iterable<MockContainerEntity> containers = Iterables.filter(app.getManagementContext().getEntityManager().getEntities(), MockContainerEntity.class);
+ Iterable<MockItemEntity> items = Iterables.filter(app.getManagementContext().getEntityManager().getEntities(), MockItemEntity.class);
+
+ Iterable<Double> containerRates = Iterables.transform(containers, new Function<MockContainerEntity, Double>() {
+ @Override public Double apply(MockContainerEntity input) {
+ return (double) input.getWorkrate();
+ }});
+
+ Iterable<Map<Entity, Double>> containerItemUsages = Iterables.transform(containers, new Function<MockContainerEntity, Map<Entity, Double>>() {
+ @Override public Map<Entity, Double> apply(MockContainerEntity input) {
+ return input.getItemUsage();
+ }});
+
+ Map<MockContainerEntity, Set<Movable>> itemDistributionByContainer = Maps.newLinkedHashMap();
+ for (MockContainerEntity container : containers) {
+ itemDistributionByContainer.put(container, container.getBalanceableItems());
+ }
+
+ Map<Movable, BalanceableContainer<?>> itemDistributionByItem = Maps.newLinkedHashMap();
+ for (Movable item : items) {
+ itemDistributionByItem.put(item, item.getAttribute(Movable.CONTAINER));
+ }
+
+ String modelItemDistribution = model.itemDistributionToString();
+
+ return "containers="+containers+"; containerRates="+containerRates
+ +"; containerItemUsages="+containerItemUsages
+ +"; itemDistributionByContainer="+itemDistributionByContainer
+ +"; itemDistributionByItem="+itemDistributionByItem
+ +"; model="+modelItemDistribution
+ +"; totalMoves="+MockItemEntityImpl.totalMoveCount
+ +"; lastMoveTime="+Time.makeDateString(MockItemEntityImpl.lastMoveTime.get());
+ }
+
+ protected MockContainerEntity newContainer(TestApplication app, Location loc, String name) {
+ return newAsyncContainer(app, loc, name, 0);
+ }
+
+ /**
+ * Creates a new container that will take "delay" millis to complete its start-up.
+ */
+ protected MockContainerEntity newAsyncContainer(TestApplication app, Location loc, String name, long delay) {
+ // FIXME Is this comment true?
+ // Annoyingly, can't set parent until after the threshold config has been defined.
+ MockContainerEntity container = app.createAndManageChild(EntitySpec.create(MockContainerEntity.class)
+ .displayName(name)
+ .configure(MockContainerEntity.DELAY, delay));
+ LOG.debug("Managed new container {}", container);
+ container.start(ImmutableList.of(loc));
+ return container;
+ }
+
+ protected static MockItemEntity newLockedItem(TestApplication app, MockContainerEntity container, String name) {
+ MockItemEntity item = app.createAndManageChild(EntitySpec.create(MockItemEntity.class)
+ .displayName(name)
+ .configure(MockItemEntity.IMMOVABLE, true));
+ LOG.debug("Managed new locked item {}", container);
+ if (container != null) {
+ item.move(container);
+ }
+ return item;
+ }
+
+ protected static MockItemEntity newItem(TestApplication app, MockContainerEntity container, String name) {
+ MockItemEntity item = app.createAndManageChild(EntitySpec.create(MockItemEntity.class)
+ .displayName(name));
+ LOG.debug("Managed new item {} at {}", item, container);
+ if (container != null) {
+ item.move(container);
+ }
+ return item;
+ }
+
+ protected static MockItemEntity newItem(TestApplication app, MockContainerEntity container, String name, Map<? extends Entity, Double> workpattern) {
+ MockItemEntity item = newItem(app, container, name);
+ if (workpattern != null) {
+ ((EntityLocal)item).setAttribute(MockItemEntity.ITEM_USAGE_METRIC, (Map) workpattern);
+ }
+ return item;
+ }
+}
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/d30ff597/policy/src/test/java/org/apache/brooklyn/policy/followthesun/FollowTheSunModelTest.java
----------------------------------------------------------------------
diff --git a/policy/src/test/java/org/apache/brooklyn/policy/followthesun/FollowTheSunModelTest.java b/policy/src/test/java/org/apache/brooklyn/policy/followthesun/FollowTheSunModelTest.java
new file mode 100644
index 0000000..0edf888
--- /dev/null
+++ b/policy/src/test/java/org/apache/brooklyn/policy/followthesun/FollowTheSunModelTest.java
@@ -0,0 +1,195 @@
+/*
+ * 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.policy.followthesun;
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertFalse;
+import static org.testng.Assert.assertTrue;
+
+import org.testng.annotations.AfterMethod;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+import org.apache.brooklyn.api.location.Location;
+import org.apache.brooklyn.location.basic.SimulatedLocation;
+
+import org.apache.brooklyn.policy.loadbalancing.MockContainerEntity;
+import org.apache.brooklyn.policy.loadbalancing.MockContainerEntityImpl;
+import org.apache.brooklyn.policy.loadbalancing.MockItemEntity;
+import org.apache.brooklyn.policy.loadbalancing.MockItemEntityImpl;
+
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+
+public class FollowTheSunModelTest {
+
+ private Location loc1 = new SimulatedLocation(DefaultFollowTheSunModel.newHashMap("name","loc1"));
+ private Location loc2 = new SimulatedLocation(DefaultFollowTheSunModel.newHashMap("name","loc2"));
+ private MockContainerEntity container1 = new MockContainerEntityImpl();
+ private MockContainerEntity container2 = new MockContainerEntityImpl();
+ private MockItemEntity item1 = new MockItemEntityImpl();
+ private MockItemEntity item2 = new MockItemEntityImpl();
+ private MockItemEntity item3 = new MockItemEntityImpl();
+
+ private DefaultFollowTheSunModel<MockContainerEntity, MockItemEntity> model;
+
+ @BeforeMethod(alwaysRun=true)
+ public void setUp() throws Exception {
+ model = new DefaultFollowTheSunModel<MockContainerEntity, MockItemEntity>("myname");
+ }
+
+ @AfterMethod(alwaysRun=true)
+ public void tearDown() throws Exception {
+ // noting to tear down; no management context created
+ }
+
+ @Test
+ public void testSimpleAddAndRemove() throws Exception {
+ model.onContainerAdded(container1, loc1);
+ model.onContainerAdded(container2, loc2);
+ model.onItemAdded(item1, container1, true);
+ model.onItemAdded(item2, container2, true);
+
+ assertEquals(model.getContainerLocation(container1), loc1);
+ assertEquals(model.getContainerLocation(container2), loc2);
+ assertEquals(model.getItems(), ImmutableSet.of(item1, item2));
+ assertEquals(model.getItemLocation(item1), loc1);
+ assertEquals(model.getItemLocation(item2), loc2);
+ assertEquals(model.getItemContainer(item1), container1);
+ assertEquals(model.getItemContainer(item2), container2);
+
+ model.onContainerRemoved(container2);
+ model.onItemRemoved(item2);
+
+ assertEquals(model.getContainerLocation(container1), loc1);
+ assertEquals(model.getContainerLocation(container2), null);
+ assertEquals(model.getItems(), ImmutableSet.of(item1));
+ assertEquals(model.getItemLocation(item1), loc1);
+ assertEquals(model.getItemLocation(item2), null);
+ assertEquals(model.getItemContainer(item1), container1);
+ assertEquals(model.getItemContainer(item2), null);
+ }
+
+ @Test
+ public void testItemUsageMetrics() throws Exception {
+ model.onContainerAdded(container1, loc1);
+ model.onContainerAdded(container2, loc2);
+ model.onItemAdded(item1, container1, true);
+ model.onItemAdded(item2, container2, true);
+
+ model.onItemUsageUpdated(item1, ImmutableMap.of(item2, 12d));
+ model.onItemUsageUpdated(item2, ImmutableMap.of(item1, 11d));
+
+ assertEquals(model.getDirectSendsToItemByLocation(),
+ ImmutableMap.of(item1, ImmutableMap.of(loc2, 12d), item2, ImmutableMap.of(loc1, 11d)));
+ }
+
+ @Test
+ public void testItemUsageReportedIfLocationSetAfterUsageUpdate() throws Exception {
+ model.onContainerAdded(container1, null);
+ model.onContainerAdded(container2, null);
+ model.onItemAdded(item1, container1, true);
+ model.onItemAdded(item2, container2, true);
+ model.onItemUsageUpdated(item1, ImmutableMap.of(item2, 12d));
+ model.onContainerLocationUpdated(container1, loc1);
+ model.onContainerLocationUpdated(container2, loc2);
+
+ assertEquals(model.getDirectSendsToItemByLocation(),
+ ImmutableMap.of(item1, ImmutableMap.of(loc2, 12d)));
+ }
+
+ @Test
+ public void testItemUsageMetricsSummedForActorsInSameLocation() throws Exception {
+ model.onContainerAdded(container1, loc1);
+ model.onContainerAdded(container2, loc2);
+ model.onItemAdded(item1, container1, true);
+ model.onItemAdded(item2, container2, true);
+ model.onItemAdded(item3, container2, true);
+
+ model.onItemUsageUpdated(item1, ImmutableMap.of(item2, 12d, item3, 13d));
+
+ assertEquals(model.getDirectSendsToItemByLocation(),
+ ImmutableMap.of(item1, ImmutableMap.of(loc2, 12d+13d)));
+ }
+
+ @Test
+ public void testItemMovedWillUpdateLocationUsage() throws Exception {
+ model.onContainerAdded(container1, loc1);
+ model.onContainerAdded(container2, loc2);
+ model.onItemAdded(item1, container1, false);
+ model.onItemAdded(item2, container2, false);
+ model.onItemUsageUpdated(item2, ImmutableMap.of(item1, 12d));
+
+ model.onItemMoved(item1, container2);
+
+ assertEquals(model.getDirectSendsToItemByLocation(),
+ ImmutableMap.of(item2, ImmutableMap.of(loc2, 12d)));
+ assertEquals(model.getItemContainer(item1), container2);
+ assertEquals(model.getItemLocation(item1), loc2);
+ }
+
+ @Test
+ public void testItemAddedWithNoContainer() throws Exception {
+ model.onItemAdded(item1, null, true);
+
+ assertEquals(model.getItems(), ImmutableSet.of(item1));
+ assertEquals(model.getItemContainer(item1), null);
+ assertEquals(model.getItemLocation(item1), null);
+ }
+
+ @Test
+ public void testItemAddedBeforeContainer() throws Exception {
+ model.onItemAdded(item1, container1, true);
+ model.onContainerAdded(container1, loc1);
+
+ assertEquals(model.getItems(), ImmutableSet.of(item1));
+ assertEquals(model.getItemContainer(item1), container1);
+ assertEquals(model.getItemLocation(item1), loc1);
+ }
+
+ @Test
+ public void testItemMovedBeforeContainerAdded() throws Exception {
+ model.onContainerAdded(container1, loc1);
+ model.onItemAdded(item1, container1, true);
+ model.onItemMoved(item1, container2);
+ model.onContainerAdded(container2, loc2);
+
+ assertEquals(model.getItems(), ImmutableSet.of(item1));
+ assertEquals(model.getItemContainer(item1), container2);
+ assertEquals(model.getItemLocation(item1), loc2);
+ }
+
+ @Test
+ public void testItemAddedAnswersMovability() throws Exception {
+ model.onItemAdded(item1, container1, false);
+ model.onItemAdded(item2, container1, true);
+ assertTrue(model.isItemMoveable(item1));
+ assertFalse(model.isItemMoveable(item2));
+ }
+
+ @Test
+ public void testWorkrateUpdateAfterItemRemovalIsNotRecorded() throws Exception {
+ model.onContainerAdded(container1, loc1);
+ model.onItemAdded(item1, container1, true);
+ model.onItemAdded(item2, container1, true);
+ model.onItemRemoved(item1);
+ model.onItemUsageUpdated(item1, ImmutableMap.of(item2, 123d));
+
+ assertFalse(model.getDirectSendsToItemByLocation().containsKey(item1));
+ }
+}
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/d30ff597/policy/src/test/java/org/apache/brooklyn/policy/followthesun/FollowTheSunPolicySoakTest.java
----------------------------------------------------------------------
diff --git a/policy/src/test/java/org/apache/brooklyn/policy/followthesun/FollowTheSunPolicySoakTest.java b/policy/src/test/java/org/apache/brooklyn/policy/followthesun/FollowTheSunPolicySoakTest.java
new file mode 100644
index 0000000..178a9ae
--- /dev/null
+++ b/policy/src/test/java/org/apache/brooklyn/policy/followthesun/FollowTheSunPolicySoakTest.java
@@ -0,0 +1,274 @@
+/*
+ * 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.policy.followthesun;
+
+import static org.testng.Assert.assertEquals;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.brooklyn.api.entity.Entity;
+import org.apache.brooklyn.api.entity.basic.EntityLocal;
+import org.apache.brooklyn.api.location.Location;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.testng.annotations.Test;
+
+import brooklyn.entity.basic.Entities;
+
+import org.apache.brooklyn.location.basic.SimulatedLocation;
+
+import org.apache.brooklyn.policy.loadbalancing.BalanceableContainer;
+import org.apache.brooklyn.policy.loadbalancing.MockContainerEntity;
+import org.apache.brooklyn.policy.loadbalancing.MockItemEntity;
+import org.apache.brooklyn.policy.loadbalancing.MockItemEntityImpl;
+import org.apache.brooklyn.policy.loadbalancing.Movable;
+import brooklyn.test.Asserts;
+import brooklyn.util.collections.MutableMap;
+
+import com.google.common.base.Function;
+import com.google.common.base.Predicates;
+import com.google.common.collect.HashMultimap;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Maps;
+import com.google.common.collect.Multimap;
+import com.google.common.collect.Sets;
+
+public class FollowTheSunPolicySoakTest extends AbstractFollowTheSunPolicyTest {
+
+ private static final Logger LOG = LoggerFactory.getLogger(FollowTheSunPolicySoakTest.class);
+
+ private static final long TIMEOUT_MS = 10*1000;
+
+ @Test
+ public void testFollowTheSunQuickTest() {
+ RunConfig config = new RunConfig();
+ config.numCycles = 1;
+ config.numLocations=3;
+ config.numContainersPerLocation = 5;
+ config.numLockedItemsPerLocation = 2;
+ config.numMovableItems = 10;
+
+ runFollowTheSunSoakTest(config);
+ }
+
+ @Test
+ public void testLoadBalancingManyItemsQuickTest() {
+ RunConfig config = new RunConfig();
+ config.numCycles = 1;
+ config.numLocations=2;
+ config.numContainersPerLocation = 3;
+ config.numLockedItemsPerLocation = 2;
+ config.numMovableItems = 10;
+ config.numContainerStopsPerCycle = 1;
+ config.numItemStopsPerCycle = 1;
+
+ runFollowTheSunSoakTest(config);
+ }
+
+ @Test(groups={"Integration"}) // takes ~2s
+ public void testLoadBalancingManyItemsNotTooLongTest() {
+ RunConfig config = new RunConfig();
+ config.numCycles = 1;
+ config.numLocations=3;
+ config.numContainersPerLocation = 5;
+ config.numLockedItemsPerLocation = 2;
+ config.numMovableItems = 500;
+ config.numContainerStopsPerCycle = 1;
+ config.numItemStopsPerCycle = 1;
+
+ runFollowTheSunSoakTest(config);
+ }
+
+ @Test(groups={"Integration","Acceptance"}) // integration group, because it's slow to run many cycles
+ public void testLoadBalancingSoakTest() {
+ RunConfig config = new RunConfig();
+ config.numCycles = 100;
+ config.numLocations=3;
+ config.numContainersPerLocation = 5;
+ config.numLockedItemsPerLocation = 2;
+ config.numMovableItems = 10;
+
+ runFollowTheSunSoakTest(config);
+ }
+
+ @Test(groups={"Integration","Acceptance"}) // integration group, because it's slow to run many cycles
+ public void testLoadBalancingManyItemsSoakTest() {
+ RunConfig config = new RunConfig();
+ config.numCycles = 100;
+ config.numLocations=3;
+ config.numContainersPerLocation = 5;
+ config.numLockedItemsPerLocation = 2;
+ config.numMovableItems = 100;
+ config.numContainerStopsPerCycle = 3;
+ config.numItemStopsPerCycle = 10;
+
+ runFollowTheSunSoakTest(config);
+ }
+
+ @Test(groups={"Integration","Acceptance"}) // integration group, because it's slow to run many cycles
+ public void testLoadBalancingManyManyItemsTest() {
+ RunConfig config = new RunConfig();
+ config.numCycles = 1;
+ config.numLocations=10;
+ config.numContainersPerLocation = 5;
+ config.numLockedItemsPerLocation = 100;
+ config.numMovableItems = 1000;
+ config.numContainerStopsPerCycle = 0;
+ config.numItemStopsPerCycle = 0;
+ config.timeout_ms = 30*1000;
+ config.verbose = false;
+
+ runFollowTheSunSoakTest(config);
+ }
+
+ private void runFollowTheSunSoakTest(RunConfig config) {
+ int numCycles = config.numCycles;
+ int numLocations = config.numLocations;
+ int numContainersPerLocation = config.numContainersPerLocation;
+ int numLockedItemsPerLocation = config.numLockedItemsPerLocation;
+ int numMovableItems = config.numMovableItems;
+
+ int numContainerStopsPerCycle = config.numContainerStopsPerCycle;
+ int numItemStopsPerCycle = config.numItemStopsPerCycle;
+ long timeout_ms = config.timeout_ms;
+ final boolean verbose = config.verbose;
+
+ MockItemEntityImpl.totalMoveCount.set(0);
+
+ List<Location> locations = new ArrayList<Location>();
+ Multimap<Location,MockContainerEntity> containers = HashMultimap.<Location,MockContainerEntity>create();
+ Multimap<Location,MockItemEntity> lockedItems = HashMultimap.<Location,MockItemEntity>create();
+ final List<MockItemEntity> movableItems = new ArrayList<MockItemEntity>();
+
+ for (int i = 1; i <= numLocations; i++) {
+ String locName = "loc"+i;
+ Location loc = new SimulatedLocation(MutableMap.of("name",locName));
+ locations.add(loc);
+
+ for (int j = 1; j <= numContainersPerLocation; j++) {
+ MockContainerEntity container = newContainer(app, loc, "container-"+locName+"-"+j);
+ containers.put(loc, container);
+ }
+ for (int j = 1; j <= numLockedItemsPerLocation; j++) {
+ MockContainerEntity container = Iterables.get(containers.get(loc), j%numContainersPerLocation);
+ MockItemEntity item = newLockedItem(app, container, "item-locked-"+locName+"-"+j);
+ lockedItems.put(loc, item);
+ }
+ }
+
+ for (int i = 1; i <= numMovableItems; i++) {
+ MockContainerEntity container = Iterables.get(containers.values(), i%containers.size());
+ MockItemEntity item = newItem(app, container, "item-movable"+i);
+ movableItems.add(item);
+ }
+
+ for (int i = 1; i <= numCycles; i++) {
+ LOG.info("{}: cycle {}", FollowTheSunPolicySoakTest.class.getSimpleName(), i);
+
+ // Stop movable items, and start others
+ for (int j = 1; j <= numItemStopsPerCycle; j++) {
+ int itemIndex = random.nextInt(numMovableItems);
+ MockItemEntity itemToStop = movableItems.get(itemIndex);
+ itemToStop.stop();
+ LOG.debug("Unmanaging item {}", itemToStop);
+ Entities.unmanage(itemToStop);
+ movableItems.set(itemIndex, newItem(app, Iterables.get(containers.values(), 0), "item-movable"+itemIndex));
+ }
+
+ // Choose a location to be busiest
+ int locIndex = random.nextInt(numLocations);
+ final Location busiestLocation = locations.get(locIndex);
+
+ // Repartition the load across the items
+ for (int j = 0; j < numMovableItems; j++) {
+ MockItemEntity item = movableItems.get(j);
+ Map<Entity, Double> workrates = Maps.newLinkedHashMap();
+
+ for (Map.Entry<Location,MockItemEntity> entry : lockedItems.entries()) {
+ Location location = entry.getKey();
+ MockItemEntity source = entry.getValue();
+ double baseWorkrate = (location == busiestLocation ? 1000 : 100);
+ double jitter = 10;
+ double jitteredWorkrate = Math.max(0, baseWorkrate + (random.nextDouble()*jitter*2 - jitter));
+ workrates.put(source, jitteredWorkrate);
+ }
+ ((EntityLocal)item).setAttribute(MockItemEntity.ITEM_USAGE_METRIC, workrates);
+ }
+
+ // Stop containers, and start others
+ // This offloads the "immovable" items to other containers in the same location!
+ for (int j = 1; j <= numContainerStopsPerCycle; j++) {
+ int containerIndex = random.nextInt(containers.size());
+ MockContainerEntity containerToStop = Iterables.get(containers.values(), containerIndex);
+ Location location = Iterables.get(containerToStop.getLocations(), 0);
+ MockContainerEntity otherContainerInLocation = Iterables.find(containers.get(location), Predicates.not(Predicates.equalTo(containerToStop)), null);
+ containerToStop.offloadAndStop(otherContainerInLocation);
+ LOG.debug("Unmanaging container {}", containerToStop);
+ Entities.unmanage(containerToStop);
+ containers.remove(location, containerToStop);
+
+ MockContainerEntity containerToAdd = newContainer(app, location, "container-"+location.getDisplayName()+"-new."+i+"."+j);
+ containers.put(location, containerToAdd);
+ }
+
+ // Assert that the items all end up in the location with maximum load-generation
+ Asserts.succeedsEventually(MutableMap.of("timeout", timeout_ms), new Runnable() {
+ public void run() {
+ Iterable<Location> itemLocs = Iterables.transform(movableItems, new Function<MockItemEntity, Location>() {
+ public Location apply(MockItemEntity input) {
+ BalanceableContainer<?> container = input.getAttribute(Movable.CONTAINER);
+ Collection<Location> locs = (container != null) ? container.getLocations(): null;
+ return (locs != null && locs.size() > 0) ? Iterables.get(locs, 0) : null;
+ }});
+
+ Iterable<String> itemLocNames = Iterables.transform(itemLocs, new Function<Location, String>() {
+ public String apply(Location input) {
+ return (input != null) ? input.getDisplayName() : null;
+ }});
+ String errMsg;
+ if (verbose) {
+ errMsg = verboseDumpToString()+"; itemLocs="+itemLocNames;
+ } else {
+ Set<String> locNamesInUse = Sets.newLinkedHashSet(itemLocNames);
+ errMsg = "locsInUse="+locNamesInUse+"; totalMoves="+MockItemEntityImpl.totalMoveCount;
+ }
+
+ assertEquals(ImmutableList.copyOf(itemLocs), Collections.nCopies(movableItems.size(), busiestLocation), errMsg);
+ }});
+ }
+ }
+
+ static class RunConfig {
+ int numCycles = 1;
+ int numLocations = 3;
+ int numContainersPerLocation = 5;
+ int numLockedItemsPerLocation = 5;
+ int numMovableItems = 5;
+ int numContainerStopsPerCycle = 0;
+ int numItemStopsPerCycle = 0;
+ long timeout_ms = TIMEOUT_MS;
+ boolean verbose = true;
+ }
+}
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/d30ff597/policy/src/test/java/org/apache/brooklyn/policy/followthesun/FollowTheSunPolicyTest.java
----------------------------------------------------------------------
diff --git a/policy/src/test/java/org/apache/brooklyn/policy/followthesun/FollowTheSunPolicyTest.java b/policy/src/test/java/org/apache/brooklyn/policy/followthesun/FollowTheSunPolicyTest.java
new file mode 100644
index 0000000..872efab
--- /dev/null
+++ b/policy/src/test/java/org/apache/brooklyn/policy/followthesun/FollowTheSunPolicyTest.java
@@ -0,0 +1,307 @@
+/*
+ * 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.policy.followthesun;
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertTrue;
+import static org.testng.Assert.fail;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.Semaphore;
+import java.util.concurrent.TimeUnit;
+
+import org.apache.brooklyn.api.entity.Entity;
+import org.apache.brooklyn.api.entity.basic.EntityLocal;
+import org.apache.brooklyn.api.event.SensorEvent;
+import org.apache.brooklyn.api.event.SensorEventListener;
+import org.apache.brooklyn.api.location.Location;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.testng.annotations.Test;
+
+import brooklyn.entity.basic.Entities;
+
+import org.apache.brooklyn.location.basic.SimulatedLocation;
+
+import org.apache.brooklyn.policy.loadbalancing.MockContainerEntity;
+import org.apache.brooklyn.policy.loadbalancing.MockItemEntity;
+import org.apache.brooklyn.policy.loadbalancing.Movable;
+import brooklyn.test.Asserts;
+import brooklyn.util.collections.MutableMap;
+
+import com.google.common.base.Function;
+import com.google.common.base.Stopwatch;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Lists;
+
+public class FollowTheSunPolicyTest extends AbstractFollowTheSunPolicyTest {
+
+ private static final Logger LOG = LoggerFactory.getLogger(FollowTheSunPolicyTest.class);
+
+ @Test
+ public void testPolicyUpdatesModel() {
+ final MockContainerEntity containerA = newContainer(app, loc1, "A");
+ final MockItemEntity item1 = newItem(app, containerA, "1");
+ final MockItemEntity item2 = newItem(app, containerA, "2");
+ ((EntityLocal)item2).setAttribute(MockItemEntity.ITEM_USAGE_METRIC, ImmutableMap.<Entity,Double>of(item1, 11d));
+
+ Asserts.succeedsEventually(MutableMap.of("timeout", TIMEOUT_MS), new Runnable() {
+ @Override public void run() {
+ assertEquals(ImmutableSet.of(item1, item2), model.getItems());
+ assertEquals(model.getItemContainer(item1), containerA);
+ assertEquals(model.getItemLocation(item1), loc1);
+ assertEquals(model.getContainerLocation(containerA), loc1);
+ assertEquals(model.getDirectSendsToItemByLocation(), ImmutableMap.of(item2, ImmutableMap.of(loc1, 11d)));
+ }});
+ }
+
+ @Test
+ public void testPolicyAcceptsLocationFinder() {
+ pool.removePolicy(policy);
+
+ Function<Entity, Location> customLocationFinder = new Function<Entity, Location>() {
+ @Override public Location apply(Entity input) {
+ return new SimulatedLocation(MutableMap.of("name", "custom location for "+input));
+ }};
+
+ FollowTheSunPolicy customPolicy = new FollowTheSunPolicy(
+ MutableMap.of("minPeriodBetweenExecs", 0, "locationFinder", customLocationFinder),
+ MockItemEntity.ITEM_USAGE_METRIC,
+ model,
+ FollowTheSunParameters.newDefault());
+
+ pool.addPolicy(customPolicy);
+
+ final MockContainerEntity containerA = newContainer(app, loc1, "A");
+
+ Asserts.succeedsEventually(MutableMap.of("timeout", TIMEOUT_MS), new Runnable() {
+ @Override public void run() {
+ assertEquals(model.getContainerLocation(containerA).getDisplayName(), "custom location for "+containerA);
+ }});
+ }
+
+ @Test
+ public void testNoopBalancing() throws Exception {
+ // Set-up containers and items.
+ MockContainerEntity containerA = newContainer(app, loc1, "A");
+ MockContainerEntity containerB = newContainer(app, loc2, "B");
+ MockItemEntity item1 = newItem(app, containerA, "1", Collections.<Entity, Double>emptyMap());
+ MockItemEntity item2 = newItem(app, containerB, "2", Collections.<Entity, Double>emptyMap());
+
+ Thread.sleep(SHORT_WAIT_MS);
+ assertItemDistributionEventually(ImmutableMap.of(containerA, ImmutableList.of(item1), containerB, ImmutableList.of(item2)));
+ }
+
+ @Test
+ public void testMovesItemToFollowDemand() throws Exception {
+ // Set-up containers and items.
+ MockContainerEntity containerA = newContainer(app, loc1, "A");
+ MockContainerEntity containerB = newContainer(app, loc2, "B");
+ MockItemEntity item1 = newItem(app, containerA, "1");
+ MockItemEntity item2 = newItem(app, containerB, "2");
+
+ ((EntityLocal)item1).setAttribute(MockItemEntity.ITEM_USAGE_METRIC, ImmutableMap.<Entity,Double>of(item2, 100d));
+
+ assertItemDistributionEventually(ImmutableMap.of(containerA, ImmutableList.<MockItemEntity>of(), containerB, ImmutableList.of(item1, item2)));
+ }
+
+ @Test
+ public void testNoopIfDemandIsTiny() throws Exception {
+ // Set-up containers and items.
+ MockContainerEntity containerA = newContainer(app, loc1, "A");
+ MockContainerEntity containerB = newContainer(app, loc2, "B");
+ MockItemEntity item1 = newItem(app, containerA, "1");
+ MockItemEntity item2 = newItem(app, containerB, "2");
+
+ ((EntityLocal)item1).setAttribute(MockItemEntity.ITEM_USAGE_METRIC, ImmutableMap.<Entity,Double>of(item2, 0.1d));
+
+ Thread.sleep(SHORT_WAIT_MS);
+ assertItemDistributionEventually(ImmutableMap.of(containerA, ImmutableList.of(item1), containerB, ImmutableList.of(item2)));
+ }
+
+ @Test
+ public void testNoopIfDemandIsSimilarToCurrentLocation() throws Exception {
+ // Set-up containers and items.
+ MockContainerEntity containerA = newContainer(app, loc1, "A");
+ MockContainerEntity containerB = newContainer(app, loc2, "B");
+ MockItemEntity item1 = newItem(app, containerA, "1");
+ MockItemEntity item2 = newItem(app, containerA, "2");
+ MockItemEntity item3 = newItem(app, containerB, "3");
+
+ ((EntityLocal)item1).setAttribute(MockItemEntity.ITEM_USAGE_METRIC, ImmutableMap.<Entity,Double>of(item2, 100d, item3, 100.1d));
+
+ Thread.sleep(SHORT_WAIT_MS);
+ assertItemDistributionEventually(ImmutableMap.of(containerA, ImmutableList.of(item1, item2), containerB, ImmutableList.of(item3)));
+ }
+
+ @Test
+ public void testMoveDecisionIgnoresDemandFromItself() throws Exception {
+ // Set-up containers and items.
+ MockContainerEntity containerA = newContainer(app, loc1, "A");
+ MockContainerEntity containerB = newContainer(app, loc2, "B");
+ MockItemEntity item1 = newItem(app, containerA, "1");
+ MockItemEntity item2 = newItem(app, containerB, "2");
+
+ ((EntityLocal)item1).setAttribute(MockItemEntity.ITEM_USAGE_METRIC, ImmutableMap.<Entity,Double>of(item1, 100d));
+ ((EntityLocal)item2).setAttribute(MockItemEntity.ITEM_USAGE_METRIC, ImmutableMap.<Entity,Double>of(item2, 100d));
+
+ Thread.sleep(SHORT_WAIT_MS);
+ assertItemDistributionEventually(ImmutableMap.of(containerA, ImmutableList.of(item1), containerB, ImmutableList.of(item2)));
+ }
+
+ @Test
+ public void testItemRemovedCausesRecalculationOfOptimalLocation() {
+ // Set-up containers and items.
+ MockContainerEntity containerA = newContainer(app, loc1, "A");
+ MockContainerEntity containerB = newContainer(app, loc2, "B");
+ MockItemEntity item1 = newItem(app, containerA, "1");
+ MockItemEntity item2 = newItem(app, containerA, "2");
+ MockItemEntity item3 = newItem(app, containerB, "3");
+
+ ((EntityLocal)item1).setAttribute(MockItemEntity.ITEM_USAGE_METRIC, ImmutableMap.<Entity,Double>of(item2, 100d, item3, 1000d));
+
+ assertItemDistributionEventually(ImmutableMap.of(containerA, ImmutableList.of(item2), containerB, ImmutableList.of(item1, item3)));
+
+ item3.stop();
+ Entities.unmanage(item3);
+
+ assertItemDistributionEventually(ImmutableMap.of(containerA, ImmutableList.of(item1, item2), containerB, ImmutableList.<MockItemEntity>of()));
+ }
+
+ @Test
+ public void testItemMovedCausesRecalculationOfOptimalLocationForOtherItems() {
+ // Set-up containers and items.
+ MockContainerEntity containerA = newContainer(app, loc1, "A");
+ MockContainerEntity containerB = newContainer(app, loc2, "B");
+ MockItemEntity item1 = newItem(app, containerA, "1");
+ MockItemEntity item2 = newItem(app, containerA, "2");
+ MockItemEntity item3 = newItem(app, containerB, "3");
+
+ ((EntityLocal)item1).setAttribute(MockItemEntity.ITEM_USAGE_METRIC, ImmutableMap.<Entity,Double>of(item2, 100d, item3, 1000d));
+
+ assertItemDistributionEventually(ImmutableMap.of(containerA, ImmutableList.of(item2), containerB, ImmutableList.of(item1, item3)));
+
+ item3.move(containerA);
+
+ assertItemDistributionEventually(ImmutableMap.of(containerA, ImmutableList.of(item1, item2, item3), containerB, ImmutableList.<MockItemEntity>of()));
+ }
+
+ @Test
+ public void testImmovableItemIsNotMoved() {
+ MockContainerEntity containerA = newContainer(app, loc1, "A");
+ MockContainerEntity containerB = newContainer(app, loc2, "B");
+ MockItemEntity item1 = newLockedItem(app, containerA, "1");
+ MockItemEntity item2 = newItem(app, containerB, "2");
+
+ ((EntityLocal)item1).setAttribute(MockItemEntity.ITEM_USAGE_METRIC, ImmutableMap.<Entity,Double>of(item2, 100d));
+
+ assertItemDistributionEventually(ImmutableMap.of(containerA, ImmutableList.of(item1), containerB, ImmutableList.of(item2)));
+ }
+
+ @Test
+ public void testImmovableItemContributesTowardsLoad() {
+ MockContainerEntity containerA = newContainer(app, loc1, "A");
+ MockContainerEntity containerB = newContainer(app, loc2, "B");
+ MockItemEntity item1 = newLockedItem(app, containerA, "1");
+ MockItemEntity item2 = newItem(app, containerA, "2");
+
+ ((EntityLocal)item1).setAttribute(MockItemEntity.ITEM_USAGE_METRIC, ImmutableMap.<Entity,Double>of(item1, 100d));
+
+ assertItemDistributionEventually(ImmutableMap.of(containerA, ImmutableList.of(item1, item2), containerB, ImmutableList.<MockItemEntity>of()));
+ }
+
+ // Marked as "Acceptance" due to time-sensitive nature :-(
+ @Test(groups={"Integration", "Acceptance"}, invocationCount=20)
+ public void testRepeatedRespectsMinPeriodBetweenExecs() throws Exception {
+ testRespectsMinPeriodBetweenExecs();
+ }
+
+ @Test(groups="Integration")
+ public void testRespectsMinPeriodBetweenExecs() throws Exception {
+ // Failed in jenkins several times, e.g. with event times [2647, 2655] and [1387, 2001].
+ // Aled's guess is that there was a delay notifying the listener the first time
+ // (which happens async), causing the listener to be notified in rapid
+ // succession. The policy execs probably did happen with a 1000ms separation.
+ //
+ // Therefore try up to three times to see if we get the desired separation. If the
+ // minPeriodBetweenExecs wasn't being respected, we'd expect the default 100ms; this
+ // test would therefore hardly ever pass.
+ final int MAX_ATTEMPTS = 3;
+
+ final long minPeriodBetweenExecs = 1000;
+ final long timePrecision = 250;
+
+ pool.removePolicy(policy);
+
+ MockContainerEntity containerA = newContainer(app, loc1, "A");
+ MockContainerEntity containerB = newContainer(app, loc2, "B");
+ MockItemEntity item1 = newItem(app, containerA, "1");
+ MockItemEntity item2 = newItem(app, containerB, "2");
+ MockItemEntity item3 = newItem(app, containerA, "3");
+
+ FollowTheSunPolicy customPolicy = new FollowTheSunPolicy(
+ MutableMap.of("minPeriodBetweenExecs", minPeriodBetweenExecs),
+ MockItemEntity.ITEM_USAGE_METRIC,
+ model,
+ FollowTheSunParameters.newDefault());
+
+ pool.addPolicy(customPolicy);
+
+ // Record times that things are moved, by lisening to the container sensor being set
+ final Stopwatch stopwatch = Stopwatch.createStarted();
+
+ final List<Long> eventTimes = Lists.newCopyOnWriteArrayList();
+ final Semaphore semaphore = new Semaphore(0);
+
+ app.subscribe(item1, Movable.CONTAINER, new SensorEventListener<Entity>() {
+ @Override public void onEvent(SensorEvent<Entity> event) {
+ long eventTime = stopwatch.elapsed(TimeUnit.MILLISECONDS);
+ LOG.info("Received {} at {}", event, eventTime);
+ eventTimes.add(eventTime);
+ semaphore.release();
+ }});
+
+ String errmsg = "";
+ for (int i = 0; i < MAX_ATTEMPTS; i++) {
+ // Set the workrate, causing the policy to move item1 to item2's location, and wait for it to happen
+ ((EntityLocal)item1).setAttribute(MockItemEntity.ITEM_USAGE_METRIC, ImmutableMap.<Entity,Double>of(item2, 100d));
+ assertTrue(semaphore.tryAcquire(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+ assertEquals(item1.getAttribute(Movable.CONTAINER), containerB);
+
+ // now cause item1 to be moved to item3's location, and wait for it to happen
+ ((EntityLocal)item1).setAttribute(MockItemEntity.ITEM_USAGE_METRIC, ImmutableMap.<Entity,Double>of(item3, 100d));
+ assertTrue(semaphore.tryAcquire(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+ assertEquals(item1.getAttribute(Movable.CONTAINER), containerA);
+
+ LOG.info("testRepeatedRespectsMinPeriodBetweenExecs event times: "+eventTimes);
+ assertEquals(eventTimes.size(), 2);
+ if (eventTimes.get(1) - eventTimes.get(0) > (minPeriodBetweenExecs-timePrecision)) {
+ return; // success
+ } else {
+ errmsg += eventTimes;
+ eventTimes.clear();
+ }
+ }
+
+ fail("Event times never had sufficient gap: "+errmsg);
+ }
+}
[21/24] incubator-brooklyn git commit: make initialize database
optional
Posted by he...@apache.org.
make initialize database optional
Project: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/commit/3fff6417
Tree: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/tree/3fff6417
Diff: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/diff/3fff6417
Branch: refs/heads/master
Commit: 3fff64171269356387f5e2a45a2eb392a3e6f356
Parents: 8cba4d3
Author: Robert Moss <ro...@gmail.com>
Authored: Tue Aug 18 11:37:57 2015 +0100
Committer: Robert Moss <ro...@gmail.com>
Committed: Tue Aug 18 11:37:57 2015 +0100
----------------------------------------------------------------------
.../database/postgresql/PostgreSqlNode.java | 13 +++-
.../postgresql/PostgreSqlSshDriver.java | 75 ++++++++++++--------
2 files changed, 55 insertions(+), 33 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/3fff6417/software/database/src/main/java/brooklyn/entity/database/postgresql/PostgreSqlNode.java
----------------------------------------------------------------------
diff --git a/software/database/src/main/java/brooklyn/entity/database/postgresql/PostgreSqlNode.java b/software/database/src/main/java/brooklyn/entity/database/postgresql/PostgreSqlNode.java
index b90d19e..eccb870 100644
--- a/software/database/src/main/java/brooklyn/entity/database/postgresql/PostgreSqlNode.java
+++ b/software/database/src/main/java/brooklyn/entity/database/postgresql/PostgreSqlNode.java
@@ -82,10 +82,15 @@ public interface PostgreSqlNode extends SoftwareProcess, HasShortName, Datastore
ConfigKey<Long> POLL_PERIOD = ConfigKeys.newLongConfigKey(
"postgresql.sensorpoll", "Poll period (in milliseconds)", 1000L);
+ @SetFromFlag("initializeDB")
+ ConfigKey<Boolean> INITIALIZE_DB = ConfigKeys.newBooleanConfigKey(
+ "postgresql.initialize", "If true, PostgreSQL will create a new user and database", false);
+
@SetFromFlag("username")
BasicAttributeSensorAndConfigKey<String> USERNAME = new BasicAttributeSensorAndConfigKey<>(
- String.class, "postgresql.username", "Username of the database user",
- "postgresuser");
+ String.class, "postgresql.username", "Username of the database user");
+
+ String DEFAULT_USERNAME = "postgresqluser";
@SetFromFlag("password")
BasicAttributeSensorAndConfigKey<String> PASSWORD = new BasicAttributeSensorAndConfigKey<>(
@@ -94,7 +99,9 @@ public interface PostgreSqlNode extends SoftwareProcess, HasShortName, Datastore
@SetFromFlag("database")
BasicAttributeSensorAndConfigKey<String> DATABASE = new BasicAttributeSensorAndConfigKey<>(
- String.class, "postgresql.database", "Database to be used", "db");
+ String.class, "postgresql.database", "Database to be used");
+
+ String DEFAULT_DB_NAME = "db";
Effector<String> EXECUTE_SCRIPT = Effectors.effector(DatastoreMixins.EXECUTE_SCRIPT)
.description("Executes the given script contents using psql")
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/3fff6417/software/database/src/main/java/brooklyn/entity/database/postgresql/PostgreSqlSshDriver.java
----------------------------------------------------------------------
diff --git a/software/database/src/main/java/brooklyn/entity/database/postgresql/PostgreSqlSshDriver.java b/software/database/src/main/java/brooklyn/entity/database/postgresql/PostgreSqlSshDriver.java
index b7b5722..f8133f8 100644
--- a/software/database/src/main/java/brooklyn/entity/database/postgresql/PostgreSqlSshDriver.java
+++ b/software/database/src/main/java/brooklyn/entity/database/postgresql/PostgreSqlSshDriver.java
@@ -38,6 +38,12 @@ import java.io.InputStream;
import javax.annotation.Nullable;
+import org.apache.brooklyn.api.location.OsDetails;
+import org.apache.brooklyn.core.util.task.DynamicTasks;
+import org.apache.brooklyn.core.util.task.ssh.SshTasks;
+import org.apache.brooklyn.core.util.task.ssh.SshTasks.OnFailingTask;
+import org.apache.brooklyn.core.util.task.system.ProcessTaskWrapper;
+import org.apache.brooklyn.location.basic.SshMachineLocation;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -46,15 +52,7 @@ import brooklyn.entity.basic.Attributes;
import brooklyn.entity.basic.SoftwareProcess;
import brooklyn.entity.database.DatastoreMixins;
import brooklyn.entity.software.SshEffectorTasks;
-
-import org.apache.brooklyn.api.entity.basic.EntityLocal;
-import org.apache.brooklyn.api.location.OsDetails;
-import org.apache.brooklyn.core.util.task.DynamicTasks;
-import org.apache.brooklyn.core.util.task.ssh.SshTasks;
-import org.apache.brooklyn.core.util.task.ssh.SshTasks.OnFailingTask;
-import org.apache.brooklyn.core.util.task.system.ProcessTaskWrapper;
-import org.apache.brooklyn.location.basic.SshMachineLocation;
-
+import brooklyn.event.basic.BasicAttributeSensorAndConfigKey;
import brooklyn.util.collections.MutableList;
import brooklyn.util.collections.MutableMap;
import brooklyn.util.exceptions.Exceptions;
@@ -296,15 +294,32 @@ public class PostgreSqlSshDriver extends AbstractSoftwareProcessSshDriver implem
// Wait for commands to complete before running the creation script
DynamicTasks.waitForLast();
+ if(entity.getConfig(PostgreSqlNode.INITIALIZE_DB)){
+ initializeNewDatabase();
+ }
+ // Capture log file contents if there is an error configuring the database
+ try {
+ executeDatabaseCreationScript();
+ } catch (RuntimeException r) {
+ logTailOfPostgresLog();
+ throw Exceptions.propagate(r);
+ }
+
+ // Try establishing an external connection. If you get a "Connection refused...accepting TCP/IP connections
+ // on port 5432?" error then the port is probably closed. Check that the firewall allows external TCP/IP
+ // connections (netstat -nap). You can open a port with lokkit or by configuring the iptables.
+ }
+
+ private void initializeNewDatabase() {
String createUserCommand = String.format(
"\"CREATE USER %s WITH PASSWORD '%s'; \"",
- StringEscapes.escapeSql(entity.getConfig(PostgreSqlNode.USERNAME)),
+ StringEscapes.escapeSql(getUsername()),
StringEscapes.escapeSql(getUserPassword())
);
String createDatabaseCommand = String.format(
"\"CREATE DATABASE %s OWNER %s\"",
- StringEscapes.escapeSql(entity.getConfig(PostgreSqlNode.DATABASE)),
- StringEscapes.escapeSql(entity.getConfig(PostgreSqlNode.USERNAME)));
+ StringEscapes.escapeSql(getDatabaseName()),
+ StringEscapes.escapeSql(getUsername()));
newScript("initializing user and database")
.body.append(
"cd " + getInstallDir(),
@@ -315,28 +330,28 @@ public class PostgreSqlSshDriver extends AbstractSoftwareProcessSshDriver implem
" --command="+ createDatabaseCommand),
callPgctl("stop", true))
.failOnNonZeroResultCode().execute();
- // Capture log file contents if there is an error configuring the database
- try {
- executeDatabaseCreationScript();
- } catch (RuntimeException r) {
- logTailOfPostgresLog();
- throw Exceptions.propagate(r);
+ }
+
+ private String getConfigOrDefault(BasicAttributeSensorAndConfigKey<String> key, String def) {
+ String config = entity.getConfig(key);
+ if(Strings.isEmpty(config)) {
+ config = def;
+ log.debug(entity + " has no config specified for " + key + "; using default `" + def + "`");
+ entity.setAttribute(key, config);
}
-
- // Try establishing an external connection. If you get a "Connection refused...accepting TCP/IP connections
- // on port 5432?" error then the port is probably closed. Check that the firewall allows external TCP/IP
- // connections (netstat -nap). You can open a port with lokkit or by configuring the iptables.
+ return config;
+ }
+
+ protected String getDatabaseName() {
+ return getConfigOrDefault(PostgreSqlNode.DATABASE, PostgreSqlNode.DEFAULT_DB_NAME);
+ }
+
+ protected String getUsername(){
+ return getConfigOrDefault(PostgreSqlNode.USERNAME, PostgreSqlNode.DEFAULT_USERNAME);
}
protected String getUserPassword() {
- String password = entity.getConfig(PostgreSqlNode.PASSWORD);
- if (Strings.isEmpty(password)) {
- log.debug(entity + " has no password specified for " + PostgreSqlNode.PASSWORD + "; using a random string");
- password = brooklyn.util.text.Strings.makeRandomId(8);
- entity.setAttribute(PostgreSqlNode.PASSWORD, password);
- entity.config().set(PostgreSqlNode.PASSWORD, password);
- }
- return password;
+ return getConfigOrDefault(PostgreSqlNode.PASSWORD, Strings.makeRandomId(8));
}
protected void executeDatabaseCreationScript() {
[20/24] incubator-brooklyn git commit: [BROOKLYN-162] Renaming
package policy
Posted by he...@apache.org.
[BROOKLYN-162] Renaming package policy
Project: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/commit/d30ff597
Tree: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/tree/d30ff597
Diff: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/diff/d30ff597
Branch: refs/heads/master
Commit: d30ff597bab1c751d8767b95e547ab995d7709e2
Parents: 5dfe944
Author: Ivana Yovcheva <iv...@gmail.com>
Authored: Mon Aug 17 14:49:58 2015 +0300
Committer: Ivana Yovcheva <iv...@gmail.com>
Committed: Tue Aug 18 12:30:48 2015 +0300
----------------------------------------------------------------------
.../yaml/example_yaml/appserver-w-policy.yaml | 2 +-
.../vanilla-bash-netcat-restarter.yaml | 4 +-
.../vanilla-bash-netcat-w-client.yaml | 4 +-
...followthesun.DefaultFollowTheSunModel$1.html | 6 +-
.../brooklyn/demo/CumulusRDFApplication.java | 6 +-
.../demo/HighAvailabilityCassandraCluster.java | 6 +-
.../brooklyn/demo/ResilientMongoDbApp.java | 6 +-
.../brooklyn/demo/RiakClusterExample.java | 4 +-
.../brooklyn/demo/WideAreaCassandraCluster.java | 6 +-
.../brooklyn/demo/ha-cassandra-cluster.yaml | 6 +-
.../demo/wide-area-cassandra-cluster.yaml | 6 +-
.../demo/WebClusterDatabaseExample.java | 2 +-
.../demo/WebClusterDatabaseExampleApp.java | 3 +-
.../demo/WebClusterDatabaseExampleGroovy.groovy | 5 +-
.../apache/brooklyn/demo/WebClusterExample.java | 2 +-
...lusterDatabaseExampleAppIntegrationTest.java | 2 +-
.../policy/autoscaling/AutoScalerPolicy.java | 1090 -----------------
.../autoscaling/MaxPoolSizeReachedEvent.java | 103 --
.../policy/autoscaling/ResizeOperator.java | 31 -
.../policy/autoscaling/SizeHistory.java | 166 ---
.../followthesun/DefaultFollowTheSunModel.java | 328 ------
.../policy/followthesun/FollowTheSunModel.java | 56 -
.../followthesun/FollowTheSunParameters.java | 95 --
.../policy/followthesun/FollowTheSunPolicy.java | 280 -----
.../policy/followthesun/FollowTheSunPool.java | 75 --
.../followthesun/FollowTheSunPoolImpl.java | 178 ---
.../followthesun/FollowTheSunStrategy.java | 161 ---
.../policy/followthesun/WeightedObject.java | 71 --
.../policy/ha/AbstractFailureDetector.java | 361 ------
.../policy/ha/ConditionalSuspendPolicy.java | 103 --
.../policy/ha/ConnectionFailureDetector.java | 126 --
.../main/java/brooklyn/policy/ha/HASensors.java | 62 -
.../policy/ha/ServiceFailureDetector.java | 340 ------
.../brooklyn/policy/ha/ServiceReplacer.java | 214 ----
.../brooklyn/policy/ha/ServiceRestarter.java | 163 ---
.../policy/ha/SshMachineFailureDetector.java | 102 --
.../loadbalancing/BalanceableContainer.java | 51 -
.../loadbalancing/BalanceablePoolModel.java | 64 -
.../loadbalancing/BalanceableWorkerPool.java | 84 --
.../BalanceableWorkerPoolImpl.java | 185 ---
.../policy/loadbalancing/BalancingStrategy.java | 622 ----------
.../DefaultBalanceablePoolModel.java | 280 -----
.../loadbalancing/ItemsInContainersGroup.java | 52 -
.../ItemsInContainersGroupImpl.java | 148 ---
.../loadbalancing/LoadBalancingPolicy.java | 343 ------
.../loadbalancing/LocationConstraint.java | 28 -
.../brooklyn/policy/loadbalancing/Movable.java | 51 -
.../policy/loadbalancing/PolicyUtilForPool.java | 96 --
.../policy/autoscaling/AutoScalerPolicy.java | 1092 ++++++++++++++++++
.../autoscaling/MaxPoolSizeReachedEvent.java | 103 ++
.../policy/autoscaling/ResizeOperator.java | 31 +
.../policy/autoscaling/SizeHistory.java | 166 +++
.../followthesun/DefaultFollowTheSunModel.java | 328 ++++++
.../policy/followthesun/FollowTheSunModel.java | 56 +
.../followthesun/FollowTheSunParameters.java | 95 ++
.../policy/followthesun/FollowTheSunPolicy.java | 282 +++++
.../policy/followthesun/FollowTheSunPool.java | 75 ++
.../followthesun/FollowTheSunPoolImpl.java | 178 +++
.../followthesun/FollowTheSunStrategy.java | 161 +++
.../policy/followthesun/WeightedObject.java | 71 ++
.../policy/ha/AbstractFailureDetector.java | 363 ++++++
.../policy/ha/ConditionalSuspendPolicy.java | 103 ++
.../policy/ha/ConnectionFailureDetector.java | 128 ++
.../apache/brooklyn/policy/ha/HASensors.java | 62 +
.../policy/ha/ServiceFailureDetector.java | 340 ++++++
.../brooklyn/policy/ha/ServiceReplacer.java | 216 ++++
.../brooklyn/policy/ha/ServiceRestarter.java | 165 +++
.../policy/ha/SshMachineFailureDetector.java | 103 ++
.../loadbalancing/BalanceableContainer.java | 51 +
.../loadbalancing/BalanceablePoolModel.java | 64 +
.../loadbalancing/BalanceableWorkerPool.java | 84 ++
.../BalanceableWorkerPoolImpl.java | 185 +++
.../policy/loadbalancing/BalancingStrategy.java | 622 ++++++++++
.../DefaultBalanceablePoolModel.java | 280 +++++
.../loadbalancing/ItemsInContainersGroup.java | 52 +
.../ItemsInContainersGroupImpl.java | 148 +++
.../loadbalancing/LoadBalancingPolicy.java | 344 ++++++
.../loadbalancing/LocationConstraint.java | 28 +
.../brooklyn/policy/loadbalancing/Movable.java | 51 +
.../policy/loadbalancing/PolicyUtilForPool.java | 96 ++
.../autoscaling/AutoScalerPolicyMetricTest.java | 274 -----
.../autoscaling/AutoScalerPolicyRebindTest.java | 137 ---
.../AutoScalerPolicyReconfigurationTest.java | 190 ---
.../autoscaling/AutoScalerPolicyTest.java | 649 -----------
.../autoscaling/LocallyResizableEntity.java | 73 --
.../AbstractFollowTheSunPolicyTest.java | 239 ----
.../followthesun/FollowTheSunModelTest.java | 195 ----
.../FollowTheSunPolicySoakTest.java | 274 -----
.../followthesun/FollowTheSunPolicyTest.java | 307 -----
.../ha/ConnectionFailureDetectorTest.java | 303 -----
.../brooklyn/policy/ha/HaPolicyRebindTest.java | 173 ---
...ServiceFailureDetectorStabilizationTest.java | 234 ----
.../policy/ha/ServiceFailureDetectorTest.java | 408 -------
.../brooklyn/policy/ha/ServiceReplacerTest.java | 340 ------
.../policy/ha/ServiceRestarterTest.java | 190 ---
.../AbstractLoadBalancingPolicyTest.java | 253 ----
.../BalanceableWorkerPoolTest.java | 133 ---
.../ItemsInContainersGroupTest.java | 189 ---
.../loadbalancing/LoadBalancingModelTest.java | 115 --
.../LoadBalancingPolicyConcurrencyTest.java | 211 ----
.../LoadBalancingPolicySoakTest.java | 273 -----
.../loadbalancing/LoadBalancingPolicyTest.java | 397 -------
.../loadbalancing/MockContainerEntity.java | 61 -
.../loadbalancing/MockContainerEntityImpl.java | 208 ----
.../policy/loadbalancing/MockItemEntity.java | 46 -
.../loadbalancing/MockItemEntityImpl.java | 113 --
.../autoscaling/AutoScalerPolicyMetricTest.java | 274 +++++
.../autoscaling/AutoScalerPolicyRebindTest.java | 137 +++
.../AutoScalerPolicyReconfigurationTest.java | 190 +++
.../autoscaling/AutoScalerPolicyTest.java | 649 +++++++++++
.../autoscaling/LocallyResizableEntity.java | 73 ++
.../AbstractFollowTheSunPolicyTest.java | 239 ++++
.../followthesun/FollowTheSunModelTest.java | 195 ++++
.../FollowTheSunPolicySoakTest.java | 274 +++++
.../followthesun/FollowTheSunPolicyTest.java | 307 +++++
.../ha/ConnectionFailureDetectorTest.java | 303 +++++
.../brooklyn/policy/ha/HaPolicyRebindTest.java | 173 +++
...ServiceFailureDetectorStabilizationTest.java | 234 ++++
.../policy/ha/ServiceFailureDetectorTest.java | 407 +++++++
.../brooklyn/policy/ha/ServiceReplacerTest.java | 340 ++++++
.../policy/ha/ServiceRestarterTest.java | 190 +++
.../AbstractLoadBalancingPolicyTest.java | 253 ++++
.../BalanceableWorkerPoolTest.java | 133 +++
.../ItemsInContainersGroupTest.java | 189 +++
.../loadbalancing/LoadBalancingModelTest.java | 113 ++
.../LoadBalancingPolicyConcurrencyTest.java | 211 ++++
.../LoadBalancingPolicySoakTest.java | 273 +++++
.../loadbalancing/LoadBalancingPolicyTest.java | 397 +++++++
.../loadbalancing/MockContainerEntity.java | 61 +
.../loadbalancing/MockContainerEntityImpl.java | 208 ++++
.../policy/loadbalancing/MockItemEntity.java | 46 +
.../loadbalancing/MockItemEntityImpl.java | 113 ++
.../webapp/TomcatAutoScalerPolicyTest.java | 3 +-
.../app/ClusterWebServerDatabaseSample.java | 2 +-
.../BrooklynYamlTypeInstantiatorTest.java | 2 +-
.../brooklyn/JavaWebAppsIntegrationTest.java | 4 +-
.../brooklyn/catalog/CatalogYamlCombiTest.java | 2 +-
.../java-web-app-and-db-with-policy.yaml | 2 +-
.../resources/vanilla-bash-netcat-w-client.yaml | 4 +-
.../main/resources/brooklyn/default.catalog.bom | 10 +-
.../src/test/resources/couchbase-w-loadgen.yaml | 2 +-
.../brooklyn/qa/load/SimulatedTheeTierApp.java | 2 +-
.../qa/longevity/webcluster/WebClusterApp.java | 2 +-
.../brooklyn/rest/domain/ConfigSummary.java | 2 +-
.../rest/resources/CatalogResourceTest.java | 2 +-
145 files changed, 12162 insertions(+), 12146 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/d30ff597/docs/guide/yaml/example_yaml/appserver-w-policy.yaml
----------------------------------------------------------------------
diff --git a/docs/guide/yaml/example_yaml/appserver-w-policy.yaml b/docs/guide/yaml/example_yaml/appserver-w-policy.yaml
index cada61f..d096818 100644
--- a/docs/guide/yaml/example_yaml/appserver-w-policy.yaml
+++ b/docs/guide/yaml/example_yaml/appserver-w-policy.yaml
@@ -12,7 +12,7 @@ services:
brooklyn.example.db.url: $brooklyn:formatString("jdbc:%s%s?user=%s\\&password=%s",
component("db").attributeWhenReady("datastore.url"), "visitors", "brooklyn", "br00k11n")
brooklyn.policies:
- - policyType: brooklyn.policy.autoscaling.AutoScalerPolicy
+ - policyType: org.apache.brooklyn.policy.autoscaling.AutoScalerPolicy
brooklyn.config:
metric: $brooklyn:sensor("brooklyn.entity.webapp.DynamicWebAppCluster", "webapp.reqs.perSec.windowed.perNode")
metricLowerBound: 10
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/d30ff597/docs/guide/yaml/example_yaml/vanilla-bash-netcat-restarter.yaml
----------------------------------------------------------------------
diff --git a/docs/guide/yaml/example_yaml/vanilla-bash-netcat-restarter.yaml b/docs/guide/yaml/example_yaml/vanilla-bash-netcat-restarter.yaml
index 1fd70c3..6752980 100644
--- a/docs/guide/yaml/example_yaml/vanilla-bash-netcat-restarter.yaml
+++ b/docs/guide/yaml/example_yaml/vanilla-bash-netcat-restarter.yaml
@@ -8,12 +8,12 @@ services:
echo hello | nc -l 4321 &
echo $! > $PID_FILE
brooklyn.enrichers:
- - type: brooklyn.policy.ha.ServiceFailureDetector
+ - type: org.apache.brooklyn.policy.ha.ServiceFailureDetector
brooklyn.config:
# wait 15s after service fails before propagating failure
serviceFailedStabilizationDelay: 15s
brooklyn.policies:
- - type: brooklyn.policy.ha.ServiceRestarter
+ - type: org.apache.brooklyn.policy.ha.ServiceRestarter
brooklyn.config:
# repeated failures in a time window can cause the restarter to abort,
# propagating the failure; a time window of 0 will mean it always restarts!
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/d30ff597/docs/guide/yaml/example_yaml/vanilla-bash-netcat-w-client.yaml
----------------------------------------------------------------------
diff --git a/docs/guide/yaml/example_yaml/vanilla-bash-netcat-w-client.yaml b/docs/guide/yaml/example_yaml/vanilla-bash-netcat-w-client.yaml
index fbf0505..4893968 100644
--- a/docs/guide/yaml/example_yaml/vanilla-bash-netcat-w-client.yaml
+++ b/docs/guide/yaml/example_yaml/vanilla-bash-netcat-w-client.yaml
@@ -14,13 +14,13 @@ services:
# a failure detector and a service restarter work together
brooklyn.enrichers:
- - type: brooklyn.policy.ha.ServiceFailureDetector
+ - type: org.apache.brooklyn.policy.ha.ServiceFailureDetector
brooklyn.config:
# wait 15s after service fails before propagating failure
serviceFailedStabilizationDelay: 15s
brooklyn.policies:
- - type: brooklyn.policy.ha.ServiceRestarter
+ - type: org.apache.brooklyn.policy.ha.ServiceRestarter
brooklyn.config:
# repeated failures in a time window can cause the restarter to abort,
# propagating the failure; a time window of 0 will mean it always restarts!
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/d30ff597/docs/website/learnmore/catalog/locations/brooklyn.policy.followthesun.DefaultFollowTheSunModel$1.html
----------------------------------------------------------------------
diff --git a/docs/website/learnmore/catalog/locations/brooklyn.policy.followthesun.DefaultFollowTheSunModel$1.html b/docs/website/learnmore/catalog/locations/brooklyn.policy.followthesun.DefaultFollowTheSunModel$1.html
index 4bf0fd3..8172d3a 100644
--- a/docs/website/learnmore/catalog/locations/brooklyn.policy.followthesun.DefaultFollowTheSunModel$1.html
+++ b/docs/website/learnmore/catalog/locations/brooklyn.policy.followthesun.DefaultFollowTheSunModel$1.html
@@ -18,7 +18,7 @@ under the License.
-->
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
<head>
- <title>Brooklyn Location - brooklyn.policy.followthesun.DefaultFollowTheSunModel$1</title>
+ <title>Brooklyn Location - org.apache.brooklyn.policy.followthesun.DefaultFollowTheSunModel$1</title>
<meta http-equiv="content-type" content="text/html; charset=iso-8859-1"/>
<link rel="stylesheet" href="../items.css" type="text/css" media="screen"/>
</head>
@@ -30,7 +30,7 @@ under the License.
</div>
</div>
<div id="content" class="objectContent">
- <h1>brooklyn.policy.followthesun.DefaultFollowTheSunModel$1</h1>
+ <h1>org.apache.brooklyn.policy.followthesun.DefaultFollowTheSunModel$1</h1>
<h2 class="typeLabel">Type:</h2><span id="type"></span>
<h2>Config Keys</h2>
@@ -50,7 +50,7 @@ under the License.
<script type="text/javascript">
$(document).ready(function () {
var item = $.grep((items.locations), function (entity) {
- return entity.type == "brooklyn.policy.followthesun.DefaultFollowTheSunModel$1";
+ return entity.type == "org.apache.brooklyn.policy.followthesun.DefaultFollowTheSunModel$1";
})[0];
$("#type").html(item.type);
item.config.forEach(function (element) {
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/d30ff597/examples/simple-nosql-cluster/src/main/java/org/apache/brooklyn/demo/CumulusRDFApplication.java
----------------------------------------------------------------------
diff --git a/examples/simple-nosql-cluster/src/main/java/org/apache/brooklyn/demo/CumulusRDFApplication.java b/examples/simple-nosql-cluster/src/main/java/org/apache/brooklyn/demo/CumulusRDFApplication.java
index da88bff..d79228d 100644
--- a/examples/simple-nosql-cluster/src/main/java/org/apache/brooklyn/demo/CumulusRDFApplication.java
+++ b/examples/simple-nosql-cluster/src/main/java/org/apache/brooklyn/demo/CumulusRDFApplication.java
@@ -63,9 +63,9 @@ import brooklyn.event.basic.DependentConfiguration;
import org.apache.brooklyn.launcher.BrooklynLauncher;
import org.apache.brooklyn.location.basic.PortRanges;
-import brooklyn.policy.ha.ServiceFailureDetector;
-import brooklyn.policy.ha.ServiceReplacer;
-import brooklyn.policy.ha.ServiceRestarter;
+import org.apache.brooklyn.policy.ha.ServiceFailureDetector;
+import org.apache.brooklyn.policy.ha.ServiceReplacer;
+import org.apache.brooklyn.policy.ha.ServiceRestarter;
import brooklyn.util.CommandLineUtil;
import brooklyn.util.collections.MutableList;
import brooklyn.util.collections.MutableMap;
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/d30ff597/examples/simple-nosql-cluster/src/main/java/org/apache/brooklyn/demo/HighAvailabilityCassandraCluster.java
----------------------------------------------------------------------
diff --git a/examples/simple-nosql-cluster/src/main/java/org/apache/brooklyn/demo/HighAvailabilityCassandraCluster.java b/examples/simple-nosql-cluster/src/main/java/org/apache/brooklyn/demo/HighAvailabilityCassandraCluster.java
index eda486f..0361ef4 100644
--- a/examples/simple-nosql-cluster/src/main/java/org/apache/brooklyn/demo/HighAvailabilityCassandraCluster.java
+++ b/examples/simple-nosql-cluster/src/main/java/org/apache/brooklyn/demo/HighAvailabilityCassandraCluster.java
@@ -36,9 +36,9 @@ import brooklyn.entity.basic.StartableApplication;
import org.apache.brooklyn.launcher.BrooklynLauncher;
-import brooklyn.policy.ha.ServiceFailureDetector;
-import brooklyn.policy.ha.ServiceReplacer;
-import brooklyn.policy.ha.ServiceRestarter;
+import org.apache.brooklyn.policy.ha.ServiceFailureDetector;
+import org.apache.brooklyn.policy.ha.ServiceReplacer;
+import org.apache.brooklyn.policy.ha.ServiceRestarter;
import brooklyn.util.CommandLineUtil;
import com.google.common.collect.Lists;
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/d30ff597/examples/simple-nosql-cluster/src/main/java/org/apache/brooklyn/demo/ResilientMongoDbApp.java
----------------------------------------------------------------------
diff --git a/examples/simple-nosql-cluster/src/main/java/org/apache/brooklyn/demo/ResilientMongoDbApp.java b/examples/simple-nosql-cluster/src/main/java/org/apache/brooklyn/demo/ResilientMongoDbApp.java
index fc0837c..1f2f4d3 100644
--- a/examples/simple-nosql-cluster/src/main/java/org/apache/brooklyn/demo/ResilientMongoDbApp.java
+++ b/examples/simple-nosql-cluster/src/main/java/org/apache/brooklyn/demo/ResilientMongoDbApp.java
@@ -39,9 +39,9 @@ import brooklyn.entity.group.DynamicCluster;
import org.apache.brooklyn.launcher.BrooklynLauncher;
-import brooklyn.policy.ha.ServiceFailureDetector;
-import brooklyn.policy.ha.ServiceReplacer;
-import brooklyn.policy.ha.ServiceRestarter;
+import org.apache.brooklyn.policy.ha.ServiceFailureDetector;
+import org.apache.brooklyn.policy.ha.ServiceReplacer;
+import org.apache.brooklyn.policy.ha.ServiceRestarter;
import brooklyn.util.CommandLineUtil;
import com.google.common.collect.Lists;
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/d30ff597/examples/simple-nosql-cluster/src/main/java/org/apache/brooklyn/demo/RiakClusterExample.java
----------------------------------------------------------------------
diff --git a/examples/simple-nosql-cluster/src/main/java/org/apache/brooklyn/demo/RiakClusterExample.java b/examples/simple-nosql-cluster/src/main/java/org/apache/brooklyn/demo/RiakClusterExample.java
index 0aaf152..3d434b7 100644
--- a/examples/simple-nosql-cluster/src/main/java/org/apache/brooklyn/demo/RiakClusterExample.java
+++ b/examples/simple-nosql-cluster/src/main/java/org/apache/brooklyn/demo/RiakClusterExample.java
@@ -36,8 +36,8 @@ import brooklyn.entity.basic.StartableApplication;
import org.apache.brooklyn.launcher.BrooklynLauncher;
-import brooklyn.policy.ha.ServiceFailureDetector;
-import brooklyn.policy.ha.ServiceRestarter;
+import org.apache.brooklyn.policy.ha.ServiceFailureDetector;
+import org.apache.brooklyn.policy.ha.ServiceRestarter;
import brooklyn.util.CommandLineUtil;
import com.google.common.base.Preconditions;
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/d30ff597/examples/simple-nosql-cluster/src/main/java/org/apache/brooklyn/demo/WideAreaCassandraCluster.java
----------------------------------------------------------------------
diff --git a/examples/simple-nosql-cluster/src/main/java/org/apache/brooklyn/demo/WideAreaCassandraCluster.java b/examples/simple-nosql-cluster/src/main/java/org/apache/brooklyn/demo/WideAreaCassandraCluster.java
index 7f010c6..16ad833 100644
--- a/examples/simple-nosql-cluster/src/main/java/org/apache/brooklyn/demo/WideAreaCassandraCluster.java
+++ b/examples/simple-nosql-cluster/src/main/java/org/apache/brooklyn/demo/WideAreaCassandraCluster.java
@@ -38,9 +38,9 @@ import brooklyn.entity.basic.StartableApplication;
import org.apache.brooklyn.launcher.BrooklynLauncher;
-import brooklyn.policy.ha.ServiceFailureDetector;
-import brooklyn.policy.ha.ServiceReplacer;
-import brooklyn.policy.ha.ServiceRestarter;
+import org.apache.brooklyn.policy.ha.ServiceFailureDetector;
+import org.apache.brooklyn.policy.ha.ServiceReplacer;
+import org.apache.brooklyn.policy.ha.ServiceRestarter;
import brooklyn.util.CommandLineUtil;
import com.google.common.collect.Lists;
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/d30ff597/examples/simple-nosql-cluster/src/main/resources/org/apache/brooklyn/demo/ha-cassandra-cluster.yaml
----------------------------------------------------------------------
diff --git a/examples/simple-nosql-cluster/src/main/resources/org/apache/brooklyn/demo/ha-cassandra-cluster.yaml b/examples/simple-nosql-cluster/src/main/resources/org/apache/brooklyn/demo/ha-cassandra-cluster.yaml
index d1c6ab4..a260e74 100644
--- a/examples/simple-nosql-cluster/src/main/resources/org/apache/brooklyn/demo/ha-cassandra-cluster.yaml
+++ b/examples/simple-nosql-cluster/src/main/resources/org/apache/brooklyn/demo/ha-cassandra-cluster.yaml
@@ -38,8 +38,8 @@ services:
$brooklyn:entitySpec:
type: org.apache.brooklyn.entity.nosql.cassandra.CassandraNode
brookyn.policies:
- - type: brooklyn.policy.ha.ServiceRestarter
+ - type: org.apache.brooklyn.policy.ha.ServiceRestarter
brooklyn.enrichers:
- - type: brooklyn.policy.ha.ServiceFailureDetector
+ - type: org.apache.brooklyn.policy.ha.ServiceFailureDetector
brooklyn.policies:
- - type: brooklyn.policy.ha.ServiceReplacer
+ - type: org.apache.brooklyn.policy.ha.ServiceReplacer
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/d30ff597/examples/simple-nosql-cluster/src/main/resources/org/apache/brooklyn/demo/wide-area-cassandra-cluster.yaml
----------------------------------------------------------------------
diff --git a/examples/simple-nosql-cluster/src/main/resources/org/apache/brooklyn/demo/wide-area-cassandra-cluster.yaml b/examples/simple-nosql-cluster/src/main/resources/org/apache/brooklyn/demo/wide-area-cassandra-cluster.yaml
index 0cf39da..91b785c 100644
--- a/examples/simple-nosql-cluster/src/main/resources/org/apache/brooklyn/demo/wide-area-cassandra-cluster.yaml
+++ b/examples/simple-nosql-cluster/src/main/resources/org/apache/brooklyn/demo/wide-area-cassandra-cluster.yaml
@@ -34,8 +34,8 @@ services:
$brooklyn:entitySpec:
type: org.apache.brooklyn.entity.nosql.cassandra.CassandraNode
brookyn.policies:
- - type: brooklyn.policy.ha.ServiceRestarter
+ - type: org.apache.brooklyn.policy.ha.ServiceRestarter
brooklyn.enrichers:
- - type: brooklyn.policy.ha.ServiceFailureDetector
+ - type: org.apache.brooklyn.policy.ha.ServiceFailureDetector
brooklyn.policies:
- - type: brooklyn.policy.ha.ServiceReplacer
+ - type: org.apache.brooklyn.policy.ha.ServiceReplacer
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/d30ff597/examples/simple-web-cluster/src/main/java/org/apache/brooklyn/demo/WebClusterDatabaseExample.java
----------------------------------------------------------------------
diff --git a/examples/simple-web-cluster/src/main/java/org/apache/brooklyn/demo/WebClusterDatabaseExample.java b/examples/simple-web-cluster/src/main/java/org/apache/brooklyn/demo/WebClusterDatabaseExample.java
index 7084b74..88f3e2f 100644
--- a/examples/simple-web-cluster/src/main/java/org/apache/brooklyn/demo/WebClusterDatabaseExample.java
+++ b/examples/simple-web-cluster/src/main/java/org/apache/brooklyn/demo/WebClusterDatabaseExample.java
@@ -46,7 +46,7 @@ import org.apache.brooklyn.entity.webapp.WebAppServiceConstants;
import org.apache.brooklyn.launcher.BrooklynLauncher;
import org.apache.brooklyn.location.basic.PortRanges;
-import brooklyn.policy.autoscaling.AutoScalerPolicy;
+import org.apache.brooklyn.policy.autoscaling.AutoScalerPolicy;
import brooklyn.util.CommandLineUtil;
import com.google.common.collect.ImmutableMap;
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/d30ff597/examples/simple-web-cluster/src/main/java/org/apache/brooklyn/demo/WebClusterDatabaseExampleApp.java
----------------------------------------------------------------------
diff --git a/examples/simple-web-cluster/src/main/java/org/apache/brooklyn/demo/WebClusterDatabaseExampleApp.java b/examples/simple-web-cluster/src/main/java/org/apache/brooklyn/demo/WebClusterDatabaseExampleApp.java
index 441999f..f9366dc 100644
--- a/examples/simple-web-cluster/src/main/java/org/apache/brooklyn/demo/WebClusterDatabaseExampleApp.java
+++ b/examples/simple-web-cluster/src/main/java/org/apache/brooklyn/demo/WebClusterDatabaseExampleApp.java
@@ -53,7 +53,8 @@ import brooklyn.event.basic.Sensors;
import org.apache.brooklyn.launcher.BrooklynLauncher;
import org.apache.brooklyn.location.basic.PortRanges;
-import brooklyn.policy.autoscaling.AutoScalerPolicy;
+import org.apache.brooklyn.policy.autoscaling.AutoScalerPolicy;
+
import brooklyn.util.CommandLineUtil;
import com.google.common.collect.ImmutableMap;
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/d30ff597/examples/simple-web-cluster/src/main/java/org/apache/brooklyn/demo/WebClusterDatabaseExampleGroovy.groovy
----------------------------------------------------------------------
diff --git a/examples/simple-web-cluster/src/main/java/org/apache/brooklyn/demo/WebClusterDatabaseExampleGroovy.groovy b/examples/simple-web-cluster/src/main/java/org/apache/brooklyn/demo/WebClusterDatabaseExampleGroovy.groovy
index 9f965b9..8bade65 100644
--- a/examples/simple-web-cluster/src/main/java/org/apache/brooklyn/demo/WebClusterDatabaseExampleGroovy.groovy
+++ b/examples/simple-web-cluster/src/main/java/org/apache/brooklyn/demo/WebClusterDatabaseExampleGroovy.groovy
@@ -29,10 +29,13 @@ import org.slf4j.LoggerFactory
import brooklyn.entity.basic.AbstractApplication
import brooklyn.entity.basic.Entities
import brooklyn.entity.database.mysql.MySqlNode
+
import org.apache.brooklyn.api.entity.proxying.EntitySpec
import org.apache.brooklyn.entity.webapp.ControlledDynamicWebAppCluster
import org.apache.brooklyn.entity.webapp.DynamicWebAppCluster
-import brooklyn.policy.autoscaling.AutoScalerPolicy
+
+import org.apache.brooklyn.policy.autoscaling.AutoScalerPolicy
+
import brooklyn.util.CommandLineUtil
import com.google.common.collect.Lists
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/d30ff597/examples/simple-web-cluster/src/main/java/org/apache/brooklyn/demo/WebClusterExample.java
----------------------------------------------------------------------
diff --git a/examples/simple-web-cluster/src/main/java/org/apache/brooklyn/demo/WebClusterExample.java b/examples/simple-web-cluster/src/main/java/org/apache/brooklyn/demo/WebClusterExample.java
index 2f6f2f9..95197ed 100644
--- a/examples/simple-web-cluster/src/main/java/org/apache/brooklyn/demo/WebClusterExample.java
+++ b/examples/simple-web-cluster/src/main/java/org/apache/brooklyn/demo/WebClusterExample.java
@@ -34,7 +34,7 @@ import org.apache.brooklyn.entity.webapp.DynamicWebAppCluster;
import org.apache.brooklyn.entity.webapp.jboss.JBoss7Server;
import org.apache.brooklyn.launcher.BrooklynLauncher;
-import brooklyn.policy.autoscaling.AutoScalerPolicy;
+import org.apache.brooklyn.policy.autoscaling.AutoScalerPolicy;
import brooklyn.util.CommandLineUtil;
import com.google.common.collect.Lists;
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/d30ff597/examples/simple-web-cluster/src/test/java/org/apache/brooklyn/demo/RebindWebClusterDatabaseExampleAppIntegrationTest.java
----------------------------------------------------------------------
diff --git a/examples/simple-web-cluster/src/test/java/org/apache/brooklyn/demo/RebindWebClusterDatabaseExampleAppIntegrationTest.java b/examples/simple-web-cluster/src/test/java/org/apache/brooklyn/demo/RebindWebClusterDatabaseExampleAppIntegrationTest.java
index 38f7059..2b1537c 100644
--- a/examples/simple-web-cluster/src/test/java/org/apache/brooklyn/demo/RebindWebClusterDatabaseExampleAppIntegrationTest.java
+++ b/examples/simple-web-cluster/src/test/java/org/apache/brooklyn/demo/RebindWebClusterDatabaseExampleAppIntegrationTest.java
@@ -56,7 +56,7 @@ import brooklyn.entity.group.DynamicCluster;
import brooklyn.entity.java.JavaEntityMethods;
import brooklyn.entity.rebind.RebindOptions;
import brooklyn.entity.rebind.RebindTestFixture;
-import brooklyn.policy.autoscaling.AutoScalerPolicy;
+import org.apache.brooklyn.policy.autoscaling.AutoScalerPolicy;
import brooklyn.test.Asserts;
import brooklyn.util.collections.MutableMap;
import brooklyn.util.time.Duration;
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/d30ff597/policy/src/main/java/brooklyn/policy/autoscaling/AutoScalerPolicy.java
----------------------------------------------------------------------
diff --git a/policy/src/main/java/brooklyn/policy/autoscaling/AutoScalerPolicy.java b/policy/src/main/java/brooklyn/policy/autoscaling/AutoScalerPolicy.java
deleted file mode 100644
index 18480bd..0000000
--- a/policy/src/main/java/brooklyn/policy/autoscaling/AutoScalerPolicy.java
+++ /dev/null
@@ -1,1090 +0,0 @@
-/*
- * 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 brooklyn.policy.autoscaling;
-
-import static brooklyn.util.JavaGroovyEquivalents.groovyTruth;
-import static com.google.common.base.Preconditions.checkNotNull;
-import groovy.lang.Closure;
-
-import java.util.Map;
-import java.util.concurrent.Callable;
-import java.util.concurrent.Executors;
-import java.util.concurrent.ScheduledExecutorService;
-import java.util.concurrent.ThreadFactory;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.atomic.AtomicBoolean;
-
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.apache.brooklyn.api.catalog.Catalog;
-import org.apache.brooklyn.api.entity.Entity;
-import org.apache.brooklyn.api.entity.basic.EntityLocal;
-import org.apache.brooklyn.api.event.AttributeSensor;
-import org.apache.brooklyn.api.event.Sensor;
-import org.apache.brooklyn.api.event.SensorEvent;
-import org.apache.brooklyn.api.event.SensorEventListener;
-import org.apache.brooklyn.api.policy.PolicySpec;
-import org.apache.brooklyn.core.policy.basic.AbstractPolicy;
-import org.apache.brooklyn.core.util.flags.SetFromFlag;
-import org.apache.brooklyn.core.util.flags.TypeCoercions;
-import org.apache.brooklyn.core.util.task.Tasks;
-
-import brooklyn.config.ConfigKey;
-import brooklyn.entity.basic.BrooklynTaskTags;
-import brooklyn.entity.basic.Entities;
-import brooklyn.entity.trait.Resizable;
-import brooklyn.entity.trait.Startable;
-import brooklyn.event.basic.BasicConfigKey;
-import brooklyn.event.basic.BasicNotificationSensor;
-import brooklyn.policy.autoscaling.SizeHistory.WindowSummary;
-import brooklyn.policy.loadbalancing.LoadBalancingPolicy;
-import brooklyn.util.collections.MutableMap;
-import brooklyn.util.time.Duration;
-
-import com.google.common.base.Function;
-import com.google.common.base.Preconditions;
-import com.google.common.base.Throwables;
-import com.google.common.reflect.TypeToken;
-import com.google.common.util.concurrent.ThreadFactoryBuilder;
-
-
-/**
- * Policy that is attached to a {@link Resizable} entity and dynamically adjusts its size in response to
- * emitted {@code POOL_COLD} and {@code POOL_HOT} events. Alternatively, the policy can be configured to
- * keep a given metric within a required range.
- * <p>
- * TThis policy does not itself determine whether the pool is hot or cold, but instead relies on these
- * events being emitted by the monitored entity itself, or by another policy that is attached to it; see,
- * for example, {@link LoadBalancingPolicy}.)
- */
-@SuppressWarnings({"rawtypes", "unchecked"})
-@Catalog(name="Auto-scaler", description="Policy that is attached to a Resizable entity and dynamically "
- + "adjusts its size in response to either keep a metric within a given range, or in response to "
- + "POOL_COLD and POOL_HOT events")
-public class AutoScalerPolicy extends AbstractPolicy {
-
- private static final Logger LOG = LoggerFactory.getLogger(AutoScalerPolicy.class);
-
- public static Builder builder() {
- return new Builder();
- }
-
- public static class Builder {
- private String id;
- private String name;
- private AttributeSensor<? extends Number> metric;
- private Entity entityWithMetric;
- private Number metricUpperBound;
- private Number metricLowerBound;
- private int minPoolSize = 1;
- private int maxPoolSize = Integer.MAX_VALUE;
- private Integer resizeDownIterationIncrement;
- private Integer resizeDownIterationMax;
- private Integer resizeUpIterationIncrement;
- private Integer resizeUpIterationMax;
- private Duration minPeriodBetweenExecs;
- private Duration resizeUpStabilizationDelay;
- private Duration resizeDownStabilizationDelay;
- private ResizeOperator resizeOperator;
- private Function<Entity,Integer> currentSizeOperator;
- private BasicNotificationSensor<?> poolHotSensor;
- private BasicNotificationSensor<?> poolColdSensor;
- private BasicNotificationSensor<?> poolOkSensor;
- private BasicNotificationSensor<? super MaxPoolSizeReachedEvent> maxSizeReachedSensor;
- private Duration maxReachedNotificationDelay;
-
- public Builder id(String val) {
- this.id = val; return this;
- }
- public Builder name(String val) {
- this.name = val; return this;
- }
- public Builder metric(AttributeSensor<? extends Number> val) {
- this.metric = val; return this;
- }
- public Builder entityWithMetric(Entity val) {
- this.entityWithMetric = val; return this;
- }
- public Builder metricLowerBound(Number val) {
- this.metricLowerBound = val; return this;
- }
- public Builder metricUpperBound(Number val) {
- this.metricUpperBound = val; return this;
- }
- public Builder metricRange(Number min, Number max) {
- metricLowerBound = checkNotNull(min);
- metricUpperBound = checkNotNull(max);
- return this;
- }
- public Builder minPoolSize(int val) {
- this.minPoolSize = val; return this;
- }
- public Builder maxPoolSize(int val) {
- this.maxPoolSize = val; return this;
- }
- public Builder sizeRange(int min, int max) {
- minPoolSize = min;
- maxPoolSize = max;
- return this;
- }
-
- public Builder resizeUpIterationIncrement(Integer val) {
- this.resizeUpIterationIncrement = val; return this;
- }
- public Builder resizeUpIterationMax(Integer val) {
- this.resizeUpIterationMax = val; return this;
- }
- public Builder resizeDownIterationIncrement(Integer val) {
- this.resizeUpIterationIncrement = val; return this;
- }
- public Builder resizeDownIterationMax(Integer val) {
- this.resizeUpIterationMax = val; return this;
- }
-
- public Builder minPeriodBetweenExecs(Duration val) {
- this.minPeriodBetweenExecs = val; return this;
- }
- public Builder resizeUpStabilizationDelay(Duration val) {
- this.resizeUpStabilizationDelay = val; return this;
- }
- public Builder resizeDownStabilizationDelay(Duration val) {
- this.resizeDownStabilizationDelay = val; return this;
- }
- public Builder resizeOperator(ResizeOperator val) {
- this.resizeOperator = val; return this;
- }
- public Builder currentSizeOperator(Function<Entity, Integer> val) {
- this.currentSizeOperator = val; return this;
- }
- public Builder poolHotSensor(BasicNotificationSensor<?> val) {
- this.poolHotSensor = val; return this;
- }
- public Builder poolColdSensor(BasicNotificationSensor<?> val) {
- this.poolColdSensor = val; return this;
- }
- public Builder poolOkSensor(BasicNotificationSensor<?> val) {
- this.poolOkSensor = val; return this;
- }
- public Builder maxSizeReachedSensor(BasicNotificationSensor<? super MaxPoolSizeReachedEvent> val) {
- this.maxSizeReachedSensor = val; return this;
- }
- public Builder maxReachedNotificationDelay(Duration val) {
- this.maxReachedNotificationDelay = val; return this;
- }
- public AutoScalerPolicy build() {
- return new AutoScalerPolicy(toFlags());
- }
- public PolicySpec<AutoScalerPolicy> buildSpec() {
- return PolicySpec.create(AutoScalerPolicy.class)
- .configure(toFlags());
- }
- private Map<String,?> toFlags() {
- return MutableMap.<String,Object>builder()
- .putIfNotNull("id", id)
- .putIfNotNull("name", name)
- .putIfNotNull("metric", metric)
- .putIfNotNull("entityWithMetric", entityWithMetric)
- .putIfNotNull("metricUpperBound", metricUpperBound)
- .putIfNotNull("metricLowerBound", metricLowerBound)
- .putIfNotNull("minPoolSize", minPoolSize)
- .putIfNotNull("maxPoolSize", maxPoolSize)
- .putIfNotNull("resizeUpIterationMax", resizeUpIterationMax)
- .putIfNotNull("resizeUpIterationIncrement", resizeUpIterationIncrement)
- .putIfNotNull("resizeDownIterationMax", resizeDownIterationMax)
- .putIfNotNull("resizeDownIterationIncrement", resizeDownIterationIncrement)
- .putIfNotNull("minPeriodBetweenExecs", minPeriodBetweenExecs)
- .putIfNotNull("resizeUpStabilizationDelay", resizeUpStabilizationDelay)
- .putIfNotNull("resizeDownStabilizationDelay", resizeDownStabilizationDelay)
- .putIfNotNull("resizeOperator", resizeOperator)
- .putIfNotNull("currentSizeOperator", currentSizeOperator)
- .putIfNotNull("poolHotSensor", poolHotSensor)
- .putIfNotNull("poolColdSensor", poolColdSensor)
- .putIfNotNull("poolOkSensor", poolOkSensor)
- .putIfNotNull("maxSizeReachedSensor", maxSizeReachedSensor)
- .putIfNotNull("maxReachedNotificationDelay", maxReachedNotificationDelay)
- .build();
- }
- }
-
- // TODO Is there a nicer pattern for registering such type-coercions?
- // Can't put it in the ResizeOperator interface, nor in core TypeCoercions class because interface is defined in policy/.
- static {
- TypeCoercions.registerAdapter(Closure.class, ResizeOperator.class, new Function<Closure,ResizeOperator>() {
- @Override
- public ResizeOperator apply(final Closure closure) {
- return new ResizeOperator() {
- @Override public Integer resize(Entity entity, Integer input) {
- return (Integer) closure.call(entity, input);
- }
- };
- }
- });
- }
-
- // Pool workrate notifications.
- public static BasicNotificationSensor<Map> DEFAULT_POOL_HOT_SENSOR = new BasicNotificationSensor<Map>(
- Map.class, "resizablepool.hot", "Pool is over-utilized; it has insufficient resource for current workload");
- public static BasicNotificationSensor<Map> DEFAULT_POOL_COLD_SENSOR = new BasicNotificationSensor<Map>(
- Map.class, "resizablepool.cold", "Pool is under-utilized; it has too much resource for current workload");
- public static BasicNotificationSensor<Map> DEFAULT_POOL_OK_SENSOR = new BasicNotificationSensor<Map>(
- Map.class, "resizablepool.cold", "Pool utilization is ok; the available resources are fine for the current workload");
-
- /**
- * A convenience for policies that want to register a {@code builder.maxSizeReachedSensor(sensor)}.
- * Note that this "default" is not set automatically; the default is for no sensor to be used (so
- * no events emitted).
- */
- public static BasicNotificationSensor<MaxPoolSizeReachedEvent> DEFAULT_MAX_SIZE_REACHED_SENSOR = new BasicNotificationSensor<MaxPoolSizeReachedEvent>(
- MaxPoolSizeReachedEvent.class, "resizablepool.maxSizeReached", "Consistently wanted to resize the pool above the max allowed size");
-
- public static final String POOL_CURRENT_SIZE_KEY = "pool.current.size";
- public static final String POOL_HIGH_THRESHOLD_KEY = "pool.high.threshold";
- public static final String POOL_LOW_THRESHOLD_KEY = "pool.low.threshold";
- public static final String POOL_CURRENT_WORKRATE_KEY = "pool.current.workrate";
-
- @SuppressWarnings("serial")
- @SetFromFlag("metric")
- public static final ConfigKey<AttributeSensor<? extends Number>> METRIC = BasicConfigKey.builder(new TypeToken<AttributeSensor<? extends Number>>() {})
- .name("autoscaler.metric")
- .build();
-
- @SetFromFlag("entityWithMetric")
- public static final ConfigKey<Entity> ENTITY_WITH_METRIC = BasicConfigKey.builder(Entity.class)
- .name("autoscaler.entityWithMetric")
- .build();
-
- @SetFromFlag("metricLowerBound")
- public static final ConfigKey<Number> METRIC_LOWER_BOUND = BasicConfigKey.builder(Number.class)
- .name("autoscaler.metricLowerBound")
- .reconfigurable(true)
- .build();
-
- @SetFromFlag("metricUpperBound")
- public static final ConfigKey<Number> METRIC_UPPER_BOUND = BasicConfigKey.builder(Number.class)
- .name("autoscaler.metricUpperBound")
- .reconfigurable(true)
- .build();
-
- @SetFromFlag("resizeUpIterationIncrement")
- public static final ConfigKey<Integer> RESIZE_UP_ITERATION_INCREMENT = BasicConfigKey.builder(Integer.class)
- .name("autoscaler.resizeUpIterationIncrement")
- .description("Batch size for resizing up; the size will be increased by a multiple of this value")
- .defaultValue(1)
- .reconfigurable(true)
- .build();
- @SetFromFlag("resizeUpIterationMax")
- public static final ConfigKey<Integer> RESIZE_UP_ITERATION_MAX = BasicConfigKey.builder(Integer.class)
- .name("autoscaler.resizeUpIterationMax")
- .defaultValue(Integer.MAX_VALUE)
- .description("Maximum change to the size on a single iteration when scaling up")
- .reconfigurable(true)
- .build();
- @SetFromFlag("resizeDownIterationIncrement")
- public static final ConfigKey<Integer> RESIZE_DOWN_ITERATION_INCREMENT = BasicConfigKey.builder(Integer.class)
- .name("autoscaler.resizeDownIterationIncrement")
- .description("Batch size for resizing down; the size will be decreased by a multiple of this value")
- .defaultValue(1)
- .reconfigurable(true)
- .build();
- @SetFromFlag("resizeDownIterationMax")
- public static final ConfigKey<Integer> RESIZE_DOWN_ITERATION_MAX = BasicConfigKey.builder(Integer.class)
- .name("autoscaler.resizeDownIterationMax")
- .defaultValue(Integer.MAX_VALUE)
- .description("Maximum change to the size on a single iteration when scaling down")
- .reconfigurable(true)
- .build();
-
- @SetFromFlag("minPeriodBetweenExecs")
- public static final ConfigKey<Duration> MIN_PERIOD_BETWEEN_EXECS = BasicConfigKey.builder(Duration.class)
- .name("autoscaler.minPeriodBetweenExecs")
- .defaultValue(Duration.millis(100))
- .build();
-
- @SetFromFlag("resizeUpStabilizationDelay")
- public static final ConfigKey<Duration> RESIZE_UP_STABILIZATION_DELAY = BasicConfigKey.builder(Duration.class)
- .name("autoscaler.resizeUpStabilizationDelay")
- .defaultValue(Duration.ZERO)
- .reconfigurable(true)
- .build();
-
- @SetFromFlag("resizeDownStabilizationDelay")
- public static final ConfigKey<Duration> RESIZE_DOWN_STABILIZATION_DELAY = BasicConfigKey.builder(Duration.class)
- .name("autoscaler.resizeDownStabilizationDelay")
- .defaultValue(Duration.ZERO)
- .reconfigurable(true)
- .build();
-
- @SetFromFlag("minPoolSize")
- public static final ConfigKey<Integer> MIN_POOL_SIZE = BasicConfigKey.builder(Integer.class)
- .name("autoscaler.minPoolSize")
- .defaultValue(1)
- .reconfigurable(true)
- .build();
-
- @SetFromFlag("maxPoolSize")
- public static final ConfigKey<Integer> MAX_POOL_SIZE = BasicConfigKey.builder(Integer.class)
- .name("autoscaler.maxPoolSize")
- .defaultValue(Integer.MAX_VALUE)
- .reconfigurable(true)
- .build();
-
- @SetFromFlag("resizeOperator")
- public static final ConfigKey<ResizeOperator> RESIZE_OPERATOR = BasicConfigKey.builder(ResizeOperator.class)
- .name("autoscaler.resizeOperator")
- .defaultValue(new ResizeOperator() {
- public Integer resize(Entity entity, Integer desiredSize) {
- return ((Resizable)entity).resize(desiredSize);
- }})
- .build();
-
- @SuppressWarnings("serial")
- @SetFromFlag("currentSizeOperator")
- public static final ConfigKey<Function<Entity,Integer>> CURRENT_SIZE_OPERATOR = BasicConfigKey.builder(new TypeToken<Function<Entity,Integer>>() {})
- .name("autoscaler.currentSizeOperator")
- .defaultValue(new Function<Entity,Integer>() {
- public Integer apply(Entity entity) {
- return ((Resizable)entity).getCurrentSize();
- }})
- .build();
-
- @SuppressWarnings("serial")
- @SetFromFlag("poolHotSensor")
- public static final ConfigKey<BasicNotificationSensor<? extends Map>> POOL_HOT_SENSOR = BasicConfigKey.builder(new TypeToken<BasicNotificationSensor<? extends Map>>() {})
- .name("autoscaler.poolHotSensor")
- .defaultValue(DEFAULT_POOL_HOT_SENSOR)
- .build();
-
- @SuppressWarnings("serial")
- @SetFromFlag("poolColdSensor")
- public static final ConfigKey<BasicNotificationSensor<? extends Map>> POOL_COLD_SENSOR = BasicConfigKey.builder(new TypeToken<BasicNotificationSensor<? extends Map>>() {})
- .name("autoscaler.poolColdSensor")
- .defaultValue(DEFAULT_POOL_COLD_SENSOR)
- .build();
-
- @SuppressWarnings("serial")
- @SetFromFlag("poolOkSensor")
- public static final ConfigKey<BasicNotificationSensor<? extends Map>> POOL_OK_SENSOR = BasicConfigKey.builder(new TypeToken<BasicNotificationSensor<? extends Map>>() {})
- .name("autoscaler.poolOkSensor")
- .defaultValue(DEFAULT_POOL_OK_SENSOR)
- .build();
-
- @SuppressWarnings("serial")
- @SetFromFlag("maxSizeReachedSensor")
- public static final ConfigKey<BasicNotificationSensor<? super MaxPoolSizeReachedEvent>> MAX_SIZE_REACHED_SENSOR = BasicConfigKey.builder(new TypeToken<BasicNotificationSensor<? super MaxPoolSizeReachedEvent>>() {})
- .name("autoscaler.maxSizeReachedSensor")
- .description("Sensor for which a notification will be emitted (on the associated entity) when " +
- "we consistently wanted to resize the pool above the max allowed size, for " +
- "maxReachedNotificationDelay milliseconds")
- .build();
-
- @SetFromFlag("maxReachedNotificationDelay")
- public static final ConfigKey<Duration> MAX_REACHED_NOTIFICATION_DELAY = BasicConfigKey.builder(Duration.class)
- .name("autoscaler.maxReachedNotificationDelay")
- .description("Time that we consistently wanted to go above the maxPoolSize for, after which the " +
- "maxSizeReachedSensor (if any) will be emitted")
- .defaultValue(Duration.ZERO)
- .build();
-
- private Entity poolEntity;
-
- private final AtomicBoolean executorQueued = new AtomicBoolean(false);
- private volatile long executorTime = 0;
- private volatile ScheduledExecutorService executor;
-
- private SizeHistory recentUnboundedResizes;
-
- private SizeHistory recentDesiredResizes;
-
- private long maxReachedLastNotifiedTime;
-
- private final SensorEventListener<Map> utilizationEventHandler = new SensorEventListener<Map>() {
- public void onEvent(SensorEvent<Map> event) {
- Map<String, ?> properties = (Map<String, ?>) event.getValue();
- Sensor<?> sensor = event.getSensor();
-
- if (sensor.equals(getPoolColdSensor())) {
- onPoolCold(properties);
- } else if (sensor.equals(getPoolHotSensor())) {
- onPoolHot(properties);
- } else if (sensor.equals(getPoolOkSensor())) {
- onPoolOk(properties);
- } else {
- throw new IllegalStateException("Unexpected sensor type: "+sensor+"; event="+event);
- }
- }
- };
-
- private final SensorEventListener<Number> metricEventHandler = new SensorEventListener<Number>() {
- public void onEvent(SensorEvent<Number> event) {
- assert event.getSensor().equals(getMetric());
- onMetricChanged(event.getValue());
- }
- };
-
- public AutoScalerPolicy() {
- this(MutableMap.<String,Object>of());
- }
-
- public AutoScalerPolicy(Map<String,?> props) {
- super(props);
- }
-
- @Override
- public void init() {
- doInit();
- }
-
- @Override
- public void rebind() {
- doInit();
- }
-
- protected void doInit() {
- long maxReachedNotificationDelay = getMaxReachedNotificationDelay().toMilliseconds();
- recentUnboundedResizes = new SizeHistory(maxReachedNotificationDelay);
-
- long maxResizeStabilizationDelay = Math.max(getResizeUpStabilizationDelay().toMilliseconds(), getResizeDownStabilizationDelay().toMilliseconds());
- recentDesiredResizes = new SizeHistory(maxResizeStabilizationDelay);
-
- // TODO Should re-use the execution manager's thread pool, somehow
- executor = Executors.newSingleThreadScheduledExecutor(newThreadFactory());
- }
-
- public void setMetricLowerBound(Number val) {
- if (LOG.isInfoEnabled()) LOG.info("{} changing metricLowerBound from {} to {}", new Object[] {this, getMetricLowerBound(), val});
- config().set(METRIC_LOWER_BOUND, checkNotNull(val));
- }
-
- public void setMetricUpperBound(Number val) {
- if (LOG.isInfoEnabled()) LOG.info("{} changing metricUpperBound from {} to {}", new Object[] {this, getMetricUpperBound(), val});
- config().set(METRIC_UPPER_BOUND, checkNotNull(val));
- }
-
- private <T> void setOrDefault(ConfigKey<T> key, T val) {
- if (val==null) val = key.getDefaultValue();
- config().set(key, val);
- }
- public int getResizeUpIterationIncrement() { return getConfig(RESIZE_UP_ITERATION_INCREMENT); }
- public void setResizeUpIterationIncrement(Integer val) {
- if (LOG.isInfoEnabled()) LOG.info("{} changing resizeUpIterationIncrement from {} to {}", new Object[] {this, getResizeUpIterationIncrement(), val});
- setOrDefault(RESIZE_UP_ITERATION_INCREMENT, val);
- }
- public int getResizeDownIterationIncrement() { return getConfig(RESIZE_DOWN_ITERATION_INCREMENT); }
- public void setResizeDownIterationIncrement(Integer val) {
- if (LOG.isInfoEnabled()) LOG.info("{} changing resizeDownIterationIncrement from {} to {}", new Object[] {this, getResizeDownIterationIncrement(), val});
- setOrDefault(RESIZE_DOWN_ITERATION_INCREMENT, val);
- }
- public int getResizeUpIterationMax() { return getConfig(RESIZE_UP_ITERATION_MAX); }
- public void setResizeUpIterationMax(Integer val) {
- if (LOG.isInfoEnabled()) LOG.info("{} changing resizeUpIterationMax from {} to {}", new Object[] {this, getResizeUpIterationMax(), val});
- setOrDefault(RESIZE_UP_ITERATION_MAX, val);
- }
- public int getResizeDownIterationMax() { return getConfig(RESIZE_DOWN_ITERATION_MAX); }
- public void setResizeDownIterationMax(Integer val) {
- if (LOG.isInfoEnabled()) LOG.info("{} changing resizeDownIterationMax from {} to {}", new Object[] {this, getResizeDownIterationMax(), val});
- setOrDefault(RESIZE_DOWN_ITERATION_MAX, val);
- }
-
- public void setMinPeriodBetweenExecs(Duration val) {
- if (LOG.isInfoEnabled()) LOG.info("{} changing minPeriodBetweenExecs from {} to {}", new Object[] {this, getMinPeriodBetweenExecs(), val});
- config().set(MIN_PERIOD_BETWEEN_EXECS, val);
- }
-
- public void setResizeUpStabilizationDelay(Duration val) {
- if (LOG.isInfoEnabled()) LOG.info("{} changing resizeUpStabilizationDelay from {} to {}", new Object[] {this, getResizeUpStabilizationDelay(), val});
- config().set(RESIZE_UP_STABILIZATION_DELAY, val);
- }
-
- public void setResizeDownStabilizationDelay(Duration val) {
- if (LOG.isInfoEnabled()) LOG.info("{} changing resizeDownStabilizationDelay from {} to {}", new Object[] {this, getResizeDownStabilizationDelay(), val});
- config().set(RESIZE_DOWN_STABILIZATION_DELAY, val);
- }
-
- public void setMinPoolSize(int val) {
- if (LOG.isInfoEnabled()) LOG.info("{} changing minPoolSize from {} to {}", new Object[] {this, getMinPoolSize(), val});
- config().set(MIN_POOL_SIZE, val);
- }
-
- public void setMaxPoolSize(int val) {
- if (LOG.isInfoEnabled()) LOG.info("{} changing maxPoolSize from {} to {}", new Object[] {this, getMaxPoolSize(), val});
- config().set(MAX_POOL_SIZE, val);
- }
-
- private AttributeSensor<? extends Number> getMetric() {
- return getConfig(METRIC);
- }
-
- private Entity getEntityWithMetric() {
- return getConfig(ENTITY_WITH_METRIC);
- }
-
- private Number getMetricLowerBound() {
- return getConfig(METRIC_LOWER_BOUND);
- }
-
- private Number getMetricUpperBound() {
- return getConfig(METRIC_UPPER_BOUND);
- }
-
- private Duration getMinPeriodBetweenExecs() {
- return getConfig(MIN_PERIOD_BETWEEN_EXECS);
- }
-
- private Duration getResizeUpStabilizationDelay() {
- return getConfig(RESIZE_UP_STABILIZATION_DELAY);
- }
-
- private Duration getResizeDownStabilizationDelay() {
- return getConfig(RESIZE_DOWN_STABILIZATION_DELAY);
- }
-
- private int getMinPoolSize() {
- return getConfig(MIN_POOL_SIZE);
- }
-
- private int getMaxPoolSize() {
- return getConfig(MAX_POOL_SIZE);
- }
-
- private ResizeOperator getResizeOperator() {
- return getConfig(RESIZE_OPERATOR);
- }
-
- private Function<Entity,Integer> getCurrentSizeOperator() {
- return getConfig(CURRENT_SIZE_OPERATOR);
- }
-
- private BasicNotificationSensor<? extends Map> getPoolHotSensor() {
- return getConfig(POOL_HOT_SENSOR);
- }
-
- private BasicNotificationSensor<? extends Map> getPoolColdSensor() {
- return getConfig(POOL_COLD_SENSOR);
- }
-
- private BasicNotificationSensor<? extends Map> getPoolOkSensor() {
- return getConfig(POOL_OK_SENSOR);
- }
-
- private BasicNotificationSensor<? super MaxPoolSizeReachedEvent> getMaxSizeReachedSensor() {
- return getConfig(MAX_SIZE_REACHED_SENSOR);
- }
-
- private Duration getMaxReachedNotificationDelay() {
- return getConfig(MAX_REACHED_NOTIFICATION_DELAY);
- }
-
- @Override
- protected <T> void doReconfigureConfig(ConfigKey<T> key, T val) {
- if (key.equals(RESIZE_UP_STABILIZATION_DELAY)) {
- Duration maxResizeStabilizationDelay = Duration.max((Duration)val, getResizeDownStabilizationDelay());
- recentDesiredResizes.setWindowSize(maxResizeStabilizationDelay);
- } else if (key.equals(RESIZE_DOWN_STABILIZATION_DELAY)) {
- Duration maxResizeStabilizationDelay = Duration.max((Duration)val, getResizeUpStabilizationDelay());
- recentDesiredResizes.setWindowSize(maxResizeStabilizationDelay);
- } else if (key.equals(METRIC_LOWER_BOUND)) {
- // TODO If recorded what last metric value was then we could recalculate immediately
- // Rely on next metric-change to trigger recalculation;
- // and same for those below...
- } else if (key.equals(METRIC_UPPER_BOUND)) {
- // see above
- } else if (key.equals(RESIZE_UP_ITERATION_INCREMENT) || key.equals(RESIZE_UP_ITERATION_MAX) || key.equals(RESIZE_DOWN_ITERATION_INCREMENT) || key.equals(RESIZE_DOWN_ITERATION_MAX)) {
- // no special actions needed
- } else if (key.equals(MIN_POOL_SIZE)) {
- int newMin = (Integer) val;
- if (newMin > getConfig(MAX_POOL_SIZE)) {
- throw new IllegalArgumentException("Min pool size "+val+" must not be greater than max pool size "+getConfig(MAX_POOL_SIZE));
- }
- onPoolSizeLimitsChanged(newMin, getConfig(MAX_POOL_SIZE));
- } else if (key.equals(MAX_POOL_SIZE)) {
- int newMax = (Integer) val;
- if (newMax < getConfig(MIN_POOL_SIZE)) {
- throw new IllegalArgumentException("Min pool size "+val+" must not be greater than max pool size "+getConfig(MAX_POOL_SIZE));
- }
- onPoolSizeLimitsChanged(getConfig(MIN_POOL_SIZE), newMax);
- } else {
- throw new UnsupportedOperationException("reconfiguring "+key+" unsupported for "+this);
- }
- }
-
- @Override
- public void suspend() {
- super.suspend();
- // TODO unsubscribe from everything? And resubscribe on resume?
- if (executor != null) executor.shutdownNow();
- }
-
- @Override
- public void resume() {
- super.resume();
- executor = Executors.newSingleThreadScheduledExecutor(newThreadFactory());
- }
-
- @Override
- public void setEntity(EntityLocal entity) {
- if (!config().getRaw(RESIZE_OPERATOR).isPresentAndNonNull()) {
- Preconditions.checkArgument(entity instanceof Resizable, "Provided entity "+entity+" must be an instance of Resizable, because no custom-resizer operator supplied");
- }
- super.setEntity(entity);
- this.poolEntity = entity;
-
- if (getMetric() != null) {
- Entity entityToSubscribeTo = (getEntityWithMetric() != null) ? getEntityWithMetric() : entity;
- subscribe(entityToSubscribeTo, getMetric(), metricEventHandler);
- }
- subscribe(poolEntity, getPoolColdSensor(), utilizationEventHandler);
- subscribe(poolEntity, getPoolHotSensor(), utilizationEventHandler);
- subscribe(poolEntity, getPoolOkSensor(), utilizationEventHandler);
- }
-
- private ThreadFactory newThreadFactory() {
- return new ThreadFactoryBuilder()
- .setNameFormat("brooklyn-autoscalerpolicy-%d")
- .build();
- }
-
- /**
- * Forces an immediate resize (without waiting for stabilization etc) if the current size is
- * not within the min and max limits. We schedule this so that all resize operations are done
- * by the same thread.
- */
- private void onPoolSizeLimitsChanged(final int min, final int max) {
- if (LOG.isTraceEnabled()) LOG.trace("{} checking pool size on limits changed for {} (between {} and {})", new Object[] {this, poolEntity, min, max});
-
- if (isRunning() && isEntityUp()) {
- executor.submit(new Runnable() {
- @Override public void run() {
- try {
- int currentSize = getCurrentSizeOperator().apply(entity);
- int desiredSize = Math.min(max, Math.max(min, currentSize));
-
- if (currentSize != desiredSize) {
- if (LOG.isInfoEnabled()) LOG.info("{} resizing pool {} immediateley from {} to {} (due to new pool size limits)", new Object[] {this, poolEntity, currentSize, desiredSize});
- getResizeOperator().resize(poolEntity, desiredSize);
- }
-
- } catch (Exception e) {
- if (isRunning()) {
- LOG.error("Error resizing: "+e, e);
- } else {
- if (LOG.isDebugEnabled()) LOG.debug("Error resizing, but no longer running: "+e, e);
- }
- } catch (Throwable t) {
- LOG.error("Error resizing: "+t, t);
- throw Throwables.propagate(t);
- }
- }});
- }
- }
-
- private enum ScalingType { HOT, COLD }
- private static class ScalingData {
- ScalingType scalingMode;
- int currentSize;
- double currentMetricValue;
- Double metricUpperBound;
- Double metricLowerBound;
-
- public double getCurrentTotalActivity() {
- return currentMetricValue * currentSize;
- }
-
- public boolean isHot() {
- return ((scalingMode==null || scalingMode==ScalingType.HOT) && isValid(metricUpperBound) && currentMetricValue > metricUpperBound);
- }
- public boolean isCold() {
- return ((scalingMode==null || scalingMode==ScalingType.COLD) && isValid(metricLowerBound) && currentMetricValue < metricLowerBound);
- }
- private boolean isValid(Double bound) {
- return (bound!=null && bound>0);
- }
- }
-
- private void onMetricChanged(Number val) {
- if (LOG.isTraceEnabled()) LOG.trace("{} recording pool-metric for {}: {}", new Object[] {this, poolEntity, val});
-
- if (val==null) {
- // occurs e.g. if using an aggregating enricher who returns null when empty, the sensor has gone away
- if (LOG.isTraceEnabled()) LOG.trace("{} not resizing pool {}, inbound metric is null", new Object[] {this, poolEntity});
- return;
- }
-
- ScalingData data = new ScalingData();
- data.currentMetricValue = val.doubleValue();
- data.currentSize = getCurrentSizeOperator().apply(entity);
- data.metricUpperBound = getMetricUpperBound().doubleValue();
- data.metricLowerBound = getMetricLowerBound().doubleValue();
-
- analyze(data, "pool");
- }
-
- private void onPoolCold(Map<String, ?> properties) {
- if (LOG.isTraceEnabled()) LOG.trace("{} recording pool-cold for {}: {}", new Object[] {this, poolEntity, properties});
- analyzeOnHotOrColdSensor(ScalingType.COLD, "cold pool", properties);
- }
-
- private void onPoolHot(Map<String, ?> properties) {
- if (LOG.isTraceEnabled()) LOG.trace("{} recording pool-hot for {}: {}", new Object[] {this, poolEntity, properties});
- analyzeOnHotOrColdSensor(ScalingType.HOT, "hot pool", properties);
- }
-
- private void analyzeOnHotOrColdSensor(ScalingType scalingMode, String description, Map<String, ?> properties) {
- ScalingData data = new ScalingData();
- data.scalingMode = scalingMode;
- data.currentMetricValue = (Double) properties.get(POOL_CURRENT_WORKRATE_KEY);
- data.currentSize = (Integer) properties.get(POOL_CURRENT_SIZE_KEY);
- data.metricUpperBound = (Double) properties.get(POOL_HIGH_THRESHOLD_KEY);
- data.metricLowerBound = (Double) properties.get(POOL_LOW_THRESHOLD_KEY);
-
- analyze(data, description);
- }
-
- private void analyze(ScalingData data, String description) {
- int desiredSizeUnconstrained;
-
- /* We always scale out (modulo stabilization delay) if:
- * currentTotalActivity > currentSize*metricUpperBound
- * With newDesiredSize the smallest n such that n*metricUpperBound >= currentTotalActivity
- * ie n >= currentTotalActiviy/metricUpperBound, thus n := Math.ceil(currentTotalActivity/metricUpperBound)
- *
- * Else consider scale back if:
- * currentTotalActivity < currentSize*metricLowerBound
- * With newDesiredSize normally the largest n such that:
- * n*metricLowerBound <= currentTotalActivity
- * BUT with an absolute requirement which trumps the above computation
- * that the newDesiredSize doesn't cause immediate scale out:
- * n*metricUpperBound >= currentTotalActivity
- * thus n := Math.max ( floor(currentTotalActiviy/metricLowerBound), ceil(currentTotal/metricUpperBound) )
- */
- if (data.isHot()) {
- // scale out
- desiredSizeUnconstrained = (int)Math.ceil(data.getCurrentTotalActivity() / data.metricUpperBound);
- data.scalingMode = ScalingType.HOT;
-
- } else if (data.isCold()) {
- // scale back
- desiredSizeUnconstrained = (int)Math.floor(data.getCurrentTotalActivity() / data.metricLowerBound);
- data.scalingMode = ScalingType.COLD;
-
- } else {
- if (LOG.isTraceEnabled()) LOG.trace("{} not resizing pool {} from {} ({} within range {}..{})", new Object[] {this, poolEntity, data.currentSize, data.currentMetricValue, data.metricLowerBound, data.metricUpperBound});
- abortResize(data.currentSize);
- return; // within the healthy range; no-op
- }
-
- if (LOG.isTraceEnabled()) LOG.debug("{} detected unconstrained desired size {}", new Object[] {this, desiredSizeUnconstrained});
- int desiredSize = applyMinMaxConstraints(desiredSizeUnconstrained);
-
- if ((data.scalingMode==ScalingType.COLD) && (desiredSize < data.currentSize)) {
-
- int delta = data.currentSize - desiredSize;
- int scaleIncrement = getResizeDownIterationIncrement();
- int scaleMax = getResizeDownIterationMax();
- if (delta>scaleMax) {
- delta=scaleMax;
- } else if (delta % scaleIncrement != 0) {
- // keep scaling to the increment
- delta += scaleIncrement - (delta % scaleIncrement);
- }
- desiredSize = data.currentSize - delta;
-
- if (data.metricUpperBound!=null) {
- // if upper bound supplied, check that this desired scale-back size
- // is not going to cause scale-out on next run; i.e. anti-thrashing
- while (desiredSize < data.currentSize && data.getCurrentTotalActivity() > data.metricUpperBound * desiredSize) {
- if (LOG.isTraceEnabled()) LOG.trace("{} when resizing back pool {} from {}, tweaking from {} to prevent thrashing", new Object[] {this, poolEntity, data.currentSize, desiredSize });
- desiredSize += scaleIncrement;
- }
- }
- desiredSize = applyMinMaxConstraints(desiredSize);
- if (desiredSize >= data.currentSize) data.scalingMode = null;
-
- } else if ((data.scalingMode==ScalingType.HOT) && (desiredSize > data.currentSize)) {
-
- int delta = desiredSize - data.currentSize;
- int scaleIncrement = getResizeUpIterationIncrement();
- int scaleMax = getResizeUpIterationMax();
- if (delta>scaleMax) {
- delta=scaleMax;
- } else if (delta % scaleIncrement != 0) {
- // keep scaling to the increment
- delta += scaleIncrement - (delta % scaleIncrement);
- }
- desiredSize = data.currentSize + delta;
- desiredSize = applyMinMaxConstraints(desiredSize);
- if (desiredSize <= data.currentSize) data.scalingMode = null;
-
- } else {
- data.scalingMode = null;
- }
-
- if (data.scalingMode!=null) {
- if (LOG.isDebugEnabled()) LOG.debug("{} provisionally resizing {} {} from {} to {} ({} < {}; ideal size {})", new Object[] {this, description, poolEntity, data.currentSize, desiredSize, data.currentMetricValue, data.metricLowerBound, desiredSizeUnconstrained});
- scheduleResize(desiredSize);
- } else {
- if (LOG.isTraceEnabled()) LOG.trace("{} not resizing {} {} from {} to {}, {} out of healthy range {}..{} but unconstrained size {} blocked by bounds/check", new Object[] {this, description, poolEntity, data.currentSize, desiredSize, data.currentMetricValue, data.metricLowerBound, data.metricUpperBound, desiredSizeUnconstrained});
- abortResize(data.currentSize);
- // but add to the unbounded record for future consideration
- }
-
- onNewUnboundedPoolSize(desiredSizeUnconstrained);
- }
-
- private int applyMinMaxConstraints(int desiredSize) {
- desiredSize = Math.max(getMinPoolSize(), desiredSize);
- desiredSize = Math.min(getMaxPoolSize(), desiredSize);
- return desiredSize;
- }
-
- private void onPoolOk(Map<String, ?> properties) {
- if (LOG.isTraceEnabled()) LOG.trace("{} recording pool-ok for {}: {}", new Object[] {this, poolEntity, properties});
-
- int poolCurrentSize = (Integer) properties.get(POOL_CURRENT_SIZE_KEY);
-
- if (LOG.isTraceEnabled()) LOG.trace("{} not resizing ok pool {} from {}", new Object[] {this, poolEntity, poolCurrentSize});
- abortResize(poolCurrentSize);
- }
-
- /**
- * Schedules a resize, if there is not already a resize operation queued up. When that resize
- * executes, it will resize to whatever the latest value is to be (rather than what it was told
- * to do at the point the job was queued).
- */
- private void scheduleResize(final int newSize) {
- recentDesiredResizes.add(newSize);
-
- scheduleResize();
- }
-
- /**
- * If a listener is registered to be notified of the max-pool-size cap being reached, then record
- * what our unbounded size would be and schedule a check to see if this unbounded size is sustained.
- *
- * Piggy-backs off the existing scheduleResize execution, which now also checks if the listener
- * needs to be called.
- */
- private void onNewUnboundedPoolSize(final int val) {
- if (getMaxSizeReachedSensor() != null) {
- recentUnboundedResizes.add(val);
- scheduleResize();
- }
- }
-
- private void abortResize(final int currentSize) {
- recentDesiredResizes.add(currentSize);
- recentUnboundedResizes.add(currentSize);
- }
-
- private boolean isEntityUp() {
- if (entity == null) {
- return false;
- } else if (entity.getEntityType().getSensors().contains(Startable.SERVICE_UP)) {
- return Boolean.TRUE.equals(entity.getAttribute(Startable.SERVICE_UP));
- } else {
- return true;
- }
- }
-
- private void scheduleResize() {
- // TODO Make scale-out calls concurrent, rather than waiting for first resize to entirely
- // finish. On ec2 for example, this can cause us to grow very slowly if first request is for
- // just one new VM to be provisioned.
-
- if (isRunning() && isEntityUp() && executorQueued.compareAndSet(false, true)) {
- long now = System.currentTimeMillis();
- long delay = Math.max(0, (executorTime + getMinPeriodBetweenExecs().toMilliseconds()) - now);
- if (LOG.isTraceEnabled()) LOG.trace("{} scheduling resize in {}ms", this, delay);
-
- executor.schedule(new Runnable() {
- @Override public void run() {
- try {
- executorTime = System.currentTimeMillis();
- executorQueued.set(false);
-
- resizeNow();
- notifyMaxReachedIfRequiredNow();
-
- } catch (Exception e) {
- if (isRunning()) {
- LOG.error("Error resizing: "+e, e);
- } else {
- if (LOG.isDebugEnabled()) LOG.debug("Error resizing, but no longer running: "+e, e);
- }
- } catch (Throwable t) {
- LOG.error("Error resizing: "+t, t);
- throw Throwables.propagate(t);
- }
- }},
- delay,
- TimeUnit.MILLISECONDS);
- }
- }
-
- /**
- * Looks at the values for "unbounded pool size" (i.e. if we ignore caps of minSize and maxSize) to report what
- * those values have been within a time window. The time window used is the "maxReachedNotificationDelay",
- * which determines how many milliseconds after being consistently above the max-size will it take before
- * we emit the sensor event (if any).
- */
- private void notifyMaxReachedIfRequiredNow() {
- BasicNotificationSensor<? super MaxPoolSizeReachedEvent> maxSizeReachedSensor = getMaxSizeReachedSensor();
- if (maxSizeReachedSensor == null) {
- return;
- }
-
- WindowSummary valsSummary = recentUnboundedResizes.summarizeWindow(getMaxReachedNotificationDelay());
- long timeWindowSize = getMaxReachedNotificationDelay().toMilliseconds();
- long currentPoolSize = getCurrentSizeOperator().apply(poolEntity);
- int maxAllowedPoolSize = getMaxPoolSize();
- long unboundedSustainedMaxPoolSize = valsSummary.min; // The sustained maximum (i.e. the smallest it's dropped down to)
- long unboundedCurrentPoolSize = valsSummary.latest;
-
- if (maxReachedLastNotifiedTime > 0) {
- // already notified the listener; don't do it again
- // TODO Could have max period for notifications, or a step increment to warn when exceeded by ever bigger amounts
-
- } else if (unboundedSustainedMaxPoolSize > maxAllowedPoolSize) {
- // We have consistently wanted to be bigger than the max allowed; tell the listener
- if (LOG.isDebugEnabled()) LOG.debug("{} notifying listener of max pool size reached; current {}, max {}, unbounded current {}, unbounded max {}",
- new Object[] {this, currentPoolSize, maxAllowedPoolSize, unboundedCurrentPoolSize, unboundedSustainedMaxPoolSize});
-
- maxReachedLastNotifiedTime = System.currentTimeMillis();
- MaxPoolSizeReachedEvent event = MaxPoolSizeReachedEvent.builder()
- .currentPoolSize(currentPoolSize)
- .maxAllowed(maxAllowedPoolSize)
- .currentUnbounded(unboundedCurrentPoolSize)
- .maxUnbounded(unboundedSustainedMaxPoolSize)
- .timeWindow(timeWindowSize)
- .build();
- entity.emit(maxSizeReachedSensor, event);
-
- } else if (valsSummary.max > maxAllowedPoolSize) {
- // We temporarily wanted to be bigger than the max allowed; check back later to see if consistent
- // TODO Could check if there has been anything bigger than "min" since min happened (would be more efficient)
- if (LOG.isTraceEnabled()) LOG.trace("{} re-scheduling max-reached check for {}, as unbounded size not stable (min {}, max {}, latest {})",
- new Object[] {this, poolEntity, valsSummary.min, valsSummary.max, valsSummary.latest});
- scheduleResize();
-
- } else {
- // nothing to write home about; continually below maxAllowed
- }
- }
-
- private void resizeNow() {
- long currentPoolSize = getCurrentSizeOperator().apply(poolEntity);
- CalculatedDesiredPoolSize calculatedDesiredPoolSize = calculateDesiredPoolSize(currentPoolSize);
- final long desiredPoolSize = calculatedDesiredPoolSize.size;
- boolean stable = calculatedDesiredPoolSize.stable;
-
- if (!stable) {
- // the desired size fluctuations are not stable; ensure we check again later (due to time-window)
- // even if no additional events have been received
- // (note we continue now with as "good" a resize as we can given the instability)
- if (LOG.isTraceEnabled()) LOG.trace("{} re-scheduling resize check for {}, as desired size not stable (current {}, desired {}); continuing with resize...",
- new Object[] {this, poolEntity, currentPoolSize, desiredPoolSize});
- scheduleResize();
- }
- if (currentPoolSize == desiredPoolSize) {
- if (LOG.isTraceEnabled()) LOG.trace("{} not resizing pool {} from {} to {}",
- new Object[] {this, poolEntity, currentPoolSize, desiredPoolSize});
- return;
- }
-
- if (LOG.isDebugEnabled()) LOG.debug("{} requesting resize to {}; current {}, min {}, max {}",
- new Object[] {this, desiredPoolSize, currentPoolSize, getMinPoolSize(), getMaxPoolSize()});
-
- Entities.submit(entity, Tasks.<Void>builder().name("Auto-scaler")
- .description("Auto-scaler recommending resize from "+currentPoolSize+" to "+desiredPoolSize)
- .tag(BrooklynTaskTags.NON_TRANSIENT_TASK_TAG)
- .body(new Callable<Void>() {
- @Override
- public Void call() throws Exception {
- // TODO Should we use int throughout, rather than casting here?
- getResizeOperator().resize(poolEntity, (int) desiredPoolSize);
- return null;
- }
- }).build())
- .blockUntilEnded();
- }
-
- /**
- * Complicated logic for stabilization-delay...
- * Only grow if we have consistently been asked to grow for the resizeUpStabilizationDelay period;
- * Only shrink if we have consistently been asked to shrink for the resizeDownStabilizationDelay period.
- *
- * @return tuple of desired pool size, and whether this is "stable" (i.e. if we receive no more events
- * will this continue to be the desired pool size)
- */
- private CalculatedDesiredPoolSize calculateDesiredPoolSize(long currentPoolSize) {
- long now = System.currentTimeMillis();
- WindowSummary downsizeSummary = recentDesiredResizes.summarizeWindow(getResizeDownStabilizationDelay());
- WindowSummary upsizeSummary = recentDesiredResizes.summarizeWindow(getResizeUpStabilizationDelay());
-
- // this is the _sustained_ growth value; the smallest size that has been requested in the "stable-for-growing" period
- long maxDesiredPoolSize = upsizeSummary.min;
- boolean stableForGrowing = upsizeSummary.stableForGrowth;
-
- // this is the _sustained_ shrink value; largest size that has been requested in the "stable-for-shrinking" period:
- long minDesiredPoolSize = downsizeSummary.max;
- boolean stableForShrinking = downsizeSummary.stableForShrinking;
-
- // (it is a logical consequence of the above that minDesired >= maxDesired -- this is correct, if confusing:
- // think of minDesired as the minimum size we are allowed to resize to, and similarly for maxDesired;
- // if min > max we can scale to max if current < max, or scale to min if current > min)
-
- long desiredPoolSize;
-
- boolean stable;
-
- if (currentPoolSize < maxDesiredPoolSize) {
- // we have valid request to grow
- // (we'll never have a valid request to grow and a valid to shrink simultaneously, btw)
- desiredPoolSize = maxDesiredPoolSize;
- stable = stableForGrowing;
- } else if (currentPoolSize > minDesiredPoolSize) {
- // we have valid request to shrink
- desiredPoolSize = minDesiredPoolSize;
- stable = stableForShrinking;
- } else {
- desiredPoolSize = currentPoolSize;
- stable = stableForGrowing && stableForShrinking;
- }
-
- if (LOG.isTraceEnabled()) LOG.trace("{} calculated desired pool size: from {} to {}; minDesired {}, maxDesired {}; " +
- "stable {}; now {}; downsizeHistory {}; upsizeHistory {}",
- new Object[] {this, currentPoolSize, desiredPoolSize, minDesiredPoolSize, maxDesiredPoolSize, stable, now, downsizeSummary, upsizeSummary});
-
- return new CalculatedDesiredPoolSize(desiredPoolSize, stable);
- }
-
- private static class CalculatedDesiredPoolSize {
- final long size;
- final boolean stable;
-
- CalculatedDesiredPoolSize(long size, boolean stable) {
- this.size = size;
- this.stable = stable;
- }
- }
-
- @Override
- public String toString() {
- return getClass().getSimpleName() + (groovyTruth(name) ? "("+name+")" : "");
- }
-}
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/d30ff597/policy/src/main/java/brooklyn/policy/autoscaling/MaxPoolSizeReachedEvent.java
----------------------------------------------------------------------
diff --git a/policy/src/main/java/brooklyn/policy/autoscaling/MaxPoolSizeReachedEvent.java b/policy/src/main/java/brooklyn/policy/autoscaling/MaxPoolSizeReachedEvent.java
deleted file mode 100644
index c0a6c7d..0000000
--- a/policy/src/main/java/brooklyn/policy/autoscaling/MaxPoolSizeReachedEvent.java
+++ /dev/null
@@ -1,103 +0,0 @@
-/*
- * 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 brooklyn.policy.autoscaling;
-
-import java.io.Serializable;
-
-import com.google.common.base.Objects;
-
-public class MaxPoolSizeReachedEvent implements Serializable {
- private static final long serialVersionUID = 1602627701360505190L;
-
- public static Builder builder() {
- return new Builder();
- }
-
- public static class Builder {
- protected long maxAllowed;
- protected long currentPoolSize;
- protected long currentUnbounded;
- protected long maxUnbounded;
- protected long timeWindow;
-
- public Builder maxAllowed(long val) {
- this.maxAllowed = val; return this;
- }
-
- public Builder currentPoolSize(long val) {
- this.currentPoolSize = val; return this;
- }
-
- public Builder currentUnbounded(long val) {
- this.currentUnbounded = val; return this;
- }
-
- public Builder maxUnbounded(long val) {
- this.maxUnbounded = val; return this;
- }
-
- public Builder timeWindow(long val) {
- this.timeWindow = val; return this;
- }
- public MaxPoolSizeReachedEvent build() {
- return new MaxPoolSizeReachedEvent(this);
- }
- }
-
- private final long maxAllowed;
- private final long currentPoolSize;
- private final long currentUnbounded;
- private final long maxUnbounded;
- private final long timeWindow;
-
- protected MaxPoolSizeReachedEvent(Builder builder) {
- maxAllowed = builder.maxAllowed;
- currentPoolSize = builder.currentPoolSize;
- currentUnbounded = builder.currentUnbounded;
- maxUnbounded = builder.maxUnbounded;
- timeWindow = builder.timeWindow;
- }
-
- public long getMaxAllowed() {
- return maxAllowed;
- }
-
- public long getCurrentPoolSize() {
- return currentPoolSize;
- }
-
- public long getCurrentUnbounded() {
- return currentUnbounded;
- }
-
- public long getMaxUnbounded() {
- return maxUnbounded;
- }
-
- public long getTimeWindow() {
- return timeWindow;
- }
-
- @Override
- public String toString() {
- return Objects.toStringHelper(this).add("maxAllowed", maxAllowed).add("currentPoolSize", currentPoolSize)
- .add("currentUnbounded", currentUnbounded).add("maxUnbounded", maxUnbounded)
- .add("timeWindow", timeWindow).toString();
- }
-}
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/d30ff597/policy/src/main/java/brooklyn/policy/autoscaling/ResizeOperator.java
----------------------------------------------------------------------
diff --git a/policy/src/main/java/brooklyn/policy/autoscaling/ResizeOperator.java b/policy/src/main/java/brooklyn/policy/autoscaling/ResizeOperator.java
deleted file mode 100644
index 80e1293..0000000
--- a/policy/src/main/java/brooklyn/policy/autoscaling/ResizeOperator.java
+++ /dev/null
@@ -1,31 +0,0 @@
-/*
- * 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 brooklyn.policy.autoscaling;
-
-import org.apache.brooklyn.api.entity.Entity;
-
-public interface ResizeOperator {
-
- /**
- * Resizes the given entity to the desired size, if possible.
- *
- * @return the new size of the entity
- */
- public Integer resize(Entity entity, Integer desiredSize);
-}