org.apache.brooklyn.entity.brooklynnode.BrooklynNodeIntegrationTest.java Source code

Java tutorial

Introduction

Here is the source code for org.apache.brooklyn.entity.brooklynnode.BrooklynNodeIntegrationTest.java

Source

/*
 * 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.entity.brooklynnode;

import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertFalse;
import static org.testng.Assert.assertTrue;
import static org.testng.Assert.fail;

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;
import java.util.Map;
import java.util.concurrent.ExecutionException;

import org.apache.brooklyn.api.effector.Effector;
import org.apache.brooklyn.api.entity.Entity;
import org.apache.brooklyn.api.entity.EntityLocal;
import org.apache.brooklyn.api.entity.EntitySpec;
import org.apache.brooklyn.core.entity.Entities;
import org.apache.brooklyn.core.entity.lifecycle.Lifecycle;
import org.apache.brooklyn.core.internal.BrooklynProperties;
import org.apache.brooklyn.core.location.Locations;
import org.apache.brooklyn.core.location.PortRanges;
import org.apache.brooklyn.core.objs.proxy.EntityProxyImpl;
import org.apache.brooklyn.core.test.BrooklynAppUnitTestSupport;
import org.apache.brooklyn.entity.brooklynnode.BrooklynNode.DeployBlueprintEffector;
import org.apache.brooklyn.entity.brooklynnode.BrooklynNode.ExistingFileBehaviour;
import org.apache.brooklyn.entity.brooklynnode.BrooklynNode.StopNodeAndKillAppsEffector;
import org.apache.brooklyn.entity.software.base.SoftwareProcess.StopSoftwareParameters.StopMode;
import org.apache.brooklyn.entity.stock.BasicApplication;
import org.apache.brooklyn.entity.stock.BasicApplicationImpl;
import org.apache.brooklyn.feed.http.JsonFunctions;
import org.apache.brooklyn.test.Asserts;
import org.apache.brooklyn.test.EntityTestUtils;
import org.apache.brooklyn.test.HttpTestUtils;
import org.apache.brooklyn.util.collections.MutableMap;
import org.apache.brooklyn.util.core.config.ConfigBag;
import org.apache.brooklyn.util.http.HttpTool;
import org.apache.brooklyn.util.http.HttpToolResponse;
import org.apache.brooklyn.util.exceptions.Exceptions;
import org.apache.brooklyn.util.guava.Functionals;
import org.apache.brooklyn.util.javalang.JavaClassNames;
import org.apache.brooklyn.util.net.Networking;
import org.apache.brooklyn.util.net.Urls;
import org.apache.brooklyn.util.os.Os;
import org.apache.brooklyn.util.text.Strings;
import org.apache.brooklyn.util.time.Duration;
import org.apache.http.auth.UsernamePasswordCredentials;
import org.apache.http.client.HttpClient;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.testng.Assert;
import org.testng.annotations.AfterMethod;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;
import org.apache.brooklyn.location.localhost.LocalhostMachineProvisioningLocation;
import org.apache.brooklyn.location.ssh.SshMachineLocation;

import com.google.common.base.Charsets;
import com.google.common.base.Function;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Iterables;
import com.google.common.io.Files;

/**
 * This test needs to able to access the binary artifact in order to run.
 * The default behaviour is to take this from maven, which works pretty well if you're downloading from hosted maven.
 * <p>
 * This class has been updated so that it does not effect or depend on the contents of ~/.brooklyn/brooklyn.properties .
 * <p>
 * If you wish to supply your own version (useful if testing changes locally!), you'll need to force download of this file.
 * The simplest way is to install:
 * <ul>
 * <li>file://$HOME/.brooklyn/repository/BrooklynNode/${VERSION}/BrooklynNode-${VERSION}.tar.gz - for snapshot versions (filename is default format due to lack of filename in sonatype inferencing; 
 *     note on case-sensitive systems it might have to be all in lower case!)
 * <li>file://$HOME/.brooklyn/repository/BrooklynNode/${VERSION}/brooklyn-${VERSION}-dist.tar.gz - for release versions, filename should match that in maven central
 * </ul>
 * In both cases, remember that you may also need to wipe the local apps cache ($BROOKLYN_DATA_DIR/installs/BrooklynNode).
 * The following commands may be useful:
 * <p>
 * <code>
 * cp ~/.m2/repository/org/apache/brooklyn/brooklyn-dist/0.7.0-SNAPSHOT/brooklyn-dist-0.7.0-SNAPSHOT-dist.tar.gz ~/.brooklyn/repository/BrooklynNode/0.7.0-SNAPSHOT/BrooklynNode-0.7.0-SNAPSHOT.tar.gz
 * rm -rf /tmp/brooklyn-`whoami`/installs/BrooklynNode*
 * </code>
 */
