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 B0C3118FC9 for ; Wed, 29 Jul 2015 15:35:36 +0000 (UTC) Received: (qmail 89671 invoked by uid 500); 29 Jul 2015 15:35:36 -0000 Delivered-To: apmail-brooklyn-commits-archive@brooklyn.apache.org Received: (qmail 89646 invoked by uid 500); 29 Jul 2015 15:35:36 -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 89637 invoked by uid 99); 29 Jul 2015 15:35:36 -0000 Received: from Unknown (HELO spamd2-us-west.apache.org) (209.188.14.142) by apache.org (qpsmtpd/0.29) with ESMTP; Wed, 29 Jul 2015 15:35:36 +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 176AE1A8B54 for ; Wed, 29 Jul 2015 15:35:36 +0000 (UTC) X-Virus-Scanned: Debian amavisd-new at spamd2-us-west.apache.org X-Spam-Flag: NO X-Spam-Score: 1.801 X-Spam-Level: * X-Spam-Status: No, score=1.801 tagged_above=-999 required=6.31 tests=[KAM_ASCII_DIVIDERS=0.8, KAM_LAZY_DOMAIN_SECURITY=1, T_FILL_THIS_FORM_SHORT=0.01, T_RP_MATCHES_RCVD=-0.01, URIBL_BLOCKED=0.001] 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 W9D_-lDkvnMZ for ; Wed, 29 Jul 2015 15:35:30 +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 AED4443DEB for ; Wed, 29 Jul 2015 15:35:29 +0000 (UTC) Received: (qmail 89421 invoked by uid 99); 29 Jul 2015 15:35:29 -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, 29 Jul 2015 15:35:29 +0000 Received: by git1-us-west.apache.org (ASF Mail Server at git1-us-west.apache.org, from userid 33) id 26EE0DFA0A; Wed, 29 Jul 2015 15:35:29 +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, 29 Jul 2015 15:35:31 -0000 Message-Id: In-Reply-To: References: X-Mailer: ASF-Git Admin Mailer Subject: [3/4] incubator-brooklyn git commit: Add tests for apps double stop Add tests for apps double stop Project: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/repo Commit: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/commit/20a52b2a Tree: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/tree/20a52b2a Diff: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/diff/20a52b2a Branch: refs/heads/master Commit: 20a52b2ab1911b9c85d79614569808edc0574f70 Parents: 79a1847 Author: Svetoslav Neykov Authored: Mon Jul 27 16:28:25 2015 +0300 Committer: Svetoslav Neykov Committed: Wed Jul 29 17:04:23 2015 +0300 ---------------------------------------------------------------------- .../entity/basic/SoftwareProcessEntityTest.java | 257 +++++++++++++++++-- .../rest/resources/ServerResourceTest.java | 76 ++++-- .../rest/resources/ServerShutdownTest.java | 187 ++++++++++++++ .../rest/testing/BrooklynRestApiTest.java | 4 + 4 files changed, 474 insertions(+), 50 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/20a52b2a/software/base/src/test/java/brooklyn/entity/basic/SoftwareProcessEntityTest.java ---------------------------------------------------------------------- diff --git a/software/base/src/test/java/brooklyn/entity/basic/SoftwareProcessEntityTest.java b/software/base/src/test/java/brooklyn/entity/basic/SoftwareProcessEntityTest.java index 70bc416..b770293 100644 --- a/software/base/src/test/java/brooklyn/entity/basic/SoftwareProcessEntityTest.java +++ b/software/base/src/test/java/brooklyn/entity/basic/SoftwareProcessEntityTest.java @@ -18,6 +18,30 @@ */ package brooklyn.entity.basic; +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertFalse; +import static org.testng.Assert.assertTrue; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +import org.jclouds.util.Throwables2; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.testng.Assert; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Iterables; + import brooklyn.config.ConfigKey; import brooklyn.entity.BrooklynAppUnitTestSupport; import brooklyn.entity.Entity; @@ -25,6 +49,8 @@ import brooklyn.entity.basic.SoftwareProcess.RestartSoftwareParameters; import brooklyn.entity.basic.SoftwareProcess.RestartSoftwareParameters.RestartMachineMode; import brooklyn.entity.basic.SoftwareProcess.StopSoftwareParameters; import brooklyn.entity.basic.SoftwareProcess.StopSoftwareParameters.StopMode; +import brooklyn.entity.drivers.BasicEntityDriverManager; +import brooklyn.entity.drivers.ReflectiveEntityDriverFactory; import brooklyn.entity.effector.Effectors; import brooklyn.entity.proxying.EntitySpec; import brooklyn.entity.proxying.ImplementedBy; @@ -33,13 +59,20 @@ import brooklyn.entity.trait.Startable; import brooklyn.event.basic.PortAttributeSensorAndConfigKey; import brooklyn.location.Location; import brooklyn.location.LocationSpec; +import brooklyn.location.MachineLocation; import brooklyn.location.basic.FixedListMachineProvisioningLocation; import brooklyn.location.basic.Locations; +import brooklyn.location.basic.SimulatedLocation; import brooklyn.location.basic.SshMachineLocation; +import brooklyn.management.EntityManager; import brooklyn.management.Task; import brooklyn.management.TaskAdaptable; +import brooklyn.test.Asserts; +import brooklyn.test.EntityTestUtils; +import brooklyn.test.entity.TestApplication; import brooklyn.util.collections.MutableMap; import brooklyn.util.config.ConfigBag; +import brooklyn.util.exceptions.Exceptions; import brooklyn.util.exceptions.PropagatedRuntimeException; import brooklyn.util.net.UserAndHostAndPort; import brooklyn.util.os.Os; @@ -48,28 +81,6 @@ import brooklyn.util.task.Tasks; import brooklyn.util.text.Strings; import brooklyn.util.time.Duration; -import com.google.common.collect.ImmutableList; -import com.google.common.collect.ImmutableSet; -import com.google.common.collect.Iterables; - -import org.jclouds.util.Throwables2; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.testng.Assert; -import org.testng.annotations.BeforeMethod; -import org.testng.annotations.Test; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.Iterator; -import java.util.List; -import java.util.Map; -import java.util.concurrent.TimeUnit; - -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertFalse; -import static org.testng.Assert.assertTrue; - public class SoftwareProcessEntityTest extends BrooklynAppUnitTestSupport { @@ -413,6 +424,133 @@ public class SoftwareProcessEntityTest extends BrooklynAppUnitTestSupport { } @Test + public void testDoubleStopEntity() { + ReflectiveEntityDriverFactory f = ((BasicEntityDriverManager)mgmt.getEntityDriverManager()).getReflectiveDriverFactory(); + f.addClassFullNameMapping(EmptySoftwareProcessDriver.class.getName(), MinimalEmptySoftwareProcessTestDriver.class.getName()); + + // Second stop on SoftwareProcess will return early, while the first stop is still in progress + // This causes the app to shutdown prematurely, leaking machines. + EntityManager emgr = mgmt.getEntityManager(); + EntitySpec appSpec = EntitySpec.create(TestApplication.class); + TestApplication app = emgr.createEntity(appSpec); + emgr.manage(app); + EntitySpec latchEntitySpec = EntitySpec.create(EmptySoftwareProcess.class); + Entity entity = app.createAndManageChild(latchEntitySpec); + + final ReleaseLatchLocation loc = mgmt.getLocationManager().createLocation(LocationSpec.create(ReleaseLatchLocation.class)); + try { + app.start(ImmutableSet.of(loc)); + EntityTestUtils.assertAttributeEquals(entity, Attributes.SERVICE_STATE_ACTUAL, Lifecycle.RUNNING); + + final Task firstStop = entity.invoke(Startable.STOP, ImmutableMap.of()); + // Wait until first task tries to release the location, at this point the entity's reference + // to the location is already cleared. + Asserts.succeedsEventually(new Runnable() { + @Override + public void run() { + assertTrue(loc.isBlocked()); + } + }); + + // Subsequent stops will end quickly - no location to release, + // while the first one is still releasing the machine. + final Task secondStop = entity.invoke(Startable.STOP, ImmutableMap.of());; + Asserts.succeedsEventually(new Runnable() { + @Override + public void run() { + assertTrue(secondStop.isDone()); + } + }); + + // Entity state is STOPPED even though first location is still releasing + EntityTestUtils.assertAttributeEquals(entity, Attributes.SERVICE_STATE_ACTUAL, Lifecycle.STOPPED); + Asserts.succeedsContinually(new Runnable() { + @Override + public void run() { + assertFalse(firstStop.isDone()); + } + }); + + loc.unblock(); + + // After the location is released, first task ends as well. + EntityTestUtils.assertAttributeEquals(entity, Attributes.SERVICE_STATE_ACTUAL, Lifecycle.STOPPED); + Asserts.succeedsEventually(new Runnable() { + @Override + public void run() { + assertTrue(firstStop.isDone()); + } + }); + + } finally { + loc.unblock(); + } + + } + + @Test + public void testDoubleStopApp() { + ReflectiveEntityDriverFactory f = ((BasicEntityDriverManager)mgmt.getEntityDriverManager()).getReflectiveDriverFactory(); + f.addClassFullNameMapping(EmptySoftwareProcessDriver.class.getName(), MinimalEmptySoftwareProcessTestDriver.class.getName()); + + // Second stop on SoftwareProcess will return early, while the first stop is still in progress + // This causes the app to shutdown prematurely, leaking machines. + EntityManager emgr = mgmt.getEntityManager(); + EntitySpec appSpec = EntitySpec.create(TestApplication.class); + final TestApplication app = emgr.createEntity(appSpec); + emgr.manage(app); + EntitySpec latchEntitySpec = EntitySpec.create(EmptySoftwareProcess.class); + final Entity entity = app.createAndManageChild(latchEntitySpec); + + final ReleaseLatchLocation loc = mgmt.getLocationManager().createLocation(LocationSpec.create(ReleaseLatchLocation.class)); + try { + app.start(ImmutableSet.of(loc)); + EntityTestUtils.assertAttributeEquals(entity, Attributes.SERVICE_STATE_ACTUAL, Lifecycle.RUNNING); + + final Task firstStop = app.invoke(Startable.STOP, ImmutableMap.of()); + // Wait until first task tries to release the location, at this point the entity's reference + // to the location is already cleared. + Asserts.succeedsEventually(new Runnable() { + @Override + public void run() { + assertTrue(loc.isBlocked()); + } + }); + + // Subsequent stops will end quickly - no location to release, + // while the first one is still releasing the machine. + final Task secondStop = app.invoke(Startable.STOP, ImmutableMap.of());; + Asserts.succeedsEventually(new Runnable() { + @Override + public void run() { + assertTrue(secondStop.isDone()); + } + }); + + // Since second stop succeeded the app will get unmanaged. + Asserts.succeedsEventually(new Runnable() { + @Override + public void run() { + assertTrue(!Entities.isManaged(entity)); + assertTrue(!Entities.isManaged(app)); + } + }); + + // Unmanage will cancel the first task + Asserts.succeedsEventually(new Runnable() { + @Override + public void run() { + assertTrue(firstStop.isDone()); + } + }); + } finally { + // We still haven't unblocked the location release, but entity is already unmanaged. + // Double STOP on an application could leak locations!!! + loc.unblock(); + } + } + + @Test public void testOpenPortsWithPortRangeConfig() throws Exception { MyService entity = app.createAndManageChild(EntitySpec.create(MyService.class) .configure("http.port", "9999+")); @@ -568,4 +706,79 @@ public class SoftwareProcessEntityTest extends BrooklynAppUnitTestSupport { return (String)getEntity().getConfigRaw(ConfigKeys.newStringConfigKey("salt"), true).or((String)null); } } + + public static class ReleaseLatchLocation extends SimulatedLocation { + private static final long serialVersionUID = 1L; + + private CountDownLatch lock = new CountDownLatch(1); + private volatile boolean isBlocked; + + public void unblock() { + lock.countDown(); + } + @Override + public void release(MachineLocation machine) { + super.release(machine); + try { + isBlocked = true; + lock.await(); + isBlocked = false; + } catch (InterruptedException e) { + throw Exceptions.propagate(e); + } + } + + public boolean isBlocked() { + return isBlocked; + } + + } + + public static class MinimalEmptySoftwareProcessTestDriver implements EmptySoftwareProcessDriver { + + private EmptySoftwareProcessImpl entity; + private Location location; + + public MinimalEmptySoftwareProcessTestDriver(EmptySoftwareProcessImpl entity, Location location) { + this.entity = entity; + this.location = location; + } + + @Override + public Location getLocation() { + return location; + } + + @Override + public EntityLocal getEntity() { + return entity; + } + + @Override + public boolean isRunning() { + return true; + } + + @Override + public void rebind() { + } + + @Override + public void start() { + } + + @Override + public void restart() { + } + + @Override + public void stop() { + } + + @Override + public void kill() { + } + + } + } http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/20a52b2a/usage/rest-server/src/test/java/brooklyn/rest/resources/ServerResourceTest.java ---------------------------------------------------------------------- diff --git a/usage/rest-server/src/test/java/brooklyn/rest/resources/ServerResourceTest.java b/usage/rest-server/src/test/java/brooklyn/rest/resources/ServerResourceTest.java index cdfd501..ca5d860 100644 --- a/usage/rest-server/src/test/java/brooklyn/rest/resources/ServerResourceTest.java +++ b/usage/rest-server/src/test/java/brooklyn/rest/resources/ServerResourceTest.java @@ -23,6 +23,8 @@ import static org.testng.Assert.assertFalse; import static org.testng.Assert.assertNotNull; import static org.testng.Assert.assertTrue; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; import javax.ws.rs.core.MultivaluedMap; @@ -38,24 +40,23 @@ import com.sun.jersey.core.util.MultivaluedMapImpl; import brooklyn.BrooklynVersion; import brooklyn.config.BrooklynProperties; +import brooklyn.entity.basic.EmptySoftwareProcess; +import brooklyn.entity.basic.EmptySoftwareProcessDriver; +import brooklyn.entity.basic.EmptySoftwareProcessImpl; +import brooklyn.entity.proxying.ImplementedBy; import brooklyn.management.ManagementContext; import brooklyn.management.internal.ManagementContextInternal; import brooklyn.rest.domain.HighAvailabilitySummary; import brooklyn.rest.domain.VersionSummary; import brooklyn.rest.testing.BrooklynRestResourceTest; import brooklyn.test.Asserts; +import brooklyn.util.exceptions.Exceptions; @Test(singleThreaded = true) public class ServerResourceTest extends BrooklynRestResourceTest { private static final Logger log = LoggerFactory.getLogger(ServerResourceTest.class); - @Override - @BeforeClass(alwaysRun = true) - public void setUp() throws Exception { - super.setUp(); - } - @Test public void testGetVersion() throws Exception { VersionSummary version = client().resource("/v1/server/version").get(VersionSummary.class); @@ -98,28 +99,6 @@ public class ServerResourceTest extends BrooklynRestResourceTest { client().resource("/v1/server/properties/reload").post(); assertEquals(reloadCount.get(), 1); } - - @Test - public void testShutdown() throws Exception { - assertTrue(getManagementContext().isRunning()); - assertFalse(shutdownListener.isRequested()); - - MultivaluedMap formData = new MultivaluedMapImpl(); - formData.add("requestTimeout", "0"); - formData.add("delayForHttpReturn", "0"); - client().resource("/v1/server/shutdown").entity(formData).post(); - - Asserts.succeedsEventually(new Runnable() { - @Override - public void run() { - assertTrue(shutdownListener.isRequested()); - } - }); - Asserts.succeedsEventually(new Runnable() { - @Override public void run() { - assertFalse(getManagementContext().isRunning()); - }}); - } @Test void testGetConfig() throws Exception { @@ -153,4 +132,45 @@ public class ServerResourceTest extends BrooklynRestResourceTest { } } } + + // Alternatively could reuse a blocking location, see brooklyn.entity.basic.SoftwareProcessEntityTest.ReleaseLatchLocation + @ImplementedBy(StopLatchEntityImpl.class) + public interface StopLatchEntity extends EmptySoftwareProcess { + public void unblock(); + public boolean isBlocked(); + } + + public static class StopLatchEntityImpl extends EmptySoftwareProcessImpl implements StopLatchEntity { + private CountDownLatch lock = new CountDownLatch(1); + private volatile boolean isBlocked; + + @Override + public void unblock() { + lock.countDown(); + } + + @Override + protected void postStop() { + super.preStop(); + try { + isBlocked = true; + lock.await(); + isBlocked = false; + } catch (InterruptedException e) { + throw Exceptions.propagate(e); + } + } + + @Override + public Class getDriverInterface() { + return EmptySoftwareProcessDriver.class; + } + + @Override + public boolean isBlocked() { + return isBlocked; + } + + } + } http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/20a52b2a/usage/rest-server/src/test/java/brooklyn/rest/resources/ServerShutdownTest.java ---------------------------------------------------------------------- diff --git a/usage/rest-server/src/test/java/brooklyn/rest/resources/ServerShutdownTest.java b/usage/rest-server/src/test/java/brooklyn/rest/resources/ServerShutdownTest.java new file mode 100644 index 0000000..061599e --- /dev/null +++ b/usage/rest-server/src/test/java/brooklyn/rest/resources/ServerShutdownTest.java @@ -0,0 +1,187 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package brooklyn.rest.resources; + +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertFalse; +import static org.testng.Assert.assertNull; +import static org.testng.Assert.assertTrue; + +import java.util.concurrent.atomic.AtomicReference; + +import javax.ws.rs.core.MultivaluedMap; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.testng.annotations.AfterClass; +import org.testng.annotations.AfterMethod; +import org.testng.annotations.BeforeClass; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSet; +import com.sun.jersey.core.util.MultivaluedMapImpl; + +import brooklyn.entity.basic.Attributes; +import brooklyn.entity.basic.Entities; +import brooklyn.entity.basic.Lifecycle; +import brooklyn.entity.drivers.BasicEntityDriverManager; +import brooklyn.entity.drivers.ReflectiveEntityDriverFactory; +import brooklyn.entity.proxying.EntitySpec; +import brooklyn.entity.trait.Startable; +import brooklyn.management.EntityManager; +import brooklyn.management.Task; +import brooklyn.rest.resources.ServerResourceTest.StopLatchEntity; +import brooklyn.rest.testing.BrooklynRestResourceTest; +import brooklyn.test.Asserts; +import brooklyn.test.EntityTestUtils; +import brooklyn.test.entity.TestApplication; +import brooklyn.util.exceptions.Exceptions; + +public class ServerShutdownTest extends BrooklynRestResourceTest { + private static final Logger log = LoggerFactory.getLogger(ServerResourceTest.class); + + // Need to initialise the ManagementContext before each test as it is destroyed. + @Override + @BeforeClass(alwaysRun = true) + public void setUp() throws Exception { + } + + @Override + @AfterClass(alwaysRun = true) + public void tearDown() throws Exception { + } + + @Override + @BeforeMethod(alwaysRun = true) + public void setUpMethod() { + setUpJersey(); + super.setUpMethod(); + } + + @AfterMethod(alwaysRun = true) + public void tearDownMethod() { + tearDownJersey(); + destroyManagementContext(); + } + + @Test + public void testShutdown() throws Exception { + assertTrue(getManagementContext().isRunning()); + assertFalse(shutdownListener.isRequested()); + + MultivaluedMap formData = new MultivaluedMapImpl(); + formData.add("requestTimeout", "0"); + formData.add("delayForHttpReturn", "0"); + client().resource("/v1/server/shutdown").entity(formData).post(); + + Asserts.succeedsEventually(new Runnable() { + @Override + public void run() { + assertTrue(shutdownListener.isRequested()); + } + }); + Asserts.succeedsEventually(new Runnable() { + @Override public void run() { + assertFalse(getManagementContext().isRunning()); + }}); + } + + @Test + public void testStopAppThenShutdownAndStopAppsWaitsForFirstStop() throws InterruptedException { + ReflectiveEntityDriverFactory f = ((BasicEntityDriverManager)getManagementContext().getEntityDriverManager()).getReflectiveDriverFactory(); + f.addClassFullNameMapping("brooklyn.entity.basic.EmptySoftwareProcessDriver", "brooklyn.rest.resources.ServerResourceTest$EmptySoftwareProcessTestDriver"); + + // Second stop on SoftwareProcess could return early, while the first stop is still in progress + // This causes the app to shutdown prematurely, leaking machines. + EntityManager emgr = getManagementContext().getEntityManager(); + EntitySpec appSpec = EntitySpec.create(TestApplication.class); + TestApplication app = emgr.createEntity(appSpec); + emgr.manage(app); + EntitySpec latchEntitySpec = EntitySpec.create(StopLatchEntity.class); + final StopLatchEntity entity = app.createAndManageChild(latchEntitySpec); + app.start(ImmutableSet.of(app.newLocalhostProvisioningLocation())); + EntityTestUtils.assertAttributeEquals(entity, Attributes.SERVICE_STATE_ACTUAL, Lifecycle.RUNNING); + + try { + final Task firstStop = app.invoke(Startable.STOP, ImmutableMap.of()); + Asserts.succeedsEventually(new Runnable() { + @Override + public void run() { + assertTrue(entity.isBlocked()); + } + }); + + final AtomicReference shutdownError = new AtomicReference<>(); + // Can't use ExecutionContext as it will be stopped on shutdown + Thread shutdownThread = new Thread() { + @Override + public void run() { + try { + MultivaluedMap formData = new MultivaluedMapImpl(); + formData.add("stopAppsFirst", "true"); + formData.add("shutdownTimeout", "0"); + formData.add("requestTimeout", "0"); + formData.add("delayForHttpReturn", "0"); + client().resource("/v1/server/shutdown").entity(formData).post(); + } catch (Exception e) { + log.error("Shutdown request error", e); + shutdownError.set(e); + throw Exceptions.propagate(e); + } + } + }; + shutdownThread.start(); + + //shutdown must wait until the first stop completes (or time out) + Asserts.succeedsContinually(new Runnable() { + @Override + public void run() { + assertFalse(firstStop.isDone()); + assertEquals(getManagementContext().getApplications().size(), 1); + assertFalse(shutdownListener.isRequested()); + } + }); + + // NOTE test is not fully deterministic. Depending on thread scheduling this will + // execute before or after ServerResource.shutdown does the app stop loop. This + // means that the shutdown code might not see the app at all. In any case though + // the test must succeed. + entity.unblock(); + + Asserts.succeedsEventually(new Runnable() { + @Override + public void run() { + assertTrue(firstStop.isDone()); + assertTrue(shutdownListener.isRequested()); + assertFalse(getManagementContext().isRunning()); + } + }); + + shutdownThread.join(); + assertNull(shutdownError.get(), "Shutdown request error, logged above"); + } finally { + // Be sure we always unblock entity stop even in the case of an exception. + // In the success path the entity is already unblocked above. + entity.unblock(); + } + } + +} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/20a52b2a/usage/rest-server/src/test/java/brooklyn/rest/testing/BrooklynRestApiTest.java ---------------------------------------------------------------------- diff --git a/usage/rest-server/src/test/java/brooklyn/rest/testing/BrooklynRestApiTest.java b/usage/rest-server/src/test/java/brooklyn/rest/testing/BrooklynRestApiTest.java index 7526254..446eef0 100644 --- a/usage/rest-server/src/test/java/brooklyn/rest/testing/BrooklynRestApiTest.java +++ b/usage/rest-server/src/test/java/brooklyn/rest/testing/BrooklynRestApiTest.java @@ -93,6 +93,10 @@ public abstract class BrooklynRestApiTest { @AfterClass public void tearDown() throws Exception { + destroyManagementContext(); + } + + protected void destroyManagementContext() { if (manager!=null) { Entities.destroyAll(manager); manager = null;