Useful methods for working with the Brooklyn API programatically.

Project: http://git-wip-us.apache.org/repos/asf/brooklyn-server/repo
Commit: http://git-wip-us.apache.org/repos/asf/brooklyn-server/commit/d1733900
Tree: http://git-wip-us.apache.org/repos/asf/brooklyn-server/tree/d1733900
Diff: http://git-wip-us.apache.org/repos/asf/brooklyn-server/diff/d1733900

Branch: refs/heads/master
Commit: d173390039887e2da6717f5ca0d1b4c91fb8657b
Parents: 6528d25
Author: Sam Corbett <sam.corb...@cloudsoftcorp.com>
Authored: Wed Feb 3 17:09:17 2016 +0000
Committer: Sam Corbett <sam.corb...@cloudsoftcorp.com>
Committed: Wed Feb 3 17:09:17 2016 +0000

----------------------------------------------------------------------
 rest/rest-client/pom.xml                        |   5 +
 .../brooklyn/rest/client/BrooklynApiUtil.java   | 154 +++++++++++++++++++
 .../rest/client/BrooklynApiUtilTest.java        | 129 ++++++++++++++++
 3 files changed, 288 insertions(+)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/d1733900/rest/rest-client/pom.xml
----------------------------------------------------------------------
diff --git a/rest/rest-client/pom.xml b/rest/rest-client/pom.xml
index 5b27148..f3fa883 100644
--- a/rest/rest-client/pom.xml
+++ b/rest/rest-client/pom.xml
@@ -144,6 +144,11 @@
             <version>${project.version}</version>
             <scope>test</scope>
         </dependency>
+        <dependency>
+            <groupId>com.google.mockwebserver</groupId>
+            <artifactId>mockwebserver</artifactId>
+            <scope>test</scope>
+        </dependency>
     </dependencies>
     
 </project>

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/d1733900/rest/rest-client/src/main/java/org/apache/brooklyn/rest/client/BrooklynApiUtil.java
----------------------------------------------------------------------
diff --git 
a/rest/rest-client/src/main/java/org/apache/brooklyn/rest/client/BrooklynApiUtil.java
 