public class BrooklynNodeIntegrationTest extends BrooklynAppUnitTestSupport {

    private static final Logger log = LoggerFactory.getLogger(BrooklynNodeIntegrationTest.class);

    private File pseudoBrooklynPropertiesFile;
    private File pseudoBrooklynCatalogFile;
    private File persistenceDir;
    private LocalhostMachineProvisioningLocation loc;
    private List<LocalhostMachineProvisioningLocation> locs;

    @BeforeMethod(alwaysRun = true)
    @Override
    public void setUp() throws Exception {
        super.setUp();
        pseudoBrooklynPropertiesFile = Os.newTempFile("brooklynnode-test", ".properties");
        pseudoBrooklynPropertiesFile.delete();

        pseudoBrooklynCatalogFile = Os.newTempFile("brooklynnode-test", ".catalog");
        pseudoBrooklynCatalogFile.delete();

        loc = app.newLocalhostProvisioningLocation();
        locs = ImmutableList.of(loc);
    }

    @AfterMethod(alwaysRun = true)
    @Override
    public void tearDown() throws Exception {
        try {
            super.tearDown();
        } finally {
            if (pseudoBrooklynPropertiesFile != null)
                pseudoBrooklynPropertiesFile.delete();
            if (pseudoBrooklynCatalogFile != null)
                pseudoBrooklynCatalogFile.delete();
            if (persistenceDir != null)
                Os.deleteRecursively(persistenceDir);
        }
    }

    protected EntitySpec<BrooklynNode> newBrooklynNodeSpecForTest() {
        // poor man's way to output which test is running
        log.info("Creating entity spec for " + JavaClassNames.callerNiceClassAndMethod(1));

        return EntitySpec.create(BrooklynNode.class)
                .configure(BrooklynNode.WEB_CONSOLE_BIND_ADDRESS, Networking.ANY_NIC)
                .configure(BrooklynNode.ON_EXISTING_PROPERTIES_FILE, ExistingFileBehaviour.DO_NOT_USE);

        /* yaml equivalent, for testing:
            
        location: localhost
        services:
        - type: org.apache.brooklyn.entity.brooklynnode.BrooklynNode
        bindAddress: 127.0.0.1
        onExistingProperties: do_not_use
            
        # some other options
        enabledHttpProtocols: [ https ]
        managementPassword: s3cr3t
        brooklynLocalPropertiesContents: |
        brooklyn.webconsole.security.https.required=true
        brooklyn.webconsole.security.users=admin
        brooklyn.webconsole.security.user.admin.password=s3cr3t
        brooklyn.location.localhost.enabled=false
            
         */
    }

    @Test(groups = "Integration")
    public void testCanStartAndStop() throws Exception {
        BrooklynNode brooklynNode = app.createAndManageChild(newBrooklynNodeSpecForTest());
        app.start(locs);
        log.info("started " + app + " containing " + brooklynNode + " for " + JavaClassNames.niceClassAndMethod());

        EntityTestUtils.assertAttributeEqualsEventually(brooklynNode, BrooklynNode.SERVICE_UP, true);

        brooklynNode.stop();
        EntityTestUtils.assertAttributeEquals(brooklynNode, BrooklynNode.SERVICE_UP, false);
    }

    @Test(groups = "Integration")
    public void testSetsGlobalBrooklynPropertiesFromContents() throws Exception {
        BrooklynNode brooklynNode = app.createAndManageChild(newBrooklynNodeSpecForTest()
                .configure(BrooklynNode.BROOKLYN_GLOBAL_PROPERTIES_REMOTE_PATH,
                        pseudoBrooklynPropertiesFile.getAbsolutePath())
                .configure(BrooklynNode.BROOKLYN_GLOBAL_PROPERTIES_CONTENTS, "abc=def"));
        app.start(locs);
        log.info("started " + app + " containing " + brooklynNode + " for " + JavaClassNames.niceClassAndMethod());

        assertEquals(Files.readLines(pseudoBrooklynPropertiesFile, Charsets.UTF_8), ImmutableList.of("abc=def"));
    }

    @Test(groups = "Integration")
    public void testSetsLocalBrooklynPropertiesFromContents() throws Exception {
        BrooklynNode brooklynNode = app.createAndManageChild(newBrooklynNodeSpecForTest()
                .configure(BrooklynNode.BROOKLYN_LOCAL_PROPERTIES_REMOTE_PATH,
                        pseudoBrooklynPropertiesFile.getAbsolutePath())
                .configure(BrooklynNode.BROOKLYN_LOCAL_PROPERTIES_CONTENTS, "abc=def"));
        app.start(locs);
        log.info("started " + app + " containing " + brooklynNode + " for " + JavaClassNames.niceClassAndMethod());

        assertEquals(Files.readLines(pseudoBrooklynPropertiesFile, Charsets.UTF_8), ImmutableList.of("abc=def"));
    }

