From 05e810fdb4377e65e72b4229b66908477be53f35 Mon Sep 17 00:00:00 2001 From: Svetoslav Neykov Date: Fri, 12 Sep 2014 18:53:05 +0300 Subject: [PATCH 1/9] Force UNIX-style line endings on shell scripts --- usage/dist/src/main/dist/bin/.gitattributes | 3 +++ usage/jsgui/src/build/.gitattributes | 2 ++ 2 files changed, 5 insertions(+) create mode 100644 usage/dist/src/main/dist/bin/.gitattributes create mode 100644 usage/jsgui/src/build/.gitattributes diff --git a/usage/dist/src/main/dist/bin/.gitattributes b/usage/dist/src/main/dist/bin/.gitattributes new file mode 100644 index 0000000000..4e2b719924 --- /dev/null +++ b/usage/dist/src/main/dist/bin/.gitattributes @@ -0,0 +1,3 @@ +#Don't auto-convert line endings for shell scripts on Windows (breaks the scripts) +brooklyn text eol=lf +cloud-explorer text eol=lf diff --git a/usage/jsgui/src/build/.gitattributes b/usage/jsgui/src/build/.gitattributes new file mode 100644 index 0000000000..83a693da91 --- /dev/null +++ b/usage/jsgui/src/build/.gitattributes @@ -0,0 +1,2 @@ +#Don't auto-convert line endings for shell scripts on Windows (breaks the scripts) +nodejs text eol=lf From 58d648a0d8acf27f91f5968f35b89366768a2968 Mon Sep 17 00:00:00 2001 From: Svetoslav Neykov Date: Fri, 12 Sep 2014 19:02:55 +0300 Subject: [PATCH 2/9] HTTP API entity helpers --- .../java/brooklyn/util/http/HttpTool.java | 32 ++++- .../entity/brooklynnode/BrooklynNode.java | 4 +- .../entity/brooklynnode/BrooklynNodeImpl.java | 41 ++---- .../entity/brooklynnode/EntityHttpClient.java | 60 +++++++++ .../brooklynnode/EntityHttpClientImpl.java | 120 ++++++++++++++++++ .../brooklynnode/SameBrooklynNodeImpl.java | 5 + .../brooklynnode/DeployBlueprintTest.java | 30 +++-- 7 files changed, 250 insertions(+), 42 deletions(-) create mode 100644 software/base/src/main/java/brooklyn/entity/brooklynnode/EntityHttpClient.java create mode 100644 software/base/src/main/java/brooklyn/entity/brooklynnode/EntityHttpClientImpl.java diff --git a/core/src/main/java/brooklyn/util/http/HttpTool.java b/core/src/main/java/brooklyn/util/http/HttpTool.java index 61a5b7a023..a81b928c99 100644 --- a/core/src/main/java/brooklyn/util/http/HttpTool.java +++ b/core/src/main/java/brooklyn/util/http/HttpTool.java @@ -24,17 +24,22 @@ import java.net.URI; import java.security.cert.CertificateException; import java.security.cert.X509Certificate; +import java.util.ArrayList; +import java.util.Collection; import java.util.Map; +import java.util.Map.Entry; import org.apache.commons.codec.binary.Base64; import org.apache.http.ConnectionReuseStrategy; import org.apache.http.HttpEntity; import org.apache.http.HttpRequest; import org.apache.http.HttpResponse; +import org.apache.http.NameValuePair; import org.apache.http.auth.AuthScope; import org.apache.http.auth.Credentials; import org.apache.http.auth.UsernamePasswordCredentials; import org.apache.http.client.HttpClient; +import org.apache.http.client.entity.UrlEncodedFormEntity; import org.apache.http.client.methods.HttpDelete; import org.apache.http.client.methods.HttpEntityEnclosingRequestBase; import org.apache.http.client.methods.HttpGet; @@ -52,6 +57,7 @@ import org.apache.http.entity.ByteArrayEntity; import org.apache.http.impl.client.DefaultHttpClient; import org.apache.http.impl.client.LaxRedirectStrategy; +import org.apache.http.message.BasicNameValuePair; import org.apache.http.params.BasicHttpParams; import org.apache.http.params.HttpConnectionParams; import org.apache.http.params.HttpParams; @@ -281,7 +287,24 @@ public static class HttpPostBuilder extends HttpEntityEnclosingRequestBaseBuilde super(new HttpPost(uri)); } } - + + public static class HttpFormPostBuilder extends HttpRequestBuilder { + HttpFormPostBuilder(URI uri) { + super(new HttpPost(uri)); + } + + public HttpFormPostBuilder params(Map params) { + if (params != null) { + Collection httpParams = new ArrayList(params.size()); + for (Entry param : params.entrySet()) { + httpParams.add(new BasicNameValuePair(param.getKey(), param.getValue())); + } + req.setEntity(new UrlEncodedFormEntity(httpParams)); + } + return self(); + } + } + public static class HttpPutBuilder extends HttpEntityEnclosingRequestBaseBuilder { public HttpPutBuilder(URI uri) { super(new HttpPut(uri)); @@ -292,7 +315,7 @@ public static HttpToolResponse httpGet(HttpClient httpClient, URI uri, Map headers, byte[] body) { HttpPost req = new HttpPostBuilder(uri).headers(headers).body(body).build(); return execAndConsume(httpClient, req); @@ -303,6 +326,11 @@ public static HttpToolResponse httpPut(HttpClient httpClient, URI uri, Map headers, Map params) { + HttpPost req = new HttpFormPostBuilder(uri).headers(headers).params(params).build(); + return execAndConsume(httpClient, req); + } + public static HttpToolResponse httpDelete(HttpClient httpClient, URI uri, Map headers) { HttpDelete req = new HttpDeleteBuilder(uri).headers(headers).build(); return execAndConsume(httpClient, req); diff --git a/software/base/src/main/java/brooklyn/entity/brooklynnode/BrooklynNode.java b/software/base/src/main/java/brooklyn/entity/brooklynnode/BrooklynNode.java index 029025597d..77e7214e03 100644 --- a/software/base/src/main/java/brooklyn/entity/brooklynnode/BrooklynNode.java +++ b/software/base/src/main/java/brooklyn/entity/brooklynnode/BrooklynNode.java @@ -222,5 +222,7 @@ public interface DeployBlueprintEffector { } public static Effector DEPLOY_BLUEPRINT = DeployBlueprintEffector.DEPLOY_BLUEPRINT; - + + public EntityHttpClient http(); + } diff --git a/software/base/src/main/java/brooklyn/entity/brooklynnode/BrooklynNodeImpl.java b/software/base/src/main/java/brooklyn/entity/brooklynnode/BrooklynNodeImpl.java index 5c29980f06..3475ef909b 100644 --- a/software/base/src/main/java/brooklyn/entity/brooklynnode/BrooklynNodeImpl.java +++ b/software/base/src/main/java/brooklyn/entity/brooklynnode/BrooklynNodeImpl.java @@ -140,39 +140,14 @@ protected String extractPlanYamlString(ConfigBag parameters) { return (String)planRaw; } - protected String submitPlan(String plan) { - URI baseUri = Preconditions.checkNotNull(entity().getAttribute(WEB_CONSOLE_URI), "Cannot be invoked until the web console URL is available"); - HttpClientBuilder builder = HttpTool.httpClientBuilder() - .trustAll() - .laxRedirect(true) - .uri(baseUri); - if (entity().getConfig(MANAGEMENT_USER)!=null) - builder.credentials(new UsernamePasswordCredentials(entity().getConfig(MANAGEMENT_USER), entity().getConfig(MANAGEMENT_PASSWORD))); - HttpClient client = builder.build(); - - return submitPlan(client, baseUri, plan); - } - @VisibleForTesting // Integration test for this in BrooklynNodeIntegrationTest in this project doesn't use this method, // but a Unit test for this does, in DeployBlueprintTest -- but in the REST server project (since it runs against local) - public static String submitPlan(HttpClient client, URI baseUri, String plan) { - URI uri = URI.create(Urls.mergePaths(baseUri.toString(), "/v1/applications")); - - HttpToolResponse result = null; - byte[] content; - try { - result = HttpTool.httpPost(client, uri, MutableMap.of(com.google.common.net.HttpHeaders.CONTENT_TYPE, "application/yaml"), plan.getBytes()); - content = result.getContent(); - } catch (Exception e) { - Exceptions.propagateIfFatal(e); - throw new IllegalStateException("Invalid response invoking "+uri+": "+e, e); - } - Tasks.addTagDynamically(BrooklynTaskTags.tagForStream("http_response", Streams.byteArray(content))); - if (!HttpTool.isStatusCodeHealthy(result.getResponseCode())) { - log.warn("Invalid response invoking "+uri+": response code "+result.getResponseCode()+"\n"+result+": "+new String(content)); - throw new IllegalStateException("Invalid response invoking "+uri+": response code "+result.getResponseCode()); - } + public String submitPlan(String plan) { + MutableMap headers = MutableMap.of(com.google.common.net.HttpHeaders.CONTENT_TYPE, "application/yaml"); + HttpToolResponse result = ((BrooklynNode)entity()).http() + .post("/v1/applications", headers, plan.getBytes()); + byte[] content = result.getContent(); return (String)new Gson().fromJson(new String(content), Map.class).get("entityId"); } } @@ -252,4 +227,10 @@ protected void disconnectSensors() { if (httpFeed != null) httpFeed.stop(); } + + @Override + public EntityHttpClient http() { + return new EntityHttpClientImpl(this); + } + } diff --git a/software/base/src/main/java/brooklyn/entity/brooklynnode/EntityHttpClient.java b/software/base/src/main/java/brooklyn/entity/brooklynnode/EntityHttpClient.java new file mode 100644 index 0000000000..904c5444e4 --- /dev/null +++ b/software/base/src/main/java/brooklyn/entity/brooklynnode/EntityHttpClient.java @@ -0,0 +1,60 @@ +/* + * 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.entity.brooklynnode; + +import java.util.Map; + +import brooklyn.util.http.HttpTool; +import brooklyn.util.http.HttpToolResponse; + +/** + * Helpful methods for making HTTP requests to {@link BrooklynNode} entities. + */ +public interface EntityHttpClient { + /** + * @return An HTTP client builder configured to access the {@link + * BrooklynNode#WEB_CONSOLE_URI web console URI} at the + * given entity, or null if the entity has no URI. + */ + public HttpTool.HttpClientBuilder getHttpClientForBrooklynNode(); + + /** + * Makes an HTTP GET to a Brooklyn node entity. + * @param path Relative path to resource on server, e.g v1/catalog + * @return The server's response + */ + public HttpToolResponse get(String path); + + /** + * Makes an HTTP POST to a Brooklyn node entity. + * @param path Relative path to resource on server, e.g v1/catalog + * @param body byte array of serialized JSON to attach to the request + * @return The server's response + */ + public HttpToolResponse post(String path, Map headers, byte[] body); + + /** + * Makes an HTTP POST to a Brooklyn node entity. + * @param path Relative path to resource on server, e.g v1/catalog + * @param body byte array of serialized JSON to attach to the request + * @return The server's response + */ + public HttpToolResponse post(String path, Map headers, Map formParams); + +} diff --git a/software/base/src/main/java/brooklyn/entity/brooklynnode/EntityHttpClientImpl.java b/software/base/src/main/java/brooklyn/entity/brooklynnode/EntityHttpClientImpl.java new file mode 100644 index 0000000000..262c485e4e --- /dev/null +++ b/software/base/src/main/java/brooklyn/entity/brooklynnode/EntityHttpClientImpl.java @@ -0,0 +1,120 @@ +/* + * 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.entity.brooklynnode; + +import java.net.URI; +import java.util.Map; + +import org.apache.http.auth.UsernamePasswordCredentials; +import org.apache.http.client.HttpClient; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import brooklyn.entity.basic.BrooklynTaskTags; +import brooklyn.util.collections.MutableMap; +import brooklyn.util.exceptions.Exceptions; +import brooklyn.util.http.HttpTool; +import brooklyn.util.http.HttpToolResponse; +import brooklyn.util.net.Urls; +import brooklyn.util.stream.Streams; +import brooklyn.util.task.Tasks; + +public class EntityHttpClientImpl implements EntityHttpClient { + private static final Logger LOG = LoggerFactory.getLogger(EntityHttpClientImpl.class); + + protected static interface HttpCall { + public HttpToolResponse call(HttpClient client, URI uri); + } + + protected BrooklynNode node; + + protected EntityHttpClientImpl(BrooklynNode node) { + this.node = node; + } + + @Override + public HttpTool.HttpClientBuilder getHttpClientForBrooklynNode() { + URI baseUri = node.getAttribute(BrooklynNode.WEB_CONSOLE_URI); + if (baseUri == null) { + return null; + } + HttpTool.HttpClientBuilder builder = HttpTool.httpClientBuilder() + .trustAll() + .laxRedirect(true) + .uri(baseUri); + if (node.getConfig(BrooklynNode.MANAGEMENT_USER) != null) { + UsernamePasswordCredentials credentials = new UsernamePasswordCredentials( + node.getConfig(BrooklynNode.MANAGEMENT_USER), + node.getConfig(BrooklynNode.MANAGEMENT_PASSWORD)); + builder.credentials(credentials); + } + return builder; + } + + protected HttpToolResponse exec(String path, HttpCall httpCall) { + HttpClient client = getHttpClientForBrooklynNode().build(); + URI baseUri = node.getAttribute(BrooklynNode.WEB_CONSOLE_URI); + URI uri = URI.create(Urls.mergePaths(baseUri.toString(), path)); + + HttpToolResponse result; + try { + result = httpCall.call(client, uri); + } catch (Exception e) { + Exceptions.propagateIfFatal(e); + throw new IllegalStateException("Invalid response invoking " + uri + ": " + e, e); + } + Tasks.addTagDynamically(BrooklynTaskTags.tagForStream("http_response", Streams.byteArray(result.getContent()))); + if (!HttpTool.isStatusCodeHealthy(result.getResponseCode())) { + LOG.warn("Invalid response invoking {}: response code {}\n{}: {}", + new Object[]{uri, result.getResponseCode(), result, new String(result.getContent())}); + throw new IllegalStateException("Invalid response invoking " + uri + ": response code " + result.getResponseCode()); + } + return result; + } + + @Override + public HttpToolResponse get(String path) { + return exec(path, new HttpCall() { + @Override + public HttpToolResponse call(HttpClient client, URI uri) { + return HttpTool.httpGet(client, uri, MutableMap.of()); + } + }); + } + + @Override + public HttpToolResponse post(String path, final Map headers, final byte[] body) { + return exec(path, new HttpCall() { + @Override + public HttpToolResponse call(HttpClient client, URI uri) { + return HttpTool.httpPost(client, uri, headers, body); + } + }); + } + + @Override + public HttpToolResponse post(String path, final Map headers, final Map formParams) { + return exec(path, new HttpCall() { + @Override + public HttpToolResponse call(HttpClient client, URI uri) { + return HttpTool.httpPost(client, uri, headers, formParams); + } + }); + } +} diff --git a/software/base/src/test/java/brooklyn/entity/brooklynnode/SameBrooklynNodeImpl.java b/software/base/src/test/java/brooklyn/entity/brooklynnode/SameBrooklynNodeImpl.java index 76e918ceef..198c257ff6 100644 --- a/software/base/src/test/java/brooklyn/entity/brooklynnode/SameBrooklynNodeImpl.java +++ b/software/base/src/test/java/brooklyn/entity/brooklynnode/SameBrooklynNodeImpl.java @@ -83,4 +83,9 @@ protected void disconnectSensors() { if (httpFeed != null) httpFeed.stop(); } + @Override + public EntityHttpClient http() { + throw new UnsupportedOperationException(); + } + } diff --git a/usage/rest-server/src/test/java/brooklyn/entity/brooklynnode/DeployBlueprintTest.java b/usage/rest-server/src/test/java/brooklyn/entity/brooklynnode/DeployBlueprintTest.java index d8214a4d2c..42f0785e2f 100644 --- a/usage/rest-server/src/test/java/brooklyn/entity/brooklynnode/DeployBlueprintTest.java +++ b/usage/rest-server/src/test/java/brooklyn/entity/brooklynnode/DeployBlueprintTest.java @@ -22,42 +22,54 @@ import java.net.URI; import java.util.List; +import java.util.Map; +import org.eclipse.jetty.server.Server; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; import brooklyn.entity.basic.BasicApplication; -import brooklyn.entity.brooklynnode.BrooklynNodeImpl.DeployBlueprintEffectorBody; -import brooklyn.event.feed.http.HttpValueFunctions; +import brooklyn.entity.basic.EntityLocal; +import brooklyn.entity.brooklynnode.BrooklynNode.DeployBlueprintEffector; +import brooklyn.entity.proxying.EntitySpec; import brooklyn.event.feed.http.JsonFunctions; +import brooklyn.management.EntityManager; import brooklyn.rest.BrooklynRestApiLauncherTestFixture; import brooklyn.test.HttpTestUtils; import brooklyn.util.guava.Functionals; -import brooklyn.util.http.HttpTool; import com.google.common.base.Function; import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; public class DeployBlueprintTest extends BrooklynRestApiLauncherTestFixture { private static final Logger log = LoggerFactory.getLogger(DeployBlueprintTest.class); - + + Server server; + @BeforeMethod(alwaysRun=true) public void setUp() throws Exception { - useServerForTest(newServer()); + server = newServer(); + useServerForTest(server); } @Test public void testStartsAppViaEffector() throws Exception { URI webConsoleUri = URI.create(getBaseUri()); - - String id = DeployBlueprintEffectorBody.submitPlan(HttpTool.httpClientBuilder().build(), webConsoleUri, - "{ services: [ serviceType: \"java:"+BasicApplication.class.getName()+"\" ] }"); + + EntitySpec spec = EntitySpec.create(BrooklynNode.class); + EntityManager mgr = getManagementContextFromJettyServerAttributes(server).getEntityManager(); + BrooklynNode node = mgr.createEntity(spec); + ((EntityLocal)node).setAttribute(BrooklynNode.WEB_CONSOLE_URI, webConsoleUri); + mgr.manage(node); + Map params = ImmutableMap.of(DeployBlueprintEffector.BLUEPRINT_CAMP_PLAN.getName(), "{ services: [ serviceType: \"java:"+BasicApplication.class.getName()+"\" ] }"); + String id = node.invoke(BrooklynNode.DEPLOY_BLUEPRINT, params).getUnchecked(); log.info("got: "+id); - + String apps = HttpTestUtils.getContent(webConsoleUri.toString()+"/v1/applications"); List appType = parseJsonList(apps, ImmutableList.of("spec", "type"), String.class); assertEquals(appType, ImmutableList.of(BasicApplication.class.getName())); From df789d90c2ba4f0837aa36f435b3e60d8e8b416f Mon Sep 17 00:00:00 2001 From: Svetoslav Neykov Date: Fri, 12 Sep 2014 19:07:50 +0300 Subject: [PATCH 3/9] Support for timeout parameter in the /server/shutdown request --- .../java/brooklyn/rest/api/ServerApi.java | 7 +++- .../rest/resources/ServerResource.java | 36 +++++++++++++++++-- 2 files changed, 39 insertions(+), 4 deletions(-) diff --git a/usage/rest-api/src/main/java/brooklyn/rest/api/ServerApi.java b/usage/rest-api/src/main/java/brooklyn/rest/api/ServerApi.java index fb9d3c1783..43cc7677fa 100644 --- a/usage/rest-api/src/main/java/brooklyn/rest/api/ServerApi.java +++ b/usage/rest-api/src/main/java/brooklyn/rest/api/ServerApi.java @@ -33,6 +33,7 @@ import com.google.common.annotations.Beta; import com.wordnik.swagger.core.ApiOperation; +import com.wordnik.swagger.core.ApiParam; @Path("/v1/server") @Apidoc("Server") @@ -52,8 +53,12 @@ public interface ServerApi { @ApiOperation(value = "Terminate this Brooklyn server instance") @Consumes({MediaType.APPLICATION_FORM_URLENCODED}) public void shutdown( + @ApiParam(name = "stopAppsFirst", value = "Whether to stop running applications before shutting down") @FormParam("stopAppsFirst") @DefaultValue("false") boolean stopAppsFirst, - @FormParam("delayMillis") @DefaultValue("250") long delayMillis); + @ApiParam(name = "delayMillis", value = "Minimum delay before system.exit") + @FormParam("delayMillis") @DefaultValue("250") long delayMillis, + @ApiParam(name = "httpReturnTimeout", value = "Maximum time to block the request for the shutdown to finish.") + @FormParam("httpReturnTimeout") String httpReturnTimeout); @GET @Path("/version") 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 4a5267276e..c1611014cc 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,6 +23,8 @@ import java.util.ArrayList; import java.util.List; import java.util.Properties; +import java.util.Timer; +import java.util.concurrent.atomic.AtomicBoolean; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -42,8 +44,10 @@ import brooklyn.rest.domain.VersionSummary; import brooklyn.rest.transform.HighAvailabilityTransformer; import brooklyn.util.ResourceUtils; +import brooklyn.util.exceptions.Exceptions; import brooklyn.util.time.CountdownTimer; import brooklyn.util.time.Duration; +import brooklyn.util.time.Time; public class ServerResource extends AbstractBrooklynRestResource implements ServerApi { @@ -58,9 +62,13 @@ public void reloadBrooklynProperties() { } @Override - public void shutdown(final boolean stopAppsFirst, final long delayMillis) { + public void shutdown(final boolean stopAppsFirst, final long delayMillis, String httpReturnTimeout) { log.info("REST call to shutdown server, stopAppsFirst="+stopAppsFirst+", delayMillis="+delayMillis); - + + Duration httpReturnTimeoutDuration = Duration.parse(httpReturnTimeout); + final boolean shouldBlock = (httpReturnTimeoutDuration != null); + final AtomicBoolean completed = new AtomicBoolean(); + new Thread() { public void run() { Duration delayBeforeSystemExit = Duration.millis(delayMillis); @@ -82,10 +90,32 @@ public void run() { ((ManagementContextInternal)mgmt()).terminate(); timer.waitForExpiryUnchecked(); - + + synchronized (completed) { + completed.set(true); + completed.notifyAll(); + } + + if (shouldBlock) { + //give the http request a chance to complete gracefully + Time.sleep(Duration.FIVE_SECONDS); + } + System.exit(0); } }.start(); + + if (shouldBlock) { + synchronized (completed) { + if (!completed.get()) { + try { + completed.wait(httpReturnTimeoutDuration.toMilliseconds()); + } catch (InterruptedException e) { + throw Exceptions.propagate(e); + } + } + } + } } @Override From 3191665ea836fbb54337485257f3a93c8fdd1b12 Mon Sep 17 00:00:00 2001 From: Svetoslav Neykov Date: Fri, 12 Sep 2014 19:08:08 +0300 Subject: [PATCH 4/9] Shutdown effector in BrooklynNode --- .../entity/brooklynnode/BrooklynNode.java | 20 +++++++++- .../entity/brooklynnode/BrooklynNodeImpl.java | 39 ++++++++++++++----- 2 files changed, 47 insertions(+), 12 deletions(-) diff --git a/software/base/src/main/java/brooklyn/entity/brooklynnode/BrooklynNode.java b/software/base/src/main/java/brooklyn/entity/brooklynnode/BrooklynNode.java index 77e7214e03..035d5aa6c9 100644 --- a/software/base/src/main/java/brooklyn/entity/brooklynnode/BrooklynNode.java +++ b/software/base/src/main/java/brooklyn/entity/brooklynnode/BrooklynNode.java @@ -41,6 +41,7 @@ import brooklyn.util.collections.MutableMap; import brooklyn.util.flags.SetFromFlag; import brooklyn.util.ssh.BashCommands; +import brooklyn.util.time.Duration; import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Function; @@ -220,8 +221,23 @@ public interface DeployBlueprintEffector { .parameter(BLUEPRINT_CAMP_PLAN) .buildAbstract(); } - - public static Effector DEPLOY_BLUEPRINT = DeployBlueprintEffector.DEPLOY_BLUEPRINT; + + public static final Effector DEPLOY_BLUEPRINT = DeployBlueprintEffector.DEPLOY_BLUEPRINT; + + public interface ShutdownEffector { + ConfigKey STOP_APPS = ConfigKeys.newBooleanConfigKey("stop_apps", "Whether to stop apps before shutting down", true); + ConfigKey DELAY = ConfigKeys.newConfigKey(Duration.class, "delay", "How long to wait before exiting the process", Duration.ZERO); + ConfigKey HTTP_RETURN_TIMEOUT = ConfigKeys.newConfigKey(Duration.class, "httpReturnTimeout", "Maximum time to block the request for the shutdown to finish. 0 for infinity, positive to block"); + Effector SHUTDOWN = Effectors.effector(Void.class, "shutdown") + .description("Shutdown the remote brooklyn instance") + .parameter(STOP_APPS) + .parameter(DELAY) + .parameter(HTTP_RETURN_TIMEOUT) + .buildAbstract(); + } + + public static final Effector SHUTDOWN = ShutdownEffector.SHUTDOWN; + public EntityHttpClient http(); diff --git a/software/base/src/main/java/brooklyn/entity/brooklynnode/BrooklynNodeImpl.java b/software/base/src/main/java/brooklyn/entity/brooklynnode/BrooklynNodeImpl.java index 3475ef909b..6cc6f34956 100644 --- a/software/base/src/main/java/brooklyn/entity/brooklynnode/BrooklynNodeImpl.java +++ b/software/base/src/main/java/brooklyn/entity/brooklynnode/BrooklynNodeImpl.java @@ -22,8 +22,7 @@ import java.util.List; import java.util.Map; -import org.apache.http.auth.UsernamePasswordCredentials; -import org.apache.http.client.HttpClient; +import org.apache.http.HttpStatus; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -31,7 +30,6 @@ import brooklyn.config.render.RendererHints; import brooklyn.entity.Effector; import brooklyn.entity.Entity; -import brooklyn.entity.basic.BrooklynTaskTags; import brooklyn.entity.basic.SoftwareProcessImpl; import brooklyn.entity.effector.EffectorBody; import brooklyn.entity.effector.Effectors; @@ -42,18 +40,12 @@ import brooklyn.util.collections.Jsonya; import brooklyn.util.collections.MutableMap; import brooklyn.util.config.ConfigBag; -import brooklyn.util.exceptions.Exceptions; -import brooklyn.util.http.HttpTool; -import brooklyn.util.http.HttpTool.HttpClientBuilder; import brooklyn.util.http.HttpToolResponse; import brooklyn.util.javalang.JavaClassNames; -import brooklyn.util.net.Urls; -import brooklyn.util.stream.Streams; -import brooklyn.util.task.Tasks; import brooklyn.util.text.Strings; +import brooklyn.util.time.Duration; import com.google.common.annotations.VisibleForTesting; -import com.google.common.base.Preconditions; import com.google.gson.Gson; public class BrooklynNodeImpl extends SoftwareProcessImpl implements BrooklynNode { @@ -83,6 +75,7 @@ public Class getDriverInterface() { public void init() { super.init(); getMutableEntityType().addEffector(DeployBlueprintEffectorBody.DEPLOY_BLUEPRINT); + getMutableEntityType().addEffector(ShutdownEffectorBody.SHUTDOWN); } public static class DeployBlueprintEffectorBody extends EffectorBody implements DeployBlueprintEffector { @@ -152,6 +145,32 @@ public String submitPlan(String plan) { } } + public static class ShutdownEffectorBody extends EffectorBody implements ShutdownEffector { + public static final Effector SHUTDOWN = Effectors.effector(BrooklynNode.SHUTDOWN).impl(new ShutdownEffectorBody()).build(); + + @Override + public Void call(ConfigBag parameters) { + Map formParams = MutableMap.of( + "stopAppsFirst", parameters.get(STOP_APPS).toString(), + "delayMillis", getDelayMillis(parameters)); + Duration httpReturnTimeout = parameters.get(HTTP_RETURN_TIMEOUT); + if (httpReturnTimeout != null) { + formParams.put("httpReturnTimeout", httpReturnTimeout.toString()); + } + HttpToolResponse resp = ((BrooklynNode)entity()).http() + .post("/v1/server/shutdown", MutableMap.of(), formParams); + if (resp.getResponseCode() != HttpStatus.SC_NO_CONTENT) { + throw new IllegalStateException("Remote node shutdown failed."); + } + return null; + } + + private String getDelayMillis(ConfigBag parameters) { + return Long.toString(parameters.get(DELAY).toMilliseconds()); + } + + } + public List getClasspath() { List classpath = getConfig(CLASSPATH); if (classpath == null || classpath.isEmpty()) { From 91d833891cb6c005d47d55f27f21ef995c46466a Mon Sep 17 00:00:00 2001 From: Svetoslav Neykov Date: Fri, 12 Sep 2014 19:11:52 +0300 Subject: [PATCH 5/9] Brooklyn node safe STOP effectors * Stop but leave apps running * Stop apps and stop node --- .../entity/brooklynnode/BrooklynNode.java | 15 +++++ .../entity/brooklynnode/BrooklynNodeImpl.java | 34 ++++++++++++ .../BrooklynNodeIntegrationTest.java | 55 +++++++++++++++++++ 3 files changed, 104 insertions(+) diff --git a/software/base/src/main/java/brooklyn/entity/brooklynnode/BrooklynNode.java b/software/base/src/main/java/brooklyn/entity/brooklynnode/BrooklynNode.java index 035d5aa6c9..e7808f10fb 100644 --- a/software/base/src/main/java/brooklyn/entity/brooklynnode/BrooklynNode.java +++ b/software/base/src/main/java/brooklyn/entity/brooklynnode/BrooklynNode.java @@ -238,6 +238,21 @@ public interface ShutdownEffector { public static final Effector SHUTDOWN = ShutdownEffector.SHUTDOWN; + public interface StopNodeButLeaveAppsEffector { + Effector STOP_NODE_BUT_LEAVE_APPS = Effectors.effector(Void.class, "stopNodeButLeaveApps") + .description("Stop the node without stopping the running applications") + .buildAbstract(); + } + + public static final Effector STOP_NODE_BUT_LEAVE_APPS = StopNodeButLeaveAppsEffector.STOP_NODE_BUT_LEAVE_APPS; + + public interface StopNodeAndKillAppsEffector { + Effector STOP_NODE_AND_KILL_APPS = Effectors.effector(Void.class, "stopNodeAndKillApps") + .description("Stop all apps running on the node and shutdown the node") + .buildAbstract(); + } + + public static final Effector STOP_NODE_AND_KILL_APPS = StopNodeAndKillAppsEffector.STOP_NODE_AND_KILL_APPS; public EntityHttpClient http(); diff --git a/software/base/src/main/java/brooklyn/entity/brooklynnode/BrooklynNodeImpl.java b/software/base/src/main/java/brooklyn/entity/brooklynnode/BrooklynNodeImpl.java index 6cc6f34956..51cf5174fd 100644 --- a/software/base/src/main/java/brooklyn/entity/brooklynnode/BrooklynNodeImpl.java +++ b/software/base/src/main/java/brooklyn/entity/brooklynnode/BrooklynNodeImpl.java @@ -30,6 +30,7 @@ import brooklyn.config.render.RendererHints; import brooklyn.entity.Effector; import brooklyn.entity.Entity; +import brooklyn.entity.basic.Entities; import brooklyn.entity.basic.SoftwareProcessImpl; import brooklyn.entity.effector.EffectorBody; import brooklyn.entity.effector.Effectors; @@ -42,6 +43,7 @@ import brooklyn.util.config.ConfigBag; import brooklyn.util.http.HttpToolResponse; import brooklyn.util.javalang.JavaClassNames; +import brooklyn.util.task.DynamicTasks; import brooklyn.util.text.Strings; import brooklyn.util.time.Duration; @@ -76,6 +78,8 @@ public void init() { super.init(); getMutableEntityType().addEffector(DeployBlueprintEffectorBody.DEPLOY_BLUEPRINT); getMutableEntityType().addEffector(ShutdownEffectorBody.SHUTDOWN); + getMutableEntityType().addEffector(StopNodeButLeaveAppsEffectorBody.STOP_NODE_BUT_LEAVE_APPS); + getMutableEntityType().addEffector(StopNodeAndKillAppsEffectorBody.STOP_NODE_AND_KILL_APPS); } public static class DeployBlueprintEffectorBody extends EffectorBody implements DeployBlueprintEffector { @@ -171,6 +175,36 @@ private String getDelayMillis(ConfigBag parameters) { } + public static class StopNodeButLeaveAppsEffectorBody extends EffectorBody implements StopNodeButLeaveAppsEffector { + public static final Effector STOP_NODE_BUT_LEAVE_APPS = Effectors.effector(BrooklynNode.STOP_NODE_BUT_LEAVE_APPS).impl(new StopNodeButLeaveAppsEffectorBody()).build(); + + @Override + public Void call(ConfigBag parameters) { + MutableMap params = MutableMap.of( + ShutdownEffector.STOP_APPS, Boolean.FALSE, + ShutdownEffector.HTTP_RETURN_TIMEOUT, Duration.ONE_HOUR); + Entity entity = entity(); + DynamicTasks.queue(Effectors.invocation(entity, SHUTDOWN, params)).asTask().getUnchecked(); + Entities.unmanage(entity); + return null; + } + } + + public static class StopNodeAndKillAppsEffectorBody extends EffectorBody implements StopNodeAndKillAppsEffector { + public static final Effector STOP_NODE_AND_KILL_APPS = Effectors.effector(BrooklynNode.STOP_NODE_AND_KILL_APPS).impl(new StopNodeAndKillAppsEffectorBody()).build(); + + @Override + public Void call(ConfigBag parameters) { + MutableMap params = MutableMap.of( + ShutdownEffector.STOP_APPS, Boolean.TRUE, + ShutdownEffector.HTTP_RETURN_TIMEOUT, Duration.ONE_HOUR); + Entity entity = entity(); + DynamicTasks.queue(Effectors.invocation(entity, SHUTDOWN, params)).asTask().getUnchecked(); + Entities.unmanage(entity); + return null; + } + } + public List getClasspath() { List classpath = getConfig(CLASSPATH); if (classpath == null || classpath.isEmpty()) { diff --git a/software/base/src/test/java/brooklyn/entity/brooklynnode/BrooklynNodeIntegrationTest.java b/software/base/src/test/java/brooklyn/entity/brooklynnode/BrooklynNodeIntegrationTest.java index a9bd21e884..8aac45e81b 100644 --- a/software/base/src/test/java/brooklyn/entity/brooklynnode/BrooklynNodeIntegrationTest.java +++ b/software/base/src/test/java/brooklyn/entity/brooklynnode/BrooklynNodeIntegrationTest.java @@ -23,7 +23,9 @@ import java.io.File; import java.net.URI; +import java.util.Collections; import java.util.List; +import java.util.concurrent.ExecutionException; import org.apache.http.auth.UsernamePasswordCredentials; import org.apache.http.client.HttpClient; @@ -35,6 +37,8 @@ import org.testng.annotations.Test; import brooklyn.config.BrooklynProperties; +import brooklyn.entity.Effector; +import brooklyn.entity.Entity; import brooklyn.entity.basic.BasicApplication; import brooklyn.entity.basic.BasicApplicationImpl; import brooklyn.entity.basic.Entities; @@ -51,6 +55,7 @@ import brooklyn.test.entity.TestApplication; import brooklyn.util.collections.MutableMap; import brooklyn.util.config.ConfigBag; +import brooklyn.util.exceptions.PropagatedRuntimeException; import brooklyn.util.guava.Functionals; import brooklyn.util.http.HttpTool; import brooklyn.util.http.HttpToolResponse; @@ -398,6 +403,56 @@ public void testAuthenticationAndHttps() throws Exception { Assert.assertEquals(response.getResponseCode(), 200); } + @Test(groups="Integration", expectedExceptions = PropagatedRuntimeException.class) + public void testStopPlainThrowsException() throws Exception { + BrooklynNode brooklynNode = setUpBrooklynNodeWithApp(); + + brooklynNode.stop(); + } + + @Test(groups="Integration") + public void testStopAndKillAppsEffector() throws Exception { + createNodeAndExecStopEffector(BrooklynNode.STOP_NODE_AND_KILL_APPS); + } + + @Test(groups="Integration") + public void testStopButLeaveAppsEffector() throws Exception { + createNodeAndExecStopEffector(BrooklynNode.STOP_NODE_BUT_LEAVE_APPS); + } + + private void createNodeAndExecStopEffector(Effector eff) throws Exception { + BrooklynNode brooklynNode = setUpBrooklynNodeWithApp(); + + brooklynNode.invoke(eff, Collections.emptyMap()).getUnchecked(); + + EntityTestUtils.assertAttributeEqualsEventually(brooklynNode, BrooklynNode.SERVICE_UP, false); + } + + private BrooklynNode setUpBrooklynNodeWithApp() throws InterruptedException, + ExecutionException { + BrooklynNode brooklynNode = app.createAndManageChild(EntitySpec.create(BrooklynNode.class) + .configure(BrooklynNode.NO_WEB_CONSOLE_AUTHENTICATION, Boolean.TRUE)); + app.start(locs); + log.info("started "+app+" containing "+brooklynNode+" for "+JavaClassNames.niceClassAndMethod()); + + EntityTestUtils.assertAttributeEqualsEventually(brooklynNode, BrooklynNode.SERVICE_UP, true); + + final String id = brooklynNode.invoke(BrooklynNode.DEPLOY_BLUEPRINT, ConfigBag.newInstance() + .configure(DeployBlueprintEffector.BLUEPRINT_TYPE, BasicApplication.class.getName()) + .getAllConfig()).get(); + + String baseUrl = brooklynNode.getAttribute(BrooklynNode.WEB_CONSOLE_URI).toString(); + String entityUrl = baseUrl + "v1/applications/" + id + "/entities/" + id; + + Entity mirror = brooklynNode.addChild(EntitySpec.create(BrooklynEntityMirror.class) + .configure(BrooklynEntityMirror.MIRRORED_ENTITY_URL, entityUrl) + .configure(BrooklynEntityMirror.MIRRORED_ENTITY_ID, id)); + Entities.manage(mirror); + + assertEquals(brooklynNode.getChildren().size(), 1); + return brooklynNode; + } + private T parseJson(String json, List elements, Class clazz) { Function func = Functionals.chain( JsonFunctions.asJson(), From 37e966c28146f30cb11c4677c28aced4c51dbe0e Mon Sep 17 00:00:00 2001 From: Svetoslav Neykov Date: Fri, 12 Sep 2014 19:13:35 +0300 Subject: [PATCH 6/9] Refuse stop of Brooklyn Node if there are child apps (possibly mirrored child apps added manually) --- .../brooklyn/entity/brooklynnode/BrooklynNodeImpl.java | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/software/base/src/main/java/brooklyn/entity/brooklynnode/BrooklynNodeImpl.java b/software/base/src/main/java/brooklyn/entity/brooklynnode/BrooklynNodeImpl.java index 51cf5174fd..0108c98e83 100644 --- a/software/base/src/main/java/brooklyn/entity/brooklynnode/BrooklynNodeImpl.java +++ b/software/base/src/main/java/brooklyn/entity/brooklynnode/BrooklynNodeImpl.java @@ -48,6 +48,7 @@ import brooklyn.util.time.Duration; import com.google.common.annotations.VisibleForTesting; +import com.google.common.base.Preconditions; import com.google.gson.Gson; public class BrooklynNodeImpl extends SoftwareProcessImpl implements BrooklynNode { @@ -81,7 +82,14 @@ public void init() { getMutableEntityType().addEffector(StopNodeButLeaveAppsEffectorBody.STOP_NODE_BUT_LEAVE_APPS); getMutableEntityType().addEffector(StopNodeAndKillAppsEffectorBody.STOP_NODE_AND_KILL_APPS); } - + + @Override + protected void doStop() { + Preconditions.checkState(getChildren().isEmpty(), "Can't stop instance with running applications."); + DynamicTasks.queue(Effectors.invocation(this, SHUTDOWN, MutableMap.of(ShutdownEffector.HTTP_RETURN_TIMEOUT, Duration.ONE_MINUTE))); + super.doStop(); + } + public static class DeployBlueprintEffectorBody extends EffectorBody implements DeployBlueprintEffector { public static final Effector DEPLOY_BLUEPRINT = Effectors.effector(BrooklynNode.DEPLOY_BLUEPRINT).impl(new DeployBlueprintEffectorBody()).build(); From 79b6dd0864967c277938bf13eba95b485aff499e Mon Sep 17 00:00:00 2001 From: Svetoslav Neykov Date: Mon, 15 Sep 2014 16:20:37 +0300 Subject: [PATCH 7/9] Support HTTP responses with empty body. --- .../brooklyn/util/http/HttpToolResponse.java | 21 ++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/core/src/main/java/brooklyn/util/http/HttpToolResponse.java b/core/src/main/java/brooklyn/util/http/HttpToolResponse.java index 610e434822..dec925320b 100644 --- a/core/src/main/java/brooklyn/util/http/HttpToolResponse.java +++ b/core/src/main/java/brooklyn/util/http/HttpToolResponse.java @@ -27,6 +27,7 @@ import java.util.Map; import org.apache.http.Header; +import org.apache.http.HttpEntity; import org.apache.http.HttpResponse; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -63,13 +64,19 @@ public HttpToolResponse(HttpResponse response, long startTime) { try { ByteArrayOutputStream out = new ByteArrayOutputStream(); - response.getEntity().getContentLength(); - durationMillisOfFirstResponse = System.currentTimeMillis() - startTime; - - ByteStreams.copy(response.getEntity().getContent(), out); - content = out.toByteArray(); - - response.getEntity().getContentLength(); + HttpEntity entity = response.getEntity(); + if (entity != null) { + entity.getContentLength(); + durationMillisOfFirstResponse = System.currentTimeMillis() - startTime; + + ByteStreams.copy(entity.getContent(), out); + content = out.toByteArray(); + + entity.getContentLength(); + } else { + durationMillisOfFirstResponse = System.currentTimeMillis() - startTime; + content = new byte[0]; + } durationMillisOfFullContent = System.currentTimeMillis() - startTime; if (log.isTraceEnabled()) log.trace("HttpPollValue latency "+Time.makeTimeStringRounded(durationMillisOfFirstResponse)+" / "+Time.makeTimeStringRounded(durationMillisOfFullContent)+", content size "+content.length); From e071802b0f3acf97aa10454eb385f81fff2fe124 Mon Sep 17 00:00:00 2001 From: Svetoslav Neykov Date: Thu, 18 Sep 2014 22:02:26 +0300 Subject: [PATCH 8/9] Improve stop logic stop_... effectors don't unmanage the node (leave it visible in the entity tree) and call STOP on after shutdown so that the machine is shut down. Enforce no children condition only when SERVICE_UP == true. --- .../entity/brooklynnode/BrooklynNodeImpl.java | 37 ++++++++++++++----- 1 file changed, 28 insertions(+), 9 deletions(-) diff --git a/software/base/src/main/java/brooklyn/entity/brooklynnode/BrooklynNodeImpl.java b/software/base/src/main/java/brooklyn/entity/brooklynnode/BrooklynNodeImpl.java index 0108c98e83..72ebbf4e30 100644 --- a/software/base/src/main/java/brooklyn/entity/brooklynnode/BrooklynNodeImpl.java +++ b/software/base/src/main/java/brooklyn/entity/brooklynnode/BrooklynNodeImpl.java @@ -30,7 +30,8 @@ import brooklyn.config.render.RendererHints; import brooklyn.entity.Effector; import brooklyn.entity.Entity; -import brooklyn.entity.basic.Entities; +import brooklyn.entity.basic.Attributes; +import brooklyn.entity.basic.ServiceStateLogic.ServiceNotUpLogic; import brooklyn.entity.basic.SoftwareProcessImpl; import brooklyn.entity.effector.EffectorBody; import brooklyn.entity.effector.Effectors; @@ -38,12 +39,14 @@ import brooklyn.event.feed.http.HttpFeed; import brooklyn.event.feed.http.HttpPollConfig; import brooklyn.event.feed.http.HttpValueFunctions; +import brooklyn.management.TaskAdaptable; import brooklyn.util.collections.Jsonya; import brooklyn.util.collections.MutableMap; import brooklyn.util.config.ConfigBag; import brooklyn.util.http.HttpToolResponse; import brooklyn.util.javalang.JavaClassNames; import brooklyn.util.task.DynamicTasks; +import brooklyn.util.task.TaskTags; import brooklyn.util.text.Strings; import brooklyn.util.time.Duration; @@ -85,8 +88,11 @@ public void init() { @Override protected void doStop() { - Preconditions.checkState(getChildren().isEmpty(), "Can't stop instance with running applications."); - DynamicTasks.queue(Effectors.invocation(this, SHUTDOWN, MutableMap.of(ShutdownEffector.HTTP_RETURN_TIMEOUT, Duration.ONE_MINUTE))); + //shutdown only if running, any of stop_* could've been already previously + if (getAttribute(Attributes.SERVICE_UP)) { + Preconditions.checkState(getChildren().isEmpty(), "Can't stop instance with running applications."); + DynamicTasks.queue(Effectors.invocation(this, SHUTDOWN, MutableMap.of(ShutdownEffector.HTTP_RETURN_TIMEOUT, Duration.ONE_MINUTE))); + } super.doStop(); } @@ -174,6 +180,7 @@ public Void call(ConfigBag parameters) { if (resp.getResponseCode() != HttpStatus.SC_NO_CONTENT) { throw new IllegalStateException("Remote node shutdown failed."); } + ServiceNotUpLogic.updateNotUpIndicator(entity(), "brooklynnode.shutdown", "Shutdown of remote node has completed successfuly"); return null; } @@ -190,10 +197,16 @@ public static class StopNodeButLeaveAppsEffectorBody extends EffectorBody public Void call(ConfigBag parameters) { MutableMap params = MutableMap.of( ShutdownEffector.STOP_APPS, Boolean.FALSE, - ShutdownEffector.HTTP_RETURN_TIMEOUT, Duration.ONE_HOUR); + ShutdownEffector.HTTP_RETURN_TIMEOUT, Duration.ONE_HOUR, + ShutdownEffector.DELAY, Duration.FIVE_SECONDS); + Entity entity = entity(); - DynamicTasks.queue(Effectors.invocation(entity, SHUTDOWN, params)).asTask().getUnchecked(); - Entities.unmanage(entity); + TaskAdaptable shutdownTask = Effectors.invocation(entity, SHUTDOWN, params); + if (!entity.getAttribute(SERVICE_UP)) { + TaskTags.markInessential(shutdownTask); + } + DynamicTasks.queue(shutdownTask); + DynamicTasks.queue(Effectors.invocation(entity, STOP, params)); return null; } } @@ -205,10 +218,16 @@ public static class StopNodeAndKillAppsEffectorBody extends EffectorBody i public Void call(ConfigBag parameters) { MutableMap params = MutableMap.of( ShutdownEffector.STOP_APPS, Boolean.TRUE, - ShutdownEffector.HTTP_RETURN_TIMEOUT, Duration.ONE_HOUR); + ShutdownEffector.HTTP_RETURN_TIMEOUT, Duration.ONE_HOUR, + ShutdownEffector.DELAY, Duration.FIVE_SECONDS); + Entity entity = entity(); - DynamicTasks.queue(Effectors.invocation(entity, SHUTDOWN, params)).asTask().getUnchecked(); - Entities.unmanage(entity); + TaskAdaptable shutdownTask = Effectors.invocation(entity, SHUTDOWN, params); + if (!entity.getAttribute(SERVICE_UP)) { + TaskTags.markInessential(shutdownTask); + } + DynamicTasks.queue(shutdownTask); + DynamicTasks.queue(Effectors.invocation(entity, STOP, params)); return null; } } From 34a6d50b54ae2f5bee807dd44391bedfdea3fce9 Mon Sep 17 00:00:00 2001 From: Svetoslav Neykov Date: Fri, 19 Sep 2014 12:39:18 +0300 Subject: [PATCH 9/9] Address #161 comments. * Use Duration.sinceUtc instead of ad-hoc logic * Check the process has actually exited in tests --- .../brooklyn/util/http/HttpToolResponse.java | 9 +++++---- .../BrooklynNodeIntegrationTest.java | 20 ++++++++++++++++++- 2 files changed, 24 insertions(+), 5 deletions(-) diff --git a/core/src/main/java/brooklyn/util/http/HttpToolResponse.java b/core/src/main/java/brooklyn/util/http/HttpToolResponse.java index dec925320b..1722e41ee2 100644 --- a/core/src/main/java/brooklyn/util/http/HttpToolResponse.java +++ b/core/src/main/java/brooklyn/util/http/HttpToolResponse.java @@ -35,6 +35,7 @@ import brooklyn.event.feed.http.HttpPollValue; import brooklyn.util.guava.Maybe; import brooklyn.util.stream.Streams; +import brooklyn.util.time.Duration; import brooklyn.util.time.Time; import com.google.common.base.Objects; @@ -67,22 +68,22 @@ public HttpToolResponse(HttpResponse response, long startTime) { HttpEntity entity = response.getEntity(); if (entity != null) { entity.getContentLength(); - durationMillisOfFirstResponse = System.currentTimeMillis() - startTime; + durationMillisOfFirstResponse = Duration.sinceUtc(startTime).toMilliseconds(); ByteStreams.copy(entity.getContent(), out); content = out.toByteArray(); entity.getContentLength(); } else { - durationMillisOfFirstResponse = System.currentTimeMillis() - startTime; + durationMillisOfFirstResponse = Duration.sinceUtc(startTime).toMilliseconds(); content = new byte[0]; } - durationMillisOfFullContent = System.currentTimeMillis() - startTime; + durationMillisOfFullContent = Duration.sinceUtc(startTime).toMilliseconds(); if (log.isTraceEnabled()) log.trace("HttpPollValue latency "+Time.makeTimeStringRounded(durationMillisOfFirstResponse)+" / "+Time.makeTimeStringRounded(durationMillisOfFullContent)+", content size "+content.length); } catch (IOException e) { throw Throwables.propagate(e); - } + } } public HttpToolResponse(int responseCode, Map> headers, byte[] content, diff --git a/software/base/src/test/java/brooklyn/entity/brooklynnode/BrooklynNodeIntegrationTest.java b/software/base/src/test/java/brooklyn/entity/brooklynnode/BrooklynNodeIntegrationTest.java index 8aac45e81b..341e25802f 100644 --- a/software/base/src/test/java/brooklyn/entity/brooklynnode/BrooklynNodeIntegrationTest.java +++ b/software/base/src/test/java/brooklyn/entity/brooklynnode/BrooklynNodeIntegrationTest.java @@ -19,9 +19,12 @@ package brooklyn.entity.brooklynnode; import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertFalse; import static org.testng.Assert.assertTrue; import java.io.File; +import java.lang.reflect.Method; +import java.lang.reflect.Proxy; import java.net.URI; import java.util.Collections; import java.util.List; @@ -44,6 +47,7 @@ import brooklyn.entity.basic.Entities; import brooklyn.entity.brooklynnode.BrooklynNode.DeployBlueprintEffector; import brooklyn.entity.brooklynnode.BrooklynNode.ExistingFileBehaviour; +import brooklyn.entity.proxying.EntityProxyImpl; import brooklyn.entity.proxying.EntitySpec; import brooklyn.event.feed.http.JsonFunctions; import brooklyn.location.Location; @@ -55,6 +59,7 @@ 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.guava.Functionals; import brooklyn.util.http.HttpTool; @@ -425,7 +430,20 @@ private void createNodeAndExecStopEffector(Effector eff) throws Exception { brooklynNode.invoke(eff, Collections.emptyMap()).getUnchecked(); - EntityTestUtils.assertAttributeEqualsEventually(brooklynNode, BrooklynNode.SERVICE_UP, false); + EntityTestUtils.assertAttributeEquals(brooklynNode, BrooklynNode.SERVICE_UP, false); + //Use the driver's check of isRunning instead of local command since on Windows + //the test would be running remotely (needs some extra hoops to do it). + assertFalse(getDriver(brooklynNode).isRunning(), "Service process still running"); + } + + private BrooklynNodeSshDriver getDriver(BrooklynNode brooklynNode) { + try { + EntityProxyImpl entityProxy = (EntityProxyImpl)Proxy.getInvocationHandler(brooklynNode); + Method getDriver = BrooklynNodeImpl.class.getMethod("getDriver"); + return (BrooklynNodeSshDriver)entityProxy.invoke(brooklynNode, getDriver, new Object[]{}); + } catch (Throwable e) { + throw Exceptions.propagate(e); + } } private BrooklynNode setUpBrooklynNodeWithApp() throws InterruptedException,