Return-Path: X-Original-To: apmail-brooklyn-commits-archive@minotaur.apache.org Delivered-To: apmail-brooklyn-commits-archive@minotaur.apache.org Received: from mail.apache.org (hermes.apache.org [140.211.11.3]) by minotaur.apache.org (Postfix) with SMTP id 1BCCE18690 for ; Wed, 12 Aug 2015 15:56:38 +0000 (UTC) Received: (qmail 68076 invoked by uid 500); 12 Aug 2015 15:56:03 -0000 Delivered-To: apmail-brooklyn-commits-archive@brooklyn.apache.org Received: (qmail 68050 invoked by uid 500); 12 Aug 2015 15:56:03 -0000 Mailing-List: contact commits-help@brooklyn.incubator.apache.org; run by ezmlm Precedence: bulk List-Help: List-Unsubscribe: List-Post: List-Id: Reply-To: dev@brooklyn.incubator.apache.org Delivered-To: mailing list commits@brooklyn.incubator.apache.org Received: (qmail 68041 invoked by uid 99); 12 Aug 2015 15:56:03 -0000 Received: from Unknown (HELO spamd2-us-west.apache.org) (209.188.14.142) by apache.org (qpsmtpd/0.29) with ESMTP; Wed, 12 Aug 2015 15:56:03 +0000 Received: from localhost (localhost [127.0.0.1]) by spamd2-us-west.apache.org (ASF Mail Server at spamd2-us-west.apache.org) with ESMTP id E4E5E1A9E40 for ; Wed, 12 Aug 2015 15:56:02 +0000 (UTC) X-Virus-Scanned: Debian amavisd-new at spamd2-us-west.apache.org X-Spam-Flag: NO X-Spam-Score: 1.558 X-Spam-Level: * X-Spam-Status: No, score=1.558 tagged_above=-999 required=6.31 tests=[KAM_ASCII_DIVIDERS=0.8, KAM_LAZY_DOMAIN_SECURITY=1, RCVD_IN_MSPIKE_H3=-0.01, RCVD_IN_MSPIKE_WL=-0.01, RP_MATCHES_RCVD=-0.222] autolearn=disabled Received: from mx1-us-east.apache.org ([10.40.0.8]) by localhost (spamd2-us-west.apache.org [10.40.0.9]) (amavisd-new, port 10024) with ESMTP id t0ZIMRhySyaQ for ; Wed, 12 Aug 2015 15:55:21 +0000 (UTC) Received: from mail.apache.org (hermes.apache.org [140.211.11.3]) by mx1-us-east.apache.org (ASF Mail Server at mx1-us-east.apache.org) with SMTP id A8DD342B4B for ; Wed, 12 Aug 2015 15:55:20 +0000 (UTC) Received: (qmail 66278 invoked by uid 99); 12 Aug 2015 15:55:20 -0000 Received: from git1-us-west.apache.org (HELO git1-us-west.apache.org) (140.211.11.23) by apache.org (qpsmtpd/0.29) with ESMTP; Wed, 12 Aug 2015 15:55:20 +0000 Received: by git1-us-west.apache.org (ASF Mail Server at git1-us-west.apache.org, from userid 33) id 1238FE3619; Wed, 12 Aug 2015 15:55:20 +0000 (UTC) Content-Type: text/plain; charset="us-ascii" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit From: aledsage@apache.org To: commits@brooklyn.incubator.apache.org Date: Wed, 12 Aug 2015 15:55:23 -0000 Message-Id: <330c9d520b7647f99361f4e103a526cf@git.apache.org> In-Reply-To: <86a4cb0cc0ac4b3c8aa5cd4b2a09ae2e@git.apache.org> References: <86a4cb0cc0ac4b3c8aa5cd4b2a09ae2e@git.apache.org> X-Mailer: ASF-Git Admin Mailer Subject: [05/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/nginx/NginxLightIntegrationTest.java ---------------------------------------------------------------------- diff --git a/software/webapp/src/test/java/org/apache/brooklyn/entity/proxy/nginx/NginxLightIntegrationTest.java b/software/webapp/src/test/java/org/apache/brooklyn/entity/proxy/nginx/NginxLightIntegrationTest.java new file mode 100644 index 0000000..ed1d47a --- /dev/null +++ b/software/webapp/src/test/java/org/apache/brooklyn/entity/proxy/nginx/NginxLightIntegrationTest.java @@ -0,0 +1,75 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.brooklyn.entity.proxy.nginx; + +import static org.testng.Assert.assertEquals; + +import java.net.URL; +import java.util.Map; + +import org.apache.brooklyn.entity.proxy.StubAppServer; +import org.apache.brooklyn.entity.proxy.nginx.NginxController; +import org.testng.annotations.Test; + +import brooklyn.entity.BrooklynAppUnitTestSupport; +import brooklyn.entity.Entity; +import brooklyn.entity.basic.Attributes; +import brooklyn.entity.basic.BasicConfigurableEntityFactory; +import brooklyn.entity.basic.EntityFactory; +import brooklyn.entity.group.DynamicCluster; +import brooklyn.entity.proxying.EntitySpec; +import brooklyn.location.basic.LocalhostMachineProvisioningLocation; +import brooklyn.test.Asserts; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.Maps; + +public class NginxLightIntegrationTest extends BrooklynAppUnitTestSupport { + + private NginxController nginx; + private DynamicCluster cluster; + + // FIXME Fails because getting addEntity callback for group members while nginx is still starting, + // so important nginx fields are still null. Therefore get NPE for cluster members, and thus targets + // is of size zero. + @Test(groups = {"Integration", "WIP"}) + public void testNginxTargetsMatchesClusterMembers() { + EntityFactory serverFactory = new BasicConfigurableEntityFactory(StubAppServer.class); + final DynamicCluster cluster = app.createAndManageChild(EntitySpec.create(DynamicCluster.class) + .configure("initialSize", 2) + .configure("factory", serverFactory)); + + nginx = app.createAndManageChild(EntitySpec.create(NginxController.class) + .configure("serverPool", cluster) + .configure("domain", "localhost")); + + app.start(ImmutableList.of(new LocalhostMachineProvisioningLocation())); + + // Wait for url-mapping to update its TARGET_ADDRESSES (through async subscription) + Asserts.succeedsEventually(new Runnable() { + @Override public void run() { + Map expectedTargets = Maps.newLinkedHashMap(); + for (Entity member : cluster.getMembers()) { + expectedTargets.put(member, member.getAttribute(Attributes.HOSTNAME)+":"+member.getAttribute(Attributes.HTTP_PORT)); + } + assertEquals(nginx.getAttribute(NginxController.SERVER_POOL_TARGETS).size(), 2); + assertEquals(nginx.getAttribute(NginxController.SERVER_POOL_TARGETS), expectedTargets); + }}); + } +} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/77dff880/software/webapp/src/test/java/org/apache/brooklyn/entity/proxy/nginx/NginxRebindIntegrationTest.java ---------------------------------------------------------------------- diff --git a/software/webapp/src/test/java/org/apache/brooklyn/entity/proxy/nginx/NginxRebindIntegrationTest.java b/software/webapp/src/test/java/org/apache/brooklyn/entity/proxy/nginx/NginxRebindIntegrationTest.java new file mode 100644 index 0000000..353790d --- /dev/null +++ b/software/webapp/src/test/java/org/apache/brooklyn/entity/proxy/nginx/NginxRebindIntegrationTest.java @@ -0,0 +1,295 @@ +/* + * 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.assertEquals; + +import java.util.List; +import java.util.Map; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +import org.apache.brooklyn.entity.proxy.nginx.NginxController; +import org.apache.brooklyn.entity.proxy.nginx.UrlMapping; +import org.apache.brooklyn.entity.proxy.nginx.UrlRewriteRule; +import org.apache.brooklyn.entity.webapp.tomcat.Tomcat8Server; +import org.apache.brooklyn.management.ManagementContext; +import org.apache.brooklyn.test.EntityTestUtils; +import org.apache.brooklyn.test.TestResourceUnavailableException; +import org.apache.brooklyn.test.WebAppMonitor; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.testng.annotations.AfterMethod; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +import brooklyn.entity.Entity; +import brooklyn.entity.Group; +import brooklyn.entity.basic.BasicGroup; +import brooklyn.entity.basic.Entities; +import brooklyn.entity.basic.Lifecycle; +import brooklyn.entity.basic.SoftwareProcess; +import brooklyn.entity.group.DynamicCluster; +import brooklyn.entity.proxying.EntitySpec; +import brooklyn.entity.rebind.RebindOptions; +import brooklyn.entity.rebind.RebindTestFixtureWithApp; +import brooklyn.location.LocationSpec; +import brooklyn.location.basic.LocalhostMachineProvisioningLocation; +import brooklyn.test.Asserts; + +import com.google.common.base.Predicates; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Iterables; + +/** + * Test the operation of the {@link NginxController} class. + */ +public class NginxRebindIntegrationTest extends RebindTestFixtureWithApp { + + private static final Logger LOG = LoggerFactory.getLogger(NginxRebindIntegrationTest.class); + + private LocalhostMachineProvisioningLocation localhostProvisioningLocation; + private List webAppMonitors = new CopyOnWriteArrayList(); + private ExecutorService executor; + + @Override + protected boolean useLiveManagementContext() { + // For Aled, the test failed without own ~/.brooklyn/brooklyn.properties. + // Suspect that was caused by local environment, with custom brooklyn.ssh.config.scriptHeader + // to set things like correct Java on path. + return true; + } + + @BeforeMethod(alwaysRun=true) + public void setUp() throws Exception { + super.setUp(); + localhostProvisioningLocation = origManagementContext.getLocationManager().createLocation(LocationSpec.create(LocalhostMachineProvisioningLocation.class)); + executor = Executors.newCachedThreadPool(); + } + + public String getTestWar() { + TestResourceUnavailableException.throwIfResourceUnavailable(getClass(), "/hello-world.war"); + return "classpath://hello-world.war"; + } + + @AfterMethod(alwaysRun=true) + public void tearDown() throws Exception { + for (WebAppMonitor monitor : webAppMonitors) { + monitor.terminate(); + } + webAppMonitors.clear(); + if (executor != null) executor.shutdownNow(); + super.tearDown(); + } + + private WebAppMonitor newWebAppMonitor(String url, int expectedResponseCode) { + WebAppMonitor monitor = new WebAppMonitor(url) +// .delayMillis(0) FIXME Re-enable to fast polling + .expectedResponseCode(expectedResponseCode) + .logFailures(LOG); + webAppMonitors.add(monitor); + executor.execute(monitor); + return monitor; + } + + /** + * Test can rebind to the simplest possible nginx configuration (i.e. no server pool). + */ + @Test(groups = "Integration") + public void testRebindsWithEmptyServerPool() throws Exception { + + // Set up nginx with a server pool + DynamicCluster origServerPool = origApp.createAndManageChild(EntitySpec.create(DynamicCluster.class) + .configure(DynamicCluster.MEMBER_SPEC, EntitySpec.create(Tomcat8Server.class)) + .configure("initialSize", 0)); + + NginxController origNginx = origApp.createAndManageChild(EntitySpec.create(NginxController.class) + .configure("serverPool", origServerPool) + .configure("domain", "localhost")); + + // Start the app, and ensure reachable; start polling the URL + origApp.start(ImmutableList.of(localhostProvisioningLocation)); + + String rootUrl = origNginx.getAttribute(NginxController.ROOT_URL); + int nginxPort = origNginx.getAttribute(NginxController.PROXY_HTTP_PORT); + + assertHttpStatusCodeEventuallyEquals(rootUrl, 404); + WebAppMonitor monitor = newWebAppMonitor(rootUrl, 404); + final String origConfigFile = origNginx.getConfigFile(); + + newApp = rebind(RebindOptions.create().terminateOrigManagementContext(true)); + final NginxController newNginx = (NginxController) Iterables.find(newApp.getChildren(), Predicates.instanceOf(NginxController.class)); + + assertEquals(newNginx.getConfigFile(), origConfigFile); + + EntityTestUtils.assertAttributeEqualsEventually(newNginx, NginxController.SERVICE_STATE_ACTUAL, Lifecycle.RUNNING); + assertEquals(newNginx.getAttribute(NginxController.PROXY_HTTP_PORT), (Integer)nginxPort); + assertEquals(newNginx.getAttribute(NginxController.ROOT_URL), rootUrl); + assertEquals(newNginx.getAttribute(NginxController.PROXY_HTTP_PORT), origNginx.getAttribute(NginxController.PROXY_HTTP_PORT)); + assertEquals(newNginx.getConfig(NginxController.STICKY), origNginx.getConfig(NginxController.STICKY)); + + assertAttributeEqualsEventually(newNginx, SoftwareProcess.SERVICE_UP, true); + assertHttpStatusCodeEventuallyEquals(rootUrl, 404); + + assertEquals(monitor.getFailures(), 0); + } + + /** + * Test can rebind with an active server pool. + */ + @Test(groups = "Integration") + public void testRebindsWithoutLosingServerPool() throws Exception { + + // Set up nginx with a server pool + DynamicCluster origServerPool = origApp.createAndManageChild(EntitySpec.create(DynamicCluster.class) + .configure(DynamicCluster.MEMBER_SPEC, EntitySpec.create(Tomcat8Server.class).configure("war", getTestWar())) + .configure("initialSize", 1)); + + NginxController origNginx = origApp.createAndManageChild(EntitySpec.create(NginxController.class) + .configure("serverPool", origServerPool) + .configure("domain", "localhost")); + + // Start the app, and ensure reachable; start polling the URL + origApp.start(ImmutableList.of(localhostProvisioningLocation)); + + String rootUrl = origNginx.getAttribute(NginxController.ROOT_URL); + Tomcat8Server origServer = (Tomcat8Server) Iterables.getOnlyElement(origServerPool.getMembers()); + assertEquals(origNginx.getAttribute(NginxController.SERVER_POOL_TARGETS).keySet(), ImmutableSet.of(origServer)); + + assertHttpStatusCodeEventuallyEquals(rootUrl, 200); + WebAppMonitor monitor = newWebAppMonitor(rootUrl, 200); + final String origConfigFile = origNginx.getConfigFile(); + + // Rebind + newApp = rebind(RebindOptions.create().terminateOrigManagementContext(true)); + ManagementContext newManagementContext = newApp.getManagementContext(); + final NginxController newNginx = (NginxController) Iterables.find(newApp.getChildren(), Predicates.instanceOf(NginxController.class)); + final DynamicCluster newServerPool = (DynamicCluster) newManagementContext.getEntityManager().getEntity(origServerPool.getId()); + final Tomcat8Server newServer = (Tomcat8Server) Iterables.getOnlyElement(newServerPool.getMembers()); + + // Expect continually to have same nginx members; should not lose them temporarily! + Asserts.succeedsContinually(new Runnable() { + public void run() { + Map newNginxMemebers = newNginx.getAttribute(NginxController.SERVER_POOL_TARGETS); + assertEquals(newNginxMemebers.keySet(), ImmutableSet.of(newServer)); + }}); + + + assertAttributeEqualsEventually(newNginx, SoftwareProcess.SERVICE_UP, true); + assertHttpStatusCodeEventuallyEquals(rootUrl, 200); + + assertEquals(newNginx.getConfigFile(), origConfigFile); + + // Check that an update doesn't break things + newNginx.update(); + + assertHttpStatusCodeEquals(rootUrl, 200); + + // Resize new cluster, and confirm change takes affect. + // - Increase size + // - wait for nginx to definitely be updates (TODO nicer way to wait for updated?) + // - terminate old server + // - confirm can still route messages + newServerPool.resize(2); + + Thread.sleep(10*1000); + + newServer.stop(); + + assertHttpStatusCodeEventuallyEquals(rootUrl, 200); + + // Check that URLs have been constantly reachable + assertEquals(monitor.getFailures(), 0); + } + + + /** + * Test can rebind to the with server pool and URL remappings. + * NOTE: This requires a redirection from localhost1 to 127.0.0.1 in your /etc/hosts file + */ + @Test(groups = "Integration") + public void testRebindsWithoutLosingUrlMappings() throws Exception { + + // Set up nginx with a url-mapping + Group origUrlMappingsGroup = origApp.createAndManageChild(EntitySpec.create(BasicGroup.class) + .configure("childrenAsMembers", true)); + + DynamicCluster origServerPool = origApp.createAndManageChild(EntitySpec.create(DynamicCluster.class) + .configure(DynamicCluster.MEMBER_SPEC, EntitySpec.create(Tomcat8Server.class).configure("war", getTestWar())) + .configure("initialSize", 1)); + + UrlMapping origMapping = origApp.getManagementContext().getEntityManager().createEntity(EntitySpec.create(UrlMapping.class) + .configure("domain", "localhost1") + .configure("target", origServerPool) + .configure("rewrites", ImmutableList.of(new UrlRewriteRule("/foo/(.*)", "/$1"))) + .parent(origUrlMappingsGroup)); + Entities.manage(origMapping); + + NginxController origNginx = origApp.createAndManageChild(EntitySpec.create(NginxController.class) + .configure("domain", "localhost") + .configure("urlMappings", origUrlMappingsGroup)); + + // Start the app, and ensure reachable; start polling the URL + origApp.start(ImmutableList.of(localhostProvisioningLocation)); + + String mappingGroupUrl = "http://localhost1:"+origNginx.getAttribute(NginxController.PROXY_HTTP_PORT)+"/foo/"; + + assertHttpStatusCodeEventuallyEquals(mappingGroupUrl, 200); + WebAppMonitor monitor = newWebAppMonitor(mappingGroupUrl, 200); + final String origConfigFile = origNginx.getConfigFile(); + + // Create a rebinding + newApp = rebind(RebindOptions.create().terminateOrigManagementContext(true)); + ManagementContext newManagementContext = newApp.getManagementContext(); + final NginxController newNginx = (NginxController) Iterables.find(newApp.getChildren(), Predicates.instanceOf(NginxController.class)); + DynamicCluster newServerPool = (DynamicCluster) newManagementContext.getEntityManager().getEntity(origServerPool.getId()); + Tomcat8Server newServer = (Tomcat8Server) Iterables.getOnlyElement(newServerPool.getMembers()); + + assertAttributeEqualsEventually(newNginx, SoftwareProcess.SERVICE_UP, true); + assertHttpStatusCodeEventuallyEquals(mappingGroupUrl, 200); + + assertEquals(newNginx.getConfigFile(), origConfigFile); + + // Check that an update doesn't break things + newNginx.update(); + + assertHttpStatusCodeEquals(mappingGroupUrl, 200); + + // Resize new cluster, and confirm change takes affect. + // - Increase size + // - wait for nginx to definitely be updates (TODO nicer way to wait for updated?) + // - terminate old server + // - confirm can still route messages + newServerPool.resize(2); + + Thread.sleep(10*1000); + + newServer.stop(); + + assertHttpStatusCodeEquals(mappingGroupUrl, 200); + + // Check that URLs have been constantly reachable + assertEquals(monitor.getFailures(), 0); + } +} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/77dff880/software/webapp/src/test/java/org/apache/brooklyn/entity/proxy/nginx/NginxRebindWithHaIntegrationTest.java ---------------------------------------------------------------------- diff --git a/software/webapp/src/test/java/org/apache/brooklyn/entity/proxy/nginx/NginxRebindWithHaIntegrationTest.java b/software/webapp/src/test/java/org/apache/brooklyn/entity/proxy/nginx/NginxRebindWithHaIntegrationTest.java new file mode 100644 index 0000000..a283bba --- /dev/null +++ b/software/webapp/src/test/java/org/apache/brooklyn/entity/proxy/nginx/NginxRebindWithHaIntegrationTest.java @@ -0,0 +1,183 @@ +/* + * 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 java.net.URL; +import java.util.Collection; +import java.util.List; +import java.util.concurrent.Callable; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +import org.apache.brooklyn.entity.proxy.nginx.NginxController; +import org.apache.brooklyn.entity.webapp.tomcat.TomcatServer; +import org.apache.brooklyn.management.Task; +import org.apache.brooklyn.management.ha.HighAvailabilityMode; +import org.apache.brooklyn.test.EntityTestUtils; +import org.apache.brooklyn.test.TestResourceUnavailableException; +import org.apache.brooklyn.test.WebAppMonitor; +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.Feed; +import brooklyn.entity.basic.Attributes; +import brooklyn.entity.basic.Entities; +import brooklyn.entity.basic.EntityInternal; +import brooklyn.entity.basic.Lifecycle; +import brooklyn.entity.group.DynamicCluster; +import brooklyn.entity.proxying.EntitySpec; +import brooklyn.entity.rebind.RebindTestFixtureWithApp; +import brooklyn.entity.rebind.RebindTestUtils; +import brooklyn.internal.BrooklynFeatureEnablement; +import brooklyn.location.LocationSpec; +import brooklyn.location.basic.LocalhostMachineProvisioningLocation; +import brooklyn.location.basic.SshMachineLocationReuseIntegrationTest.RecordingSshjTool; +import brooklyn.management.internal.LocalManagementContext; +import brooklyn.test.entity.TestApplication; +import brooklyn.util.internal.ssh.SshTool; +import brooklyn.util.net.Networking; +import brooklyn.util.repeat.Repeater; +import brooklyn.util.task.BasicExecutionManager; +import brooklyn.util.time.Duration; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.Iterables; + +/** + * Test the operation of the {@link NginxController} class. + */ +public class NginxRebindWithHaIntegrationTest extends RebindTestFixtureWithApp { + + private static final Logger LOG = LoggerFactory.getLogger(NginxRebindWithHaIntegrationTest.class); + + private List webAppMonitors = new CopyOnWriteArrayList(); + private ExecutorService executor; + private LocalhostMachineProvisioningLocation loc; + + private Boolean feedRegistration; + + @Override + protected boolean useLiveManagementContext() { + // For Aled, the test failed without own ~/.brooklyn/brooklyn.properties. + // Suspect that was caused by local environment, with custom brooklyn.ssh.config.scriptHeader + // to set things like correct Java on path. + return true; + } + + public String getTestWar() { + TestResourceUnavailableException.throwIfResourceUnavailable(getClass(), "/hello-world.war"); + return "classpath://hello-world.war"; + } + + @BeforeMethod(alwaysRun=true) + public void setUp() throws Exception { + super.setUp(); + loc = origManagementContext.getLocationManager().createLocation(LocationSpec.create(LocalhostMachineProvisioningLocation.class) + .configure("address", Networking.getLocalHost()) + .configure(SshTool.PROP_TOOL_CLASS, RecordingSshjTool.class.getName())); + executor = Executors.newCachedThreadPool(); + + feedRegistration = BrooklynFeatureEnablement.isEnabled(BrooklynFeatureEnablement.FEATURE_FEED_REGISTRATION_PROPERTY); + BrooklynFeatureEnablement.setEnablement(BrooklynFeatureEnablement.FEATURE_FEED_REGISTRATION_PROPERTY, true); + } + + @AfterMethod(alwaysRun=true) + public void tearDown() throws Exception { + try { + if (feedRegistration!=null) + BrooklynFeatureEnablement.setEnablement(BrooklynFeatureEnablement.FEATURE_FEED_REGISTRATION_PROPERTY, feedRegistration); + + for (WebAppMonitor monitor : webAppMonitors) { + monitor.terminate(); + } + webAppMonitors.clear(); + if (executor != null) executor.shutdownNow(); + super.tearDown(); + } finally { + RecordingSshjTool.reset(); + } + } + + @Override + protected TestApplication createApp() { + origManagementContext.getHighAvailabilityManager().changeMode(HighAvailabilityMode.MASTER); + return super.createApp(); + } + + @Test(groups = "Integration") + public void testChangeModeFailureStopsTasksButHappyUponResumption() throws Exception { + DynamicCluster origServerPool = origApp.createAndManageChild(EntitySpec.create(DynamicCluster.class) + .configure(DynamicCluster.MEMBER_SPEC, EntitySpec.create(TomcatServer.class).configure("war", getTestWar())) + .configure("initialSize", 1)); + + NginxController origNginx = origApp.createAndManageChild(EntitySpec.create(NginxController.class) + .configure("serverPool", origServerPool) + .configure("domain", "localhost")); + + origApp.start(ImmutableList.of(loc)); + Assert.assertTrue(RecordingSshjTool.connectionCount.get()>0); + + Collection origFeeds = ((EntityInternal)origNginx).feeds().getFeeds(); + LOG.info("feeds before rebind are: "+origFeeds); + Assert.assertTrue(origFeeds.size() >= 1); + + origManagementContext.getRebindManager().forcePersistNow(); + + List> tasksBefore = ((BasicExecutionManager)origManagementContext.getExecutionManager()).getAllTasks(); + LOG.info("tasks before disabling HA, "+tasksBefore.size()+": "+tasksBefore); + Assert.assertFalse(tasksBefore.isEmpty()); + origManagementContext.getHighAvailabilityManager().changeMode(HighAvailabilityMode.DISABLED); + origApp = null; + + Repeater.create().every(Duration.millis(20)).backoffTo(Duration.ONE_SECOND).limitTimeTo(Duration.THIRTY_SECONDS).until(new Callable() { + @Override + public Boolean call() throws Exception { + origManagementContext.getGarbageCollector().gcIteration(); + List> tasksAfter = ((BasicExecutionManager)origManagementContext.getExecutionManager()).getAllTasks(); + LOG.info("tasks after disabling HA, "+tasksAfter.size()+": "+tasksAfter); + return tasksAfter.isEmpty(); + } + }).runRequiringTrue(); + + // disable SSH to localhost to ensure we don't try to ssh while rebinding + + RecordingSshjTool.forbidden.set(true); + newManagementContext = createNewManagementContext(); + newApp = (TestApplication) RebindTestUtils.rebind((LocalManagementContext)newManagementContext, classLoader); + + NginxController newNginx = Iterables.getOnlyElement(Entities.descendants(newApp, NginxController.class)); + + Collection newFeeds = ((EntityInternal)newNginx).feeds().getFeeds(); + LOG.info("feeds after rebind are: "+newFeeds); + Assert.assertTrue(newFeeds.size() >= 1); + + // eventually goes on fire, because we disabled ssh + EntityTestUtils.assertAttributeEqualsEventually(newNginx, Attributes.SERVICE_STATE_ACTUAL, Lifecycle.ON_FIRE); + + // re-enable SSH and it should right itself + RecordingSshjTool.forbidden.set(false); + EntityTestUtils.assertAttributeEqualsEventually(newNginx, Attributes.SERVICE_STATE_ACTUAL, Lifecycle.RUNNING); + } + +} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/77dff880/software/webapp/src/test/java/org/apache/brooklyn/entity/proxy/nginx/NginxUrlMappingIntegrationTest.java ---------------------------------------------------------------------- diff --git a/software/webapp/src/test/java/org/apache/brooklyn/entity/proxy/nginx/NginxUrlMappingIntegrationTest.java b/software/webapp/src/test/java/org/apache/brooklyn/entity/proxy/nginx/NginxUrlMappingIntegrationTest.java new file mode 100644 index 0000000..822c16d --- /dev/null +++ b/software/webapp/src/test/java/org/apache/brooklyn/entity/proxy/nginx/NginxUrlMappingIntegrationTest.java @@ -0,0 +1,528 @@ +/* + * 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 static org.testng.Assert.fail; + +import java.net.Inet4Address; +import java.net.InetAddress; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.apache.brooklyn.entity.proxy.nginx.NginxController; +import org.apache.brooklyn.entity.proxy.nginx.UrlMapping; +import org.apache.brooklyn.entity.proxy.nginx.UrlRewriteRule; +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.entity.webapp.tomcat.Tomcat8Server; +import org.apache.brooklyn.entity.webapp.tomcat.Tomcat8ServerImpl; +import org.apache.brooklyn.management.EntityManager; +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.Entity; +import brooklyn.entity.Group; +import brooklyn.entity.basic.Attributes; +import brooklyn.entity.basic.BasicGroup; +import brooklyn.entity.basic.Entities; +import brooklyn.entity.basic.EntityFactory; +import brooklyn.entity.group.DynamicCluster; +import brooklyn.entity.proxying.EntitySpec; +import brooklyn.location.basic.LocalhostMachineProvisioningLocation; +import brooklyn.test.Asserts; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.Iterables; +import com.google.common.collect.Sets; + +/** + * Test the operation of the {@link NginxController} class, for URL mapped groups (two different pools). + * + * These tests require that /etc/hosts contains some extra entries, such as: + * 127.0.0.1 localhost localhost1 localhost2 localhost3 localhost4 + */ +public class NginxUrlMappingIntegrationTest extends BrooklynAppLiveTestSupport { + + // TODO Make JBoss7Server.deploy wait for the web-app to actually be deployed. + // That may simplify some of the tests, because we can assert some things immediately rather than in a succeedsEventually. + + private static final Logger log = LoggerFactory.getLogger(NginxUrlMappingIntegrationTest.class); + + private NginxController nginx; + private Group urlMappingsGroup; + private EntityManager entityManager; + private LocalhostMachineProvisioningLocation localLoc; + + @BeforeMethod(alwaysRun=true) + @Override + public void setUp() throws Exception { + super.setUp(); + + urlMappingsGroup = app.createAndManageChild(EntitySpec.create(BasicGroup.class) + .configure("childrenAsMembers", true)); + entityManager = app.getManagementContext().getEntityManager(); + + localLoc = new LocalhostMachineProvisioningLocation(); + } + + public String getTestWar() { + TestResourceUnavailableException.throwIfResourceUnavailable(getClass(), "/hello-world.war"); + return "classpath://hello-world.war"; + } + + protected void checkExtraLocalhosts() throws Exception { + Set failedHosts = Sets.newLinkedHashSet(); + List allHosts = ImmutableList.of("localhost", "localhost1", "localhost2", "localhost3", "localhost4"); + for (String host : allHosts) { + try { + InetAddress i = InetAddress.getByName(host); + byte[] b = ((Inet4Address)i).getAddress(); + if (b[0]!=127 || b[1]!=0 || b[2]!=0 || b[3]!=1) { + log.warn("Failed to resolve "+host+" (test will subsequently fail, but looking for more errors first; see subsequent failure for more info): wrong IP "+Arrays.asList(b)); + failedHosts.add(host); + } + } catch (Exception e) { + log.warn("Failed to resolve "+host+" (test will subsequently fail, but looking for more errors first; see subsequent failure for more info): "+e, e); + failedHosts.add(host); + } + } + if (!failedHosts.isEmpty()) { + fail("These tests (in "+this+") require special hostnames to map to 127.0.0.1, in /etc/hosts: "+failedHosts); + } + } + + @Test(groups = "Integration") + public void testUrlMappingServerNameAndPath() throws Exception { + nginx = app.createAndManageChild(EntitySpec.create(NginxController.class) + .configure("urlMappings", urlMappingsGroup)); + + //cluster 0 mounted at localhost1 / + DynamicCluster c0 = app.createAndManageChild(EntitySpec.create(DynamicCluster.class) + .configure("initialSize", 1) + .configure(DynamicCluster.MEMBER_SPEC, EntitySpec.create(Tomcat8Server.class).configure("httpPort", "8100+")) + .configure(JavaWebAppService.ROOT_WAR, getTestWar())); + UrlMapping u0 = entityManager.createEntity(EntitySpec.create(UrlMapping.class) + .configure("domain", "localhost1") + .configure("target", c0) + .parent(urlMappingsGroup)); + Entities.manage(u0); + + //cluster 1 at localhost2 /hello-world/ + DynamicCluster c1 = app.createAndManageChild(EntitySpec.create(DynamicCluster.class) + .configure("initialSize", 1) + .configure(DynamicCluster.MEMBER_SPEC, EntitySpec.create(Tomcat8Server.class).configure("httpPort", "8100+")) + .configure(JavaWebAppService.NAMED_WARS, ImmutableList.of(getTestWar()))); + UrlMapping u1 = entityManager.createEntity(EntitySpec.create(UrlMapping.class) + .configure("domain", "localhost2") + .configure("path", "/hello-world($|/.*)") + .configure("target", c1) + .parent(urlMappingsGroup)); + Entities.manage(u1); + + // cluster 2 at localhost3 /c2/ and mapping /hello/xxx to /hello/new xxx + DynamicCluster c2 = app.createAndManageChild(EntitySpec.create(DynamicCluster.class) + .configure("initialSize", 1) + .configure(DynamicCluster.MEMBER_SPEC, EntitySpec.create(Tomcat8Server.class).configure("httpPort", "8100+"))); + UrlMapping u2 = entityManager.createEntity(EntitySpec.create(UrlMapping.class) + .configure("domain", "localhost3") + .configure("path", "/c2($|/.*)") + .configure("target", c2) + .configure("rewrites", ImmutableList.of(new UrlRewriteRule("(.*/|)(hello/)(.*)", "$1$2new $3").setBreak())) + .parent(urlMappingsGroup)); + Entities.manage(u2); + // FIXME rewrite not a config + + app.start(ImmutableList.of(localLoc)); + final int port = nginx.getAttribute(NginxController.PROXY_HTTP_PORT); + for (Entity member : c2.getMembers()) { + ((Tomcat8Server)member).deploy(getTestWar(), "c2.war"); + } + + Entities.dumpInfo(app); + + // Confirm routes requests to the correct cluster + // Do more than one request for each in-case just lucky with round-robin... + Asserts.succeedsEventually(new Runnable() { + public void run() { + //cluster 0 + for (int i = 0; i < 2; i++) { + HttpTestUtils.assertContentContainsText("http://localhost1:"+port, "Hello"); + HttpTestUtils.assertContentContainsText("http://localhost1:"+port+"/", "Hello"); + HttpTestUtils.assertContentContainsText("http://localhost1:"+port+"/hello/frank", "http://localhost1:"+port+"/hello/frank"); + } + //cluster 1 + for (int i = 0; i < 2; i++) { + HttpTestUtils.assertContentContainsText("http://localhost2:"+port+"/hello-world", "Hello"); + HttpTestUtils.assertContentContainsText("http://localhost2:"+port+"/hello-world/", "Hello"); + HttpTestUtils.assertContentContainsText("http://localhost2:"+port+"/hello-world/hello/bob", "http://localhost2:"+port+"/hello-world/hello/bob"); + } + //cluster 2 + for (int i = 0; i < 2; i++) { + HttpTestUtils.assertContentContainsText("http://localhost3:"+port+"/c2", "Hello"); + HttpTestUtils.assertContentContainsText("http://localhost3:"+port+"/c2/", "Hello"); + HttpTestUtils.assertContentContainsText("http://localhost3:"+port+"/c2/hello/joe", "http://localhost3:"+port+"/c2/hello/new%20joe"); + } + }}); + + //these should *not* be available + HttpTestUtils.assertHttpStatusCodeEquals("http://localhost:"+port+"/", 404); + HttpTestUtils.assertHttpStatusCodeEquals("http://localhost1:"+port+"/hello-world", 404); + HttpTestUtils.assertHttpStatusCodeEquals("http://localhost2:"+port+"/", 404); + HttpTestUtils.assertHttpStatusCodeEquals("http://localhost2:"+port+"/hello-world/notexists", 404); + HttpTestUtils.assertHttpStatusCodeEquals("http://localhost3:"+port+"/", 404); + + // TODO previously said "make sure nginx default welcome page isn't displayed", + // but the assertion only worked because threw exception on 404 trying to read + // stdin of http connection. If reading stderr of http connection, we do see + // "ginx" in the output. Why were we asserting this? Can we just delete it? + // Previous code was: + // Asserts.assertFails { HttpTestUtils.assertContentContainsText([timeout:1], "http://localhost:${port}/", "ginx"); } + } + + @Test(groups = "Integration") + public void testUrlMappingRoutesRequestByPathToCorrectGroup() throws Exception { + DynamicCluster c0 = app.createAndManageChild(EntitySpec.create(DynamicCluster.class) + .configure("initialSize", 1) + .configure(DynamicCluster.MEMBER_SPEC, EntitySpec.create(Tomcat8Server.class).configure("httpPort", "8100+"))); + UrlMapping u0 = entityManager.createEntity(EntitySpec.create(UrlMapping.class) + .configure("domain", "localhost") + .configure("path", "/atC0($|/.*)") + .configure("target", c0) + .parent(urlMappingsGroup)); + Entities.manage(u0); + + DynamicCluster c1 = app.createAndManageChild(EntitySpec.create(DynamicCluster.class) + .configure("initialSize", 1) + .configure(DynamicCluster.MEMBER_SPEC, EntitySpec.create(Tomcat8Server.class).configure("httpPort", "8100+"))); + UrlMapping u1 = entityManager.createEntity(EntitySpec.create(UrlMapping.class) + .configure("domain", "localhost") + .configure("path", "/atC1($|/.*)") + .configure("target", c1) + .parent(urlMappingsGroup)); + Entities.manage(u1); + + nginx = app.createAndManageChild(EntitySpec.create(NginxController.class) + .configure("domain", "localhost") + .configure("port", "8000+") + .configure("portNumberSensor", WebAppService.HTTP_PORT) + .configure("urlMappings", urlMappingsGroup)); + + app.start(ImmutableList.of(localLoc)); + final int port = nginx.getAttribute(NginxController.PROXY_HTTP_PORT); + + for (Entity child : c0.getMembers()) { + ((Tomcat8Server)child).deploy(getTestWar(), "atC0.war"); + } + for (Entity child : c1.getMembers()) { + ((Tomcat8Server)child).deploy(getTestWar(), "atC1.war"); + } + + // Confirm routes requests to the correct cluster + // Do more than one request for each in-case just lucky with round-robin... + Asserts.succeedsEventually(new Runnable() { + public void run() { + for (int i = 0; i < 2; i++) { + HttpTestUtils.assertContentContainsText("http://localhost:"+port+"/atC0", "Hello"); + HttpTestUtils.assertContentContainsText("http://localhost:"+port+"/atC0/", "Hello"); + } + for (int i = 0; i < 2; i++) { + HttpTestUtils.assertContentContainsText("http://localhost:"+port+"/atC1", "Hello"); + HttpTestUtils.assertContentContainsText("http://localhost:"+port+"/atC1/", "Hello"); + } + }}); + } + + @Test(groups = "Integration") + public void testUrlMappingRemovedWhenMappingEntityRemoved() throws Exception { + DynamicCluster c0 = app.createAndManageChild(EntitySpec.create(DynamicCluster.class) + .configure("initialSize", 1) + .configure(DynamicCluster.MEMBER_SPEC, EntitySpec.create(Tomcat8Server.class).configure("httpPort", "8100+")) + .configure(JavaWebAppService.ROOT_WAR, getTestWar())); + UrlMapping u0 = entityManager.createEntity(EntitySpec.create(UrlMapping.class) + .configure("domain", "localhost2") + .configure("target", c0) + .parent(urlMappingsGroup)); + Entities.manage(u0); + + nginx = app.createAndManageChild(EntitySpec.create(NginxController.class) + .configure("domain", "localhost") + .configure("port", "8000+") + .configure("portNumberSensor", WebAppService.HTTP_PORT) + .configure("urlMappings", urlMappingsGroup)); + + app.start(ImmutableList.of(localLoc)); + int port = nginx.getAttribute(NginxController.PROXY_HTTP_PORT); + + // Wait for deployment to be successful + HttpTestUtils.assertHttpStatusCodeEventuallyEquals("http://localhost2:"+port+"/", 200); + + // Now remove mapping; will no longer route requests + Entities.unmanage(u0); + HttpTestUtils.assertHttpStatusCodeEventuallyEquals("http://localhost2:"+port+"/", 404); + } + + @Test(groups = "Integration") + public void testWithCoreClusterAndUrlMappedGroup() throws Exception { + // TODO Should use different wars, so can confirm content from each cluster + // TODO Could also assert on: nginx.getConfigFile() + + checkExtraLocalhosts(); + + DynamicCluster coreCluster = app.createAndManageChild(EntitySpec.create(DynamicCluster.class) + .configure("initialSize", 1) + .configure(DynamicCluster.MEMBER_SPEC, EntitySpec.create(Tomcat8Server.class).configure("httpPort", "8100+")) + .configure(JavaWebAppService.ROOT_WAR, getTestWar())); + + DynamicCluster c1 = app.createAndManageChild(EntitySpec.create(DynamicCluster.class) + .configure("initialSize", 1) + .configure(DynamicCluster.MEMBER_SPEC, EntitySpec.create(Tomcat8Server.class).configure("httpPort", "8100+")) + .configure(JavaWebAppService.NAMED_WARS, ImmutableList.of(getTestWar()))); + UrlMapping u1 = entityManager.createEntity(EntitySpec.create(UrlMapping.class) + .configure("domain", "localhost1") + .configure("target", c1) + .parent(urlMappingsGroup)); + Entities.manage(u1); + + nginx = app.createAndManageChild(EntitySpec.create(NginxController.class) + .configure("serverPool", coreCluster) + .configure("domain", "localhost") + .configure("port", "8000+") + .configure("portNumberSensor", WebAppService.HTTP_PORT) + .configure("urlMappings", urlMappingsGroup)); + + app.start(ImmutableList.of(localLoc)); + final int port = nginx.getAttribute(NginxController.PROXY_HTTP_PORT); + + // check nginx forwards localhost1 to c1, and localhost to core group + Asserts.succeedsEventually(new Runnable() { + public void run() { + HttpTestUtils.assertContentContainsText("http://localhost1:"+port+"/hello-world", "Hello"); + HttpTestUtils.assertHttpStatusCodeEquals("http://localhost1:"+port+"", 404); + + HttpTestUtils.assertContentContainsText("http://localhost:"+port+"", "Hello"); + HttpTestUtils.assertHttpStatusCodeEquals("http://localhost:"+port+"/hello-world", 404); + }}); + } + + @Test(groups = "Integration") + public void testUrlMappingMultipleRewrites() throws Exception { + nginx = app.createAndManageChild(EntitySpec.create(NginxController.class) + .configure("urlMappings", urlMappingsGroup)); + + //cluster 0 mounted at localhost1 / + DynamicCluster c0 = app.createAndManageChild(EntitySpec.create(DynamicCluster.class) + .configure("initialSize", 1) + .configure(DynamicCluster.MEMBER_SPEC, EntitySpec.create(Tomcat8Server.class).configure("httpPort", "8100+")) + .configure(JavaWebAppService.ROOT_WAR, getTestWar())); + UrlMapping u0 = entityManager.createEntity(EntitySpec.create(UrlMapping.class) + .configure("domain", "localhost1") + .configure("target", c0) + .parent(urlMappingsGroup)); + u0.addRewrite("/goodbye/al(.*)", "/hello/al$1"); + u0.addRewrite(new UrlRewriteRule("/goodbye(|/.*)$", "/hello$1").setBreak()); + u0.addRewrite("(.*)/hello/al(.*)", "$1/hello/Big Al$2"); + u0.addRewrite("/hello/an(.*)", "/hello/Sir An$1"); + Entities.manage(u0); + + app.start(ImmutableList.of(localLoc)); + final int port = nginx.getAttribute(NginxController.PROXY_HTTP_PORT); + + // Confirm routes requests to the correct cluster + Asserts.succeedsEventually(new Runnable() { + public void run() { + // health check + HttpTestUtils.assertContentContainsText("http://localhost1:"+port+"", "Hello"); + HttpTestUtils.assertContentContainsText("http://localhost1:"+port+"/hello/frank", "http://localhost1:"+port+"/hello/frank"); + + // goodbye rewritten to hello + HttpTestUtils.assertContentContainsText("http://localhost1:"+port+"/goodbye/frank", "http://localhost1:"+port+"/hello/frank"); + // hello al rewritten to hello Big Al + HttpTestUtils.assertContentContainsText("http://localhost1:"+port+"/hello/aled", "http://localhost1:"+port+"/hello/Big%20Aled"); + // hello andrew rewritten to hello Sir Andrew + HttpTestUtils.assertContentContainsText("http://localhost1:"+port+"/hello/andrew", "http://localhost1:"+port+"/hello/Sir%20Andrew"); + + // goodbye alex rewritten to hello Big Alex (two rewrites) + HttpTestUtils.assertContentContainsText("http://localhost1:"+port+"/goodbye/alex", "http://localhost1:"+port+"/hello/Big%20Alex"); + // but goodbye andrew rewritten only to hello Andrew -- test the "break" logic above (won't continue rewriting) + HttpTestUtils.assertContentContainsText("http://localhost1:"+port+"/goodbye/andrew", "http://localhost1:"+port+"/hello/andrew"); + + // al rewrite can be anywhere + HttpTestUtils.assertContentContainsText("http://localhost1:"+port+"/hello/hello/alex", "http://localhost1:"+port+"/hello/hello/Big%20Alex"); + // but an rewrite must be at beginning + HttpTestUtils.assertContentContainsText("http://localhost1:"+port+"/hello/hello/andrew", "http://localhost1:"+port+"/hello/hello/andrew"); + }}); + } + + @Test(groups = "Integration") + public void testUrlMappingGroupRespondsToScaleOut() throws Exception { + checkExtraLocalhosts(); + + nginx = app.createAndManageChild(EntitySpec.create(NginxController.class) + .configure("domain", "localhost") + .configure("port", "8000+") + .configure("portNumberSensor", WebAppService.HTTP_PORT) + .configure("urlMappings", urlMappingsGroup)); + + final DynamicCluster c1 = app.createAndManageChild(EntitySpec.create(DynamicCluster.class) + .configure("initialSize", 1) + .configure(DynamicCluster.MEMBER_SPEC, EntitySpec.create(Tomcat8Server.class).configure("httpPort", "8100+")) + .configure(JavaWebAppService.ROOT_WAR, getTestWar())); + final UrlMapping u1 = entityManager.createEntity(EntitySpec.create(UrlMapping.class) + .configure("domain", "localhost1") + .configure("target", c1) + .parent(urlMappingsGroup)); + Entities.manage(u1); + + app.start(ImmutableList.of(localLoc)); + int port = nginx.getAttribute(NginxController.PROXY_HTTP_PORT); + + Entity c1jboss = Iterables.getOnlyElement(c1.getMembers()); + + // Wait for app-server to be responsive, and url-mapping to update its TARGET_ADDRESSES (through async subscription) + Asserts.succeedsEventually(new Runnable() { + public void run() { + // Entities.dumpInfo(app); + assertEquals(u1.getAttribute(UrlMapping.TARGET_ADDRESSES).size(), 1); + }}); + + // check nginx forwards localhost1 to c1 + HttpTestUtils.assertContentEventuallyContainsText("http://localhost1:"+port+"", "Hello"); + + // Resize target cluster of url-mapping + c1.resize(2); + List c1jbosses = new ArrayList(c1.getMembers()); + c1jbosses.remove(c1jboss); + // the unnecessary (Entity) cast is required as a work-around to an IntelliJ issue that prevents Brooklyn from launching from the IDE + Entity c1jboss2 = (Entity)Iterables.getOnlyElement(c1jbosses); + + // TODO Have to wait for new app-server; should fix app-servers to block + // Also wait for TARGET_ADDRESSES to update + assertAppServerRespondsEventually(c1jboss2); + Asserts.succeedsEventually(new Runnable() { + public void run() { + assertEquals(u1.getAttribute(UrlMapping.TARGET_ADDRESSES).size(), 2); + }}); + + // check jboss2 is included in nginx rules + // TODO Should getConfigFile return the current config file, rather than recalculate? + // This assertion isn't good enough to tell if it's been deployed. + final String c1jboss2addr = c1jboss2.getAttribute(Attributes.HOSTNAME)+":"+c1jboss2.getAttribute(Attributes.HTTP_PORT); + Asserts.succeedsEventually(new Runnable() { + public void run() { + String conf = nginx.getConfigFile(); + assertTrue(conf.contains(c1jboss2addr), "could not find "+c1jboss2addr+" in:\n"+conf); + }}); + + // and check forwarding to c1 by nginx still works + for (int i = 0; i < 2; i++) { + HttpTestUtils.assertContentContainsText("http://localhost1:"+port+"", "Hello"); + } + } + + @Test(groups = "Integration") + public void testUrlMappingWithEmptyCoreCluster() throws Exception { + DynamicCluster nullCluster = app.createAndManageChild(EntitySpec.create(DynamicCluster.class) + .configure("initialSize", 0) + .configure("factory", new EntityFactory() { + public Entity newEntity(Map flags, Entity parent) { + throw new UnsupportedOperationException(); + }})); + + DynamicCluster c0 = app.createAndManageChild(EntitySpec.create(DynamicCluster.class) + .configure("initialSize", 1) + .configure(DynamicCluster.MEMBER_SPEC, EntitySpec.create(Tomcat8Server.class).configure("httpPort", "8100+"))); + UrlMapping u0 = entityManager.createEntity(EntitySpec.create(UrlMapping.class) + .configure("domain", "localhost") + .configure("path", "/atC0($|/.*)") + .configure("target", c0) + .parent(urlMappingsGroup)); + Entities.manage(u0); + + nginx = app.createAndManageChild(EntitySpec.create(NginxController.class) + .configure("cluster", nullCluster) + .configure("domain", "localhost") + .configure("port", "8000+") + .configure("portNumberSensor", WebAppService.HTTP_PORT) + .configure("urlMappings", urlMappingsGroup)); + + app.start(ImmutableList.of(localLoc)); + final int port = nginx.getAttribute(NginxController.PROXY_HTTP_PORT); + + for (Entity child : c0.getMembers()) { + ((Tomcat8Server)child).deploy(getTestWar(), "atC0.war"); + } + + // Confirm routes requests to the correct cluster + // Do more than one request for each in-case just lucky with round-robin... + Asserts.succeedsEventually(new Runnable() { + public void run() { + for (int i = 0; i < 2; i++) { + HttpTestUtils.assertContentContainsText("http://localhost:"+port+"/atC0/", "Hello"); + HttpTestUtils.assertContentContainsText("http://localhost:"+port+"/atC0", "Hello"); + } + }}); + + // And empty-core should return 404 + HttpTestUtils.assertHttpStatusCodeEquals("http://localhost:"+port+"", 404); + } + + @Test(groups = "Integration") + public void testDiscardUrlMapping() throws Exception { + //cluster 0 mounted at localhost1 / + DynamicCluster c0 = app.createAndManageChild(EntitySpec.create(DynamicCluster.class) + .configure("initialSize", 1) + .configure(DynamicCluster.MEMBER_SPEC, EntitySpec.create(Tomcat8Server.class).configure("httpPort", "8100+")) + .configure(JavaWebAppService.ROOT_WAR, getTestWar())); + UrlMapping u0 = entityManager.createEntity(EntitySpec.create(UrlMapping.class) + .configure("domain", "localhost1") + .configure("target", c0) + .parent(urlMappingsGroup)); + Entities.manage(u0); + + nginx = app.createAndManageChild(EntitySpec.create(NginxController.class) + .configure("urlMappings", urlMappingsGroup)); + + app.start(ImmutableList.of(localLoc)); + int port = nginx.getAttribute(NginxController.PROXY_HTTP_PORT); + + HttpTestUtils.assertHttpStatusCodeEventuallyEquals("http://localhost1:"+port+"", 200); + + // Discard, and confirm that subsequently get a 404 instead + u0.discard(); + + HttpTestUtils.assertHttpStatusCodeEventuallyEquals("http://localhost1:"+port+"", 404); + } + + private void assertAppServerRespondsEventually(Entity server) { + String hostname = server.getAttribute(Attributes.HOSTNAME); + int port = server.getAttribute(Attributes.HTTP_PORT); + HttpTestUtils.assertHttpStatusCodeEventuallyEquals("http://"+hostname+":"+port, 200); + } +} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/77dff880/software/webapp/src/test/java/org/apache/brooklyn/entity/proxy/nginx/NginxWebClusterEc2LiveTest.java ---------------------------------------------------------------------- diff --git a/software/webapp/src/test/java/org/apache/brooklyn/entity/proxy/nginx/NginxWebClusterEc2LiveTest.java b/software/webapp/src/test/java/org/apache/brooklyn/entity/proxy/nginx/NginxWebClusterEc2LiveTest.java new file mode 100644 index 0000000..76c4bb5 --- /dev/null +++ b/software/webapp/src/test/java/org/apache/brooklyn/entity/proxy/nginx/NginxWebClusterEc2LiveTest.java @@ -0,0 +1,116 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.brooklyn.entity.proxy.nginx; + +import static org.testng.Assert.assertNotNull; + +import java.net.URL; + +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.management.ManagementContext; +import org.apache.brooklyn.test.HttpTestUtils; +import org.apache.brooklyn.test.TestResourceUnavailableException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.testng.annotations.AfterMethod; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +import brooklyn.entity.Entity; +import brooklyn.entity.basic.ApplicationBuilder; +import brooklyn.entity.basic.Entities; +import brooklyn.entity.group.DynamicCluster; +import brooklyn.entity.proxying.EntitySpec; +import brooklyn.location.Location; +import brooklyn.location.MachineLocation; +import brooklyn.location.basic.Machines; +import brooklyn.test.Asserts; +import brooklyn.test.entity.TestApplication; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; + +/** + * Test Nginx proxying a cluster of JBoss7Server entities on AWS for ENGR-1689. + * + * This test is a proof-of-concept for the Brooklyn demo application, with each + * service running on a separate Amazon EC2 instance. + */ +public class NginxWebClusterEc2LiveTest { + private static final Logger LOG = LoggerFactory.getLogger(NginxWebClusterEc2LiveTest.class); + + private TestApplication app; + private NginxController nginx; + private DynamicCluster cluster; + private Location loc; + + @BeforeMethod(alwaysRun = true) + public void setUp() { + ManagementContext managementContext = Entities.newManagementContext( + ImmutableMap.of("brooklyn.location.jclouds.aws-ec2.image-id", "us-east-1/ami-2342a94a")); + + loc = managementContext.getLocationRegistry().resolve("aws-ec2:us-east-1"); + app = ApplicationBuilder.newManagedApp(TestApplication.class, managementContext); + } + + @AfterMethod(alwaysRun = true) + public void shutdown() { + if (app != null) Entities.destroyAll(app.getManagementContext()); + } + + @Test(groups = "Live") + public void testProvisionAwsCluster() { + String warName = "/hello-world.war"; + TestResourceUnavailableException.throwIfResourceUnavailable(getClass(), warName); + URL war = getClass().getResource(warName); + assertNotNull(war, "Unable to locate resource "+warName); + + cluster = app.createAndManageChild(EntitySpec.create(DynamicCluster.class) + .configure(DynamicCluster.MEMBER_SPEC, EntitySpec.create(JBoss7Server.class)) + .configure("initialSize", 2) + .configure("httpPort", 8080) + .configure(JavaWebAppService.ROOT_WAR, war.getPath())); + + nginx = app.createAndManageChild(EntitySpec.create(NginxController.class) + .configure("cluster", cluster) + .configure("domain", "localhost") + .configure("port", 8000) + .configure("portNumberSensor", WebAppService.HTTP_PORT)); + + app.start(ImmutableList.of(loc)); + + Asserts.succeedsEventually(new Runnable() { + public void run() { + // Nginx URL is available + MachineLocation machine = Machines.findUniqueMachineLocation(nginx.getLocations()).get(); + String url = "http://" + machine.getAddress().getHostName() + ":" + nginx.getAttribute(NginxController.PROXY_HTTP_PORT) + "/swf-booking-mvc"; + HttpTestUtils.assertHttpStatusCodeEquals(url, 200); + + // Web-app URL is available + for (Entity member : cluster.getMembers()) { + HttpTestUtils.assertHttpStatusCodeEquals(member.getAttribute(JavaWebAppService.ROOT_URL) + "swf-booking-mvc", 200); + } + }}); + + nginx.stop(); + } +} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/77dff880/software/webapp/src/test/java/org/apache/brooklyn/entity/webapp/AbstractWebAppFixtureIntegrationTest.java ---------------------------------------------------------------------- diff --git a/software/webapp/src/test/java/org/apache/brooklyn/entity/webapp/AbstractWebAppFixtureIntegrationTest.java b/software/webapp/src/test/java/org/apache/brooklyn/entity/webapp/AbstractWebAppFixtureIntegrationTest.java new file mode 100644 index 0000000..f21a14a --- /dev/null +++ b/software/webapp/src/test/java/org/apache/brooklyn/entity/webapp/AbstractWebAppFixtureIntegrationTest.java @@ -0,0 +1,506 @@ +/* + * 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.webapp; + +import static org.apache.brooklyn.test.HttpTestUtils.connectToUrl; +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertFalse; +import static org.testng.Assert.assertNotNull; +import static org.testng.Assert.assertTrue; + +import java.io.File; +import java.io.FileOutputStream; +import java.net.HttpURLConnection; +import java.net.URL; +import java.net.URLConnection; +import java.security.KeyStore; +import java.security.cert.Certificate; +import java.util.List; +import java.util.concurrent.Callable; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; + +import brooklyn.entity.basic.SoftwareProcessDriver; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.testng.annotations.AfterClass; +import org.testng.annotations.AfterMethod; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.DataProvider; +import org.testng.annotations.Test; + +import brooklyn.config.BrooklynProperties; +import brooklyn.entity.Application; +import brooklyn.entity.Entity; +import brooklyn.entity.basic.ApplicationBuilder; +import brooklyn.entity.basic.Entities; +import brooklyn.entity.basic.EntityInternal; +import brooklyn.entity.basic.EntityLocal; +import brooklyn.entity.basic.SoftwareProcess; +import brooklyn.entity.drivers.DriverDependentEntity; +import brooklyn.entity.trait.Startable; +import brooklyn.event.SensorEvent; +import brooklyn.event.SensorEventListener; +import brooklyn.location.LocationSpec; +import brooklyn.location.basic.LocalhostMachineProvisioningLocation; +import brooklyn.test.Asserts; + +import org.apache.brooklyn.entity.webapp.JavaWebAppService; +import org.apache.brooklyn.entity.webapp.JavaWebAppSoftwareProcess; +import org.apache.brooklyn.entity.webapp.WebAppService; +import org.apache.brooklyn.entity.webapp.WebAppServiceMethods; +import org.apache.brooklyn.management.ManagementContext; +import org.apache.brooklyn.management.SubscriptionContext; +import org.apache.brooklyn.management.SubscriptionHandle; +import org.apache.brooklyn.test.EntityTestUtils; +import org.apache.brooklyn.test.HttpTestUtils; +import org.apache.brooklyn.test.TestResourceUnavailableException; + +import brooklyn.test.entity.LocalManagementContextForTests; +import brooklyn.test.entity.TestApplication; +import brooklyn.util.collections.MutableMap; +import brooklyn.util.crypto.FluentKeySigner; +import brooklyn.util.crypto.SecureKeys; +import brooklyn.util.net.Urls; +import brooklyn.util.stream.Streams; +import brooklyn.util.time.Time; + +import com.google.common.base.Stopwatch; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Lists; + +/** + * Test fixture for implementations of JavaWebApp, checking start up and shutdown, + * post request and error count metrics and deploy wars, etc. + */ +public abstract class AbstractWebAppFixtureIntegrationTest { + + private static final Logger log = LoggerFactory.getLogger(AbstractWebAppFixtureIntegrationTest.class); + + // Don't use 8080 since that is commonly used by testing software + public static final String DEFAULT_HTTP_PORT = "7880+"; + + // Port increment for JBoss 6. + public static final int PORT_INCREMENT = 400; + + // The parent application entity for these tests + protected ManagementContext mgmt; + protected List applications = Lists.newArrayList(); + protected SoftwareProcess entity; + protected LocalhostMachineProvisioningLocation loc; + + protected synchronized ManagementContext getMgmt() { + if (mgmt==null) + mgmt = new LocalManagementContextForTests(BrooklynProperties.Factory.newDefault()); + return mgmt; + } + + @BeforeMethod(alwaysRun=true) + public void setUp() throws Exception { + loc = getMgmt().getLocationManager().createLocation(LocationSpec.create(LocalhostMachineProvisioningLocation.class)); + } + + /* + * Use of @DataProvider with test methods gives surprising behaviour with @AfterMethod. + * Unless careful, this causes problems when trying to ensure everything is shutdown cleanly. + * + * Empirically, the rules seem to be... + * - @DataProvider method is called first; it creates a bunch of cases to run + * (all sharing the same instance of WebAppIntegrationTest). + * - It runs the test method for the first time with the first @DataProvider value + * - It runs @AfterMethod + * - It runs the test method for the second @DataProvider value + * - It runs @AfterMethod + * - etc... + * + * Previously shutdownApp was calling stop on each app in applications, and clearing the applications set; + * but then the second invocation of the method was starting an entity that was never stopped. Until recently, + * every test method was also terminating the entity (belt-and-braces, but also brittle for if the method threw + * an exception earlier). When that "extra" termination was removed, it meant the second and subsequent + * entities were never being stopped. + * + * Now we rely on having the test method set the entity field, so we can find out which application instance + * it is and calling stop on just that app + entity. + */ + @AfterMethod(alwaysRun=true) + public void shutdownApp() { + if (entity != null) { + Application app = entity.getApplication(); + if (app != null) Entities.destroy(app); + } + } + + @AfterClass(alwaysRun=true) + public synchronized void shutdownMgmt() { + try { + if (mgmt != null) Entities.destroyAll(mgmt); + } finally { + mgmt = null; + } + } + + public static File createTemporaryKeyStore(String alias, String password) throws Exception { + FluentKeySigner signer = new FluentKeySigner("brooklyn-test").selfsign(); + + KeyStore ks = SecureKeys.newKeyStore(); + ks.setKeyEntry( + alias, + signer.getKey().getPrivate(), + password.toCharArray(), + new Certificate[]{signer.getAuthorityCertificate()}); + + File file = File.createTempFile("test", "keystore"); + FileOutputStream fos = new FileOutputStream(file); + try { + ks.store(fos, password.toCharArray()); + return file; + } finally { + Streams.closeQuietly(fos); + } + } + + /** + * Create a new instance of TestApplication and append it to applications list + * so it can be terminated suitable after each test has run. + * @return + */ + protected TestApplication newTestApplication() { + TestApplication ta = ApplicationBuilder.newManagedApp(TestApplication.class, getMgmt()); + applications.add(ta); + return ta; + } + + /** + * Provides instances of the WebAppServer to test + * (arrays of 1-element array arguments to some of the other methods) + * + * NB annotation must be placed on concrete impl method + * + * TODO combine the data provider here with live integration test + * @see WebAppLiveIntegrationTest#basicEntities() + */ + @DataProvider(name = "basicEntities") + public abstract Object[][] basicEntities(); + + /** + * Checks an entity can start, set SERVICE_UP to true and shutdown again. + */ + @Test(groups = "Integration", dataProvider = "basicEntities") + public void canStartAndStop(final SoftwareProcess entity) { + this.entity = entity; + log.info("test=canStartAndStop; entity="+entity+"; app="+entity.getApplication()); + + Entities.start(entity.getApplication(), ImmutableList.of(loc)); + Asserts.succeedsEventually(MutableMap.of("timeout", 120*1000), new Runnable() { + public void run() { + assertTrue(entity.getAttribute(Startable.SERVICE_UP)); + }}); + + entity.stop(); + assertFalse(entity.getAttribute(Startable.SERVICE_UP)); + } + + /** + * Checks an entity can start, set SERVICE_UP to true and shutdown again. + */ + @Test(groups = "Integration", dataProvider = "basicEntities") + public void testReportsServiceDownWhenKilled(final SoftwareProcess entity) throws Exception { + this.entity = entity; + log.info("test=testReportsServiceDownWithKilled; entity="+entity+"; app="+entity.getApplication()); + + Entities.start(entity.getApplication(), ImmutableList.of(loc)); + EntityTestUtils.assertAttributeEqualsEventually(MutableMap.of("timeout", 120*1000), entity, Startable.SERVICE_UP, true); + + // Stop the underlying entity, but without our entity instance being told! + killEntityBehindBack(entity); + log.info("Killed {} behind mgmt's back, waiting for service up false in mgmt context", entity); + + EntityTestUtils.assertAttributeEqualsEventually(entity, Startable.SERVICE_UP, false); + + log.info("success getting service up false in primary mgmt universe"); + } + + /** + * Stop the given underlying entity, but without our entity instance being told! + */ + protected void killEntityBehindBack(Entity tokill) throws Exception { + ((SoftwareProcessDriver)((DriverDependentEntity) Entities.deproxy(entity)).getDriver()).stop(); + // old method of doing this did some dodgy legacy rebind and failed due to too many dangling refs; above is better in any case + // but TODO we should have some rebind tests for these! + } + + /** + * Checks that an entity correctly sets request and error count metrics by + * connecting to a non-existent URL several times. + */ + @Test(groups = "Integration", dataProvider = "basicEntities") + public void publishesRequestAndErrorCountMetrics(final SoftwareProcess entity) throws Exception { + this.entity = entity; + log.info("test=publishesRequestAndErrorCountMetrics; entity="+entity+"; app="+entity.getApplication()); + + Entities.start(entity.getApplication(), ImmutableList.of(loc)); + + Asserts.succeedsEventually(MutableMap.of("timeout", 10*1000), new Runnable() { + public void run() { + assertTrue(entity.getAttribute(SoftwareProcess.SERVICE_UP)); + }}); + + String url = entity.getAttribute(WebAppService.ROOT_URL) + "does_not_exist"; + + final int n = 10; + for (int i = 0; i < n; i++) { + URLConnection connection = HttpTestUtils.connectToUrl(url); + int status = ((HttpURLConnection) connection).getResponseCode(); + log.info("connection to {} gives {}", url, status); + } + + Asserts.succeedsEventually(MutableMap.of("timeout", 20*1000), new Runnable() { + public void run() { + Integer requestCount = entity.getAttribute(WebAppService.REQUEST_COUNT); + Integer errorCount = entity.getAttribute(WebAppService.ERROR_COUNT); + log.info("req={}, err={}", requestCount, errorCount); + + assertNotNull(errorCount, "errorCount not set yet ("+errorCount+")"); + + // AS 7 seems to take a very long time to report error counts, + // hence not using ==. >= in case error pages include a favicon, etc. + assertEquals(errorCount, (Integer)n); + assertTrue(requestCount >= errorCount); + }}); + } + + /** + * Checks an entity publishes correct requests/second figures and that these figures + * fall to zero after a period of no activity. + */ + @Test(groups = "Integration", dataProvider = "basicEntities") + public void publishesRequestsPerSecondMetric(final SoftwareProcess entity) throws Exception { + this.entity = entity; + log.info("test=publishesRequestsPerSecondMetric; entity="+entity+"; app="+entity.getApplication()); + + Entities.start(entity.getApplication(), ImmutableList.of(loc)); + + log.info("Entity "+entity+" started"); + + try { + // reqs/sec initially zero + log.info("Waiting for initial avg-requests to be zero..."); + Asserts.succeedsEventually(MutableMap.of("timeout", 20*1000), new Runnable() { + public void run() { + Double activityValue = entity.getAttribute(WebAppService.REQUESTS_PER_SECOND_IN_WINDOW); + assertNotNull(activityValue, "activity not set yet "+activityValue+")"); + assertEquals(activityValue.doubleValue(), 0.0d, 0.000001d); + }}); + + // apply workload on 1 per sec; reqs/sec should update + Asserts.succeedsEventually(MutableMap.of("timeout", 30*1000), new Callable() { + public Void call() throws Exception { + String url = entity.getAttribute(WebAppService.ROOT_URL) + "does_not_exist"; + final int desiredMsgsPerSec = 10; + + Stopwatch stopwatch = Stopwatch.createStarted(); + final AtomicInteger reqsSent = new AtomicInteger(); + final Integer preRequestCount = entity.getAttribute(WebAppService.REQUEST_COUNT); + + // need to maintain n requests per second for the duration of the window size + log.info("Applying load for "+WebAppServiceMethods.DEFAULT_WINDOW_DURATION); + while (stopwatch.elapsed(TimeUnit.MILLISECONDS) < WebAppServiceMethods.DEFAULT_WINDOW_DURATION.toMilliseconds()) { + long preReqsTime = stopwatch.elapsed(TimeUnit.MILLISECONDS); + for (int i = 0; i < desiredMsgsPerSec; i++) { connectToUrl(url); } + Time.sleep(1000 - (stopwatch.elapsed(TimeUnit.MILLISECONDS)-preReqsTime)); + reqsSent.addAndGet(desiredMsgsPerSec); + } + + Asserts.succeedsEventually(MutableMap.of("timeout", 4000), new Runnable() { + public void run() { + Double avgReqs = entity.getAttribute(WebAppService.REQUESTS_PER_SECOND_IN_WINDOW); + Integer requestCount = entity.getAttribute(WebAppService.REQUEST_COUNT); + + log.info("avg-requests="+avgReqs+"; total-requests="+requestCount); + assertEquals(avgReqs.doubleValue(), (double)desiredMsgsPerSec, 3.0d); + assertEquals(requestCount.intValue(), preRequestCount+reqsSent.get()); + }}); + + return null; + }}); + + // After suitable delay, expect to again get zero msgs/sec + log.info("Waiting for avg-requests to drop to zero, for "+WebAppServiceMethods.DEFAULT_WINDOW_DURATION); + Thread.sleep(WebAppServiceMethods.DEFAULT_WINDOW_DURATION.toMilliseconds()); + + Asserts.succeedsEventually(MutableMap.of("timeout", 10*1000), new Runnable() { + public void run() { + Double avgReqs = entity.getAttribute(WebAppService.REQUESTS_PER_SECOND_IN_WINDOW); + assertNotNull(avgReqs); + assertEquals(avgReqs.doubleValue(), 0.0d, 0.00001d); + }}); + } finally { + entity.stop(); + } + } + + /** + * Tests that we get consecutive events with zero workrate, and with suitably small timestamps between them. + */ + @Test(groups = "Integration", dataProvider = "basicEntities") + @SuppressWarnings("rawtypes") + public void publishesZeroRequestsPerSecondMetricRepeatedly(final SoftwareProcess entity) { + this.entity = entity; + log.info("test=publishesZeroRequestsPerSecondMetricRepeatedly; entity="+entity+"; app="+entity.getApplication()); + + final int MAX_INTERVAL_BETWEEN_EVENTS = 4000; // TomcatServerImpl publishes events every 3000ms so this should be enough overhead + final int NUM_CONSECUTIVE_EVENTS = 3; + + Entities.start(entity.getApplication(), ImmutableList.of(loc)); + + SubscriptionHandle subscriptionHandle = null; + SubscriptionContext subContext = ((EntityInternal)entity).getSubscriptionContext(); + + try { + final List events = new CopyOnWriteArrayList(); + subscriptionHandle = subContext.subscribe(entity, WebAppService.REQUESTS_PER_SECOND_IN_WINDOW, new SensorEventListener() { + public void onEvent(SensorEvent event) { + log.info("publishesRequestsPerSecondMetricRepeatedly.onEvent: {}", event); + events.add(event); + }}); + + + Asserts.succeedsEventually(new Runnable() { + public void run() { + assertTrue(events.size() > NUM_CONSECUTIVE_EVENTS, "events "+events.size()+" > "+NUM_CONSECUTIVE_EVENTS); + long eventTime = 0; + + for (SensorEvent event : events.subList(events.size()-NUM_CONSECUTIVE_EVENTS, events.size())) { + assertEquals(event.getSource(), entity); + assertEquals(event.getSensor(), WebAppService.REQUESTS_PER_SECOND_IN_WINDOW); + assertEquals(event.getValue(), 0.0d); + if (eventTime > 0) assertTrue(event.getTimestamp()-eventTime < MAX_INTERVAL_BETWEEN_EVENTS, + "events at "+eventTime+" and "+event.getTimestamp()+" exceeded maximum allowable interval "+MAX_INTERVAL_BETWEEN_EVENTS); + eventTime = event.getTimestamp(); + } + }}); + } finally { + if (subscriptionHandle != null) subContext.unsubscribe(subscriptionHandle); + entity.stop(); + } + } + + /** + * Twins the entities given by basicEntities() with links to WAR files + * they should be able to deploy. Correct deployment can be checked by + * pinging the given URL. + * + * Everything can deploy hello world. Some subclasses deploy add'l apps. + * We're using the simplest hello-world (with no URL mapping) because JBoss 6 does not + * support URL mappings. + */ + @DataProvider(name = "entitiesWithWarAndURL") + public Object[][] entitiesWithWar() { + TestResourceUnavailableException.throwIfResourceUnavailable(getClass(), "/hello-world-no-mapping.war"); + List result = Lists.newArrayList(); + + for (Object[] entity : basicEntities()) { + result.add(new Object[] { + entity[0], + "hello-world-no-mapping.war", + "hello-world-no-mapping/", + "" // no sub-page path + }); + } + + return result.toArray(new Object[][] {}); + } + + /** + * Tests given entity can deploy the given war. Checks given httpURL to confirm success. + */ + @Test(groups = "Integration", dataProvider = "entitiesWithWarAndURL") + public void initialRootWarDeployments(final SoftwareProcess entity, final String war, + final String urlSubPathToWebApp, final String urlSubPathToPageToQuery) { + this.entity = entity; + log.info("test=initialRootWarDeployments; entity="+entity+"; app="+entity.getApplication()); + + URL resource = getClass().getClassLoader().getResource(war); + assertNotNull(resource); + + ((EntityLocal)entity).setConfig(JavaWebAppService.ROOT_WAR, resource.toString()); + Entities.start(entity.getApplication(), ImmutableList.of(loc)); + + //tomcat may need a while to unpack everything + Asserts.succeedsEventually(MutableMap.of("timeout", 60*1000), new Runnable() { + public void run() { + // TODO get this URL from a WAR file entity + HttpTestUtils.assertHttpStatusCodeEquals(Urls.mergePaths(entity.getAttribute(WebAppService.ROOT_URL), urlSubPathToPageToQuery), 200); + + assertEquals(entity.getAttribute(JavaWebAppSoftwareProcess.DEPLOYED_WARS), ImmutableSet.of("/")); + }}); + } + + @Test(groups = "Integration", dataProvider = "entitiesWithWarAndURL") + public void initialNamedWarDeployments(final SoftwareProcess entity, final String war, + final String urlSubPathToWebApp, final String urlSubPathToPageToQuery) { + this.entity = entity; + log.info("test=initialNamedWarDeployments; entity="+entity+"; app="+entity.getApplication()); + + URL resource = getClass().getClassLoader().getResource(war); + assertNotNull(resource); + + ((EntityLocal)entity).setConfig(JavaWebAppService.NAMED_WARS, ImmutableList.of(resource.toString())); + Entities.start(entity.getApplication(), ImmutableList.of(loc)); + + Asserts.succeedsEventually(MutableMap.of("timeout", 60*1000), new Runnable() { + public void run() { + // TODO get this URL from a WAR file entity + HttpTestUtils.assertHttpStatusCodeEquals(Urls.mergePaths(entity.getAttribute(WebAppService.ROOT_URL), urlSubPathToWebApp, urlSubPathToPageToQuery), 200); + }}); + } + + @Test(groups = "Integration", dataProvider = "entitiesWithWarAndURL") + public void testWarDeployAndUndeploy(final JavaWebAppSoftwareProcess entity, final String war, + final String urlSubPathToWebApp, final String urlSubPathToPageToQuery) { + this.entity = entity; + log.info("test=testWarDeployAndUndeploy; entity="+entity+"; app="+entity.getApplication()); + + URL resource = getClass().getClassLoader().getResource(war);; + assertNotNull(resource); + + Entities.start(entity.getApplication(), ImmutableList.of(loc)); + + // Test deploying + entity.deploy(resource.toString(), "myartifactname.war"); + Asserts.succeedsEventually(MutableMap.of("timeout", 60*1000), new Runnable() { + public void run() { + // TODO get this URL from a WAR file entity + HttpTestUtils.assertHttpStatusCodeEquals(Urls.mergePaths(entity.getAttribute(WebAppService.ROOT_URL), "myartifactname/", urlSubPathToPageToQuery), 200); + assertEquals(entity.getAttribute(JavaWebAppSoftwareProcess.DEPLOYED_WARS), ImmutableSet.of("/myartifactname")); + }}); + + // And undeploying + entity.undeploy("/myartifactname"); + Asserts.succeedsEventually(MutableMap.of("timeout", 60*1000), new Runnable() { + public void run() { + // TODO get this URL from a WAR file entity + HttpTestUtils.assertHttpStatusCodeEquals(Urls.mergePaths(entity.getAttribute(WebAppService.ROOT_URL), "myartifactname", urlSubPathToPageToQuery), 404); + assertEquals(entity.getAttribute(JavaWebAppSoftwareProcess.DEPLOYED_WARS), ImmutableSet.of()); + }}); + } +}