    @Test(groups = "Integration")
    public void testSetsBrooklynPropertiesFromUri() throws Exception {
        File brooklynPropertiesSourceFile = File.createTempFile("brooklynnode-test", ".properties");
        Files.write("abc=def", brooklynPropertiesSourceFile, Charsets.UTF_8);

        BrooklynNode brooklynNode = app.createAndManageChild(newBrooklynNodeSpecForTest()
                .configure(BrooklynNode.BROOKLYN_GLOBAL_PROPERTIES_REMOTE_PATH,
                        pseudoBrooklynPropertiesFile.getAbsolutePath())
                .configure(BrooklynNode.BROOKLYN_GLOBAL_PROPERTIES_URI,
                        brooklynPropertiesSourceFile.toURI().toString()));
        app.start(locs);
        log.info("started " + app + " containing " + brooklynNode + " for " + JavaClassNames.niceClassAndMethod());

        assertEquals(Files.readLines(pseudoBrooklynPropertiesFile, Charsets.UTF_8), ImmutableList.of("abc=def"));
    }

    @Test(groups = "Integration")
    public void testSetsBrooklynCatalogFromContents() throws Exception {
        BrooklynNode brooklynNode = app.createAndManageChild(newBrooklynNodeSpecForTest()
                .configure(BrooklynNode.BROOKLYN_CATALOG_REMOTE_PATH, pseudoBrooklynCatalogFile.getAbsolutePath())
                .configure(BrooklynNode.BROOKLYN_CATALOG_CONTENTS, "<catalog/>"));
        app.start(locs);
        log.info("started " + app + " containing " + brooklynNode + " for " + JavaClassNames.niceClassAndMethod());

        assertEquals(Files.readLines(pseudoBrooklynCatalogFile, Charsets.UTF_8), ImmutableList.of("<catalog/>"));
    }

    @Test(groups = "Integration")
    public void testSetsBrooklynCatalogFromUri() throws Exception {
        File brooklynCatalogSourceFile = File.createTempFile("brooklynnode-test", ".catalog");
        Files.write("abc=def", brooklynCatalogSourceFile, Charsets.UTF_8);

        BrooklynNode brooklynNode = app.createAndManageChild(newBrooklynNodeSpecForTest()
                .configure(BrooklynNode.BROOKLYN_CATALOG_REMOTE_PATH, pseudoBrooklynCatalogFile.getAbsolutePath())
                .configure(BrooklynNode.BROOKLYN_CATALOG_URI, brooklynCatalogSourceFile.toURI().toString()));
        app.start(locs);
        log.info("started " + app + " containing " + brooklynNode + " for " + JavaClassNames.niceClassAndMethod());

        assertEquals(Files.readLines(pseudoBrooklynCatalogFile, Charsets.UTF_8), ImmutableList.of("abc=def"));
    }

    @Test(groups = "Integration")
    public void testCopiesResources() throws Exception {
        File sourceFile = File.createTempFile("brooklynnode-test", ".properties");
        Files.write("abc=def", sourceFile, Charsets.UTF_8);
        File tempDir = Files.createTempDir();
        File expectedFile = new File(tempDir, "myfile.txt");

        try {
            BrooklynNode brooklynNode = app.createAndManageChild(
                    newBrooklynNodeSpecForTest().configure(BrooklynNode.RUN_DIR, tempDir.getAbsolutePath())
                            .configure(BrooklynNode.COPY_TO_RUNDIR,
                                    ImmutableMap.of(sourceFile.getAbsolutePath(), "${RUN}/myfile.txt")));
            app.start(locs);
            log.info("started " + app + " containing " + brooklynNode + " for "
                    + JavaClassNames.niceClassAndMethod());

            assertEquals(Files.readLines(expectedFile, Charsets.UTF_8), ImmutableList.of("abc=def"));
        } finally {
            expectedFile.delete();
            tempDir.delete();
            sourceFile.delete();
        }
    }

