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(); +// } + +}