b/rest/rest-client/src/main/java/org/apache/brooklyn/rest/client/BrooklynApiUtil.java
new file mode 100644
index 0000000..ce57da5
--- /dev/null
+++ 
b/rest/rest-client/src/main/java/org/apache/brooklyn/rest/client/BrooklynApiUtil.java
@@ -0,0 +1,154 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.brooklyn.rest.client;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import java.util.Date;
+import java.util.concurrent.Callable;
+import java.util.concurrent.atomic.AtomicReference;
+import javax.ws.rs.core.Response;
+
+import org.apache.brooklyn.rest.api.EffectorApi;
+import org.apache.brooklyn.rest.domain.Status;
+import org.apache.brooklyn.rest.domain.TaskSummary;
+import org.apache.brooklyn.util.repeat.Repeater;
+import org.apache.brooklyn.util.time.Duration;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.common.collect.ImmutableMap;
+
+public class BrooklynApiUtil {
+
+    private static final Logger LOG = 
LoggerFactory.getLogger(BrooklynApiUtil.class);
+    private static final Duration DEFAULT_POLL_PERIOD = Duration.FIVE_SECONDS;
+    private static final Duration DEFAULT_TIMEOUT = Duration.FIVE_MINUTES;
+
+    private BrooklynApiUtil() {}
+
+    /**
+     * Deploys the blueprint and returns the task summary.
+     * @throws Exception If the response from the server when deploying was 
{@link #isUnhealthyResponse unhealthy}.
+     */
+    public static TaskSummary deployBlueprint(BrooklynApi api, String 
blueprint) throws Exception {
+        Response r = api.getApplicationApi().createFromYaml(blueprint);
+        if (isUnhealthyResponse(r)) {
+            throw new Exception("Unexpected response deploying blueprint to 
server: " + r.getStatus());
+        } else {
+            LOG.debug("Server response to deploy blueprint: " + r.getStatus());
+        }
+        return BrooklynApi.getEntity(r, TaskSummary.class);
+    }
+
+    /**
+     * Waits for the application with the given ID to be running.
+     *
+     * @throws IllegalStateException If the application was not running after 
{@link #DEFAULT_TIMEOUT}.
+     */
+    public static void waitForRunningAndThrowOtherwise(BrooklynApi api, String 
applicationId, String taskId) throws IllegalStateException {
+        waitForRunningAndThrowOtherwise(api, applicationId, taskId, 
DEFAULT_TIMEOUT);
+    }
+
+    /**
+     * Waits for the application with the given ID to be running.
+     *
+     * @throws IllegalStateException If the application was not running after 
the given timeout.
+     */
+    public static void waitForRunningAndThrowOtherwise(BrooklynApi api, String 
applicationId, String taskId, Duration timeout) throws IllegalStateException {
+        Status finalStatus = waitForAppStatus(api, applicationId, 
Status.RUNNING, timeout, DEFAULT_POLL_PERIOD);
+        if (!Status.RUNNING.equals(finalStatus)) {
+            LOG.error("Application is not running. Is: " + 
finalStatus.name().toLowerCase());
+
+            StringBuilder message = new StringBuilder();
+            message.append("Application ").append(applicationId)
+                    .append(" should be running but is 
").append(finalStatus.name().toLowerCase())
+                    .append(". ");
+
+            if (Status.ERROR.equals(finalStatus) || 
Status.UNKNOWN.equals(finalStatus)) {
+                String result = getTaskResult(api, taskId);
+                message.append("\nThe result of the task on the server was:\n")
+                        .append(result);
+            }
+            throw new IllegalStateException(message.toString());
+        }
+    }
+
+    /**
+     * Polls Brooklyn until the given application has the given status. Quits 
early if the
+     * application's status is {@link 
org.apache.brooklyn.rest.domain.Status#ERROR} or
+     * {@link org.apache.brooklyn.rest.domain.Status#UNKNOWN} and 
desiredStatus is something else.
+     *
+     * @return The final polled status.
+     */
+    public static Status waitForAppStatus(final BrooklynApi api, final String 
application, final Status desiredStatus,
+            Duration timeout, Duration pollPeriod) {
+        final AtomicReference<Status> appStatus = new 
AtomicReference<>(Status.UNKNOWN);
+        final boolean shortcutOnError = !Status.ERROR.equals(desiredStatus) && 
!Status.UNKNOWN.equals(desiredStatus);
+        LOG.info("Waiting " + timeout + " from " + new Date() + " for 
application " + application + " to be " + desiredStatus);
+        boolean finalAppStatusKnown = Repeater.create("Waiting for application 
" + application + " status to be " + desiredStatus)
+                .every(pollPeriod)
+                .limitTimeTo(timeout)
+                .rethrowExceptionImmediately()
+                .until(new Callable<Boolean>() {
+                    @Override
+                    public Boolean call() throws Exception {
+                        Status status = 
api.getApplicationApi().get(application).getStatus();
+                        LOG.debug("Application " + application + " status is: 
" + status);
+                        appStatus.set(status);
+                        return desiredStatus.equals(status) || 
(shortcutOnError &&
+                                (Status.ERROR.equals(status) || 
Status.UNKNOWN.equals(status)));
+                    }
+                })
+                .run();
+        if (appStatus.get().equals(desiredStatus)) {
+            LOG.info("Application " + application + " is " + 
desiredStatus.name());
+        } else {
+            LOG.warn("Application is not " + desiredStatus.name() + " within " 
+ timeout +
+                    ". Status is: " + appStatus.get());
+        }
+        return appStatus.get();
+    }
+
+    /**
+     * Use the {@link EffectorApi effector API} to invoke the stop effector on 
the given application.
+     */
+    public static void attemptStop(BrooklynApi api, String application, 
Duration timeout) {
+        api.getEffectorApi().invoke(application, application, "stop", 
String.valueOf(timeout.toMilliseconds()),
+                ImmutableMap.<String, Object>of());
+    }
+
+    /**
+     * @return The result of the task with the given id, or "unknown" if it 
could not be found.
+     */
+    public static String getTaskResult(BrooklynApi api, String taskId) {
+        checkNotNull(taskId, "taskId");
+        TaskSummary summary = api.getActivityApi().get(taskId);
+        return summary == null || summary.getResult() == null ? "unknown" : 
summary.getResult().toString();
+    }
+
+    /**
+     * @return true if response's status code is not between 200 and 299 
inclusive.
+     */
+    public static boolean isUnhealthyResponse(Response response) {
+        return response.getStatus() < 200 || response.getStatus() >= 300;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/d1733900/rest/rest-client/src/test/java/org/apache/brooklyn/rest/client/BrooklynApiUtilTest.java
----------------------------------------------------------------------
diff --git 
a/rest/rest-client/src/test/java/org/apache/brooklyn/rest/client/BrooklynApiUtilTest.java
 
b/rest/rest-client/src/test/java/org/apache/brooklyn/rest/client/BrooklynApiUtilTest.java
new file mode 100644
index 0000000..eae9b6d
--- /dev/null
+++ 
b/rest/rest-client/src/test/java/org/apache/brooklyn/rest/client/BrooklynApiUtilTest.java
@@ -0,0 +1,129 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.brooklyn.rest.client;
+
+import static org.apache.brooklyn.test.Asserts.assertEquals;
+
+import javax.ws.rs.core.HttpHeaders;
+import javax.ws.rs.core.MediaType;
+
+import org.apache.brooklyn.util.collections.Jsonya;
+import org.apache.brooklyn.util.core.http.BetterMockWebServer;
+import org.testng.annotations.AfterMethod;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import com.google.common.base.Joiner;
+import com.google.mockwebserver.MockResponse;
+import com.google.mockwebserver.RecordedRequest;
+
+public class BrooklynApiUtilTest {
+
+    private static final String APP_ID = "fedcba";
+
+    private static final String YAML = Joiner.on("\n").join(
+            "name: test-blueprint",
+            "location: localhost",
+            "services:",
+            "- type: brooklyn.entity.basic.EmptySoftwareProcess");
+
+    private BetterMockWebServer server;
+
+    @BeforeMethod(alwaysRun = true)
+    public void newMockWebServer() {
+        server = BetterMockWebServer.newInstanceLocalhost();
+    }
+
+    @AfterMethod(alwaysRun = true)
+    public void shutDownServer() throws Exception {
+        if (server != null) server.shutdown();
+    }
+
+    @Test
+    public void testDeployBlueprint() throws Exception {
+        server.enqueue(taskSummaryResponse());
+        server.play();
+
+        BrooklynApi api = 
BrooklynApi.newInstance(server.getUrl("/").toString());
+        BrooklynApiUtil.deployBlueprint(api, YAML);
+
+        RecordedRequest request = server.takeRequest();
+        assertEquals("/v1/applications", request.getPath());
+        assertEquals("POST", request.getMethod());
+        assertEquals(YAML, new String(request.getBody()));
+    }
+
+    @Test
+    public void testWaitForRunningExitsCleanlyWhenAppRunning() throws 
Exception {
+        server.enqueue(applicationStatusResponse("RUNNING"));
+        server.play();
+
+        BrooklynApi api = 
BrooklynApi.newInstance(server.getUrl("/").toString());
+        BrooklynApiUtil.waitForRunningAndThrowOtherwise(api, "appId", 
"taskId");
+        // i.e. no exception
+    }
+
+    @Test(expectedExceptions = {IllegalStateException.class})
+    public void testWaitForRunningFailsWhenAppStatusError() throws Exception {
+        server.enqueue(applicationStatusResponse("ERROR"));
+        // Method checks for status of task.
+        server.enqueue(taskSummaryResponse());
+        server.play();
+
+        BrooklynApi api = 
BrooklynApi.newInstance(server.getUrl("/").toString());
+        BrooklynApiUtil.waitForRunningAndThrowOtherwise(api, "appId", 
"taskId");
+    }
+
+    @Test(expectedExceptions = {IllegalStateException.class})
+    public void testWaitForRunningFailsWhenAppStatusUnknown() throws Exception 
{
+        server.enqueue(applicationStatusResponse("UNKNOWN"));
+        // Method checks for status of task.
+        server.enqueue(taskSummaryResponse());
+        server.play();
+
+        BrooklynApi api = 
BrooklynApi.newInstance(server.getUrl("/").toString());
+        BrooklynApiUtil.waitForRunningAndThrowOtherwise(api, "appId", 
"taskId");
+    }
+
+    /** @return a response whose Content-Type header is application/json. */
+    private MockResponse newJsonResponse() {
+        return new MockResponse()
+                .setHeader(HttpHeaders.CONTENT_TYPE, 
MediaType.APPLICATION_JSON);
+    }
+
+    private MockResponse taskSummaryResponse() {
+        String body = Jsonya.newInstance()
+                .put("id", "taskid")
+                .put("entityId", APP_ID)
+                .toString();
+        return newJsonResponse().setBody(body);
+    }
+
+    private MockResponse applicationStatusResponse(String status) {
+        String body = Jsonya.newInstance()
+                .put("status", status)
+                .at("spec", "locations").list().add("localhost")
+                .root()
+                .toString();
+        return newJsonResponse()
+                .setBody(body);
+    }
+
+}

Reply via email to