    @Test(groups = "Integration")
    public void testCopiesClasspathEntriesInConfigKey() throws Exception {
        String content = "abc=def";
        File classpathEntry1 = File.createTempFile("first", ".properties");
        File classpathEntry2 = File.createTempFile("second", ".properties");
        Files.write(content, classpathEntry1, Charsets.UTF_8);
        Files.write(content, classpathEntry2, Charsets.UTF_8);
        File tempDir = Files.createTempDir();
        File destDir = new File(new File(tempDir, "lib"), "dropins");
        File expectedFile1 = new File(destDir, classpathEntry1.getName());
        File expectedFile2 = new File(destDir, classpathEntry2.getName());

        try {
            BrooklynNode brooklynNode = app.createAndManageChild(
                    newBrooklynNodeSpecForTest().configure(BrooklynNode.RUN_DIR, tempDir.getAbsolutePath())
                            .configure(BrooklynNode.CLASSPATH, ImmutableList.of(classpathEntry1.getAbsolutePath(),
                                    classpathEntry2.getAbsolutePath())));
            app.start(locs);
            log.info("started " + app + " containing " + brooklynNode + " for "
                    + JavaClassNames.niceClassAndMethod());

            assertEquals(Files.readLines(expectedFile1, Charsets.UTF_8), ImmutableList.of(content));
            assertEquals(Files.readLines(expectedFile2, Charsets.UTF_8), ImmutableList.of(content));
        } finally {
            expectedFile1.delete();
            expectedFile2.delete();
            tempDir.delete();
            classpathEntry1.delete();
            classpathEntry2.delete();
        }
    }

    @Test(groups = "Integration")
    public void testCopiesClasspathEntriesInConfigKey2() throws Exception {
        String content = "abc=def";
        File classpathEntry1 = File.createTempFile("first", ".properties");
        File classpathEntry2 = File.createTempFile("second", ".properties");
        Files.write(content, classpathEntry1, Charsets.UTF_8);
        Files.write(content, classpathEntry2, Charsets.UTF_8);
        File tempDir = Files.createTempDir();
        String testName1 = "test_" + classpathEntry1.getName();
        File destDir = new File(new File(tempDir, "lib"), "dropins");
        File expectedFile1 = new File(destDir, testName1);
        String testName2 = "test_" + classpathEntry2.getName();
        File expectedFile2 = new File(destDir, testName2);
        Map entry1 = ImmutableMap.of("url", classpathEntry1.getAbsolutePath(), "filename", testName1);
        Map entry2 = ImmutableMap.of("url", classpathEntry2.getAbsolutePath(), "filename", testName2);

        try {
            BrooklynNode brooklynNode = app.createAndManageChild(
                    newBrooklynNodeSpecForTest().configure(BrooklynNode.RUN_DIR, tempDir.getAbsolutePath())
                            .configure(BrooklynNode.CLASSPATH, ImmutableList.of(entry1, entry2)));
            app.start(locs);
            log.info("started " + app + " containing " + brooklynNode + " for "
                    + JavaClassNames.niceClassAndMethod());

            assertEquals(Files.readLines(expectedFile1, Charsets.UTF_8), ImmutableList.of(content));
            assertEquals(Files.readLines(expectedFile2, Charsets.UTF_8), ImmutableList.of(content));
        } finally {
            expectedFile1.delete();
            expectedFile2.delete();
            tempDir.delete();
            classpathEntry1.delete();
            classpathEntry2.delete();
        }
    }

