Closes #146
Unit test AbstractNonProvisionedController
diff --git a/software/webapp/src/main/java/org/apache/brooklyn/entity/proxy/AbstractNonProvisionedControllerImpl.java b/software/webapp/src/main/java/org/apache/brooklyn/entity/proxy/AbstractNonProvisionedControllerImpl.java
index 7ad7224..15cd94d 100644
--- a/software/webapp/src/main/java/org/apache/brooklyn/entity/proxy/AbstractNonProvisionedControllerImpl.java
+++ b/software/webapp/src/main/java/org/apache/brooklyn/entity/proxy/AbstractNonProvisionedControllerImpl.java
@@ -137,6 +137,9 @@
sensors().set(MAIN_URI, URI.create(inferUrl()));
sensors().set(ROOT_URL, inferUrl());
addServerPoolMemberTrackingPolicy();
+
+ isActive = true;
+ update();
}
protected void preStop() {
@@ -273,7 +276,6 @@
}
}
-
protected void onServerPoolMemberChanged(Entity member) {
synchronized (mutex) {
if (LOG.isTraceEnabled()) LOG.trace("For {}, considering membership of {} which is in locations {}",
diff --git a/software/webapp/src/test/java/org/apache/brooklyn/entity/proxy/AbstractAbstractControllerTest.java b/software/webapp/src/test/java/org/apache/brooklyn/entity/proxy/AbstractAbstractControllerTest.java
new file mode 100644
index 0000000..eba82c5
--- /dev/null
+++ b/software/webapp/src/test/java/org/apache/brooklyn/entity/proxy/AbstractAbstractControllerTest.java
@@ -0,0 +1,318 @@
+/*
+ * 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.proxy;
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertTrue;
+
+import java.net.Inet4Address;
+import java.net.URI;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.List;
+import java.util.Set;
+
+import org.apache.brooklyn.api.entity.Entity;
+import org.apache.brooklyn.api.entity.EntitySpec;
+import org.apache.brooklyn.api.location.Location;
+import org.apache.brooklyn.api.location.LocationSpec;
+import org.apache.brooklyn.api.location.MachineLocation;
+import org.apache.brooklyn.api.location.MachineProvisioningLocation;
+import org.apache.brooklyn.api.location.NoMachinesAvailableException;
+import org.apache.brooklyn.api.sensor.AttributeSensor;
+import org.apache.brooklyn.core.entity.Attributes;
+import org.apache.brooklyn.core.entity.trait.Startable;
+import org.apache.brooklyn.core.location.HasSubnetHostname;
+import org.apache.brooklyn.core.location.Machines;
+import org.apache.brooklyn.core.test.BrooklynAppUnitTestSupport;
+import org.apache.brooklyn.core.test.entity.TestEntity;
+import org.apache.brooklyn.core.test.entity.TestEntityImpl;
+import org.apache.brooklyn.entity.group.Cluster;
+import org.apache.brooklyn.entity.group.DynamicCluster;
+import org.apache.brooklyn.location.byon.FixedListMachineProvisioningLocation;
+import org.apache.brooklyn.location.ssh.SshMachineLocation;
+import org.apache.brooklyn.test.Asserts;
+import org.apache.brooklyn.util.collections.MutableMap;
+import org.apache.brooklyn.util.collections.MutableSet;
+import org.apache.brooklyn.util.core.flags.SetFromFlag;
+import org.apache.brooklyn.util.exceptions.Exceptions;
+import org.apache.brooklyn.util.guava.Maybe;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Lists;
+
+/**
+ * Sub-classes are concrete tests of {@link AbstractController} and {@link AbstractNonProvisionedController},
+ * hence the weird double-abstract name!
+ */
+public abstract class AbstractAbstractControllerTest<T extends LoadBalancer> extends BrooklynAppUnitTestSupport {
+
+ private static final Logger log = LoggerFactory.getLogger(AbstractAbstractControllerTest.class);
+
+ protected FixedListMachineProvisioningLocation<?> loc;
+ protected Cluster cluster;
+ protected T controller;
+
+ @BeforeMethod(alwaysRun = true)
+ @Override
+ public void setUp() throws Exception {
+ super.setUp();
+
+ List<SshMachineLocation> machines = new ArrayList<SshMachineLocation>();
+ for (int i = 1; i <= 10; i++) {
+ SshMachineLocation machine = mgmt.getLocationManager().createLocation(LocationSpec.create(SshMachineLocation.class)
+ .configure("address", Inet4Address.getByName("1.1.1."+i)));
+ machines.add(machine);
+ }
+ loc = mgmt.getLocationManager().createLocation(LocationSpec.create(FixedListMachineProvisioningLocation.class)
+ .configure("machines", machines));
+
+ cluster = app.addChild(EntitySpec.create(DynamicCluster.class)
+ .configure("initialSize", 0)
+ .configure("memberSpec", EntitySpec.create(TestEntity.class).impl(WebServerEntity.class)));
+
+ controller = newController();
+
+ app.start(ImmutableList.of(loc));
+ }
+
+ /**
+ * Called during {@link #setUp()}, after app and cluster are created (but before app.start is called).
+ */
+ protected abstract T newController();
+
+ protected abstract List<Collection<String>> getUpdates(T controller);
+
+ // Fixes bug where entity that wrapped an AS7 entity was never added to nginx because hostname+port
+ // was set after service_up. Now we listen to those changes and reset the nginx pool when these
+ // values change.
+ @Test
+ public void testUpdateCalledWhenChildHostnameAndPortChanges() throws Exception {
+ log.info("adding child (no effect until up)");
+ TestEntity child = cluster.addChild(EntitySpec.create(TestEntity.class));
+ cluster.addMember(child);
+
+ List<Collection<String>> u = Lists.newArrayList(getUpdates(controller));
+ assertTrue(u.isEmpty(), "expected no updates, but got "+u);
+
+ log.info("setting child service_up");
+ child.sensors().set(Startable.SERVICE_UP, true);
+ // above may trigger error logged about no hostname, but should update again with the settings below
+
+ log.info("setting mymachine:1234");
+ child.sensors().set(WebServerEntity.HOSTNAME, "mymachine");
+ child.sensors().set(Attributes.SUBNET_HOSTNAME, "mymachine");
+ child.sensors().set(WebServerEntity.HTTP_PORT, 1234);
+ assertEventuallyExplicitAddressesMatch(ImmutableList.of("mymachine:1234"));
+
+ /* a race failure has been observed, https://issues.apache.org/jira/browse/BROOKLYN-206
+ * but now (two months later) i (alex) can't see how it could happen.
+ * probably optimistic but maybe it is fixed. if not we'll need the debug logs to see what is happening.
+ * i've confirmed:
+ * * the policy is attached and active during setup, before start completes
+ * * the child is added as a member synchronously
+ * * the policy which is "subscribed to members" is in fact subscribed to everything
+ * then filtered for members, not ideal, but there shouldn't be a race in the policy getting notices
+ * * the handling of those events are both processed in order and look up the current values
+ * rather than relying on the published values; either should be sufficient to cause the addresses to change
+ * there was a sleep(100) marked "Ugly sleep to allow AbstractController to detect node having been added"
+ * from the test's addition by aled in early 2014, but can't see why that would be necessary
+ */
+
+ log.info("setting mymachine2:1234");
+ child.sensors().set(WebServerEntity.HOSTNAME, "mymachine2");
+ child.sensors().set(Attributes.SUBNET_HOSTNAME, "mymachine2");
+ assertEventuallyExplicitAddressesMatch(ImmutableList.of("mymachine2:1234"));
+
+ log.info("setting mymachine2:1235");
+ child.sensors().set(WebServerEntity.HTTP_PORT, 1235);
+ assertEventuallyExplicitAddressesMatch(ImmutableList.of("mymachine2:1235"));
+
+ log.info("clearing");
+ child.sensors().set(WebServerEntity.HOSTNAME, null);
+ child.sensors().set(Attributes.SUBNET_HOSTNAME, null);
+ assertEventuallyExplicitAddressesMatch(ImmutableList.<String>of());
+ }
+
+ @Test
+ public void testUpdateCalledWithAddressesOfNewChildren() {
+ // First child
+ cluster.resize(1);
+ Entity child = Iterables.getOnlyElement(cluster.getMembers());
+
+ List<Collection<String>> u = Lists.newArrayList(getUpdates(controller));
+ assertTrue(u.isEmpty(), "expected empty list but got "+u);
+
+ child.sensors().set(WebServerEntity.HTTP_PORT, 1234);
+ child.sensors().set(Startable.SERVICE_UP, true);
+ assertEventuallyAddressesMatchCluster();
+
+ // Second child
+ cluster.resize(2);
+ Asserts.succeedsEventually(new Runnable() {
+ @Override
+ public void run() {
+ assertEquals(cluster.getMembers().size(), 2);
+ }});
+ Entity child2 = Iterables.getOnlyElement(MutableSet.<Entity>builder().addAll(cluster.getMembers()).remove(child).build());
+
+ child2.sensors().set(WebServerEntity.HTTP_PORT, 1234);
+ child2.sensors().set(Startable.SERVICE_UP, true);
+ assertEventuallyAddressesMatchCluster();
+
+ // And remove all children; expect all addresses to go away
+ cluster.resize(0);
+ assertEventuallyAddressesMatchCluster();
+ }
+
+ @Test(groups = "Integration", invocationCount=10)
+ public void testUpdateCalledWithAddressesOfNewChildrenManyTimes() {
+ testUpdateCalledWithAddressesOfNewChildren();
+ }
+
+ @Test
+ public void testUpdateCalledWithAddressesRemovedForStoppedChildren() {
+ // Get some children, so we can remove one...
+ cluster.resize(2);
+ for (Entity it: cluster.getMembers()) {
+ it.sensors().set(WebServerEntity.HTTP_PORT, 1234);
+ it.sensors().set(Startable.SERVICE_UP, true);
+ }
+ assertEventuallyAddressesMatchCluster();
+
+ // Now remove one child
+ cluster.resize(1);
+ assertEquals(cluster.getMembers().size(), 1);
+ assertEventuallyAddressesMatchCluster();
+ }
+
+ @Test
+ public void testUpdateCalledWithAddressesRemovedForServiceDownChildrenThatHaveClearedHostnamePort() {
+ // Get some children, so we can remove one...
+ cluster.resize(2);
+ for (Entity it: cluster.getMembers()) {
+ it.sensors().set(WebServerEntity.HTTP_PORT, 1234);
+ it.sensors().set(Startable.SERVICE_UP, true);
+ }
+ assertEventuallyAddressesMatchCluster();
+
+ // Now unset host/port, and remove children
+ // Note the unsetting of hostname is done in SoftwareProcessImpl.stop(), so this is realistic
+ for (Entity it : cluster.getMembers()) {
+ it.sensors().set(WebServerEntity.HTTP_PORT, null);
+ it.sensors().set(WebServerEntity.HOSTNAME, null);
+ it.sensors().set(Startable.SERVICE_UP, false);
+ }
+ assertEventuallyAddressesMatch(ImmutableList.<Entity>of());
+ }
+
+ protected void assertEventuallyAddressesMatchCluster() {
+ assertEventuallyAddressesMatchCluster(cluster);
+ }
+
+ protected void assertEventuallyAddressesMatchCluster(Cluster cluster) {
+ assertEventuallyAddressesMatch(cluster.getMembers());
+ }
+
+ protected void assertEventuallyAddressesMatch(final Collection<Entity> expectedMembers) {
+ Asserts.succeedsEventually(new Runnable() {
+ @Override public void run() {
+ assertAddressesMatch(locationsToAddresses(1234, expectedMembers));
+ }} );
+ }
+
+ protected void assertEventuallyExplicitAddressesMatch(final Collection<String> expectedAddresses) {
+ Asserts.succeedsEventually(new Runnable() {
+ @Override public void run() {
+ assertAddressesMatch(expectedAddresses);
+ }} );
+ }
+
+ protected void assertAddressesMatch(final Collection<String> expectedAddresses) {
+ List<Collection<String>> u = Lists.newArrayList(getUpdates(controller));
+ Collection<String> last = Iterables.getLast(u, null);
+ log.debug("test "+u.size()+" updates, expecting "+expectedAddresses+"; actual "+last);
+ assertTrue(!u.isEmpty(), "no updates; expecting "+expectedAddresses);
+ assertEquals(ImmutableSet.copyOf(last), ImmutableSet.copyOf(expectedAddresses), "actual="+last+" expected="+expectedAddresses);
+ assertEquals(last.size(), expectedAddresses.size(), "actual="+last+" expected="+expectedAddresses);
+ }
+
+ protected Collection<String> locationsToAddresses(int port, Collection<Entity> entities) {
+ Set<String> result = MutableSet.of();
+ for (Entity e : entities) {
+ SshMachineLocation machine = Machines.findUniqueMachineLocation(e.getLocations(), SshMachineLocation.class).get();
+ result.add(machine.getAddress().getHostName()+":"+port);
+ }
+ return result;
+ }
+
+ public static class SshMachineLocationWithSubnetHostname extends SshMachineLocation implements HasSubnetHostname {
+ @Override public String getSubnetHostname() {
+ return getSubnetIp();
+ }
+ @Override public String getSubnetIp() {
+ Set<String> addrs = getPrivateAddresses();
+ return (addrs.isEmpty()) ? getAddress().getHostAddress() : Iterables.get(addrs, 0);
+ }
+ }
+
+ public static class WebServerEntity extends TestEntityImpl {
+ @SetFromFlag("hostname")
+ public static final AttributeSensor<String> HOSTNAME = Attributes.HOSTNAME;
+
+ @SetFromFlag("port")
+ public static final AttributeSensor<Integer> HTTP_PORT = Attributes.HTTP_PORT;
+
+ @SetFromFlag("hostAndPort")
+ public static final AttributeSensor<String> HOST_AND_PORT = Attributes.HOST_AND_PORT;
+
+ MachineProvisioningLocation<MachineLocation> provisioner;
+
+ @Override
+ public void start(Collection<? extends Location> locs) {
+ provisioner = (MachineProvisioningLocation<MachineLocation>) locs.iterator().next();
+ MachineLocation machine;
+ try {
+ machine = provisioner.obtain(MutableMap.of());
+ } catch (NoMachinesAvailableException e) {
+ throw Exceptions.propagate(e);
+ }
+ addLocations(Arrays.asList(machine));
+ sensors().set(HOSTNAME, machine.getAddress().getHostName());
+ sensors().set(Attributes.SUBNET_HOSTNAME, machine.getAddress().getHostName());
+ sensors().set(Attributes.MAIN_URI_MAPPED_SUBNET, URI.create(machine.getAddress().getHostName()));
+ sensors().set(Attributes.MAIN_URI_MAPPED_PUBLIC, URI.create("http://8.8.8.8:" + sensors().get(HTTP_PORT)));
+ }
+
+ @Override
+ public void stop() {
+ Maybe<MachineLocation> machine = Machines.findUniqueMachineLocation(getLocations(), MachineLocation.class);
+ if (provisioner != null) {
+ provisioner.release(machine.get());
+ }
+ }
+ }
+}
diff --git a/software/webapp/src/test/java/org/apache/brooklyn/entity/proxy/AbstractControllerTest.java b/software/webapp/src/test/java/org/apache/brooklyn/entity/proxy/AbstractControllerTest.java
index ea4b4d8..25627d8 100644
--- a/software/webapp/src/test/java/org/apache/brooklyn/entity/proxy/AbstractControllerTest.java
+++ b/software/webapp/src/test/java/org/apache/brooklyn/entity/proxy/AbstractControllerTest.java
@@ -18,210 +18,46 @@
*/
package org.apache.brooklyn.entity.proxy;
-import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertTrue;
import java.net.Inet4Address;
import java.net.URI;
-import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
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.EntitySpec;
import org.apache.brooklyn.api.location.Location;
import org.apache.brooklyn.api.location.LocationSpec;
-import org.apache.brooklyn.api.location.MachineLocation;
-import org.apache.brooklyn.api.location.MachineProvisioningLocation;
-import org.apache.brooklyn.api.location.NoMachinesAvailableException;
-import org.apache.brooklyn.api.sensor.AttributeSensor;
import org.apache.brooklyn.core.entity.Attributes;
import org.apache.brooklyn.core.entity.EntityAsserts;
import org.apache.brooklyn.core.entity.trait.Startable;
-import org.apache.brooklyn.core.location.HasSubnetHostname;
-import org.apache.brooklyn.core.location.Machines;
import org.apache.brooklyn.core.location.PortRanges;
-import org.apache.brooklyn.core.test.BrooklynAppUnitTestSupport;
import org.apache.brooklyn.core.test.entity.TestEntity;
-import org.apache.brooklyn.core.test.entity.TestEntityImpl;
-import org.apache.brooklyn.entity.group.Cluster;
-import org.apache.brooklyn.entity.group.DynamicCluster;
-import org.apache.brooklyn.location.byon.FixedListMachineProvisioningLocation;
import org.apache.brooklyn.location.ssh.SshMachineLocation;
-import org.apache.brooklyn.test.Asserts;
-import org.apache.brooklyn.util.collections.MutableMap;
-import org.apache.brooklyn.util.collections.MutableSet;
-import org.apache.brooklyn.util.core.flags.SetFromFlag;
import org.apache.brooklyn.util.exceptions.Exceptions;
-import org.apache.brooklyn.util.guava.Maybe;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;
import com.google.common.collect.ImmutableList;
-import com.google.common.collect.ImmutableSet;
-import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
-public class AbstractControllerTest extends BrooklynAppUnitTestSupport {
+public class AbstractControllerTest extends AbstractAbstractControllerTest<TrackingAbstractController> {
private static final Logger log = LoggerFactory.getLogger(AbstractControllerTest.class);
- FixedListMachineProvisioningLocation<?> loc;
- Cluster cluster;
- TrackingAbstractController controller;
-
- @BeforeMethod(alwaysRun = true)
@Override
- public void setUp() throws Exception {
- super.setUp();
-
- List<SshMachineLocation> machines = new ArrayList<SshMachineLocation>();
- for (int i = 1; i <= 10; i++) {
- SshMachineLocation machine = mgmt.getLocationManager().createLocation(LocationSpec.create(SshMachineLocation.class)
- .configure("address", Inet4Address.getByName("1.1.1."+i)));
- machines.add(machine);
- }
- loc = mgmt.getLocationManager().createLocation(LocationSpec.create(FixedListMachineProvisioningLocation.class)
- .configure("machines", machines));
-
- cluster = app.addChild(EntitySpec.create(DynamicCluster.class)
- .configure("initialSize", 0)
- .configure("memberSpec", EntitySpec.create(TestEntity.class).impl(WebServerEntity.class)));
-
- controller = app.addChild(EntitySpec.create(TrackingAbstractController.class)
+ protected TrackingAbstractController newController() {
+ return app.addChild(EntitySpec.create(TrackingAbstractController.class)
.configure("serverPool", cluster)
.configure("portNumberSensor", WebServerEntity.HTTP_PORT)
.configure("domain", "mydomain"));
-
- app.start(ImmutableList.of(loc));
}
- // Fixes bug where entity that wrapped an AS7 entity was never added to nginx because hostname+port
- // was set after service_up. Now we listen to those changes and reset the nginx pool when these
- // values change.
- @Test
- public void testUpdateCalledWhenChildHostnameAndPortChanges() throws Exception {
- log.info("adding child (no effect until up)");
- TestEntity child = cluster.addChild(EntitySpec.create(TestEntity.class));
- cluster.addMember(child);
-
- List<Collection<String>> u = Lists.newArrayList(controller.getUpdates());
- assertTrue(u.isEmpty(), "expected no updates, but got "+u);
-
- log.info("setting child service_up");
- child.sensors().set(Startable.SERVICE_UP, true);
- // above may trigger error logged about no hostname, but should update again with the settings below
-
- log.info("setting mymachine:1234");
- child.sensors().set(WebServerEntity.HOSTNAME, "mymachine");
- child.sensors().set(Attributes.SUBNET_HOSTNAME, "mymachine");
- child.sensors().set(WebServerEntity.HTTP_PORT, 1234);
- assertEventuallyExplicitAddressesMatch(ImmutableList.of("mymachine:1234"));
-
- /* a race failure has been observed, https://issues.apache.org/jira/browse/BROOKLYN-206
- * but now (two months later) i (alex) can't see how it could happen.
- * probably optimistic but maybe it is fixed. if not we'll need the debug logs to see what is happening.
- * i've confirmed:
- * * the policy is attached and active during setup, before start completes
- * * the child is added as a member synchronously
- * * the policy which is "subscribed to members" is in fact subscribed to everything
- * then filtered for members, not ideal, but there shouldn't be a race in the policy getting notices
- * * the handling of those events are both processed in order and look up the current values
- * rather than relying on the published values; either should be sufficient to cause the addresses to change
- * there was a sleep(100) marked "Ugly sleep to allow AbstractController to detect node having been added"
- * from the test's addition by aled in early 2014, but can't see why that would be necessary
- */
-
- log.info("setting mymachine2:1234");
- child.sensors().set(WebServerEntity.HOSTNAME, "mymachine2");
- child.sensors().set(Attributes.SUBNET_HOSTNAME, "mymachine2");
- assertEventuallyExplicitAddressesMatch(ImmutableList.of("mymachine2:1234"));
-
- log.info("setting mymachine2:1235");
- child.sensors().set(WebServerEntity.HTTP_PORT, 1235);
- assertEventuallyExplicitAddressesMatch(ImmutableList.of("mymachine2:1235"));
-
- log.info("clearing");
- child.sensors().set(WebServerEntity.HOSTNAME, null);
- child.sensors().set(Attributes.SUBNET_HOSTNAME, null);
- assertEventuallyExplicitAddressesMatch(ImmutableList.<String>of());
- }
-
- @Test
- public void testUpdateCalledWithAddressesOfNewChildren() {
- // First child
- cluster.resize(1);
- Entity child = Iterables.getOnlyElement(cluster.getMembers());
-
- List<Collection<String>> u = Lists.newArrayList(controller.getUpdates());
- assertTrue(u.isEmpty(), "expected empty list but got "+u);
-
- child.sensors().set(WebServerEntity.HTTP_PORT, 1234);
- child.sensors().set(Startable.SERVICE_UP, true);
- assertEventuallyAddressesMatchCluster();
-
- // Second child
- cluster.resize(2);
- Asserts.succeedsEventually(new Runnable() {
- @Override
- public void run() {
- assertEquals(cluster.getMembers().size(), 2);
- }});
- Entity child2 = Iterables.getOnlyElement(MutableSet.<Entity>builder().addAll(cluster.getMembers()).remove(child).build());
-
- child2.sensors().set(WebServerEntity.HTTP_PORT, 1234);
- child2.sensors().set(Startable.SERVICE_UP, true);
- assertEventuallyAddressesMatchCluster();
-
- // And remove all children; expect all addresses to go away
- cluster.resize(0);
- assertEventuallyAddressesMatchCluster();
- }
-
- @Test(groups = "Integration", invocationCount=10)
- public void testUpdateCalledWithAddressesOfNewChildrenManyTimes() {
- testUpdateCalledWithAddressesOfNewChildren();
- }
-
- @Test
- public void testUpdateCalledWithAddressesRemovedForStoppedChildren() {
- // Get some children, so we can remove one...
- cluster.resize(2);
- for (Entity it: cluster.getMembers()) {
- it.sensors().set(WebServerEntity.HTTP_PORT, 1234);
- it.sensors().set(Startable.SERVICE_UP, true);
- }
- assertEventuallyAddressesMatchCluster();
-
- // Now remove one child
- cluster.resize(1);
- assertEquals(cluster.getMembers().size(), 1);
- assertEventuallyAddressesMatchCluster();
- }
-
- @Test
- public void testUpdateCalledWithAddressesRemovedForServiceDownChildrenThatHaveClearedHostnamePort() {
- // Get some children, so we can remove one...
- cluster.resize(2);
- for (Entity it: cluster.getMembers()) {
- it.sensors().set(WebServerEntity.HTTP_PORT, 1234);
- it.sensors().set(Startable.SERVICE_UP, true);
- }
- assertEventuallyAddressesMatchCluster();
-
- // Now unset host/port, and remove children
- // Note the unsetting of hostname is done in SoftwareProcessImpl.stop(), so this is realistic
- for (Entity it : cluster.getMembers()) {
- it.sensors().set(WebServerEntity.HTTP_PORT, null);
- it.sensors().set(WebServerEntity.HOSTNAME, null);
- it.sensors().set(Startable.SERVICE_UP, false);
- }
- assertEventuallyAddressesMatch(ImmutableList.<Entity>of());
+ @Override
+ protected List<Collection<String>> getUpdates(TrackingAbstractController controller) {
+ return controller.getUpdates();
}
@Test
@@ -333,86 +169,4 @@
EntityAsserts.assertAttributeEquals(controller2, Attributes.MAIN_URI_MAPPED_PUBLIC, URI.create("http://1.1.1.1:8081/"));
EntityAsserts.assertAttributeEquals(controller2, Attributes.MAIN_URI_MAPPED_SUBNET, URI.create("http://2.2.2.2:8081/"));
}
- public static class SshMachineLocationWithSubnetHostname extends SshMachineLocation implements HasSubnetHostname {
- @Override public String getSubnetHostname() {
- return getSubnetIp();
- }
- @Override public String getSubnetIp() {
- Set<String> addrs = getPrivateAddresses();
- return (addrs.isEmpty()) ? getAddress().getHostAddress() : Iterables.get(addrs, 0);
- }
- }
-
- private void assertEventuallyAddressesMatchCluster() {
- assertEventuallyAddressesMatch(cluster.getMembers());
- }
-
- private void assertEventuallyAddressesMatch(final Collection<Entity> expectedMembers) {
- Asserts.succeedsEventually(new Runnable() {
- @Override public void run() {
- assertAddressesMatch(locationsToAddresses(1234, expectedMembers));
- }} );
- }
-
- private void assertEventuallyExplicitAddressesMatch(final Collection<String> expectedAddresses) {
- Asserts.succeedsEventually(new Runnable() {
- @Override public void run() {
- assertAddressesMatch(expectedAddresses);
- }} );
- }
-
- private void assertAddressesMatch(final Collection<String> expectedAddresses) {
- List<Collection<String>> u = Lists.newArrayList(controller.getUpdates());
- Collection<String> last = Iterables.getLast(u, null);
- log.debug("test "+u.size()+" updates, expecting "+expectedAddresses+"; actual "+last);
- assertTrue(!u.isEmpty(), "no updates; expecting "+expectedAddresses);
- assertEquals(ImmutableSet.copyOf(last), ImmutableSet.copyOf(expectedAddresses), "actual="+last+" expected="+expectedAddresses);
- assertEquals(last.size(), expectedAddresses.size(), "actual="+last+" expected="+expectedAddresses);
- }
-
- private Collection<String> locationsToAddresses(int port, Collection<Entity> entities) {
- Set<String> result = MutableSet.of();
- for (Entity e : entities) {
- SshMachineLocation machine = Machines.findUniqueMachineLocation(e.getLocations(), SshMachineLocation.class).get();
- result.add(machine.getAddress().getHostName()+":"+port);
- }
- return result;
- }
-
- public static class WebServerEntity extends TestEntityImpl {
- @SetFromFlag("hostname")
- public static final AttributeSensor<String> HOSTNAME = Attributes.HOSTNAME;
-
- @SetFromFlag("port")
- public static final AttributeSensor<Integer> HTTP_PORT = Attributes.HTTP_PORT;
-
- @SetFromFlag("hostAndPort")
- public static final AttributeSensor<String> HOST_AND_PORT = Attributes.HOST_AND_PORT;
-
- MachineProvisioningLocation<MachineLocation> provisioner;
-
- @Override
- public void start(Collection<? extends Location> locs) {
- provisioner = (MachineProvisioningLocation<MachineLocation>) locs.iterator().next();
- MachineLocation machine;
- try {
- machine = provisioner.obtain(MutableMap.of());
- } catch (NoMachinesAvailableException e) {
- throw Exceptions.propagate(e);
- }
- addLocations(Arrays.asList(machine));
- sensors().set(HOSTNAME, machine.getAddress().getHostName());
- sensors().set(Attributes.SUBNET_HOSTNAME, machine.getAddress().getHostName());
- sensors().set(Attributes.MAIN_URI_MAPPED_SUBNET, URI.create(machine.getAddress().getHostName()));
- sensors().set(Attributes.MAIN_URI_MAPPED_PUBLIC, URI.create("http://8.8.8.8:" + sensors().get(HTTP_PORT)));
- }
-
- @Override
- public void stop() {
- Maybe<MachineLocation> machine = Machines.findUniqueMachineLocation(getLocations(), MachineLocation.class);
- if (provisioner != null) {
- provisioner.release(machine.get());
- }
- }
- }
}
diff --git a/software/webapp/src/test/java/org/apache/brooklyn/entity/proxy/AbstractNonProvisionedControllerTest.java b/software/webapp/src/test/java/org/apache/brooklyn/entity/proxy/AbstractNonProvisionedControllerTest.java
new file mode 100644
index 0000000..88094f4
--- /dev/null
+++ b/software/webapp/src/test/java/org/apache/brooklyn/entity/proxy/AbstractNonProvisionedControllerTest.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.entity.proxy;
+
+import java.util.Collection;
+import java.util.List;
+
+import org.apache.brooklyn.api.entity.EntitySpec;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class AbstractNonProvisionedControllerTest extends AbstractAbstractControllerTest<TrackingAbstractNonProvisionedController> {
+
+ // TODO Duplication of AbstractControllerTest
+
+ private static final Logger log = LoggerFactory.getLogger(AbstractNonProvisionedControllerTest.class);
+
+ @Override
+ protected TrackingAbstractNonProvisionedController newController() {
+ return app.addChild(EntitySpec.create(TrackingAbstractNonProvisionedController.class)
+ .configure("serverPool", cluster)
+ .configure("portNumberSensor", WebServerEntity.HTTP_PORT)
+ .configure("domain", "mydomain"));
+ }
+
+ @Override
+ protected List<Collection<String>> getUpdates(TrackingAbstractNonProvisionedController controller) {
+ return controller.getUpdates();
+ }
+}
diff --git a/software/webapp/src/test/java/org/apache/brooklyn/entity/proxy/TrackingAbstractNonProvisionedController.java b/software/webapp/src/test/java/org/apache/brooklyn/entity/proxy/TrackingAbstractNonProvisionedController.java
new file mode 100644
index 0000000..f0c6606
--- /dev/null
+++ b/software/webapp/src/test/java/org/apache/brooklyn/entity/proxy/TrackingAbstractNonProvisionedController.java
@@ -0,0 +1,29 @@
+/*
+ * 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.proxy;
+
+import java.util.Collection;
+import java.util.List;
+
+import org.apache.brooklyn.api.entity.ImplementedBy;
+
+@ImplementedBy(TrackingAbstractNonProvisionedControllerImpl.class)
+public interface TrackingAbstractNonProvisionedController extends AbstractNonProvisionedController {
+ List<Collection<String>> getUpdates();
+}
diff --git a/software/webapp/src/test/java/org/apache/brooklyn/entity/proxy/TrackingAbstractNonProvisionedControllerImpl.java b/software/webapp/src/test/java/org/apache/brooklyn/entity/proxy/TrackingAbstractNonProvisionedControllerImpl.java
new file mode 100644
index 0000000..896e5d6
--- /dev/null
+++ b/software/webapp/src/test/java/org/apache/brooklyn/entity/proxy/TrackingAbstractNonProvisionedControllerImpl.java
@@ -0,0 +1,81 @@
+/*
+ * 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.proxy;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.Set;
+
+import org.apache.brooklyn.api.location.Location;
+import org.apache.brooklyn.util.text.Identifiers;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.common.collect.Lists;
+
+public class TrackingAbstractNonProvisionedControllerImpl extends AbstractNonProvisionedControllerImpl implements TrackingAbstractNonProvisionedController {
+
+ private static final Logger log = LoggerFactory.getLogger(TrackingAbstractNonProvisionedControllerImpl.class);
+
+ private final List<Collection<String>> updates = Lists.newCopyOnWriteArrayList();
+
+ @Override
+ public void start(Collection<? extends Location> locations) {
+ sensors().set(HOSTNAME, Identifiers.makeRandomId(8) + ".test.brooklyn.apache.org");
+ super.start(locations);
+ }
+
+ @Override
+ public List<Collection<String>> getUpdates() {
+ return updates;
+ }
+
+ @Override
+ protected void reconfigureService() {
+ Set<String> addresses = getServerPoolAddresses();
+ log.info("test controller reconfigure, targets "+addresses);
+ if ((!addresses.isEmpty() && updates.isEmpty()) || (!updates.isEmpty() && addresses != updates.get(updates.size()-1))) {
+ updates.add(addresses);
+ }
+ }
+
+ @Override
+ public void reload() {
+ // no-op
+ }
+
+ @Override
+ public void restart() {
+ // no-op
+ }
+
+ @Override
+ protected String inferProtocol() {
+ String result = config().get(PROTOCOL);
+ return (result == null ? result : result.toLowerCase());
+ }
+
+ @Override
+ protected String inferUrl() {
+ String scheme = inferProtocol();
+ Integer port = sensors().get(PROXY_HTTP_PORT);
+ String domainName = sensors().get(HOSTNAME);
+ return scheme + "://" + domainName + ":" + port;
+ }
+}