You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@brooklyn.apache.org by al...@apache.org on 2015/08/12 17:55:24 UTC
[06/35] incubator-brooklyn git commit: [BROOKLYN-162] package rename
to org.apache.brooklyn: software/webapp
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/77dff880/software/webapp/src/test/java/org/apache/brooklyn/entity/proxy/AbstractControllerTest.java
----------------------------------------------------------------------
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
new file mode 100644
index 0000000..42649e8
--- /dev/null
+++ b/software/webapp/src/test/java/org/apache/brooklyn/entity/proxy/AbstractControllerTest.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.entity.proxy;
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertTrue;
+
+import java.net.Inet4Address;
+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.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import brooklyn.entity.BrooklynAppUnitTestSupport;
+import brooklyn.entity.Entity;
+import brooklyn.entity.basic.Attributes;
+import brooklyn.entity.basic.Entities;
+import brooklyn.entity.basic.EntityFactory;
+import brooklyn.entity.basic.EntityLocal;
+import brooklyn.entity.group.Cluster;
+import brooklyn.entity.group.DynamicCluster;
+import brooklyn.entity.proxying.EntitySpec;
+import brooklyn.entity.trait.Startable;
+import brooklyn.event.AttributeSensor;
+import brooklyn.location.Location;
+import brooklyn.location.LocationSpec;
+import brooklyn.location.MachineLocation;
+import brooklyn.location.MachineProvisioningLocation;
+import brooklyn.location.NoMachinesAvailableException;
+import brooklyn.location.basic.FixedListMachineProvisioningLocation;
+import brooklyn.location.basic.SshMachineLocation;
+import brooklyn.test.Asserts;
+import brooklyn.test.entity.TestEntity;
+import brooklyn.test.entity.TestEntityImpl;
+import brooklyn.util.collections.MutableMap;
+import brooklyn.util.collections.MutableSet;
+import brooklyn.util.exceptions.Exceptions;
+import brooklyn.util.flags.SetFromFlag;
+
+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 {
+
+ 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.createAndManageChild(EntitySpec.create(DynamicCluster.class)
+ .configure("initialSize", 0)
+ .configure("factory", new ClusteredEntity.Factory()));
+
+ controller = app.createAndManageChild(EntitySpec.create(TrackingAbstractController.class)
+ .configure("serverPool", cluster)
+ .configure("portNumberSensor", ClusteredEntity.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 {
+ TestEntity child = cluster.addChild(EntitySpec.create(TestEntity.class));
+ Entities.manage(child);
+ cluster.addMember(child);
+
+ List<Collection<String>> u = Lists.newArrayList(controller.getUpdates());
+ assertTrue(u.isEmpty(), "expected no updates, but got "+u);
+
+ child.setAttribute(Startable.SERVICE_UP, true);
+
+ // TODO Ugly sleep to allow AbstractController to detect node having been added
+ Thread.sleep(100);
+
+ child.setAttribute(ClusteredEntity.HOSTNAME, "mymachine");
+ child.setAttribute(Attributes.SUBNET_HOSTNAME, "mymachine");
+ child.setAttribute(ClusteredEntity.HTTP_PORT, 1234);
+ assertEventuallyExplicitAddressesMatch(ImmutableList.of("mymachine:1234"));
+
+ child.setAttribute(ClusteredEntity.HOSTNAME, "mymachine2");
+ child.setAttribute(Attributes.SUBNET_HOSTNAME, "mymachine2");
+ assertEventuallyExplicitAddressesMatch(ImmutableList.of("mymachine2:1234"));
+
+ child.setAttribute(ClusteredEntity.HTTP_PORT, 1235);
+ assertEventuallyExplicitAddressesMatch(ImmutableList.of("mymachine2:1235"));
+
+ child.setAttribute(ClusteredEntity.HOSTNAME, null);
+ child.setAttribute(Attributes.SUBNET_HOSTNAME, null);
+ assertEventuallyExplicitAddressesMatch(ImmutableList.<String>of());
+ }
+
+ @Test
+ public void testUpdateCalledWithAddressesOfNewChildren() {
+ // First child
+ cluster.resize(1);
+ EntityLocal child = (EntityLocal) Iterables.getOnlyElement(cluster.getMembers());
+
+ List<Collection<String>> u = Lists.newArrayList(controller.getUpdates());
+ assertTrue(u.isEmpty(), "expected empty list but got "+u);
+
+ child.setAttribute(ClusteredEntity.HTTP_PORT, 1234);
+ child.setAttribute(Startable.SERVICE_UP, true);
+ assertEventuallyAddressesMatchCluster();
+
+ // Second child
+ cluster.resize(2);
+ Asserts.succeedsEventually(new Runnable() {
+ @Override
+ public void run() {
+ assertEquals(cluster.getMembers().size(), 2);
+ }});
+ EntityLocal child2 = (EntityLocal) Iterables.getOnlyElement(MutableSet.builder().addAll(cluster.getMembers()).remove(child).build());
+
+ child2.setAttribute(ClusteredEntity.HTTP_PORT, 1234);
+ child2.setAttribute(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()) {
+ ((EntityLocal)it).setAttribute(ClusteredEntity.HTTP_PORT, 1234);
+ ((EntityLocal)it).setAttribute(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()) {
+ ((EntityLocal)it).setAttribute(ClusteredEntity.HTTP_PORT, 1234);
+ ((EntityLocal)it).setAttribute(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()) {
+ ((EntityLocal)it).setAttribute(ClusteredEntity.HTTP_PORT, null);
+ ((EntityLocal)it).setAttribute(ClusteredEntity.HOSTNAME, null);
+ ((EntityLocal)it).setAttribute(Startable.SERVICE_UP, false);
+ }
+ assertEventuallyAddressesMatch(ImmutableList.<Entity>of());
+ }
+
+ @Test
+ public void testUsesHostAndPortSensor() throws Exception {
+ controller = app.createAndManageChild(EntitySpec.create(TrackingAbstractController.class)
+ .configure("serverPool", cluster)
+ .configure("hostAndPortSensor", ClusteredEntity.HOST_AND_PORT)
+ .configure("domain", "mydomain"));
+ controller.start(Arrays.asList(loc));
+
+ TestEntity child = cluster.addChild(EntitySpec.create(TestEntity.class));
+ Entities.manage(child);
+ cluster.addMember(child);
+
+ List<Collection<String>> u = Lists.newArrayList(controller.getUpdates());
+ assertTrue(u.isEmpty(), "expected no updates, but got "+u);
+
+ child.setAttribute(Startable.SERVICE_UP, true);
+
+ // TODO Ugly sleep to allow AbstractController to detect node having been added
+ Thread.sleep(100);
+
+ child.setAttribute(ClusteredEntity.HOST_AND_PORT, "mymachine:1234");
+ assertEventuallyExplicitAddressesMatch(ImmutableList.of("mymachine:1234"));
+ }
+
+ @Test
+ public void testFailsIfSetHostAndPortAndHostnameOrPortNumberSensor() throws Exception {
+ try {
+ TrackingAbstractController controller2 = app.createAndManageChild(EntitySpec.create(TrackingAbstractController.class)
+ .configure("serverPool", cluster)
+ .configure("hostAndPortSensor", ClusteredEntity.HOST_AND_PORT)
+ .configure("hostnameSensor", ClusteredEntity.HOSTNAME)
+ .configure("domain", "mydomain"));
+ controller2.start(Arrays.asList(loc));
+ } catch (Exception e) {
+ IllegalStateException unwrapped = Exceptions.getFirstThrowableOfType(e, IllegalStateException.class);
+ if (unwrapped != null && unwrapped.toString().contains("Must not set Sensor")) {
+ // success
+ } else {
+ throw e;
+ }
+ }
+
+ try {
+ TrackingAbstractController controller3 = app.createAndManageChild(EntitySpec.create(TrackingAbstractController.class)
+ .configure("serverPool", cluster)
+ .configure("hostAndPortSensor", ClusteredEntity.HOST_AND_PORT)
+ .configure("portNumberSensor", ClusteredEntity.HTTP_PORT)
+ .configure("domain", "mydomain"));
+ controller3.start(Arrays.asList(loc));
+ } catch (Exception e) {
+ IllegalStateException unwrapped = Exceptions.getFirstThrowableOfType(e, IllegalStateException.class);
+ if (unwrapped != null && unwrapped.toString().contains("Must not set Sensor")) {
+ // success
+ } else {
+ throw e;
+ }
+ }
+ }
+
+ // Manual visual inspection test. Previously it repeatedly logged:
+ // Unable to construct hostname:port representation for TestEntityImpl{id=jzwSBRQ2} (null:null); skipping in TrackingAbstractControllerImpl{id=tOn4k5BA}
+ // every time the service-up was set to true again.
+ @Test
+ public void testMemberWithoutHostAndPortDoesNotLogErrorRepeatedly() throws Exception {
+ controller = app.createAndManageChild(EntitySpec.create(TrackingAbstractController.class)
+ .configure("serverPool", cluster)
+ .configure("domain", "mydomain"));
+ controller.start(ImmutableList.of(loc));
+
+ TestEntity child = app.createAndManageChild(EntitySpec.create(TestEntity.class));
+ cluster.addMember(child);
+
+ for (int i = 0; i < 100; i++) {
+ child.setAttribute(Attributes.SERVICE_UP, true);
+ }
+
+ Thread.sleep(100);
+ List<Collection<String>> u = Lists.newArrayList(controller.getUpdates());
+ assertTrue(u.isEmpty(), "expected no updates, but got "+u);
+ }
+
+ private void assertEventuallyAddressesMatchCluster() {
+ assertEventuallyAddressesMatch(cluster.getMembers());
+ }
+
+ private void assertEventuallyAddressesMatch(final Collection<Entity> expectedMembers) {
+ Asserts.succeedsEventually(MutableMap.of("timeout", 15000), new Runnable() {
+ @Override public void run() {
+ assertAddressesMatch(locationsToAddresses(1234, expectedMembers));
+ }} );
+ }
+
+ private void assertEventuallyExplicitAddressesMatch(final Collection<String> expectedAddresses) {
+ Asserts.succeedsEventually(MutableMap.of("timeout", 15000), 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.size() > 0);
+ 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) {
+ result.add( ((SshMachineLocation) e.getLocations().iterator().next()) .getAddress().getHostName()+":"+port);
+ }
+ return result;
+ }
+
+ public static class ClusteredEntity extends TestEntityImpl {
+ public static class Factory implements EntityFactory<ClusteredEntity> {
+ @Override
+ public ClusteredEntity newEntity(Map flags, Entity parent) {
+ return new ClusteredEntity(flags, parent);
+ }
+ }
+ public ClusteredEntity(Map flags, Entity parent) { super(flags,parent); }
+ public ClusteredEntity(Entity parent) { super(MutableMap.of(),parent); }
+ public ClusteredEntity(Map flags) { super(flags,null); }
+ public ClusteredEntity() { super(MutableMap.of(),null); }
+
+ @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 provisioner;
+
+ public void start(Collection<? extends Location> locs) {
+ provisioner = (MachineProvisioningLocation) locs.iterator().next();
+ MachineLocation machine;
+ try {
+ machine = provisioner.obtain(MutableMap.of());
+ } catch (NoMachinesAvailableException e) {
+ throw Exceptions.propagate(e);
+ }
+ addLocations(Arrays.asList(machine));
+ setAttribute(HOSTNAME, machine.getAddress().getHostName());
+ setAttribute(Attributes.SUBNET_HOSTNAME, machine.getAddress().getHostName());
+ }
+ public void stop() {
+ if (provisioner!=null) provisioner.release((MachineLocation) firstLocation());
+ }
+ }
+}
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/77dff880/software/webapp/src/test/java/org/apache/brooklyn/entity/proxy/ProxySslConfigTest.java
----------------------------------------------------------------------
diff --git a/software/webapp/src/test/java/org/apache/brooklyn/entity/proxy/ProxySslConfigTest.java b/software/webapp/src/test/java/org/apache/brooklyn/entity/proxy/ProxySslConfigTest.java
new file mode 100644
index 0000000..218debf
--- /dev/null
+++ b/software/webapp/src/test/java/org/apache/brooklyn/entity/proxy/ProxySslConfigTest.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.entity.proxy;
+
+import org.apache.brooklyn.entity.proxy.ProxySslConfig;
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+import brooklyn.util.collections.MutableMap;
+import brooklyn.util.flags.TypeCoercions;
+
+@Test
+public class ProxySslConfigTest {
+
+ @Test
+ public void testFromMap() {
+ ProxySslConfig config = TypeCoercions.coerce(MutableMap.of(
+ "certificateSourceUrl", "file://tmp/cert.txt",
+ "keySourceUrl", "file://tmp/key.txt",
+ "keyDestination", "dest.txt",
+ "targetIsSsl", true,
+ "reuseSessions", true),
+ ProxySslConfig.class);
+ Assert.assertEquals(config.getCertificateSourceUrl(), "file://tmp/cert.txt");
+ Assert.assertEquals(config.getKeySourceUrl(), "file://tmp/key.txt");
+ Assert.assertEquals(config.getKeyDestination(), "dest.txt");
+ Assert.assertEquals(config.getTargetIsSsl(), true);
+ Assert.assertEquals(config.getReuseSessions(), true);
+ }
+
+ @Test
+ public void testFromMapWithNullsAndDefaults() {
+ ProxySslConfig config = TypeCoercions.coerce(MutableMap.of(
+ "certificateSourceUrl", "file://tmp/cert.txt",
+ "keySourceUrl", null,
+ "targetIsSsl", "false"),
+ ProxySslConfig.class);
+ Assert.assertEquals(config.getCertificateSourceUrl(), "file://tmp/cert.txt");
+ Assert.assertEquals(config.getKeySourceUrl(), null);
+ Assert.assertEquals(config.getKeyDestination(), null);
+ Assert.assertEquals(config.getTargetIsSsl(), false);
+ Assert.assertEquals(config.getReuseSessions(), false);
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/77dff880/software/webapp/src/test/java/org/apache/brooklyn/entity/proxy/StubAppServer.java
----------------------------------------------------------------------
diff --git a/software/webapp/src/test/java/org/apache/brooklyn/entity/proxy/StubAppServer.java b/software/webapp/src/test/java/org/apache/brooklyn/entity/proxy/StubAppServer.java
new file mode 100644
index 0000000..92cdd84
--- /dev/null
+++ b/software/webapp/src/test/java/org/apache/brooklyn/entity/proxy/StubAppServer.java
@@ -0,0 +1,86 @@
+/*
+ * 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.Map;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import brooklyn.entity.Entity;
+import brooklyn.entity.basic.AbstractEntity;
+import brooklyn.entity.basic.Attributes;
+import brooklyn.entity.trait.Startable;
+import brooklyn.event.AttributeSensor;
+import brooklyn.event.basic.PortAttributeSensorAndConfigKey;
+import brooklyn.location.Location;
+import brooklyn.location.MachineLocation;
+import brooklyn.location.MachineProvisioningLocation;
+import brooklyn.location.NoMachinesAvailableException;
+import brooklyn.util.collections.MutableMap;
+
+import com.google.common.base.Throwables;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Iterables;
+
+public class StubAppServer extends AbstractEntity implements Startable {
+ public static final AttributeSensor<String> HOSTNAME = Attributes.HOSTNAME;
+ public static final PortAttributeSensorAndConfigKey HTTP_PORT = Attributes.HTTP_PORT;
+ public static AtomicInteger nextPort = new AtomicInteger(1234);
+
+ public StubAppServer(Map flags) {
+ super(flags);
+ }
+
+ public StubAppServer(Map flags, Entity parent) {
+ super(flags, parent);
+ }
+
+ @Override
+ public void start(Collection<? extends Location> locations) {
+ Location location = Iterables.getOnlyElement(locations);
+ if (location instanceof MachineProvisioningLocation) {
+ startInLocation((MachineProvisioningLocation)location);
+ } else {
+ startInLocation((MachineLocation)location);
+ }
+ }
+
+ private void startInLocation(MachineProvisioningLocation loc) {
+ try {
+ startInLocation(loc.obtain(MutableMap.of()));
+ } catch (NoMachinesAvailableException e) {
+ throw Throwables.propagate(e);
+ }
+ }
+
+ private void startInLocation(MachineLocation loc) {
+ addLocations(ImmutableList.of((Location)loc));
+ setAttribute(HOSTNAME, loc.getAddress().getHostName());
+ setAttribute(HTTP_PORT, nextPort.getAndIncrement());
+ setAttribute(SERVICE_UP, true);
+ }
+
+ public void stop() {
+ setAttribute(SERVICE_UP, false);
+ }
+
+ @Override
+ public void restart() {
+ }
+}
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/77dff880/software/webapp/src/test/java/org/apache/brooklyn/entity/proxy/TrackingAbstractController.java
----------------------------------------------------------------------
diff --git a/software/webapp/src/test/java/org/apache/brooklyn/entity/proxy/TrackingAbstractController.java b/software/webapp/src/test/java/org/apache/brooklyn/entity/proxy/TrackingAbstractController.java
new file mode 100644
index 0000000..f5e7ff5
--- /dev/null
+++ b/software/webapp/src/test/java/org/apache/brooklyn/entity/proxy/TrackingAbstractController.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.entity.proxy;
+
+import java.util.Collection;
+import java.util.List;
+
+import org.apache.brooklyn.entity.proxy.AbstractController;
+
+import brooklyn.entity.proxying.ImplementedBy;
+
+@ImplementedBy(TrackingAbstractControllerImpl.class)
+public interface TrackingAbstractController extends AbstractController {
+ List<Collection<String>> getUpdates();
+}
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/77dff880/software/webapp/src/test/java/org/apache/brooklyn/entity/proxy/TrackingAbstractControllerImpl.java
----------------------------------------------------------------------
diff --git a/software/webapp/src/test/java/org/apache/brooklyn/entity/proxy/TrackingAbstractControllerImpl.java b/software/webapp/src/test/java/org/apache/brooklyn/entity/proxy/TrackingAbstractControllerImpl.java
new file mode 100644
index 0000000..90fea43
--- /dev/null
+++ b/software/webapp/src/test/java/org/apache/brooklyn/entity/proxy/TrackingAbstractControllerImpl.java
@@ -0,0 +1,68 @@
+/*
+ * 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.entity.proxy.AbstractControllerImpl;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import brooklyn.entity.driver.MockSshDriver;
+
+import com.google.common.collect.Lists;
+
+public class TrackingAbstractControllerImpl extends AbstractControllerImpl implements TrackingAbstractController {
+
+ private static final Logger log = LoggerFactory.getLogger(TrackingAbstractControllerImpl.class);
+
+ private final List<Collection<String>> updates = Lists.newCopyOnWriteArrayList();
+
+ @Override
+ public List<Collection<String>> getUpdates() {
+ return updates;
+ }
+
+ @Override
+ public void connectSensors() {
+ super.connectSensors();
+ setAttribute(SERVICE_UP, true);
+ }
+
+ @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 Class getDriverInterface() {
+ return MockSshDriver.class;
+ }
+
+ @Override
+ public void reload() {
+ // no-op
+ }
+}
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/77dff880/software/webapp/src/test/java/org/apache/brooklyn/entity/proxy/UrlMappingTest.java
----------------------------------------------------------------------
diff --git a/software/webapp/src/test/java/org/apache/brooklyn/entity/proxy/UrlMappingTest.java b/software/webapp/src/test/java/org/apache/brooklyn/entity/proxy/UrlMappingTest.java
new file mode 100644
index 0000000..ba51549
--- /dev/null
+++ b/software/webapp/src/test/java/org/apache/brooklyn/entity/proxy/UrlMappingTest.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.entity.proxy;
+
+import static org.testng.Assert.assertEquals;
+
+import java.io.File;
+import java.util.HashSet;
+
+import javax.annotation.Nullable;
+
+import org.apache.brooklyn.entity.proxy.nginx.UrlMapping;
+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.Entity;
+import brooklyn.entity.basic.ApplicationBuilder;
+import brooklyn.entity.basic.Attributes;
+import brooklyn.entity.basic.BasicConfigurableEntityFactory;
+import brooklyn.entity.basic.Entities;
+import brooklyn.entity.basic.EntityFactory;
+import brooklyn.entity.basic.EntityInternal;
+import brooklyn.entity.group.DynamicCluster;
+import brooklyn.entity.proxying.EntitySpec;
+import brooklyn.entity.rebind.RebindTestUtils;
+import brooklyn.location.LocationSpec;
+import brooklyn.location.basic.LocalhostMachineProvisioningLocation;
+import brooklyn.management.internal.LocalManagementContext;
+import brooklyn.test.Asserts;
+import brooklyn.test.entity.TestApplication;
+import brooklyn.util.collections.MutableMap;
+import brooklyn.util.time.Duration;
+
+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.Sets;
+import com.google.common.io.Files;
+
+public class UrlMappingTest {
+
+ private static final Logger log = LoggerFactory.getLogger(UrlMappingTest.class);
+
+ private final int initialClusterSize = 2;
+
+ private ClassLoader classLoader = getClass().getClassLoader();
+ private LocalManagementContext managementContext;
+ private File mementoDir;
+
+ private TestApplication app;
+ private DynamicCluster cluster;
+ private UrlMapping urlMapping;
+
+ @BeforeMethod(alwaysRun=true)
+ public void setup() {
+ mementoDir = Files.createTempDir();
+ managementContext = RebindTestUtils.newPersistingManagementContext(mementoDir, classLoader);
+
+ app = ApplicationBuilder.newManagedApp(TestApplication.class, managementContext);
+
+ EntityFactory<StubAppServer> serverFactory = new BasicConfigurableEntityFactory<StubAppServer>(StubAppServer.class);
+ cluster = app.createAndManageChild(EntitySpec.create(DynamicCluster.class)
+ .configure("initialSize", initialClusterSize)
+ .configure("factory", serverFactory));
+
+ urlMapping = app.createAndManageChild(EntitySpec.create(UrlMapping.class)
+ .configure("domain", "localhost")
+ .configure("target", cluster));
+
+ app.start( ImmutableList.of(
+ managementContext.getLocationManager().createLocation(
+ LocationSpec.create(LocalhostMachineProvisioningLocation.class))
+ ));
+ log.info("app's location managed: "+managementContext.getLocationManager().isManaged(Iterables.getOnlyElement(app.getLocations())));
+ log.info("clusters's location managed: "+managementContext.getLocationManager().isManaged(Iterables.getOnlyElement(cluster.getLocations())));
+ }
+
+ @AfterMethod(alwaysRun=true)
+ public void shutdown() {
+ if (app != null) Entities.destroyAll(app.getManagementContext());
+ if (mementoDir != null) RebindTestUtils.deleteMementoDir(mementoDir);
+ }
+
+ @Test(groups = "Integration")
+ public void testTargetMappingsMatchesClusterMembers() {
+ // Check updates its TARGET_ADDRESSES (through async subscription)
+ assertExpectedTargetsEventually(cluster.getMembers());
+ }
+
+ @Test(groups = "Integration")
+ public void testTargetMappingsRemovesUnmanagedMember() {
+ Iterable<StubAppServer> members = Iterables.filter(cluster.getChildren(), StubAppServer.class);
+ assertEquals(Iterables.size(members), 2);
+ StubAppServer target1 = Iterables.get(members, 0);
+ StubAppServer target2 = Iterables.get(members, 1);
+
+ // First wait for targets to be listed
+ assertExpectedTargetsEventually(members);
+
+ // Unmanage one member, and expect the URL Mapping to be updated accordingly
+ Entities.unmanage(target1);
+
+ assertExpectedTargetsEventually(ImmutableSet.of(target2));
+ }
+
+ @Test(groups = "Integration", invocationCount=50)
+ public void testTargetMappingsRemovesUnmanagedMemberManyTimes() {
+ testTargetMappingsRemovesUnmanagedMember();
+ }
+
+ @Test(groups = "Integration")
+ public void testTargetMappingsRemovesDownMember() {
+ Iterable<StubAppServer> members = Iterables.filter(cluster.getChildren(), StubAppServer.class);
+ StubAppServer target1 = Iterables.get(members, 0);
+ StubAppServer target2 = Iterables.get(members, 1);
+
+ // First wait for targets to be listed
+ assertExpectedTargetsEventually(members);
+
+ // Stop one member, and expect the URL Mapping to be updated accordingly
+ target1.setAttribute(StubAppServer.SERVICE_UP, false);
+
+ assertExpectedTargetsEventually(ImmutableSet.of(target2));
+ }
+
+ // i think no real reason for other methods to be Integration apart from the time they take;
+ // having one in the unit tests is very handy however, and this is a good choice because it does quite a lot!
+ @Test
+ public void testTargetMappingUpdatesAfterRebind() throws Exception {
+ log.info("starting testTargetMappingUpdatesAfterRebind");
+ Iterable<StubAppServer> members = Iterables.filter(cluster.getChildren(), StubAppServer.class);
+ assertExpectedTargetsEventually(members);
+
+ Assert.assertTrue(managementContext.getLocationManager().isManaged(Iterables.getOnlyElement(cluster.getLocations())));
+ rebind();
+ Assert.assertTrue(managementContext.getLocationManager().isManaged(Iterables.getOnlyElement(cluster.getLocations())),
+ "location not managed after rebind");
+
+ Iterable<StubAppServer> members2 = Iterables.filter(cluster.getChildren(), StubAppServer.class);
+ StubAppServer target1 = Iterables.get(members2, 0);
+ StubAppServer target2 = Iterables.get(members2, 1);
+
+ // Expect to have existing targets
+ assertExpectedTargetsEventually(ImmutableSet.of(target1, target2));
+
+ // Add a new member; expect member to be added
+ log.info("resizing "+cluster+" - "+cluster.getChildren());
+ Integer result = cluster.resize(3);
+ Assert.assertTrue(managementContext.getLocationManager().isManaged(Iterables.getOnlyElement(cluster.getLocations())));
+ log.info("resized "+cluster+" ("+result+") - "+cluster.getChildren());
+ HashSet<StubAppServer> newEntities = Sets.newHashSet(Iterables.filter(cluster.getChildren(), StubAppServer.class));
+ newEntities.remove(target1);
+ newEntities.remove(target2);
+ StubAppServer target3 = Iterables.getOnlyElement(newEntities);
+ log.info("expecting "+ImmutableSet.of(target1, target2, target3));
+ assertExpectedTargetsEventually(ImmutableSet.of(target1, target2, target3));
+
+ // Stop one member, and expect the URL Mapping to be updated accordingly
+ log.info("pretending one node down");
+ target1.setAttribute(StubAppServer.SERVICE_UP, false);
+ assertExpectedTargetsEventually(ImmutableSet.of(target2, target3));
+
+ // Unmanage a member, and expect the URL Mapping to be updated accordingly
+ log.info("unmanaging another node");
+ Entities.unmanage(target2);
+ assertExpectedTargetsEventually(ImmutableSet.of(target3));
+ log.info("success - testTargetMappingUpdatesAfterRebind");
+ }
+
+ private void assertExpectedTargetsEventually(final Iterable<? extends Entity> members) {
+ Asserts.succeedsEventually(MutableMap.of("timeout", Duration.ONE_MINUTE), new Runnable() {
+ public void run() {
+ Iterable<String> expectedTargets = Iterables.transform(members, new Function<Entity,String>() {
+ @Override public String apply(@Nullable Entity input) {
+ return input.getAttribute(Attributes.HOSTNAME)+":"+input.getAttribute(Attributes.HTTP_PORT);
+ }});
+
+ assertEquals(ImmutableSet.copyOf(urlMapping.getAttribute(UrlMapping.TARGET_ADDRESSES)), ImmutableSet.copyOf(expectedTargets));
+ assertEquals(urlMapping.getAttribute(UrlMapping.TARGET_ADDRESSES).size(), Iterables.size(members));
+ }});
+ }
+
+ private void rebind() throws Exception {
+ RebindTestUtils.waitForPersisted(app);
+
+ // Stop the old management context, so original nginx won't interfere
+ managementContext.terminate();
+
+ app = (TestApplication) RebindTestUtils.rebind(mementoDir, getClass().getClassLoader());
+ managementContext = (LocalManagementContext) ((EntityInternal)app).getManagementContext();
+ cluster = (DynamicCluster) Iterables.find(app.getChildren(), Predicates.instanceOf(DynamicCluster.class));
+ urlMapping = (UrlMapping) Iterables.find(app.getChildren(), Predicates.instanceOf(UrlMapping.class));
+ }
+}
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/77dff880/software/webapp/src/test/java/org/apache/brooklyn/entity/proxy/nginx/NginxClusterIntegrationTest.java
----------------------------------------------------------------------
diff --git a/software/webapp/src/test/java/org/apache/brooklyn/entity/proxy/nginx/NginxClusterIntegrationTest.java b/software/webapp/src/test/java/org/apache/brooklyn/entity/proxy/nginx/NginxClusterIntegrationTest.java
new file mode 100644
index 0000000..6eeb5c8
--- /dev/null
+++ b/software/webapp/src/test/java/org/apache/brooklyn/entity/proxy/nginx/NginxClusterIntegrationTest.java
@@ -0,0 +1,243 @@
+/*
+ * 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.nginx;
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertTrue;
+
+import java.util.Collections;
+import java.util.List;
+
+import org.apache.brooklyn.entity.proxy.LoadBalancerCluster;
+import org.apache.brooklyn.entity.proxy.nginx.NginxController;
+import org.apache.brooklyn.entity.proxy.nginx.UrlMapping;
+import org.apache.brooklyn.entity.webapp.JavaWebAppService;
+import org.apache.brooklyn.entity.webapp.jboss.JBoss7Server;
+import org.apache.brooklyn.management.EntityManager;
+import org.apache.brooklyn.test.EntityTestUtils;
+import org.apache.brooklyn.test.HttpTestUtils;
+import org.apache.brooklyn.test.TestResourceUnavailableException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import brooklyn.entity.BrooklynAppLiveTestSupport;
+import brooklyn.entity.Group;
+import brooklyn.entity.basic.BasicGroup;
+import brooklyn.entity.basic.Entities;
+import brooklyn.entity.group.DynamicCluster;
+import brooklyn.entity.proxying.EntitySpec;
+import brooklyn.entity.trait.Startable;
+import brooklyn.location.Location;
+import brooklyn.location.basic.PortRanges;
+import brooklyn.test.Asserts;
+import brooklyn.util.collections.MutableMap;
+
+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;
+
+/**
+ * Test the operation of the {@link NginxController} class.
+ */
+public class NginxClusterIntegrationTest extends BrooklynAppLiveTestSupport {
+ @SuppressWarnings("unused")
+ private static final Logger log = LoggerFactory.getLogger(NginxClusterIntegrationTest.class);
+
+ private static final long TIMEOUT_MS = 60*1000;
+
+ private Location localhostProvisioningLoc;
+ private EntityManager entityManager;
+ private LoadBalancerCluster loadBalancerCluster;
+ private EntitySpec<NginxController> nginxSpec;
+ private Group urlMappings;
+
+ @BeforeMethod(alwaysRun=true)
+ @Override
+ public void setUp() throws Exception {
+ super.setUp();
+ localhostProvisioningLoc = app.newLocalhostProvisioningLocation();
+
+ urlMappings = app.createAndManageChild(EntitySpec.create(BasicGroup.class)
+ .configure("childrenAsMembers", true));
+ entityManager = app.getManagementContext().getEntityManager();
+
+ nginxSpec = EntitySpec.create(NginxController.class);
+ }
+
+ public String getTestWar() {
+ TestResourceUnavailableException.throwIfResourceUnavailable(getClass(), "/hello-world.war");
+ return "classpath://hello-world.war";
+ }
+
+ @Test(groups = "Integration")
+ public void testCreatesNginxInstancesAndResizes() {
+ loadBalancerCluster = app.createAndManageChild(EntitySpec.create(LoadBalancerCluster.class)
+ .configure(LoadBalancerCluster.MEMBER_SPEC, nginxSpec)
+ .configure("initialSize", 1)
+ .configure(NginxController.DOMAIN_NAME, "localhost"));
+
+ app.start(ImmutableList.of(localhostProvisioningLoc));
+
+ assertEquals(findNginxs().size(), 1);
+ assertNginxsResponsiveEvenutally(findNginxs());
+
+ // Resize load-balancer cluster
+ loadBalancerCluster.resize(2);
+ assertEquals(findNginxs().size(), 2);
+ assertNoDuplicates(findNginxRootUrls());
+ assertNginxsResponsiveEvenutally(findNginxs());
+ }
+
+ @Test(groups = "Integration")
+ public void testNginxInstancesConfiguredWithServerPool() {
+ DynamicCluster serverPool = app.createAndManageChild(EntitySpec.create(DynamicCluster.class)
+ .configure(DynamicCluster.MEMBER_SPEC, EntitySpec.create(JBoss7Server.class))
+ .configure("initialSize", 1)
+ .configure(JavaWebAppService.ROOT_WAR, getTestWar()));
+
+ loadBalancerCluster = app.createAndManageChild(EntitySpec.create(LoadBalancerCluster.class)
+ .configure("serverPool", serverPool)
+ .configure(LoadBalancerCluster.MEMBER_SPEC, nginxSpec)
+ .configure("initialSize", 1)
+ .configure(NginxController.DOMAIN_NAME, "localhost"));
+
+ app.start(ImmutableList.of(localhostProvisioningLoc));
+
+ assertEquals(findNginxs().size(), 1);
+
+ String hostname = "localhost";
+ List<String> pathsFor200 = ImmutableList.of(""); // i.e. app deployed at root
+ assertNginxsResponsiveEvenutally(findNginxs(), hostname, pathsFor200);
+ }
+
+ @Test(groups = "Integration")
+ public void testNginxInstancesConfiguredWithUrlMappings() {
+ DynamicCluster c1 = app.createAndManageChild(EntitySpec.create(DynamicCluster.class)
+ .configure(DynamicCluster.MEMBER_SPEC, EntitySpec.create(JBoss7Server.class))
+ .configure("initialSize", 1)
+ .configure(JavaWebAppService.NAMED_WARS, ImmutableList.of(getTestWar())));
+
+ UrlMapping urlMapping = entityManager.createEntity(EntitySpec.create(UrlMapping.class)
+ .configure("domain", "localhost")
+ .configure("path", "/hello-world($|/.*)")
+ .configure("target", c1)
+ .parent(urlMappings));
+ Entities.manage(urlMapping);
+
+ loadBalancerCluster = app.createAndManageChild(EntitySpec.create(LoadBalancerCluster.class)
+ .configure("urlMappings", urlMappings)
+ .configure(LoadBalancerCluster.MEMBER_SPEC, nginxSpec)
+ .configure("initialSize", 1));
+
+ app.start(ImmutableList.of(localhostProvisioningLoc));
+
+ assertEquals(findNginxs().size(), 1);
+
+ String hostname = "localhost";
+ List<String> pathsFor200 = ImmutableList.of("hello-world", "hello-world/");
+ assertNginxsResponsiveEvenutally(findNginxs(), hostname, pathsFor200);
+ }
+
+ @Test(groups = "Integration")
+ public void testClusterIsUpIffHasChildLoadBalancer() {
+ // Note the up-quorum-check behaves different for initialSize==0 (if explicit value not given):
+ // it would accept a size==0 as being serviceUp=true. Therefore don't do that!
+ loadBalancerCluster = app.createAndManageChild(EntitySpec.create(LoadBalancerCluster.class)
+ .configure(LoadBalancerCluster.MEMBER_SPEC, nginxSpec)
+ .configure("initialSize", 1)
+ .configure(NginxController.DOMAIN_NAME, "localhost"));
+
+ app.start(ImmutableList.of(localhostProvisioningLoc));
+ EntityTestUtils.assertAttributeEqualsContinually(loadBalancerCluster, Startable.SERVICE_UP, true);
+
+ loadBalancerCluster.resize(0);
+ EntityTestUtils.assertAttributeEqualsEventually(loadBalancerCluster, Startable.SERVICE_UP, false);
+
+ loadBalancerCluster.resize(1);
+ EntityTestUtils.assertAttributeEqualsEventually(loadBalancerCluster, Startable.SERVICE_UP, true);
+ }
+
+ // Warning: test is a little brittle for if a previous run leaves something on these required ports
+ @Test(groups = "Integration")
+ public void testConfiguresNginxInstancesWithInheritedPortConfig() {
+ loadBalancerCluster = app.createAndManageChild(EntitySpec.create(LoadBalancerCluster.class)
+ .configure(LoadBalancerCluster.MEMBER_SPEC, nginxSpec)
+ .configure("initialSize", 1)
+ .configure(NginxController.DOMAIN_NAME, "localhost")
+ .configure(NginxController.PROXY_HTTP_PORT, PortRanges.fromString("8765+")));
+
+ app.start(ImmutableList.of(localhostProvisioningLoc));
+
+ NginxController nginx1 = Iterables.getOnlyElement(findNginxs());
+
+ loadBalancerCluster.resize(2);
+ NginxController nginx2 = Iterables.getOnlyElement(Iterables.filter(findNginxs(),
+ Predicates.not(Predicates.in(ImmutableList.of(nginx1)))));
+
+ assertEquals((int) nginx1.getAttribute(NginxController.PROXY_HTTP_PORT), 8765);
+ assertEquals((int) nginx2.getAttribute(NginxController.PROXY_HTTP_PORT), 8766);
+ }
+
+ @SuppressWarnings({ "rawtypes", "unchecked" })
+ private List<NginxController> findNginxs() {
+ ImmutableList result = ImmutableList.copyOf(Iterables.filter(app.getManagementContext().getEntityManager().getEntities(), Predicates.instanceOf(NginxController.class)));
+ return (List<NginxController>) result;
+ }
+
+ private List<String> findNginxRootUrls() {
+ List<String> result = Lists.newArrayList();
+ for (NginxController nginx : findNginxs()) {
+ result.add(nginx.getAttribute(NginxController.ROOT_URL));
+ }
+ return result;
+ }
+
+ private void assertNginxsResponsiveEvenutally(final Iterable<NginxController> nginxs) {
+ assertNginxsResponsiveEvenutally(nginxs, null, Collections.<String>emptyList());
+ }
+
+ private void assertNginxsResponsiveEvenutally(final Iterable<NginxController> nginxs, final String hostname, final List<String> pathsFor200) {
+ Asserts.succeedsEventually(MutableMap.of("timeout", TIMEOUT_MS), new Runnable() {
+ public void run() {
+ for (NginxController nginx : nginxs) {
+ assertTrue(nginx.getAttribute(NginxController.SERVICE_UP));
+
+ String normalRootUrl = nginx.getAttribute(NginxController.ROOT_URL);
+ int port = nginx.getAttribute(NginxController.PROXY_HTTP_PORT);
+ String rootUrl = (hostname != null) ? ("http://"+hostname+":"+port+"/") : normalRootUrl;
+
+ String wrongUrl = rootUrl+"doesnotexist";
+ HttpTestUtils.assertHttpStatusCodeEquals(wrongUrl, 404);
+
+ for (String pathFor200 : pathsFor200) {
+ String url = rootUrl+pathFor200;
+ HttpTestUtils.assertHttpStatusCodeEquals(url, 200);
+ }
+ }
+ }});
+ }
+
+ private void assertNoDuplicates(Iterable<String> c) {
+ assertEquals(Iterables.size(c), ImmutableSet.copyOf(c).size(), "c="+c);
+ }
+}
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/77dff880/software/webapp/src/test/java/org/apache/brooklyn/entity/proxy/nginx/NginxEc2LiveTest.java
----------------------------------------------------------------------
diff --git a/software/webapp/src/test/java/org/apache/brooklyn/entity/proxy/nginx/NginxEc2LiveTest.java b/software/webapp/src/test/java/org/apache/brooklyn/entity/proxy/nginx/NginxEc2LiveTest.java
new file mode 100644
index 0000000..0902444
--- /dev/null
+++ b/software/webapp/src/test/java/org/apache/brooklyn/entity/proxy/nginx/NginxEc2LiveTest.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.entity.proxy.nginx;
+
+import org.apache.brooklyn.entity.proxy.nginx.NginxController;
+import org.apache.brooklyn.entity.webapp.WebAppService;
+import org.apache.brooklyn.test.EntityTestUtils;
+import org.apache.brooklyn.test.HttpTestUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.testng.annotations.Test;
+
+import brooklyn.entity.AbstractEc2LiveTest;
+import brooklyn.entity.basic.SoftwareProcess;
+import brooklyn.entity.proxying.EntitySpec;
+import brooklyn.location.Location;
+
+import com.google.common.collect.ImmutableList;
+
+/**
+ * A simple test of installing+running on AWS-EC2, using various OS distros and versions.
+ */
+public class NginxEc2LiveTest extends AbstractEc2LiveTest {
+
+ /* FIXME Currently fails on:
+ * test_Debian_5: installation of nginx failed
+ * test_Ubuntu_12_0: invocation error for disable requiretty
+ */
+
+ /* PASSED: test_CentOS_5
+ * PASSED: test_CentOS_6_3
+ * PASSED: test_Debian_6
+ * PASSED: test_Ubuntu_10_0
+ *
+ * test_Red_Hat_Enterprise_Linux_6 passes, if get it to wait for ssh-login rather than "failed to SSH in as root"
+ */
+
+ @SuppressWarnings("unused")
+ private static final Logger log = LoggerFactory.getLogger(NginxEc2LiveTest.class);
+
+ private NginxController nginx;
+
+ @Override
+ protected void doTest(Location loc) throws Exception {
+ nginx = app.createAndManageChild(EntitySpec.create(NginxController.class)
+ .configure("portNumberSensor", WebAppService.HTTP_PORT));
+
+ app.start(ImmutableList.of(loc));
+
+ // nginx should be up, and URL reachable
+ EntityTestUtils.assertAttributeEqualsEventually(nginx, SoftwareProcess.SERVICE_UP, true);
+ HttpTestUtils.assertHttpStatusCodeEventuallyEquals(nginx.getAttribute(NginxController.ROOT_URL), 404);
+ }
+
+ @Test(enabled=false)
+ public void testDummy() {} // Convince testng IDE integration that this really does have test methods
+}
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/77dff880/software/webapp/src/test/java/org/apache/brooklyn/entity/proxy/nginx/NginxHttpsSslIntegrationTest.java
----------------------------------------------------------------------
diff --git a/software/webapp/src/test/java/org/apache/brooklyn/entity/proxy/nginx/NginxHttpsSslIntegrationTest.java b/software/webapp/src/test/java/org/apache/brooklyn/entity/proxy/nginx/NginxHttpsSslIntegrationTest.java
new file mode 100644
index 0000000..5e53054
--- /dev/null
+++ b/software/webapp/src/test/java/org/apache/brooklyn/entity/proxy/nginx/NginxHttpsSslIntegrationTest.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.entity.proxy.nginx;
+
+import static org.testng.Assert.assertFalse;
+import static org.testng.Assert.assertTrue;
+
+import java.io.File;
+
+import org.apache.brooklyn.entity.proxy.LoadBalancer;
+import org.apache.brooklyn.entity.proxy.ProxySslConfig;
+import org.apache.brooklyn.entity.proxy.nginx.NginxController;
+import org.apache.brooklyn.entity.webapp.JavaWebAppService;
+import org.apache.brooklyn.entity.webapp.WebAppService;
+import org.apache.brooklyn.entity.webapp.jboss.JBoss7Server;
+import org.apache.brooklyn.test.HttpTestUtils;
+import org.apache.brooklyn.test.TestResourceUnavailableException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.testng.Assert;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import brooklyn.entity.BrooklynAppLiveTestSupport;
+import brooklyn.entity.Entity;
+import brooklyn.entity.basic.EntityInternal;
+import brooklyn.entity.basic.SoftwareProcess;
+import brooklyn.entity.group.DynamicCluster;
+import brooklyn.entity.proxying.EntitySpec;
+import brooklyn.event.basic.PortAttributeSensorAndConfigKey;
+import brooklyn.location.Location;
+import brooklyn.location.basic.PortRanges;
+import brooklyn.test.Asserts;
+import brooklyn.util.exceptions.Exceptions;
+
+import com.google.common.base.Preconditions;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Iterables;
+
+/**
+ * Test the operation of the {@link NginxController} class.
+ */
+public class NginxHttpsSslIntegrationTest extends BrooklynAppLiveTestSupport {
+
+ private static final Logger log = LoggerFactory.getLogger(NginxHttpsSslIntegrationTest.class);
+
+ private NginxController nginx;
+ private DynamicCluster cluster;
+ private Location localLoc;
+
+ private static final String CERTIFICATE_URL = "classpath://ssl/certs/localhost/server.crt";
+ private static final String KEY_URL = "classpath://ssl/certs/localhost/server.key";
+
+ @BeforeMethod(alwaysRun=true)
+ @Override
+ public void setUp() throws Exception {
+ super.setUp();
+ localLoc = mgmt.getLocationRegistry().resolve("localhost");
+ }
+
+ private static void urlContainsPort(NginxController nginx, PortAttributeSensorAndConfigKey sensor, String portRange) {
+ Integer port = nginx.getAttribute(sensor);
+ Assert.assertTrue(Iterables.contains(PortRanges.fromString(portRange), port), "Port "+port+" not in range "+portRange);
+ String url = Preconditions.checkNotNull(nginx.getAttribute(LoadBalancer.MAIN_URI), "main uri").toString();
+ Assert.assertTrue(url.contains(":"+port), "URL does not contain expected port; port "+port+", url "+url);
+ }
+
+ public String getTestWar() {
+ TestResourceUnavailableException.throwIfResourceUnavailable(getClass(), "/hello-world.war");
+ return "classpath://hello-world.war";
+ }
+
+ /**
+ * Test that the Nginx proxy starts up and sets SERVICE_UP correctly.
+ */
+ @Test(groups = "Integration")
+ public void testStartsWithGlobalSsl_withCertificateAndKeyCopy() {
+ cluster = app.createAndManageChild(EntitySpec.create(DynamicCluster.class)
+ .configure(DynamicCluster.MEMBER_SPEC, EntitySpec.create(JBoss7Server.class))
+ .configure("initialSize", 1)
+ .configure(JavaWebAppService.ROOT_WAR, getTestWar()));
+
+ ProxySslConfig ssl = ProxySslConfig.builder()
+ .certificateSourceUrl(CERTIFICATE_URL)
+ .keySourceUrl(KEY_URL)
+ .build();
+
+ nginx = app.createAndManageChild(EntitySpec.create(NginxController.class)
+ .configure("sticky", false)
+ .configure("serverPool", cluster)
+ .configure("domain", "localhost")
+ .configure("httpsPort", "8453+")
+ .configure("ssl", ssl));
+
+ app.start(ImmutableList.of(localLoc));
+
+ urlContainsPort(nginx, LoadBalancer.PROXY_HTTPS_PORT, "8453+");
+
+ final String url = nginx.getAttribute(WebAppService.ROOT_URL);
+ log.info("URL for nginx is "+url);
+ if (!url.startsWith("https://")) Assert.fail("URL should be https: "+url);
+
+ Asserts.succeedsEventually(new Runnable() {
+ public void run() {
+ // Services are running
+ assertTrue(cluster.getAttribute(SoftwareProcess.SERVICE_UP));
+ for (Entity member : cluster.getMembers()) {
+ assertTrue(member.getAttribute(SoftwareProcess.SERVICE_UP));
+ }
+
+ assertTrue(nginx.getAttribute(SoftwareProcess.SERVICE_UP));
+
+ // Nginx URL is available
+ HttpTestUtils.assertHttpStatusCodeEquals(url, 200);
+
+ // Web-server URL is available
+ for (Entity member : cluster.getMembers()) {
+ HttpTestUtils.assertHttpStatusCodeEquals(member.getAttribute(WebAppService.ROOT_URL), 200);
+ }
+ }});
+
+ app.stop();
+
+ // Services have stopped
+ assertFalse(nginx.getAttribute(SoftwareProcess.SERVICE_UP));
+ assertFalse(cluster.getAttribute(SoftwareProcess.SERVICE_UP));
+ for (Entity member : cluster.getMembers()) {
+ assertFalse(member.getAttribute(SoftwareProcess.SERVICE_UP));
+ }
+ }
+
+ private String getFile(String file) {
+ return new File(getClass().getResource("/" + file).getFile()).getAbsolutePath();
+ }
+
+ @Test(groups = "Integration")
+ public void testStartsWithGlobalSsl_withPreinstalledCertificateAndKey() {
+ cluster = app.createAndManageChild(EntitySpec.create(DynamicCluster.class)
+ .configure(DynamicCluster.MEMBER_SPEC, EntitySpec.create(JBoss7Server.class))
+ .configure("initialSize", 1)
+ .configure(JavaWebAppService.ROOT_WAR, getTestWar()));
+
+ ProxySslConfig ssl = ProxySslConfig.builder()
+ .certificateDestination(getFile("ssl/certs/localhost/server.crt"))
+ .keyDestination(getFile("ssl/certs/localhost/server.key"))
+ .build();
+
+ nginx = app.createAndManageChild(EntitySpec.create(NginxController.class)
+ .configure("sticky", false)
+ .configure("serverPool", cluster)
+ .configure("domain", "localhost")
+ .configure("port", "8443+")
+ .configure("ssl", ssl));
+
+ app.start(ImmutableList.of(localLoc));
+
+ final String url = nginx.getAttribute(WebAppService.ROOT_URL);
+ if (!url.startsWith("https://")) Assert.fail("URL should be https: "+url);
+
+ Asserts.succeedsEventually(new Runnable() {
+ public void run() {
+ // Services are running
+ assertTrue(cluster.getAttribute(SoftwareProcess.SERVICE_UP));
+ for (Entity member : cluster.getMembers()) {
+ assertTrue(member.getAttribute(SoftwareProcess.SERVICE_UP));
+ }
+
+ assertTrue(nginx.getAttribute(SoftwareProcess.SERVICE_UP));
+
+ // Nginx URL is available
+ HttpTestUtils.assertHttpStatusCodeEquals(url, 200);
+
+ // Web-server URL is available
+ for (Entity member : cluster.getMembers()) {
+ HttpTestUtils.assertHttpStatusCodeEquals(member.getAttribute(WebAppService.ROOT_URL), 200);
+ }
+ }});
+
+ app.stop();
+
+ // Services have stopped
+ assertFalse(nginx.getAttribute(SoftwareProcess.SERVICE_UP));
+ assertFalse(cluster.getAttribute(SoftwareProcess.SERVICE_UP));
+ for (Entity member : cluster.getMembers()) {
+ assertFalse(member.getAttribute(SoftwareProcess.SERVICE_UP));
+ }
+ }
+
+ @Test(groups = "Integration")
+ public void testStartsNonSslThenBecomesSsl() {
+ cluster = app.createAndManageChild(EntitySpec.create(DynamicCluster.class)
+ .configure(DynamicCluster.MEMBER_SPEC, EntitySpec.create(JBoss7Server.class))
+ .configure("initialSize", 1)
+ .configure(JavaWebAppService.ROOT_WAR, getTestWar()));
+
+ nginx = app.createAndManageChild(EntitySpec.create(NginxController.class)
+ .configure("serverPool", cluster)
+ .configure("domain", "localhost"));
+
+ app.start(ImmutableList.of(localLoc));
+
+ urlContainsPort(nginx, LoadBalancer.PROXY_HTTP_PORT, "8000-8100");
+
+ ProxySslConfig ssl = ProxySslConfig.builder()
+ .certificateDestination(getFile("ssl/certs/localhost/server.crt"))
+ .keyDestination(getFile("ssl/certs/localhost/server.key"))
+ .build();
+ ((EntityInternal)nginx).setConfig(LoadBalancer.PROXY_HTTPS_PORT, PortRanges.fromString("8443+"));
+ ((EntityInternal)nginx).setConfig(NginxController.SSL_CONFIG, ssl);
+
+ try {
+ log.info("restarting nginx as ssl");
+ nginx.restart();
+ urlContainsPort(nginx, LoadBalancer.PROXY_HTTPS_PORT, "8443-8543");
+
+ app.stop();
+
+ } catch (Exception e) {
+ throw Exceptions.propagate(e);
+ }
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/77dff880/software/webapp/src/test/java/org/apache/brooklyn/entity/proxy/nginx/NginxIntegrationTest.java
----------------------------------------------------------------------
diff --git a/software/webapp/src/test/java/org/apache/brooklyn/entity/proxy/nginx/NginxIntegrationTest.java b/software/webapp/src/test/java/org/apache/brooklyn/entity/proxy/nginx/NginxIntegrationTest.java
new file mode 100644
index 0000000..d208cfb
--- /dev/null
+++ b/software/webapp/src/test/java/org/apache/brooklyn/entity/proxy/nginx/NginxIntegrationTest.java
@@ -0,0 +1,454 @@
+/*
+ * 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.nginx;
+
+import static org.apache.brooklyn.test.EntityTestUtils.assertAttributeEqualsEventually;
+import static org.apache.brooklyn.test.HttpTestUtils.assertHttpStatusCodeEquals;
+import static org.apache.brooklyn.test.HttpTestUtils.assertHttpStatusCodeEventuallyEquals;
+import static org.testng.Assert.assertFalse;
+import static org.testng.Assert.assertNotEquals;
+import static org.testng.Assert.assertTrue;
+
+import java.util.Map;
+
+import org.apache.brooklyn.entity.proxy.nginx.NginxController;
+import org.apache.brooklyn.entity.webapp.JavaWebAppService;
+import org.apache.brooklyn.entity.webapp.WebAppService;
+import org.apache.brooklyn.entity.webapp.jboss.JBoss7Server;
+import org.apache.brooklyn.test.HttpTestUtils;
+import org.apache.brooklyn.test.TestResourceUnavailableException;
+import org.apache.brooklyn.test.WebAppMonitor;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import brooklyn.entity.BrooklynAppLiveTestSupport;
+import brooklyn.entity.Entity;
+import brooklyn.entity.basic.EntityFactory;
+import brooklyn.entity.basic.SoftwareProcess;
+import brooklyn.entity.group.DynamicCluster;
+import brooklyn.entity.proxying.EntitySpec;
+import brooklyn.location.Location;
+import brooklyn.test.Asserts;
+import brooklyn.util.time.Duration;
+import brooklyn.util.time.Time;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Iterables;
+/**
+ * Test the operation of the {@link NginxController} class.
+ */
+public class NginxIntegrationTest extends BrooklynAppLiveTestSupport {
+ private static final Logger log = LoggerFactory.getLogger(NginxIntegrationTest.class);
+
+ private NginxController nginx;
+ private DynamicCluster serverPool;
+ private Location localLoc;
+
+ @BeforeMethod(alwaysRun=true)
+ @Override
+ public void setUp() throws Exception {
+ super.setUp();
+ localLoc = mgmt.getLocationRegistry().resolve("localhost");
+ }
+
+ public String getTestWar() {
+ TestResourceUnavailableException.throwIfResourceUnavailable(getClass(), "/hello-world.war");
+ return "classpath://hello-world.war";
+ }
+
+ /**
+ * Test that the Nginx proxy starts up and sets SERVICE_UP correctly.
+ */
+ @Test(groups = "Integration")
+ public void testWhenNoServersReturns404() {
+ serverPool = app.createAndManageChild(EntitySpec.create(DynamicCluster.class)
+ .configure("initialSize", 0)
+ .configure(DynamicCluster.FACTORY, new EntityFactory<Entity>() {
+ @Override public Entity newEntity(Map flags, Entity parent) {
+ throw new UnsupportedOperationException();
+ }}));
+
+ nginx = app.createAndManageChild(EntitySpec.create(NginxController.class)
+ .configure("serverPool", serverPool)
+ .configure("domain", "localhost"));
+
+ app.start(ImmutableList.of(localLoc));
+
+ assertAttributeEqualsEventually(nginx, SoftwareProcess.SERVICE_UP, true);
+ assertHttpStatusCodeEventuallyEquals(nginx.getAttribute(NginxController.ROOT_URL), 404);
+ }
+
+ @Test(groups = "Integration")
+ public void testRestart() {
+ serverPool = app.createAndManageChild(EntitySpec.create(DynamicCluster.class)
+ .configure("initialSize", 0)
+ .configure(DynamicCluster.FACTORY, new EntityFactory<Entity>() {
+ @Override public Entity newEntity(Map flags, Entity parent) {
+ throw new UnsupportedOperationException();
+ }}));
+
+ nginx = app.createAndManageChild(EntitySpec.create(NginxController.class)
+ .configure("serverPool", serverPool)
+ .configure("domain", "localhost"));
+
+ app.start(ImmutableList.of(localLoc));
+
+ nginx.restart();
+
+ assertAttributeEqualsEventually(nginx, SoftwareProcess.SERVICE_UP, true);
+ assertHttpStatusCodeEventuallyEquals(nginx.getAttribute(NginxController.ROOT_URL), 404);
+ }
+
+ /**
+ * Test that the Nginx proxy starts up and sets SERVICE_UP correctly.
+ */
+ @Test(groups = "Integration")
+ public void testCanStartupAndShutdown() {
+ serverPool = app.createAndManageChild(EntitySpec.create(DynamicCluster.class)
+ .configure(DynamicCluster.MEMBER_SPEC, EntitySpec.create(JBoss7Server.class))
+ .configure("initialSize", 1)
+ .configure(JavaWebAppService.ROOT_WAR, getTestWar()));
+
+ nginx = app.createAndManageChild(EntitySpec.create(NginxController.class)
+ .configure("serverPool", serverPool)
+ .configure("domain", "localhost")
+ .configure("portNumberSensor", WebAppService.HTTP_PORT));
+
+ app.start(ImmutableList.of(localLoc));
+
+ // App-servers and nginx has started
+ Asserts.succeedsEventually(new Runnable() {
+ public void run() {
+ for (Entity member : serverPool.getMembers()) {
+ assertTrue(member.getAttribute(SoftwareProcess.SERVICE_UP));
+ }
+ assertTrue(nginx.getAttribute(SoftwareProcess.SERVICE_UP));
+ }});
+
+ // URLs reachable
+ assertHttpStatusCodeEventuallyEquals(nginx.getAttribute(NginxController.ROOT_URL), 200);
+ for (Entity member : serverPool.getMembers()) {
+ assertHttpStatusCodeEventuallyEquals(member.getAttribute(WebAppService.ROOT_URL), 200);
+ }
+
+ app.stop();
+
+ // Services have stopped
+ assertFalse(nginx.getAttribute(SoftwareProcess.SERVICE_UP));
+ assertFalse(serverPool.getAttribute(SoftwareProcess.SERVICE_UP));
+ for (Entity member : serverPool.getMembers()) {
+ assertFalse(member.getAttribute(SoftwareProcess.SERVICE_UP));
+ }
+ }
+
+ /**
+ * Test that the Nginx proxy starts up and sets SERVICE_UP correctly using the config file template.
+ */
+ @Test(groups = "Integration")
+ public void testCanStartupAndShutdownUsingTemplate() {
+ serverPool = app.createAndManageChild(EntitySpec.create(DynamicCluster.class)
+ .configure(DynamicCluster.MEMBER_SPEC, EntitySpec.create(JBoss7Server.class))
+ .configure("initialSize", 1)
+ .configure(JavaWebAppService.ROOT_WAR, getTestWar()));
+
+ nginx = app.createAndManageChild(EntitySpec.create(NginxController.class)
+ .configure("serverPool", serverPool)
+ .configure("domain", "localhost")
+ .configure("portNumberSensor", WebAppService.HTTP_PORT)
+ .configure("configTemplate", "classpath://org/apache/brooklyn/entity/proxy/nginx/server.conf"));
+
+ app.start(ImmutableList.of(localLoc));
+
+ // App-servers and nginx has started
+ Asserts.succeedsEventually(new Runnable() {
+ public void run() {
+ for (Entity member : serverPool.getMembers()) {
+ assertTrue(member.getAttribute(SoftwareProcess.SERVICE_UP));
+ }
+ assertTrue(nginx.getAttribute(SoftwareProcess.SERVICE_UP));
+ }});
+
+ // URLs reachable
+ assertHttpStatusCodeEventuallyEquals(nginx.getAttribute(NginxController.ROOT_URL), 200);
+ for (Entity member : serverPool.getMembers()) {
+ assertHttpStatusCodeEventuallyEquals(member.getAttribute(WebAppService.ROOT_URL), 200);
+ }
+
+ app.stop();
+
+ // Services have stopped
+ assertFalse(nginx.getAttribute(SoftwareProcess.SERVICE_UP));
+ assertFalse(serverPool.getAttribute(SoftwareProcess.SERVICE_UP));
+ for (Entity member : serverPool.getMembers()) {
+ assertFalse(member.getAttribute(SoftwareProcess.SERVICE_UP));
+ }
+ }
+
+ /**
+ * Test that the Nginx proxy works, serving all domains, if no domain is set
+ */
+ @Test(groups = "Integration")
+ public void testDomainless() {
+ serverPool = app.createAndManageChild(EntitySpec.create(DynamicCluster.class)
+ .configure(DynamicCluster.MEMBER_SPEC, EntitySpec.create(JBoss7Server.class))
+ .configure("initialSize", 1)
+ .configure(JavaWebAppService.ROOT_WAR, getTestWar()));
+
+ nginx = app.createAndManageChild(EntitySpec.create(NginxController.class)
+ .configure("serverPool", serverPool)
+ .configure("domain", "localhost")
+ .configure("portNumberSensor", WebAppService.HTTP_PORT));
+
+ app.start(ImmutableList.of(localLoc));
+
+ // App-servers and nginx has started
+ assertAttributeEqualsEventually(serverPool, SoftwareProcess.SERVICE_UP, true);
+ for (Entity member : serverPool.getMembers()) {
+ assertAttributeEqualsEventually(member, SoftwareProcess.SERVICE_UP, true);
+ }
+ assertAttributeEqualsEventually(nginx, SoftwareProcess.SERVICE_UP, true);
+
+ // URLs reachable
+ assertHttpStatusCodeEventuallyEquals(nginx.getAttribute(NginxController.ROOT_URL), 200);
+ for (Entity member : serverPool.getMembers()) {
+ assertHttpStatusCodeEventuallyEquals(member.getAttribute(WebAppService.ROOT_URL), 200);
+ }
+
+ app.stop();
+
+ // Services have stopped
+ assertFalse(nginx.getAttribute(SoftwareProcess.SERVICE_UP));
+ assertFalse(serverPool.getAttribute(SoftwareProcess.SERVICE_UP));
+ for (Entity member : serverPool.getMembers()) {
+ assertFalse(member.getAttribute(SoftwareProcess.SERVICE_UP));
+ }
+ }
+
+ @Test(groups = "Integration")
+ public void testTwoNginxesGetDifferentPorts() {
+ serverPool = app.createAndManageChild(EntitySpec.create(DynamicCluster.class)
+ .configure("initialSize", 0)
+ .configure(DynamicCluster.FACTORY, new EntityFactory<Entity>() {
+ @Override public Entity newEntity(Map flags, Entity parent) {
+ throw new UnsupportedOperationException();
+ }}));
+
+ NginxController nginx1 = app.createAndManageChild(EntitySpec.create(NginxController.class)
+ .configure("serverPool", serverPool)
+ .configure("domain", "localhost")
+ .configure("port", "14000+"));
+
+ NginxController nginx2 = app.createAndManageChild(EntitySpec.create(NginxController.class)
+ .configure("serverPool", serverPool)
+ .configure("domain", "localhost")
+ .configure("port", "14000+"));
+
+ app.start(ImmutableList.of(localLoc));
+
+ String url1 = nginx1.getAttribute(NginxController.ROOT_URL);
+ String url2 = nginx2.getAttribute(NginxController.ROOT_URL);
+
+ assertTrue(url1.contains(":1400"), url1);
+ assertTrue(url2.contains(":1400"), url2);
+ assertNotEquals(url1, url2, "Two nginxs should listen on different ports, not both on "+url1);
+
+ // Nginx has started
+ assertAttributeEqualsEventually(nginx1, SoftwareProcess.SERVICE_UP, true);
+ assertAttributeEqualsEventually(nginx2, SoftwareProcess.SERVICE_UP, true);
+
+ // Nginx reachable (returning default 404)
+ assertHttpStatusCodeEventuallyEquals(url1, 404);
+ assertHttpStatusCodeEventuallyEquals(url2, 404);
+ }
+
+ /** Test that site access does not fail even while nginx is reloaded */
+ // FIXME test disabled -- reload isn't a problem, but #365 is
+ @Test(enabled = false, groups = "Integration")
+ public void testServiceContinuity() throws Exception {
+ serverPool = app.createAndManageChild(EntitySpec.create(DynamicCluster.class)
+ .configure(DynamicCluster.MEMBER_SPEC, EntitySpec.create(JBoss7Server.class))
+ .configure("initialSize", 1)
+ .configure(JavaWebAppService.ROOT_WAR, getTestWar()));
+
+ nginx = app.createAndManageChild(EntitySpec.create(NginxController.class)
+ .configure("serverPool", serverPool));
+
+ app.start(ImmutableList.of(localLoc));
+
+ Asserts.succeedsEventually(new Runnable() {
+ public void run() {
+ for (Entity member : serverPool.getMembers()) {
+ assertHttpStatusCodeEquals(member.getAttribute(WebAppService.ROOT_URL), 200);
+ }
+ assertHttpStatusCodeEquals(nginx.getAttribute(WebAppService.ROOT_URL), 200);
+ }});
+
+ WebAppMonitor monitor = new WebAppMonitor(nginx.getAttribute(WebAppService.ROOT_URL))
+ .logFailures(log)
+ .delayMillis(0);
+ Thread t = new Thread(monitor);
+ t.start();
+
+ try {
+ Thread.sleep(1*1000);
+ log.info("service continuity test, startup, "+monitor.getAttempts()+" requests made");
+ monitor.assertAttemptsMade(10, "startup").assertNoFailures("startup").resetCounts();
+
+ for (int i=0; i<20; i++) {
+ nginx.reload();
+ Thread.sleep(500);
+ log.info("service continuity test, iteration "+i+", "+monitor.getAttempts()+" requests made");
+ monitor.assertAttemptsMade(10, "reloaded").assertNoFailures("reloaded").resetCounts();
+ }
+
+ } finally {
+ t.interrupt();
+ }
+
+ app.stop();
+
+ // Services have stopped
+ assertFalse(nginx.getAttribute(SoftwareProcess.SERVICE_UP));
+ assertFalse(serverPool.getAttribute(SoftwareProcess.SERVICE_UP));
+ for (Entity member : serverPool.getMembers()) {
+ assertFalse(member.getAttribute(SoftwareProcess.SERVICE_UP));
+ }
+ }
+
+ // FIXME test disabled -- issue #365
+ /*
+ * This currently makes no assertions, but writes out the number of sequential reqs per sec
+ * supported with nginx and jboss.
+ * <p>
+ * jboss is (now) steady, at 6k+, since we close the connections in HttpTestUtils.getHttpStatusCode.
+ * but nginx still hits problems, after about 15k reqs, something is getting starved in nginx.
+ */
+ @Test(enabled=false, groups = "Integration")
+ public void testContinuityNginxAndJboss() throws Exception {
+ serverPool = app.createAndManageChild(EntitySpec.create(DynamicCluster.class)
+ .configure(DynamicCluster.MEMBER_SPEC, EntitySpec.create(JBoss7Server.class))
+ .configure("initialSize", 1)
+ .configure(JavaWebAppService.ROOT_WAR, getTestWar()));
+
+ nginx = app.createAndManageChild(EntitySpec.create(NginxController.class)
+ .configure("serverPool", serverPool));
+
+ app.start(ImmutableList.of(localLoc));
+
+ final String nginxUrl = nginx.getAttribute(WebAppService.ROOT_URL);
+
+ Asserts.succeedsEventually(new Runnable() {
+ public void run() {
+ for (Entity member : serverPool.getMembers()) {
+ String jbossUrl = member.getAttribute(WebAppService.ROOT_URL);
+ assertHttpStatusCodeEquals(jbossUrl, 200);
+ }
+ assertHttpStatusCodeEquals(nginxUrl, 200);
+ }});
+
+ final String jbossUrl = Iterables.get(serverPool.getMembers(), 0).getAttribute(WebAppService.ROOT_URL);
+
+ Thread t = new Thread(new Runnable() {
+ public void run() {
+ long lastReportTime = System.currentTimeMillis();
+ int num = 0;
+ while (true) {
+ try {
+ num++;
+ int code = HttpTestUtils.getHttpStatusCode(nginxUrl);
+ if (code!=200) log.info("NGINX GOT: "+code);
+ else log.debug("NGINX GOT: "+code);
+ if (System.currentTimeMillis()>=lastReportTime+1000) {
+ log.info("NGINX DID "+num+" requests in last "+(System.currentTimeMillis()-lastReportTime)+"ms");
+ num=0;
+ lastReportTime = System.currentTimeMillis();
+ }
+ } catch (Exception e) {
+ log.info("NGINX GOT: "+e);
+ }
+ }
+ }});
+ t.start();
+
+ Thread t2 = new Thread(new Runnable() {
+ public void run() {
+ long lastReportTime = System.currentTimeMillis();
+ int num = 0;
+ while (true) {
+ try {
+ num++;
+ int code = HttpTestUtils.getHttpStatusCode(jbossUrl);
+ if (code!=200) log.info("JBOSS GOT: "+code);
+ else log.debug("JBOSS GOT: "+code);
+ if (System.currentTimeMillis()>=1000+lastReportTime) {
+ log.info("JBOSS DID "+num+" requests in last "+(System.currentTimeMillis()-lastReportTime)+"ms");
+ num=0;
+ lastReportTime = System.currentTimeMillis();
+ }
+ } catch (Exception e) {
+ log.info("JBOSS GOT: "+e);
+ }
+ }
+ }});
+ t2.start();
+
+ t2.join();
+ }
+
+ /**
+ * Test that the Nginx proxy starts up and sets SERVICE_UP correctly.
+ */
+ @Test(groups = "Integration")
+ public void testCanRestart() {
+ nginx = app.createAndManageChild(EntitySpec.create(NginxController.class)
+ .configure("serverPool", serverPool)
+ .configure("domain", "localhost")
+ .configure("portNumberSensor", WebAppService.HTTP_PORT));
+
+ app.start(ImmutableList.of(localLoc));
+
+ // App-servers and nginx has started
+ Asserts.succeedsEventually(new Runnable() {
+ public void run() {
+ assertTrue(nginx.getAttribute(SoftwareProcess.SERVICE_UP));
+ }});
+
+ log.info("started, will restart soon");
+ Time.sleep(Duration.ONE_SECOND);
+
+ nginx.restart();
+
+ Time.sleep(Duration.ONE_SECOND);
+ Asserts.succeedsEventually(new Runnable() {
+ public void run() {
+ assertTrue(nginx.getAttribute(SoftwareProcess.SERVICE_UP));
+ }});
+ log.info("restarted and got service up");
+ }
+
+// public static void main(String[] args) {
+// NginxIntegrationTest t = new NginxIntegrationTest();
+// t.setup();
+// t.testCanRestart();
+// t.shutdown();
+// }
+
+}