    /*
    Exception java.io.FileNotFoundException
        
    Message: /tmp/1445824492556-0/lib/first4759470075693094333.properties (No such file or directory)
    Stacktrace:
        
        
    at java.io.FileInputStream.open(Native Method)
    at java.io.FileInputStream.<init>(FileInputStream.java:146)
    at com.google.common.io.Files$FileByteSource.openStream(Files.java:126)
    at com.google.common.io.Files$FileByteSource.openStream(Files.java:116)
    at com.google.common.io.ByteSource$AsCharSource.openStream(ByteSource.java:435)
    at com.google.common.io.CharSource.getInput(CharSource.java:94)
    at com.google.common.io.CharSource.getInput(CharSource.java:65)
    at com.google.common.io.CharStreams.readLines(CharStreams.java:344)
    at com.google.common.io.Files.readLines(Files.java:741)
    at com.google.common.io.Files.readLines(Files.java:712)
    at org.apache.brooklyn.entity.brooklynnode.BrooklynNodeIntegrationTest.testCopiesClasspathEntriesInBrooklynProperties(BrooklynNodeIntegrationTest.java:358)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:606)
    at org.testng.internal.MethodInvocationHelper.invokeMethod(MethodInvocationHelper.java:84)
    at org.testng.internal.Invoker.invokeMethod(Invoker.java:714)
    at org.testng.internal.Invoker.invokeTestMethod(Invoker.java:901)
    at org.testng.internal.Invoker.invokeTestMethods(Invoker.java:1231)
    at org.testng.internal.TestMethodWorker.invokeTestMethods(TestMethodWorker.java:127)
    at org.testng.internal.TestMethodWorker.run(TestMethodWorker.java:111)
    at org.testng.TestRunner.privateRun(TestRunner.java:767)
    at org.testng.TestRunner.run(TestRunner.java:617)
    at org.testng.SuiteRunner.runTest(SuiteRunner.java:348)
    at org.testng.SuiteRunner.runSequentially(SuiteRunner.java:343)
    at org.testng.SuiteRunner.privateRun(SuiteRunner.java:305)
    at org.testng.SuiteRunner.run(SuiteRunner.java:254)
    at org.testng.SuiteRunnerWorker.runSuite(SuiteRunnerWorker.java:52)
    at org.testng.SuiteRunnerWorker.run(SuiteRunnerWorker.java:86)
    at org.testng.TestNG.runSuitesSequentially(TestNG.java:1224)
    at org.testng.TestNG.runSuitesLocally(TestNG.java:1149)
    at org.testng.TestNG.run(TestNG.java:1057)
    at org.apache.maven.surefire.testng.TestNGExecutor.run(TestNGExecutor.java:115)
    at org.apache.maven.surefire.testng.TestNGDirectoryTestSuite.executeMulti(TestNGDirectoryTestSuite.java:205)
    at org.apache.maven.surefire.testng.TestNGDirectoryTestSuite.execute(TestNGDirectoryTestSuite.java:108)
    at org.apache.maven.surefire.testng.TestNGProvider.invoke(TestNGProvider.java:111)
    at org.apache.maven.surefire.booter.ForkedBooter.invokeProviderInSameClassLoader(ForkedBooter.java:203)
    at org.apache.maven.surefire.booter.ForkedBooter.runSuitesInProcess(ForkedBooter.java:155)
    at org.apache.maven.surefire.booter.ForkedBooter.main(ForkedBooter.java:103)
    */
    @Test(groups = { "Integration", "Broken" })
    public void testCopiesClasspathEntriesInBrooklynProperties() throws Exception {
        String content = "abc=def";
        File classpathEntry1 = File.createTempFile("first", ".properties");
        File classpathEntry2 = File.createTempFile("second", ".properties");
        Files.write(content, classpathEntry1, Charsets.UTF_8);
        Files.write(content, classpathEntry2, Charsets.UTF_8);
        File tempDir = Files.createTempDir();
        File expectedFile1 = new File(new File(tempDir, "lib"), classpathEntry1.getName());
        File expectedFile2 = new File(new File(tempDir, "lib"), classpathEntry2.getName());

        try {
            String propName = BrooklynNode.CLASSPATH.getName();
            String propValue = classpathEntry1.toURI().toString() + "," + classpathEntry2.toURI().toString();
            ((BrooklynProperties) app.getManagementContext().getConfig()).put(propName, propValue);

            BrooklynNode brooklynNode = app.createAndManageChild(
                    newBrooklynNodeSpecForTest().configure(BrooklynNode.RUN_DIR, tempDir.getAbsolutePath()));
            app.start(locs);
            log.info("started " + app + " containing " + brooklynNode + " for "
                    + JavaClassNames.niceClassAndMethod());

            assertEquals(Files.readLines(expectedFile1, Charsets.UTF_8), ImmutableList.of(content));
            assertEquals(Files.readLines(expectedFile2, Charsets.UTF_8), ImmutableList.of(content));
        } finally {
            expectedFile1.delete();
            expectedFile2.delete();
            tempDir.delete();
            classpathEntry1.delete();
            classpathEntry2.delete();
        }
    }

    // TODO test that the classpath set above is actually used

    @Test(groups = "Integration")
    public void testSetsBrooklynWebConsolePort() throws Exception {
        BrooklynNode brooklynNode = app.createAndManageChild(
                newBrooklynNodeSpecForTest().configure(BrooklynNode.HTTP_PORT, PortRanges.fromString("45000+")));
        app.start(locs);
        log.info("started " + app + " containing " + brooklynNode + " for " + JavaClassNames.niceClassAndMethod());

        Integer httpPort = brooklynNode.getAttribute(BrooklynNode.HTTP_PORT);
        URI webConsoleUri = brooklynNode.getAttribute(BrooklynNode.WEB_CONSOLE_URI);
        assertTrue(httpPort >= 45000 && httpPort < 54100, "httpPort=" + httpPort);
        assertEquals((Integer) webConsoleUri.getPort(), httpPort);
        HttpTestUtils.assertHttpStatusCodeEquals(webConsoleUri.toString(), 200, 401);
    }

