Repository: incubator-brooklyn Updated Branches: refs/heads/master 05b95f377 -> 1d5480687
Graceful exit on REST shutdown call Instead of calling System.exit, exit the main thread. Project: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/repo Commit: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/commit/b83ef4da Tree: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/tree/b83ef4da Diff: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/diff/b83ef4da Branch: refs/heads/master Commit: b83ef4dab63407fbb8d0f2f02800e9b85723ef5b Parents: efaceed Author: Svetoslav Neykov <[email protected]> Authored: Thu Jul 23 18:54:30 2015 +0300 Committer: Svetoslav Neykov <[email protected]> Committed: Wed Jul 29 17:04:14 2015 +0300 ---------------------------------------------------------------------- usage/cli/src/main/java/brooklyn/cli/Main.java | 105 +++++++++++++------ .../cli/src/test/java/brooklyn/cli/CliTest.java | 5 +- usage/launcher/pom.xml | 7 ++ .../brooklyn/launcher/BrooklynLauncher.java | 39 ++++--- .../brooklyn/launcher/BrooklynWebServer.java | 33 ++++-- .../brooklyn/rest/resources/ServerResource.java | 26 +++-- .../brooklyn/rest/util/ShutdownHandler.java | 23 ++++ .../rest/util/ShutdownHandlerProvider.java | 30 ++++++ .../brooklyn/rest/BrooklynRestApiLauncher.java | 9 +- .../rest/resources/ServerResourceTest.java | 33 ++++-- .../rest/testing/BrooklynRestApiTest.java | 31 ++++-- .../rest/testing/BrooklynRestResourceTest.java | 1 + .../brooklyn/rest/util/TestShutdownHandler.java | 39 +++++++ 13 files changed, 295 insertions(+), 86 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/b83ef4da/usage/cli/src/main/java/brooklyn/cli/Main.java ---------------------------------------------------------------------- diff --git a/usage/cli/src/main/java/brooklyn/cli/Main.java b/usage/cli/src/main/java/brooklyn/cli/Main.java index 80d2b05..2379bdd 100644 --- a/usage/cli/src/main/java/brooklyn/cli/Main.java +++ b/usage/cli/src/main/java/brooklyn/cli/Main.java @@ -19,12 +19,6 @@ package brooklyn.cli; import static com.google.common.base.Preconditions.checkNotNull; -import groovy.lang.GroovyClassLoader; -import groovy.lang.GroovyShell; -import io.airlift.command.Cli; -import io.airlift.command.Cli.CliBuilder; -import io.airlift.command.Command; -import io.airlift.command.Option; import java.io.Console; import java.io.IOException; @@ -33,10 +27,23 @@ import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; import java.util.Arrays; import java.util.Collection; +import java.util.concurrent.Callable; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeoutException; +import java.util.concurrent.atomic.AtomicBoolean; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import com.google.common.annotations.Beta; +import com.google.common.annotations.VisibleForTesting; +import com.google.common.base.Function; +import com.google.common.base.Objects.ToStringHelper; +import com.google.common.base.Stopwatch; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.Iterables; + import brooklyn.BrooklynVersion; import brooklyn.basic.BrooklynTypes; import brooklyn.catalog.BrooklynCatalog; @@ -69,9 +76,11 @@ import brooklyn.launcher.BrooklynLauncher; import brooklyn.launcher.BrooklynServerDetails; import brooklyn.launcher.config.StopWhichAppsOnShutdown; import brooklyn.management.ManagementContext; +import brooklyn.management.Task; import brooklyn.management.ha.HighAvailabilityMode; import brooklyn.management.ha.OsgiManager; import brooklyn.rest.security.PasswordHasher; +import brooklyn.rest.util.ShutdownHandler; import brooklyn.util.ResourceUtils; import brooklyn.util.exceptions.Exceptions; import brooklyn.util.exceptions.FatalConfigurationRuntimeException; @@ -84,14 +93,12 @@ import brooklyn.util.text.Identifiers; import brooklyn.util.text.StringEscapes.JavaStringEscapes; import brooklyn.util.text.Strings; import brooklyn.util.time.Duration; - -import com.google.common.annotations.Beta; -import com.google.common.annotations.VisibleForTesting; -import com.google.common.base.Function; -import com.google.common.base.Objects.ToStringHelper; -import com.google.common.base.Stopwatch; -import com.google.common.collect.ImmutableList; -import com.google.common.collect.Iterables; +import groovy.lang.GroovyClassLoader; +import groovy.lang.GroovyShell; +import io.airlift.command.Cli; +import io.airlift.command.Cli.CliBuilder; +import io.airlift.command.Command; +import io.airlift.command.Option; /** * This class is the primary CLI for brooklyn. @@ -384,6 +391,7 @@ public class Main extends AbstractMain { public Void call() throws Exception { // Configure launcher BrooklynLauncher launcher; + AppShutdownHandler shutdownHandler = new AppShutdownHandler(); failIfArguments(); try { if (log.isDebugEnabled()) log.debug("Invoked launch command {}", this); @@ -440,6 +448,7 @@ public class Main extends AbstractMain { launcher.highAvailabilityMode(highAvailabilityMode); launcher.stopWhichAppsOnShutdown(stopWhichAppsOnShutdownMode); + launcher.shutdownHandler(shutdownHandler); computeAndSetApp(launcher, utils, loader); @@ -478,8 +487,10 @@ public class Main extends AbstractMain { } if (!exitAndLeaveAppsRunningAfterStarting) { - waitAfterLaunch(ctx); + waitAfterLaunch(ctx, shutdownHandler); } + + // will call mgmt.terminate() in BrooklynShutdownHookJob return null; } @@ -687,31 +698,37 @@ public class Main extends AbstractMain { } } - protected void waitAfterLaunch(ManagementContext ctx) throws IOException { + protected void waitAfterLaunch(ManagementContext ctx, AppShutdownHandler shutdownHandler) throws IOException { if (stopOnKeyPress) { // Wait for the user to type a key log.info("Server started. Press return to stop."); - stdin.read(); + // Read in another thread so we can use timeout on the wait. + Task<Void> readTask = ctx.getExecutionManager().submit(new Callable<Void>() { + @Override + public Void call() throws Exception { + stdin.read(); + return null; + } + }); + while (!shutdownHandler.isRequested()) { + try { + readTask.get(Duration.ONE_SECOND); + break; + } catch (TimeoutException e) { + //check if there's a shutdown request + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + throw Exceptions.propagate(e); + } catch (ExecutionException e) { + throw Exceptions.propagate(e); + } + } + log.info("Shutting down applications."); stopAllApps(ctx.getApplications()); } else { // Block forever so that Brooklyn doesn't exit (until someone does cntrl-c or kill) log.info("Launched Brooklyn; will now block until shutdown command received via GUI/API (recommended) or process interrupt."); - waitUntilInterrupted(); - } - } - - protected void waitUntilInterrupted() { - Object mutex = new Object(); - synchronized (mutex) { - try { - while (!Thread.currentThread().isInterrupted()) { - mutex.wait(); - log.debug("Spurious wake in brooklyn Main while waiting for interrupt, how about that!"); - } - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - return; // exit gracefully - } + shutdownHandler.waitOnShutdownRequest(); } } @@ -964,4 +981,26 @@ public class Main extends AbstractMain { protected Class<? extends BrooklynCommand> cliDefaultInfoCommand() { return DefaultInfoCommand.class; } + + public static class AppShutdownHandler implements ShutdownHandler { + private CountDownLatch lock = new CountDownLatch(1); + + @Override + public void onShutdownRequest() { + lock.countDown(); + } + + public boolean isRequested() { + return lock.getCount() == 0; + } + + public void waitOnShutdownRequest() { + try { + lock.await(); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + return; // exit gracefully + } + } + } } http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/b83ef4da/usage/cli/src/test/java/brooklyn/cli/CliTest.java ---------------------------------------------------------------------- diff --git a/usage/cli/src/test/java/brooklyn/cli/CliTest.java b/usage/cli/src/test/java/brooklyn/cli/CliTest.java index a6c6b13..13b27c1 100644 --- a/usage/cli/src/test/java/brooklyn/cli/CliTest.java +++ b/usage/cli/src/test/java/brooklyn/cli/CliTest.java @@ -52,6 +52,7 @@ import org.testng.annotations.Test; import brooklyn.cli.AbstractMain.BrooklynCommand; import brooklyn.cli.AbstractMain.BrooklynCommandCollectingArgs; import brooklyn.cli.AbstractMain.HelpCommand; +import brooklyn.cli.Main.AppShutdownHandler; import brooklyn.cli.Main.GeneratePasswordCommand; import brooklyn.cli.Main.LaunchCommand; import brooklyn.entity.Entity; @@ -214,10 +215,10 @@ public class CliTest { @Test public void testWaitsForInterrupt() throws Exception { - final LaunchCommand launchCommand = new Main.LaunchCommand(); + final AppShutdownHandler listener = new AppShutdownHandler(); Thread t = new Thread(new Runnable() { @Override public void run() { - launchCommand.waitUntilInterrupted(); + listener.waitOnShutdownRequest(); }}); t.start(); http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/b83ef4da/usage/launcher/pom.xml ---------------------------------------------------------------------- diff --git a/usage/launcher/pom.xml b/usage/launcher/pom.xml index 9da43e7..37b03d1 100644 --- a/usage/launcher/pom.xml +++ b/usage/launcher/pom.xml @@ -194,6 +194,13 @@ </dependency> <dependency> <groupId>org.apache.brooklyn</groupId> + <artifactId>brooklyn-rest-server</artifactId> + <version>${project.version}</version> + <classifier>tests</classifier> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.apache.brooklyn</groupId> <artifactId>brooklyn-software-messaging</artifactId> <version>${project.version}</version> <scope>test</scope> http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/b83ef4da/usage/launcher/src/main/java/brooklyn/launcher/BrooklynLauncher.java ---------------------------------------------------------------------- diff --git a/usage/launcher/src/main/java/brooklyn/launcher/BrooklynLauncher.java b/usage/launcher/src/main/java/brooklyn/launcher/BrooklynLauncher.java index 7b1d437..a6db5f7 100644 --- a/usage/launcher/src/main/java/brooklyn/launcher/BrooklynLauncher.java +++ b/usage/launcher/src/main/java/brooklyn/launcher/BrooklynLauncher.java @@ -19,11 +19,6 @@ package brooklyn.launcher; import static com.google.common.base.Preconditions.checkNotNull; -import io.brooklyn.camp.CampPlatform; -import io.brooklyn.camp.brooklyn.BrooklynCampPlatformLauncherNoServer; -import io.brooklyn.camp.brooklyn.spi.creation.BrooklynAssemblyTemplateInstantiator; -import io.brooklyn.camp.spi.AssemblyTemplate; -import io.brooklyn.camp.spi.instantiate.AssemblyTemplateInstantiator; import java.io.Closeable; import java.io.File; @@ -41,6 +36,14 @@ import javax.annotation.Nullable; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import com.google.common.annotations.Beta; +import com.google.common.base.Function; +import com.google.common.base.Splitter; +import com.google.common.base.Stopwatch; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.Lists; +import com.google.common.collect.Maps; + import brooklyn.catalog.internal.CatalogInitialization; import brooklyn.config.BrooklynProperties; import brooklyn.config.BrooklynServerConfig; @@ -86,6 +89,7 @@ import brooklyn.mementos.BrooklynMementoRawData; import brooklyn.rest.BrooklynWebConfig; import brooklyn.rest.filter.BrooklynPropertiesSecurityFilter; import brooklyn.rest.security.provider.BrooklynUserWithRandomPasswordSecurityProvider; +import brooklyn.rest.util.ShutdownHandler; import brooklyn.util.exceptions.Exceptions; import brooklyn.util.exceptions.FatalConfigurationRuntimeException; import brooklyn.util.exceptions.FatalRuntimeException; @@ -98,14 +102,11 @@ import brooklyn.util.stream.Streams; import brooklyn.util.text.Strings; import brooklyn.util.time.Duration; import brooklyn.util.time.Time; - -import com.google.common.annotations.Beta; -import com.google.common.base.Function; -import com.google.common.base.Splitter; -import com.google.common.base.Stopwatch; -import com.google.common.collect.ImmutableList; -import com.google.common.collect.Lists; -import com.google.common.collect.Maps; +import io.brooklyn.camp.CampPlatform; +import io.brooklyn.camp.brooklyn.BrooklynCampPlatformLauncherNoServer; +import io.brooklyn.camp.brooklyn.spi.creation.BrooklynAssemblyTemplateInstantiator; +import io.brooklyn.camp.spi.AssemblyTemplate; +import io.brooklyn.camp.spi.instantiate.AssemblyTemplateInstantiator; /** * Example usage is: @@ -156,6 +157,7 @@ public class BrooklynLauncher { private boolean ignoreAppErrors = true; private StopWhichAppsOnShutdown stopWhichAppsOnShutdown = StopWhichAppsOnShutdown.THESE_IF_NOT_PERSISTED; + private ShutdownHandler shutdownHandler; private Function<ManagementContext,Void> customizeManagement = null; private CatalogInitialization catalogInitialization = null; @@ -492,6 +494,14 @@ public class BrooklynLauncher { } /** + * A listener to call when the user requests a shutdown (i.e. through the REST API) + */ + public BrooklynLauncher shutdownHandler(ShutdownHandler shutdownHandler) { + this.shutdownHandler = shutdownHandler; + return this; + } + + /** * @param destinationDir Directory for state to be copied to * @param destinationLocation Optional location if target for copied state is a blob store. */ @@ -772,6 +782,7 @@ public class BrooklynLauncher { webServer.setPublicAddress(publicAddress); if (port!=null) webServer.setPort(port); if (useHttps!=null) webServer.setHttpsEnabled(useHttps); + webServer.setShutdownHandler(shutdownHandler); webServer.putAttributes(brooklynProperties); if (skipSecurityFilter != Boolean.TRUE) { webServer.setSecurityFilter(BrooklynPropertiesSecurityFilter.class); @@ -1048,5 +1059,5 @@ public class BrooklynLauncher { } } } - + } http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/b83ef4da/usage/launcher/src/main/java/brooklyn/launcher/BrooklynWebServer.java ---------------------------------------------------------------------- diff --git a/usage/launcher/src/main/java/brooklyn/launcher/BrooklynWebServer.java b/usage/launcher/src/main/java/brooklyn/launcher/BrooklynWebServer.java index ad8bd7a..9b46624 100644 --- a/usage/launcher/src/main/java/brooklyn/launcher/BrooklynWebServer.java +++ b/usage/launcher/src/main/java/brooklyn/launcher/BrooklynWebServer.java @@ -34,6 +34,7 @@ import java.util.LinkedHashMap; import java.util.List; import java.util.Map; +import javax.annotation.Nullable; import javax.servlet.DispatcherType; import org.eclipse.jetty.server.Connector; @@ -48,6 +49,16 @@ import org.eclipse.jetty.webapp.WebAppContext; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import com.google.common.annotations.VisibleForTesting; +import com.google.common.base.Splitter; +import com.google.common.base.Throwables; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.Maps; +import com.sun.jersey.api.container.filter.GZIPContentEncodingFilter; +import com.sun.jersey.api.core.DefaultResourceConfig; +import com.sun.jersey.api.core.ResourceConfig; +import com.sun.jersey.spi.container.servlet.ServletContainer; + import brooklyn.BrooklynVersion; import brooklyn.config.BrooklynServerPaths; import brooklyn.config.BrooklynServiceAttributes; @@ -68,6 +79,8 @@ import brooklyn.rest.filter.LoggingFilter; import brooklyn.rest.filter.NoCacheFilter; import brooklyn.rest.filter.RequestTaggingFilter; import brooklyn.rest.util.ManagementContextProvider; +import brooklyn.rest.util.ShutdownHandler; +import brooklyn.rest.util.ShutdownHandlerProvider; import brooklyn.util.BrooklynNetworkUtils; import brooklyn.util.ResourceUtils; import brooklyn.util.collections.MutableMap; @@ -86,16 +99,6 @@ import brooklyn.util.text.Identifiers; import brooklyn.util.text.Strings; import brooklyn.util.web.ContextHandlerCollectionHotSwappable; -import com.google.common.annotations.VisibleForTesting; -import com.google.common.base.Splitter; -import com.google.common.base.Throwables; -import com.google.common.collect.ImmutableList; -import com.google.common.collect.Maps; -import com.sun.jersey.api.container.filter.GZIPContentEncodingFilter; -import com.sun.jersey.api.core.DefaultResourceConfig; -import com.sun.jersey.api.core.ResourceConfig; -import com.sun.jersey.spi.container.servlet.ServletContainer; - /** * Starts the web-app running, connected to the given management context */ @@ -194,6 +197,8 @@ public class BrooklynWebServer { private Class<BrooklynPropertiesSecurityFilter> securityFilterClazz; + private ShutdownHandler shutdownHandler; + public BrooklynWebServer(ManagementContext managementContext) { this(Maps.newLinkedHashMap(), managementContext); } @@ -225,6 +230,10 @@ public class BrooklynWebServer { this.securityFilterClazz = filterClazz; } + public void setShutdownHandler(@Nullable ShutdownHandler shutdownHandler) { + this.shutdownHandler = shutdownHandler; + } + public BrooklynWebServer setPort(Object port) { if (getActualPort()>0) throw new IllegalStateException("Can't set port after port has been assigned to server (using "+getActualPort()+")"); @@ -324,7 +333,7 @@ public class BrooklynWebServer { return this; } - public static void installAsServletFilter(ServletContextHandler context) { + public void installAsServletFilter(ServletContextHandler context) { ResourceConfig config = new DefaultResourceConfig(); // load all our REST API modules, JSON, and Swagger for (Object r: BrooklynRestApi.getAllResources()) @@ -347,6 +356,8 @@ public class BrooklynWebServer { ManagementContext mgmt = (ManagementContext) context.getAttribute(BrooklynServiceAttributes.BROOKLYN_MANAGEMENT_CONTEXT); config.getSingletons().add(new ManagementContextProvider(mgmt)); + + config.getSingletons().add(new ShutdownHandlerProvider(shutdownHandler)); } ContextHandlerCollectionHotSwappable handlers = new ContextHandlerCollectionHotSwappable(); http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/b83ef4da/usage/rest-server/src/main/java/brooklyn/rest/resources/ServerResource.java ---------------------------------------------------------------------- diff --git a/usage/rest-server/src/main/java/brooklyn/rest/resources/ServerResource.java b/usage/rest-server/src/main/java/brooklyn/rest/resources/ServerResource.java index 1bb3d95..a4e9800 100644 --- a/usage/rest-server/src/main/java/brooklyn/rest/resources/ServerResource.java +++ b/usage/rest-server/src/main/java/brooklyn/rest/resources/ServerResource.java @@ -23,7 +23,6 @@ import java.io.File; import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; -import java.util.Collection; import java.util.List; import java.util.Map; import java.util.Properties; @@ -31,12 +30,16 @@ import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import java.util.concurrent.atomic.AtomicBoolean; +import javax.ws.rs.core.Context; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import com.google.common.base.Preconditions; +import com.google.common.collect.FluentIterable; + import brooklyn.BrooklynVersion; import brooklyn.config.ConfigKey; import brooklyn.entity.Application; @@ -63,6 +66,7 @@ import brooklyn.rest.domain.HighAvailabilitySummary; import brooklyn.rest.domain.VersionSummary; import brooklyn.rest.transform.BrooklynFeatureTransformer; import brooklyn.rest.transform.HighAvailabilityTransformer; +import brooklyn.rest.util.ShutdownHandler; import brooklyn.rest.util.WebResourceUtils; import brooklyn.util.ResourceUtils; import brooklyn.util.collections.MutableMap; @@ -77,9 +81,6 @@ import brooklyn.util.time.CountdownTimer; import brooklyn.util.time.Duration; import brooklyn.util.time.Time; -import com.google.common.base.Preconditions; -import com.google.common.collect.FluentIterable; - public class ServerResource extends AbstractBrooklynRestResource implements ServerApi { private static final int SHUTDOWN_TIMEOUT_CHECK_INTERVAL = 200; @@ -88,6 +89,9 @@ public class ServerResource extends AbstractBrooklynRestResource implements Serv private static final String BUILD_SHA_1_PROPERTY = "git-sha-1"; private static final String BUILD_BRANCH_PROPERTY = "git-branch-name"; + + @Context + private ShutdownHandler shutdownHandler; @Override public void reloadBrooklynProperties() { @@ -122,13 +126,14 @@ public class ServerResource extends AbstractBrooklynRestResource implements Serv delayForHttpReturn = Duration.of(delayMillis, TimeUnit.MILLISECONDS); } - Preconditions.checkState(delayForHttpReturn.isPositive(), "Only positive delay allowed for delayForHttpReturn"); + Preconditions.checkState(delayForHttpReturn.nanos() >= 0, "Only positive or 0 delay allowed for delayForHttpReturn"); boolean isSingleTimeout = shutdownTimeout.equals(requestTimeout); final AtomicBoolean completed = new AtomicBoolean(); final AtomicBoolean hasAppErrorsOrTimeout = new AtomicBoolean(); new Thread("shutdown") { + @Override public void run() { boolean terminateTried = false; try { @@ -178,10 +183,15 @@ public class ServerResource extends AbstractBrooklynRestResource implements Serv complete(); if (!hasAppErrorsOrTimeout.get() || forceShutdownOnError) { - //give the http request a chance to complete gracefully + //give the http request a chance to complete gracefully, the server will be stopped in a shutdown hook Time.sleep(delayForHttpReturn); - - System.exit(0); + + if (shutdownHandler != null) { + shutdownHandler.onShutdownRequest(); + } else { + log.warn("ShutdownHandler not set, exiting process"); + System.exit(0); + } } else { // There are app errors, don't exit the process, allowing any exception to continue throwing http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/b83ef4da/usage/rest-server/src/main/java/brooklyn/rest/util/ShutdownHandler.java ---------------------------------------------------------------------- diff --git a/usage/rest-server/src/main/java/brooklyn/rest/util/ShutdownHandler.java b/usage/rest-server/src/main/java/brooklyn/rest/util/ShutdownHandler.java new file mode 100644 index 0000000..a09d99f --- /dev/null +++ b/usage/rest-server/src/main/java/brooklyn/rest/util/ShutdownHandler.java @@ -0,0 +1,23 @@ +/* + * 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.util; + +public interface ShutdownHandler { + void onShutdownRequest(); +} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/b83ef4da/usage/rest-server/src/main/java/brooklyn/rest/util/ShutdownHandlerProvider.java ---------------------------------------------------------------------- diff --git a/usage/rest-server/src/main/java/brooklyn/rest/util/ShutdownHandlerProvider.java b/usage/rest-server/src/main/java/brooklyn/rest/util/ShutdownHandlerProvider.java new file mode 100644 index 0000000..09f395c --- /dev/null +++ b/usage/rest-server/src/main/java/brooklyn/rest/util/ShutdownHandlerProvider.java @@ -0,0 +1,30 @@ +/* + * 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.util; + +import javax.annotation.Nullable; +import javax.ws.rs.core.Context; + +import com.sun.jersey.spi.inject.SingletonTypeInjectableProvider; + +public class ShutdownHandlerProvider extends SingletonTypeInjectableProvider<Context, ShutdownHandler> { + public ShutdownHandlerProvider(@Nullable ShutdownHandler instance) { + super(ShutdownHandler.class, instance); + } +} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/b83ef4da/usage/rest-server/src/test/java/brooklyn/rest/BrooklynRestApiLauncher.java ---------------------------------------------------------------------- diff --git a/usage/rest-server/src/test/java/brooklyn/rest/BrooklynRestApiLauncher.java b/usage/rest-server/src/test/java/brooklyn/rest/BrooklynRestApiLauncher.java index dff7925..138a4ca 100644 --- a/usage/rest-server/src/test/java/brooklyn/rest/BrooklynRestApiLauncher.java +++ b/usage/rest-server/src/test/java/brooklyn/rest/BrooklynRestApiLauncher.java @@ -56,6 +56,8 @@ import brooklyn.rest.filter.RequestTaggingFilter; import brooklyn.rest.security.provider.AnyoneSecurityProvider; import brooklyn.rest.security.provider.SecurityProvider; import brooklyn.rest.util.ManagementContextProvider; +import brooklyn.rest.util.ShutdownHandlerProvider; +import brooklyn.rest.util.TestShutdownHandler; import brooklyn.util.exceptions.Exceptions; import brooklyn.util.net.Networking; import brooklyn.util.text.WildcardGlobs; @@ -107,6 +109,7 @@ public class BrooklynRestApiLauncher { private ContextHandler customContext; private boolean deployJsgui = true; private boolean disableHighAvailability = true; + private final TestShutdownHandler shutdownListener = new TestShutdownHandler(); protected BrooklynRestApiLauncher() {} @@ -235,6 +238,7 @@ public class BrooklynRestApiLauncher { ResourceConfig config = new DefaultResourceConfig(); for (Object r: BrooklynRestApi.getAllResources()) config.getSingletons().add(r); + config.getSingletons().add(new ShutdownHandlerProvider(shutdownListener)); ServletContextHandler context = new ServletContextHandler(ServletContextHandler.SESSIONS); context.setAttribute(BrooklynServiceAttributes.BROOKLYN_MANAGEMENT_CONTEXT, managementContext); @@ -326,11 +330,11 @@ public class BrooklynRestApiLauncher { .start(); } - public static void installAsServletFilter(ServletContextHandler context) { + public void installAsServletFilter(ServletContextHandler context) { installAsServletFilter(context, DEFAULT_FILTERS); } - private static void installAsServletFilter(ServletContextHandler context, List<Class<? extends Filter>> filters) { + private void installAsServletFilter(ServletContextHandler context, List<Class<? extends Filter>> filters) { installBrooklynFilters(context, filters); // now set up the REST servlet resources @@ -354,6 +358,7 @@ public class BrooklynRestApiLauncher { ManagementContext mgmt = (ManagementContext) context.getAttribute(BrooklynServiceAttributes.BROOKLYN_MANAGEMENT_CONTEXT); config.getSingletons().add(new ManagementContextProvider(mgmt)); + config.getSingletons().add(new ShutdownHandlerProvider(shutdownListener)); } private static void installBrooklynFilters(ServletContextHandler context, List<Class<? extends Filter>> filters) { http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/b83ef4da/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 ed5c1b4..cdfd501 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 @@ -25,10 +25,17 @@ import static org.testng.Assert.assertTrue; import java.util.concurrent.atomic.AtomicInteger; +import javax.ws.rs.core.MultivaluedMap; + import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.testng.annotations.BeforeClass; import org.testng.annotations.Test; +import com.google.common.collect.ImmutableSet; +import com.sun.jersey.api.client.UniformInterfaceException; +import com.sun.jersey.core.util.MultivaluedMapImpl; + import brooklyn.BrooklynVersion; import brooklyn.config.BrooklynProperties; import brooklyn.management.ManagementContext; @@ -38,13 +45,16 @@ import brooklyn.rest.domain.VersionSummary; import brooklyn.rest.testing.BrooklynRestResourceTest; import brooklyn.test.Asserts; -import com.google.common.collect.ImmutableSet; -import com.sun.jersey.api.client.UniformInterfaceException; - @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 { @@ -89,13 +99,22 @@ public class ServerResourceTest extends BrooklynRestResourceTest { assertEquals(reloadCount.get(), 1); } - // TODO Do not run this! It does a system.exit in ServerResource.shutdown - @Test(enabled=false) + @Test public void testShutdown() throws Exception { assertTrue(getManagementContext().isRunning()); + assertFalse(shutdownListener.isRequested()); + + MultivaluedMap<String, String> formData = new MultivaluedMapImpl(); + formData.add("requestTimeout", "0"); + formData.add("delayForHttpReturn", "0"); + client().resource("/v1/server/shutdown").entity(formData).post(); - client().resource("/v1/server/shutdown").post(); - + Asserts.succeedsEventually(new Runnable() { + @Override + public void run() { + assertTrue(shutdownListener.isRequested()); + } + }); Asserts.succeedsEventually(new Runnable() { @Override public void run() { assertFalse(getManagementContext().isRunning()); http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/b83ef4da/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 10c15d2..7526254 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 @@ -18,10 +18,16 @@ */ package brooklyn.rest.testing; -import io.brooklyn.camp.brooklyn.BrooklynCampPlatformLauncherNoServer; - import org.codehaus.jackson.map.ObjectMapper; import org.testng.annotations.AfterClass; +import org.testng.annotations.BeforeMethod; + +import com.google.common.base.Preconditions; +import com.sun.jersey.api.client.Client; +import com.sun.jersey.api.core.DefaultResourceConfig; +import com.sun.jersey.test.framework.AppDescriptor; +import com.sun.jersey.test.framework.JerseyTest; +import com.sun.jersey.test.framework.LowLevelAppDescriptor; import brooklyn.entity.basic.Entities; import brooklyn.location.LocationRegistry; @@ -34,22 +40,24 @@ import brooklyn.rest.BrooklynRestApiLauncherTest; import brooklyn.rest.util.BrooklynRestResourceUtils; import brooklyn.rest.util.NullHttpServletRequestProvider; import brooklyn.rest.util.NullServletConfigProvider; +import brooklyn.rest.util.ShutdownHandlerProvider; +import brooklyn.rest.util.TestShutdownHandler; import brooklyn.rest.util.json.BrooklynJacksonJsonProvider; import brooklyn.test.entity.LocalManagementContextForTests; import brooklyn.util.exceptions.Exceptions; - -import com.google.common.base.Preconditions; -import com.sun.jersey.api.client.Client; -import com.sun.jersey.api.core.DefaultResourceConfig; -import com.sun.jersey.test.framework.AppDescriptor; -import com.sun.jersey.test.framework.JerseyTest; -import com.sun.jersey.test.framework.LowLevelAppDescriptor; +import io.brooklyn.camp.brooklyn.BrooklynCampPlatformLauncherNoServer; public abstract class BrooklynRestApiTest { private ManagementContext manager; protected boolean useLocalScannedCatalog = false; + protected TestShutdownHandler shutdownListener = createShutdownHandler(); + + @BeforeMethod(alwaysRun = true) + public void setUpMethod() { + shutdownListener.reset(); + } protected synchronized void useLocalScannedCatalog() { if (manager!=null && !useLocalScannedCatalog) @@ -57,6 +65,10 @@ public abstract class BrooklynRestApiTest { useLocalScannedCatalog = true; } + private TestShutdownHandler createShutdownHandler() { + return new TestShutdownHandler(); + } + protected synchronized ManagementContext getManagementContext() { if (manager==null) { if (useLocalScannedCatalog) { @@ -119,6 +131,7 @@ public abstract class BrooklynRestApiTest { // and the servlet config provider must be an instance; addClasses doesn't work for some reason addResource(new NullServletConfigProvider()); addProvider(NullHttpServletRequestProvider.class); + addResource(new ShutdownHandlerProvider(shutdownListener)); } protected final void setUpResources() { http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/b83ef4da/usage/rest-server/src/test/java/brooklyn/rest/testing/BrooklynRestResourceTest.java ---------------------------------------------------------------------- diff --git a/usage/rest-server/src/test/java/brooklyn/rest/testing/BrooklynRestResourceTest.java b/usage/rest-server/src/test/java/brooklyn/rest/testing/BrooklynRestResourceTest.java index 42a622a..81c392e 100644 --- a/usage/rest-server/src/test/java/brooklyn/rest/testing/BrooklynRestResourceTest.java +++ b/usage/rest-server/src/test/java/brooklyn/rest/testing/BrooklynRestResourceTest.java @@ -60,6 +60,7 @@ public abstract class BrooklynRestResourceTest extends BrooklynRestApiTest { setUpJersey(); } + @Override @AfterClass(alwaysRun = true) public void tearDown() throws Exception { tearDownJersey(); http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/b83ef4da/usage/rest-server/src/test/java/brooklyn/rest/util/TestShutdownHandler.java ---------------------------------------------------------------------- diff --git a/usage/rest-server/src/test/java/brooklyn/rest/util/TestShutdownHandler.java b/usage/rest-server/src/test/java/brooklyn/rest/util/TestShutdownHandler.java new file mode 100644 index 0000000..19c6d19 --- /dev/null +++ b/usage/rest-server/src/test/java/brooklyn/rest/util/TestShutdownHandler.java @@ -0,0 +1,39 @@ +/* + * 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.util; + +import brooklyn.rest.util.ShutdownHandler; + +public class TestShutdownHandler implements ShutdownHandler { + private volatile boolean isRequested; + + @Override + public void onShutdownRequest() { + isRequested = true; + } + + public boolean isRequested() { + return isRequested; + } + + public void reset() { + isRequested = false; + } + +}
