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;
+    }
+
+}


Reply via email to