    @Test(groups = "Integration")
    public void testStartsAppOnStartup() throws Exception {
        BrooklynNode brooklynNode = app.createAndManageChild(
                newBrooklynNodeSpecForTest().configure(BrooklynNode.APP, BasicApplicationImpl.class.getName()));
        app.start(locs);
        log.info("started " + app + " containing " + brooklynNode + " for " + JavaClassNames.niceClassAndMethod());

        URI webConsoleUri = brooklynNode.getAttribute(BrooklynNode.WEB_CONSOLE_URI);
        waitForApps(webConsoleUri, 1);
        String apps = HttpTestUtils.getContent(webConsoleUri.toString() + "/v1/applications");
        List<String> appType = parseJsonList(apps, ImmutableList.of("spec", "type"), String.class);
        assertEquals(appType, ImmutableList.of(BasicApplication.class.getName()));
    }

    protected static void waitForApps(String webConsoleUri) {
        HttpTestUtils.assertHttpStatusCodeEquals(webConsoleUri + "/v1/applications", 200, 403);
        HttpTestUtils.assertHttpStatusCodeEventuallyEquals(webConsoleUri + "/v1/applications", 200);
    }

    // TODO Should introduce startup stages and let the client select which stage it expects to be complete
    protected void waitForApps(final URI webConsoleUri, final int num) {
        waitForApps(webConsoleUri.toString());

        // e.g. [{"id":"UnBqPcqg","spec":{"name":"Application (UnBqPcqg)","type":"org.apache.brooklyn.entity.stock.BasicApplication","locations":["pOL4NtiW"]},"status":"RUNNING","links":{"self":"/v1/applications/UnBqPcqg","entities":"/v1/applications/UnBqPcqg/entities"}}]
        Asserts.succeedsEventually(new Runnable() {
            @Override
            public void run() {
                //Wait all apps to become managed
                String appsContent = HttpTestUtils.getContent(webConsoleUri.toString() + "/v1/applications");
                List<String> appIds = parseJsonList(appsContent, ImmutableList.of("id"), String.class);
                assertEquals(appIds.size(), num);

                // and then to start
                List<String> statuses = parseJsonList(appsContent, ImmutableList.of("status"), String.class);
                for (String status : statuses) {
                    assertEquals(status, Lifecycle.RUNNING.toString().toUpperCase());
                }
            }
        });
    }

    @Test(groups = "Integration")
    public void testStartsAppViaEffector() throws Exception {
        BrooklynNode brooklynNode = app.createAndManageChild(newBrooklynNodeSpecForTest());
        app.start(locs);
        log.info("started " + app + " containing " + brooklynNode + " for " + JavaClassNames.niceClassAndMethod());

        // note there is also a test for this in DeployApplication
        final URI webConsoleUri = brooklynNode.getAttribute(BrooklynNode.WEB_CONSOLE_URI);
        waitForApps(webConsoleUri.toString());

        final String id = brooklynNode.invoke(BrooklynNode.DEPLOY_BLUEPRINT, ConfigBag.newInstance()
                .configure(DeployBlueprintEffector.BLUEPRINT_TYPE, BasicApplication.class.getName()).getAllConfig())
                .get();

        String apps = HttpTestUtils.getContent(webConsoleUri.toString() + "/v1/applications");
        List<String> appType = parseJsonList(apps, ImmutableList.of("spec", "type"), String.class);
        assertEquals(appType, ImmutableList.of(BasicApplication.class.getName()));

        HttpTestUtils.assertContentEventuallyMatches(
                webConsoleUri.toString() + "/v1/applications/" + id + "/entities/" + id + "/sensors/service.state",
                "\"?(running|RUNNING)\"?");
    }

    @Test(groups = "Integration")
    public void testUsesLocation() throws Exception {
        String brooklynPropertiesContents = "brooklyn.location.named.mynamedloc=localhost:(name=myname)\n" +
        //force lat+long so test will work when offline
                "brooklyn.location.named.mynamedloc.latitude=123\n"
                + "brooklyn.location.named.mynamedloc.longitude=45.6\n";

        BrooklynNode brooklynNode = app.createAndManageChild(newBrooklynNodeSpecForTest()
                .configure(BrooklynNode.BROOKLYN_LOCAL_PROPERTIES_CONTENTS, brooklynPropertiesContents)
                .configure(BrooklynNode.APP, BasicApplicationImpl.class.getName())
                .configure(BrooklynNode.LOCATIONS, "named:mynamedloc"));
        app.start(locs);
        log.info("started " + app + " containing " + brooklynNode + " for " + JavaClassNames.niceClassAndMethod());

        URI webConsoleUri = brooklynNode.getAttribute(BrooklynNode.WEB_CONSOLE_URI);
        waitForApps(webConsoleUri, 1);

        // Check that "mynamedloc" has been picked up from the brooklyn.properties
        String locsContent = HttpTestUtils.getContent(webConsoleUri.toString() + "/v1/locations");
        List<String> locNames = parseJsonList(locsContent, ImmutableList.of("name"), String.class);
        assertTrue(locNames.contains("mynamedloc"), "locNames=" + locNames);

        // Find the id of the concrete location instance of the app
        String appsContent = HttpTestUtils.getContent(webConsoleUri.toString() + "/v1/applications");
        List<String[]> appLocationIds = parseJsonList(appsContent, ImmutableList.of("spec", "locations"),
                String[].class);
        String appLocationId = Iterables.getOnlyElement(appLocationIds)[0]; // app.getManagementContext().getLocationRegistry()

        // Check that the concrete location is of the required type
        String locatedLocationsContent = HttpTestUtils
                .getContent(webConsoleUri.toString() + "/v1/locations/usage/LocatedLocations");
        assertEquals(parseJson(locatedLocationsContent, ImmutableList.of(appLocationId, "name"), String.class),
                "myname");
        assertEquals(parseJson(locatedLocationsContent, ImmutableList.of(appLocationId, "longitude"), Double.class),
                45.6, 0.00001);
    }

    @Test(groups = "Integration")
    public void testAuthenticationAndHttps() throws Exception {
        String adminPassword = "p4ssw0rd";
        BrooklynNode brooklynNode = app.createAndManageChild(newBrooklynNodeSpecForTest()
                .configure(BrooklynNode.ENABLED_HTTP_PROTOCOLS, ImmutableList.of("https"))
                .configure(BrooklynNode.MANAGEMENT_PASSWORD, adminPassword)
                .configure(BrooklynNode.BROOKLYN_LOCAL_PROPERTIES_CONTENTS,
                        Strings.lines("brooklyn.webconsole.security.https.required=true",
                                "brooklyn.webconsole.security.users=admin",
                                "brooklyn.webconsole.security.user.admin.password=" + adminPassword,
                                "brooklyn.location.localhost.enabled=false")));
        app.start(locs);
        log.info("started " + app + " containing " + brooklynNode + " for " + JavaClassNames.niceClassAndMethod());

        URI webConsoleUri = brooklynNode.getAttribute(BrooklynNode.WEB_CONSOLE_URI);
        Assert.assertTrue(webConsoleUri.toString().startsWith("https://"),
                "web console not https: " + webConsoleUri);
        Integer httpsPort = brooklynNode.getAttribute(BrooklynNode.HTTPS_PORT);
        Assert.assertTrue(httpsPort != null && httpsPort >= 8443 && httpsPort <= 8500);
        Assert.assertTrue(webConsoleUri.toString().contains("" + httpsPort),
                "web console not using right https port (" + httpsPort + "): " + webConsoleUri);
        HttpTestUtils.assertHttpStatusCodeEquals(webConsoleUri.toString(), 401);

        HttpClient http = HttpTool.httpClientBuilder().trustAll().uri(webConsoleUri).laxRedirect(true)
                .credentials(new UsernamePasswordCredentials("admin", adminPassword)).build();
        HttpToolResponse response = HttpTool.httpGet(http, webConsoleUri, MutableMap.<String, String>of());
        Assert.assertEquals(response.getResponseCode(), 200);
    }

    @Test(groups = "Integration")
    public void testStopPlainThrowsException() throws Exception {
        BrooklynNode brooklynNode = setUpBrooklynNodeWithApp();

        // Not using annotation with `expectedExceptions = PropagatedRuntimeException.class` because want to 
        // ensure exception comes from stop. On jenkins, was seeing setUpBrooklynNodeWithApp fail in 
        // testStopAndKillAppsEffector; so can't tell if this method was really passing!
        try {
            brooklynNode.stop();
            fail("Expected " + brooklynNode + " stop to fail, because has app");
        } catch (Exception e) {
            IllegalStateException ise = Exceptions.getFirstThrowableOfType(e, IllegalStateException.class);
            if (ise != null && ise.toString().contains("Can't stop instance with running applications")) {
                // success
            } else {
                throw e;
            }
        } finally {
            try {
                brooklynNode
                        .invoke(BrooklynNode.STOP_NODE_AND_KILL_APPS, ImmutableMap
                                .of(StopNodeAndKillAppsEffector.TIMEOUT.getName(), Duration.THIRTY_SECONDS))
                        .getUnchecked();
            } catch (Exception e) {
                log.warn("Error in stopNodeAndKillApps for " + brooklynNode + " (continuing)", e);
            }
        }
    }

    @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);
    }

    @Test(groups = "Integration")
    public void testStopAndRestartProcess() throws Exception {
        persistenceDir = Files.createTempDir();
        BrooklynNode brooklynNode = app.createAndManageChild(newBrooklynNodeSpecForTest()
                .configure(BrooklynNode.EXTRA_LAUNCH_PARAMETERS,
                        "--persist auto --persistenceDir " + persistenceDir.getAbsolutePath())
                .configure(BrooklynNode.APP, BasicApplicationImpl.class.getName()));
        app.start(locs);
        log.info("started " + app + " containing " + brooklynNode + " for " + JavaClassNames.niceClassAndMethod());
        File pidFile = new File(getDriver(brooklynNode).getPidFile());
        URI webConsoleUri = brooklynNode.getAttribute(BrooklynNode.WEB_CONSOLE_URI);

        waitForApps(webConsoleUri, 1);

        // Stop just the process; will not have unmanaged entity unless machine was being terminated 
        brooklynNode
                .invoke(BrooklynNode.STOP,
                        ImmutableMap.<String, Object>of(
                                BrooklynNode.StopSoftwareParameters.STOP_MACHINE_MODE.getName(), StopMode.NEVER,
                                BrooklynNode.StopSoftwareParameters.STOP_PROCESS_MODE.getName(), StopMode.ALWAYS))
                .getUnchecked();

        assertTrue(Entities.isManaged(brooklynNode));
        assertFalse(isPidRunning(pidFile), "pid in " + pidFile + " still running");

        // Clear the startup app so it's not started second time, in addition to the rebind state
        // TODO remove this once the startup app is created only if no previous persistence state
        brooklynNode.config().set(BrooklynNode.APP, (String) null);
        ((EntityLocal) brooklynNode).sensors().set(BrooklynNode.APP, null);

        // Restart the process; expect persisted state to have been restored, so apps still known about
        brooklynNode
                .invoke(BrooklynNode.RESTART,
                        ImmutableMap.<String, Object>of(
                                BrooklynNode.RestartSoftwareParameters.RESTART_MACHINE.getName(), "false"))
                .getUnchecked();

        waitForApps(webConsoleUri.toString());
        String apps = HttpTestUtils.getContent(webConsoleUri.toString() + "/v1/applications");
        List<String> appType = parseJsonList(apps, ImmutableList.of("spec", "type"), String.class);
        assertEquals(appType, ImmutableList.of(BasicApplication.class.getName()));
    }

    private void createNodeAndExecStopEffector(Effector<?> eff) throws Exception {
        BrooklynNode brooklynNode = setUpBrooklynNodeWithApp();
        File pidFile = new File(getDriver(brooklynNode).getPidFile());
        assertTrue(isPidRunning(pidFile));

        brooklynNode.invoke(eff, Collections.<String, Object>emptyMap()).getUnchecked();

        // Note can't use driver.isRunning to check shutdown; can't invoke scripts on an unmanaged entity
        EntityTestUtils.assertAttributeEquals(brooklynNode, BrooklynNode.SERVICE_UP, false);

        // unmanaged if the machine is destroyed - ie false on localhost (this test by default), but true in the cloud 
        //        assertFalse(Entities.isManaged(brooklynNode));

        assertFalse(isPidRunning(pidFile), "pid in " + pidFile + " still running");
    }

    private boolean isPidRunning(File pidFile) throws Exception {
        SshMachineLocation machine = loc.obtain();
        try {
            int result = machine.execScript("check-pid",
                    ImmutableList.of("test -f " + pidFile + " || exit 1", "ps -p `cat " + pidFile + "`"));
            return result == 0;
        } finally {
            loc.release(machine);
            Locations.unmanage(machine);
        }
    }

    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, 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);

        String baseUrl = brooklynNode.getAttribute(BrooklynNode.WEB_CONSOLE_URI).toString();
        waitForApps(baseUrl);

        final String id = brooklynNode.invoke(BrooklynNode.DEPLOY_BLUEPRINT, ConfigBag.newInstance()
                .configure(DeployBlueprintEffector.BLUEPRINT_TYPE, BasicApplication.class.getName()).getAllConfig())
                .get();

        String entityUrl = Urls.mergePaths(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));

        assertEquals(brooklynNode.getChildren().size(), 1);
        return brooklynNode;
    }

    private <T> T parseJson(String json, List<String> elements, Class<T> clazz) {
        Function<String, T> func = Functionals.chain(JsonFunctions.asJson(), JsonFunctions.walk(elements),
                JsonFunctions.cast(clazz));
        return func.apply(json);
    }

    private <T> List<T> parseJsonList(String json, List<String> elements, Class<T> clazz) {
        Function<String, List<T>> func = Functionals.chain(JsonFunctions.asJson(),
                JsonFunctions.forEach(Functionals.chain(JsonFunctions.walk(elements), JsonFunctions.cast(clazz))));
        return func.apply(json);
    }
}