brooklyn-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From henev...@apache.org
Subject [10/71] [abbrv] incubator-brooklyn git commit: Merge commit 'e430723' into reorg2
Date Wed, 23 Dec 2015 11:06:33 GMT
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/018a0e15/brooklyn-server/core/src/test/java/org/apache/brooklyn/core/typereg/RegisteredTypePredicatesTest.java
----------------------------------------------------------------------
diff --cc brooklyn-server/core/src/test/java/org/apache/brooklyn/core/typereg/RegisteredTypePredicatesTest.java
index 0000000,145c056..9523946
mode 000000,100644..100644
--- a/brooklyn-server/core/src/test/java/org/apache/brooklyn/core/typereg/RegisteredTypePredicatesTest.java
+++ b/brooklyn-server/core/src/test/java/org/apache/brooklyn/core/typereg/RegisteredTypePredicatesTest.java
@@@ -1,0 -1,172 +1,157 @@@
+ /*
+  * 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.core.typereg;
+ 
+ import static org.testng.Assert.assertFalse;
+ import static org.testng.Assert.assertTrue;
+ 
+ import org.apache.brooklyn.api.catalog.CatalogItem;
+ import org.apache.brooklyn.api.typereg.RegisteredType;
+ import org.apache.brooklyn.core.catalog.internal.CatalogItemBuilder;
 -import org.apache.brooklyn.core.entity.Entities;
 -import org.apache.brooklyn.core.mgmt.internal.LocalManagementContext;
 -import org.apache.brooklyn.core.test.entity.LocalManagementContextForTests;
 -import org.testng.annotations.AfterMethod;
 -import org.testng.annotations.BeforeMethod;
++import org.apache.brooklyn.core.test.BrooklynMgmtUnitTestSupport;
+ import org.testng.annotations.Test;
+ 
+ import com.google.common.base.Predicates;
+ 
+ 
 -public class RegisteredTypePredicatesTest {
 -    private LocalManagementContext mgmt;
 -    
 -    @BeforeMethod(alwaysRun = true)
 -    public void setUp() throws Exception {
 -        mgmt = LocalManagementContextForTests.newInstance();
 -    }
 -    
 -    @AfterMethod(alwaysRun = true)
 -    public void tearDown() throws Exception {
 -        if (mgmt != null) Entities.destroyAll(mgmt);
 -    }
++public class RegisteredTypePredicatesTest extends BrooklynMgmtUnitTestSupport {
+ 
+     @Test
+     public void testDisplayName() {
+         RegisteredType item = createItem(CatalogItemBuilder.newEntity("foo", "1.0")
+                 .plan("services:\n- type: org.apache.brooklyn.entity.stock.BasicEntity")
+                 .displayName("myname")
+                 .build());
+ 
+         assertTrue(RegisteredTypePredicates.displayName(Predicates.equalTo("myname")).apply(item));
+         assertFalse(RegisteredTypePredicates.displayName(Predicates.equalTo("wrongname")).apply(item));
+     }
+     
+     @Test
+     public void testDeprecated() {
+         RegisteredType item = createItem(CatalogItemBuilder.newEntity("foo", "1.0")
+                 .plan("services:\n- type: org.apache.brooklyn.entity.stock.BasicEntity")
+                 .build());
+ 
+         assertTrue(RegisteredTypePredicates.deprecated(false).apply(item));
+         assertFalse(RegisteredTypePredicates.deprecated(true).apply(item));
+         
+         item = deprecateItem(item);
+         
+         assertFalse(RegisteredTypePredicates.deprecated(false).apply(item));
+         assertTrue(RegisteredTypePredicates.deprecated(true).apply(item));
+     }
+     
+     @Test
+     public void testDisabled() {
+         RegisteredType item = createItem(CatalogItemBuilder.newEntity("foo", "1.0")
+                 .plan("services:\n- type: org.apache.brooklyn.entity.stock.BasicEntity")
+                 .build());
+ 
+         assertTrue(RegisteredTypePredicates.disabled(false).apply(item));
+         assertFalse(RegisteredTypePredicates.disabled(true).apply(item));
+         
+         item = disableItem(item);
+         
+         assertFalse(RegisteredTypePredicates.disabled(false).apply(item));
+         assertTrue(RegisteredTypePredicates.disabled(true).apply(item));
+     }
+     
+     @Test
+     public void testIsCatalogItemType() {
+         RegisteredType item = createItem(CatalogItemBuilder.newEntity("foo", "1.0")
+                 .plan("services:\n- type: org.apache.brooklyn.entity.stock.BasicEntity")
+                 .build());
+ 
+         assertTrue(RegisteredTypePredicates.IS_ENTITY.apply(item));
+         assertFalse(RegisteredTypePredicates.IS_LOCATION.apply(item));
+     }
+     
+     @Test
+     public void testSymbolicName() {
+         RegisteredType item = createItem(CatalogItemBuilder.newEntity("foo", "1.0")
+                 .plan("services:\n- type: org.apache.brooklyn.entity.stock.BasicEntity")
+                 .build());
+ 
+         assertTrue(RegisteredTypePredicates.symbolicName(Predicates.equalTo("foo")).apply(item));
+         assertFalse(RegisteredTypePredicates.symbolicName(Predicates.equalTo("wrongname")).apply(item));
+     }
+ 
+     @Test
+     public void testIsBestVersion() {
+         RegisteredType itemV1 = createItem(CatalogItemBuilder.newEntity("foo", "1.0")
+                 .plan("services:\n- type: org.apache.brooklyn.entity.stock.BasicEntity")
+                 .build());
+         RegisteredType itemV2 = createItem(CatalogItemBuilder.newEntity("foo", "2.0")
+                 .plan("services:\n- type: org.apache.brooklyn.entity.stock.BasicEntity")
+                 .build());
+         RegisteredType itemV3Disabled = createItem(CatalogItemBuilder.newEntity("foo", "3.0")
+                 .disabled(true)
+                 .plan("services:\n- type: org.apache.brooklyn.entity.stock.BasicEntity")
+                 .build());
+ 
+         assertTrue(RegisteredTypePredicates.isBestVersion(mgmt).apply(itemV2));
+         assertFalse(RegisteredTypePredicates.isBestVersion(mgmt).apply(itemV1));
+         assertFalse(RegisteredTypePredicates.isBestVersion(mgmt).apply(itemV3Disabled));
+     }
+ 
+     @Test
+     public void testEntitledToSee() {
+         // TODO No entitlements configured, so everything allowed - therefore test not thorough enough!
+         RegisteredType item = createItem(CatalogItemBuilder.newEntity("foo", "1.0")
+                 .plan("services:\n- type: org.apache.brooklyn.entity.stock.BasicEntity")
+                 .build());
+ 
+         assertTrue(RegisteredTypePredicates.entitledToSee(mgmt).apply(item));
+     }
+ 
+     // TODO do we need this predicate?
+ //    @SuppressWarnings("deprecation")
+ //    @Test
+ //    public void testJavaType() {
+ //        RegisteredType item = createItem(CatalogItemBuilder.newEntity("foo", "1.0")
+ //                .javaType("org.apache.brooklyn.entity.stock.BasicEntity")
+ //                .build());
+ //
+ //        assertTrue(RegisteredTypePredicates.javaType(Predicates.equalTo("org.apache.brooklyn.entity.stock.BasicEntity")).apply(item));
+ //        assertFalse(RegisteredTypePredicates.javaType(Predicates.equalTo("wrongtype")).apply(item));
+ //    }
+ 
+     @SuppressWarnings("deprecation")
+     protected RegisteredType createItem(CatalogItem<?,?> item) {
+         mgmt.getCatalog().addItem(item);
+         return RegisteredTypes.of(item);
+     }
+     
+     @SuppressWarnings({ "deprecation" })
+     protected <T, SpecT> RegisteredType deprecateItem(RegisteredType orig) {
+         CatalogItem<?,?> item = (CatalogItem<?,?>) mgmt.getCatalog().getCatalogItem(orig.getSymbolicName(), orig.getVersion());
+         item.setDeprecated(true);
+         mgmt.getCatalog().persist(item);
+         return RegisteredTypes.of(item);
+     }
+     
+     @SuppressWarnings({ "deprecation" })
+     protected RegisteredType disableItem(RegisteredType orig) {
+         CatalogItem<?,?> item = (CatalogItem<?,?>) mgmt.getCatalog().getCatalogItem(orig.getSymbolicName(), orig.getVersion());
+         item.setDisabled(true);
+         mgmt.getCatalog().persist(item);
+         return RegisteredTypes.of(item);
+     }
+ }

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/018a0e15/brooklyn-server/core/src/test/java/org/apache/brooklyn/location/byon/ByonLocationResolverTest.java
----------------------------------------------------------------------
diff --cc brooklyn-server/core/src/test/java/org/apache/brooklyn/location/byon/ByonLocationResolverTest.java
index 0000000,edc374d..675125f
mode 000000,100644..100644
--- a/brooklyn-server/core/src/test/java/org/apache/brooklyn/location/byon/ByonLocationResolverTest.java
+++ b/brooklyn-server/core/src/test/java/org/apache/brooklyn/location/byon/ByonLocationResolverTest.java
@@@ -1,0 -1,411 +1,411 @@@
+ /*
+  * 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.location.byon;
+ 
+ import static org.testng.Assert.assertEquals;
+ import static org.testng.Assert.assertTrue;
+ import static org.testng.Assert.fail;
+ 
+ import java.net.InetAddress;
+ import java.util.List;
+ import java.util.Map;
+ import java.util.NoSuchElementException;
+ import java.util.Set;
+ 
+ import javax.annotation.Nullable;
+ 
+ import org.apache.brooklyn.api.location.MachineLocation;
+ import org.apache.brooklyn.api.location.MachineProvisioningLocation;
+ import org.apache.brooklyn.api.location.NoMachinesAvailableException;
+ import org.apache.brooklyn.core.config.ConfigKeys;
+ import org.apache.brooklyn.core.entity.Entities;
+ import org.apache.brooklyn.core.internal.BrooklynProperties;
+ import org.apache.brooklyn.core.location.BasicLocationRegistry;
+ import org.apache.brooklyn.core.location.LocationConfigKeys;
+ import org.apache.brooklyn.core.location.NamedLocationResolver;
+ import org.apache.brooklyn.core.location.internal.LocationInternal;
+ import org.apache.brooklyn.core.mgmt.internal.LocalManagementContext;
+ import org.apache.brooklyn.core.test.entity.LocalManagementContextForTests;
+ import org.apache.brooklyn.location.ssh.SshMachineLocation;
+ import org.apache.brooklyn.test.Asserts;
+ import org.apache.brooklyn.util.collections.MutableMap;
+ import org.apache.brooklyn.util.net.Networking;
+ import org.apache.brooklyn.util.net.UserAndHostAndPort;
+ import org.apache.brooklyn.util.os.Os;
+ import org.apache.brooklyn.util.text.StringPredicates;
+ 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.DataProvider;
+ import org.testng.annotations.Test;
+ 
+ import com.google.common.base.Function;
+ import com.google.common.base.Joiner;
+ import com.google.common.base.Predicate;
+ import com.google.common.base.Predicates;
+ import com.google.common.collect.ImmutableList;
+ import com.google.common.collect.ImmutableMap;
+ import com.google.common.collect.ImmutableSet;
+ import com.google.common.collect.Iterables;
+ 
+ public class ByonLocationResolverTest {
+ 
+     private static final Logger log = LoggerFactory.getLogger(ByonLocationResolverTest.class);
+     
+     private BrooklynProperties brooklynProperties;
+     private LocalManagementContext managementContext;
+     private Predicate<CharSequence> defaultNamePredicate;
+     
+     @BeforeMethod(alwaysRun=true)
+     public void setUp() throws Exception {
+         managementContext = LocalManagementContextForTests.newInstance();
+         brooklynProperties = managementContext.getBrooklynProperties();
+         defaultNamePredicate = StringPredicates.startsWith(FixedListMachineProvisioningLocation.class.getSimpleName());
+     }
+     
+     @AfterMethod(alwaysRun=true)
+     public void tearDown() throws Exception {
+         if (managementContext != null) Entities.destroyAll(managementContext);
+     }
+     
+     @Test
+     public void testTakesByonScopedProperties() {
+         brooklynProperties.put("brooklyn.location.byon.privateKeyFile", "myprivatekeyfile");
+         brooklynProperties.put("brooklyn.location.byon.publicKeyFile", "mypublickeyfile");
+         brooklynProperties.put("brooklyn.location.byon.privateKeyData", "myprivateKeyData");
+         brooklynProperties.put("brooklyn.location.byon.publicKeyData", "myPublicKeyData");
+         brooklynProperties.put("brooklyn.location.byon.privateKeyPassphrase", "myprivateKeyPassphrase");
+ 
+         Map<String, Object> conf = resolve("byon(hosts=\"1.1.1.1\")").config().getBag().getAllConfig();
+         
+         assertEquals(conf.get("privateKeyFile"), "myprivatekeyfile");
+         assertEquals(conf.get("publicKeyFile"), "mypublickeyfile");
+         assertEquals(conf.get("privateKeyData"), "myprivateKeyData");
+         assertEquals(conf.get("publicKeyData"), "myPublicKeyData");
+         assertEquals(conf.get("privateKeyPassphrase"), "myprivateKeyPassphrase");
+     }
+ 
+     @Test
+     public void testNamedByonLocation() throws Exception {
+         brooklynProperties.put("brooklyn.location.named.mynamed", "byon(hosts=\"1.1.1.1\")");
+         
+         FixedListMachineProvisioningLocation<MachineLocation> loc = resolve("named:mynamed");
+         assertEquals(loc.obtain().getAddress(), InetAddress.getByName("1.1.1.1"));
+     }
+ 
+     @Test
+     public void testPropertiesInSpec() throws Exception {
+         FixedListMachineProvisioningLocation<MachineLocation> loc = resolve("byon(privateKeyFile=myprivatekeyfile,hosts=\"1.1.1.1\")");
+         SshMachineLocation machine = (SshMachineLocation)loc.obtain();
+         
+         assertEquals(machine.config().getBag().getStringKey("privateKeyFile"), "myprivatekeyfile");
+         assertEquals(machine.getAddress(), Networking.getInetAddressWithFixedName("1.1.1.1"));
+     }
+ 
+     @Test
+     public void testPropertyScopePrecedence() throws Exception {
+         brooklynProperties.put("brooklyn.location.named.mynamed", "byon(hosts=\"1.1.1.1\")");
+         
+         // prefer those in "named" over everything else
+         brooklynProperties.put("brooklyn.location.named.mynamed.privateKeyFile", "privateKeyFile-inNamed");
+         brooklynProperties.put("brooklyn.location.byon.privateKeyFile", "privateKeyFile-inProviderSpecific");
+         brooklynProperties.put("brooklyn.localhost.privateKeyFile", "privateKeyFile-inGeneric");
+ 
+         // prefer those in provider-specific over generic
+         brooklynProperties.put("brooklyn.location.byon.publicKeyFile", "publicKeyFile-inProviderSpecific");
+         brooklynProperties.put("brooklyn.location.publicKeyFile", "publicKeyFile-inGeneric");
+ 
+         // prefer location-generic if nothing else
+         brooklynProperties.put("brooklyn.location.privateKeyData", "privateKeyData-inGeneric");
+ 
+         Map<String, Object> conf = resolve("named:mynamed").config().getBag().getAllConfig();
+         
+         assertEquals(conf.get("privateKeyFile"), "privateKeyFile-inNamed");
+         assertEquals(conf.get("publicKeyFile"), "publicKeyFile-inProviderSpecific");
+         assertEquals(conf.get("privateKeyData"), "privateKeyData-inGeneric");
+     }
+ 
+     @Test
+     public void testThrowsOnInvalid() throws Exception {
+         assertThrowsNoSuchElement("wrongprefix:(hosts=\"1.1.1.1\")");
+         assertThrowsIllegalArgument("byon"); // no hosts
+         assertThrowsIllegalArgument("byon()"); // no hosts
+         assertThrowsIllegalArgument("byon(hosts=\"\")"); // empty hosts
+         assertThrowsIllegalArgument("byon(hosts=\"1.1.1.1\""); // no closing bracket
+         assertThrowsIllegalArgument("byon(hosts=\"1.1.1.1\", name)"); // no value for name
+         assertThrowsIllegalArgument("byon(hosts=\"1.1.1.1\", name=)"); // no value for name
+     }
+     
+     @Test(expectedExceptions={IllegalArgumentException.class})
+     public void testRegistryCommaResolutionInListNotAllowed() throws NoMachinesAvailableException {
+         // disallowed since 0.7.0
+         // fails because it interprets the entire string as a single byon spec, which does not parse
+         managementContext.getLocationRegistry().resolve(ImmutableList.of("byon(hosts=\"192.168.0.1\",user=bob),byon(hosts=\"192.168.0.2\",user=bob2)"));
+     }
+ 
+     @Test
+     public void testResolvesHosts() throws Exception {
+         assertByonClusterEquals(resolve("byon(hosts=\"1.1.1.1\")"), ImmutableSet.of("1.1.1.1"));
+         assertByonClusterEquals(resolve("byon(hosts=\"1.1.1.1\")"), ImmutableSet.of("1.1.1.1"));
+         assertByonClusterEquals(resolve("byon(hosts=\"1.1.1.1,1.1.1.2\")"), ImmutableSet.of("1.1.1.1","1.1.1.2"));
+         assertByonClusterEquals(resolve("byon(hosts=\"1.1.1.1, 1.1.1.2\")"), ImmutableSet.of("1.1.1.1","1.1.1.2"));
+     }
+ 
+     @Test
+     public void testWithOldStyleColon() throws Exception {
+         assertByonClusterEquals(resolve("byon:(hosts=\"1.1.1.1\")"), ImmutableSet.of("1.1.1.1"));
+         assertByonClusterEquals(resolve("byon:(hosts=\"1.1.1.1\", name=myname)"), ImmutableSet.of("1.1.1.1"), "myname");
+     }
+ 
+     @Test
+     public void testUsesDisplayName() throws Exception {
+         assertByonClusterEquals(resolve("byon(hosts=\"1.1.1.1\", name=myname)"), ImmutableSet.of("1.1.1.1"), "myname");
+         assertByonClusterEquals(resolve("byon(hosts=\"1.1.1.1\", name=\"myname\")"), ImmutableSet.of("1.1.1.1"), "myname");
+     }
+ 
+     @Test
+     public void testResolvesHostsGlobExpansion() throws Exception {
+         assertByonClusterEquals(resolve("byon(hosts=\"1.1.1.{1,2}\")"), ImmutableSet.of("1.1.1.1","1.1.1.2"));
+         assertByonClusterEquals(resolve("byon(hosts=\"1.1.{1.1,2.{1,2}}\")"), 
+                 ImmutableSet.of("1.1.1.1","1.1.2.1","1.1.2.2"));
+         assertByonClusterEquals(resolve("byon(hosts=\"1.1.{1,2}.{1,2}\")"), 
+                 ImmutableSet.of("1.1.1.1","1.1.1.2","1.1.2.1","1.1.2.2"));
+     }
+ 
+     @Test(groups="Integration")
+     public void testNiceError() throws Exception {
+         Asserts.assertFailsWith(new Runnable() {
+             @Override public void run() {
+                 FixedListMachineProvisioningLocation<MachineLocation> x =
+                         resolve("byon(hosts=\"1.1.1.{1,2}}\")");
+                 log.error("got "+x+" but should have failed (your DNS is giving an IP for hostname '1.1.1.1}' (with the extra '}')");
+             }
+         }, new Predicate<Throwable>() {
+             @Override
+             public boolean apply(@Nullable Throwable input) {
+                 String s = input.toString();
+                 // words
+                 if (!s.contains("Invalid host")) return false;
+                 // problematic entry
+                 if (!s.contains("1.1.1.1}")) return false;
+                 // original spec
+                 if (!s.contains("1.1.1.{1,2}}")) return false;
+                 return true;
+             }
+         });
+     }
+ 
+     @Test
+     public void testResolvesUsernameAtHost() throws Exception {
+         assertByonClusterWithUsersEquals(resolve("byon(hosts=\"myuser@1.1.1.1\")"), 
+                 ImmutableSet.of(UserAndHostAndPort.fromParts("myuser", "1.1.1.1", 22)));
+         assertByonClusterWithUsersEquals(resolve("byon(hosts=\"myuser@1.1.1.1,myuser2@1.1.1.1\")"), ImmutableSet.of(
+                 UserAndHostAndPort.fromParts("myuser", "1.1.1.1", 22), UserAndHostAndPort.fromParts("myuser2", "1.1.1.1", 22)));
+         assertByonClusterWithUsersEquals(resolve("byon(hosts=\"myuser@1.1.1.1,myuser2@1.1.1.2\")"), ImmutableSet.of(
+                 UserAndHostAndPort.fromParts("myuser", "1.1.1.1", 22), UserAndHostAndPort.fromParts("myuser2", "1.1.1.2", 22)));
+     }
+ 
+     @Test
+     public void testResolvesUserArg() throws Exception {
+         assertByonClusterWithUsersEquals(resolve("byon(hosts=\"1.1.1.1\",user=bob)"), 
+                 ImmutableSet.of(UserAndHostAndPort.fromParts("bob", "1.1.1.1", 22)));
+         assertByonClusterWithUsersEquals(resolve("byon(user=\"bob\",hosts=\"myuser@1.1.1.1,1.1.1.1\")"), 
+                 ImmutableSet.of(UserAndHostAndPort.fromParts("myuser", "1.1.1.1", 22), UserAndHostAndPort.fromParts("bob", "1.1.1.1", 22)));
+     }
+ 
+     @Test
+     public void testResolvesUserArg2() throws Exception {
+         String spec = "byon(hosts=\"1.1.1.1\",user=bob)";
+         FixedListMachineProvisioningLocation<MachineLocation> ll = resolve(spec);
+         SshMachineLocation l = (SshMachineLocation)ll.obtain();
+         Assert.assertEquals("bob", l.getUser());
+     }
+ 
+     @SuppressWarnings("unchecked")
+     @Test
+     public void testResolvesUserArg3() throws Exception {
+         String spec = "byon(hosts=\"1.1.1.1\")";
 -        managementContext.getLocationRegistry().getProperties().putAll(MutableMap.of(
++        ((BasicLocationRegistry)managementContext.getLocationRegistry()).putProperties(MutableMap.of(
+                 "brooklyn.location.named.foo", spec,
+                 "brooklyn.location.named.foo.user", "bob"));
+         ((BasicLocationRegistry)managementContext.getLocationRegistry()).updateDefinedLocations();
+         
+         MachineProvisioningLocation<SshMachineLocation> ll = (MachineProvisioningLocation<SshMachineLocation>)
+                 new NamedLocationResolver().newLocationFromString(MutableMap.of(), "named:foo", managementContext.getLocationRegistry());
+         SshMachineLocation l = ll.obtain(MutableMap.of());
+         Assert.assertEquals("bob", l.getUser());
+     }
+ 
+     @Test
+     public void testResolvesPortArg() throws Exception {
+         assertByonClusterWithUsersEquals(resolve("byon(user=bob,port=8022,hosts=\"1.1.1.1\")"), 
+                 ImmutableSet.of(UserAndHostAndPort.fromParts("bob", "1.1.1.1", 8022)));
+         assertByonClusterWithUsersEquals(resolve("byon(user=bob,port=8022,hosts=\"myuser@1.1.1.1,1.1.1.2:8901\")"), 
+                 ImmutableSet.of(UserAndHostAndPort.fromParts("myuser", "1.1.1.1", 8022), UserAndHostAndPort.fromParts("bob", "1.1.1.2", 8901)));
+     }
+ 
+     @SuppressWarnings("unchecked")
+     @Test
+     /** private key should be inherited, so confirm that happens correctly */
+     public void testResolvesPrivateKeyArgInheritance() throws Exception {
+         String spec = "byon(hosts=\"1.1.1.1\")";
 -        managementContext.getLocationRegistry().getProperties().putAll(MutableMap.of(
++        ((BasicLocationRegistry)managementContext.getLocationRegistry()).putProperties(MutableMap.of(
+                 "brooklyn.location.named.foo", spec,
+                 "brooklyn.location.named.foo.user", "bob",
+                 "brooklyn.location.named.foo.privateKeyFile", "/tmp/x"));
+         ((BasicLocationRegistry)managementContext.getLocationRegistry()).updateDefinedLocations();
+         
+         MachineProvisioningLocation<SshMachineLocation> ll = (MachineProvisioningLocation<SshMachineLocation>) 
+                 new NamedLocationResolver().newLocationFromString(MutableMap.of(), "named:foo", managementContext.getLocationRegistry());
+         
+         Assert.assertEquals("/tmp/x", ll.getConfig(LocationConfigKeys.PRIVATE_KEY_FILE));
+         Assert.assertTrue(((LocationInternal)ll).config().getLocalRaw(LocationConfigKeys.PRIVATE_KEY_FILE).isPresent());
+         Assert.assertEquals("/tmp/x", ((LocationInternal)ll).config().getLocalBag().getStringKey(LocationConfigKeys.PRIVATE_KEY_FILE.getName()));
+         Assert.assertEquals("/tmp/x", ((LocationInternal)ll).config().getBag().get(LocationConfigKeys.PRIVATE_KEY_FILE));
+ 
+         SshMachineLocation l = ll.obtain(MutableMap.of());
+         
+         Assert.assertEquals("/tmp/x", l.getConfig(LocationConfigKeys.PRIVATE_KEY_FILE));
+         
+         Assert.assertTrue(l.config().getRaw(LocationConfigKeys.PRIVATE_KEY_FILE).isPresent());
+         Assert.assertTrue(l.config().getLocalRaw(LocationConfigKeys.PRIVATE_KEY_FILE).isAbsent());
+ 
+         Assert.assertEquals("/tmp/x", l.config().getBag().getStringKey(LocationConfigKeys.PRIVATE_KEY_FILE.getName()));
+         Assert.assertEquals("/tmp/x", l.config().getBag().getStringKey(LocationConfigKeys.PRIVATE_KEY_FILE.getName()));
+ 
+         Assert.assertEquals("/tmp/x", l.config().getBag().get(LocationConfigKeys.PRIVATE_KEY_FILE));
+     }
+ 
+     // FIXME: move @Test to the brooklyn-software-winrm module
+     public void testResolvesLocalTempDir() throws Exception {
+         String localTempDir = Os.mergePaths(Os.tmp(), "testResolvesUsernameAtHost");
+         brooklynProperties.put("brooklyn.location.byon.localTempDir", localTempDir);
+ 
+         FixedListMachineProvisioningLocation<MachineLocation> byon = resolve("byon(hosts=\"1.1.1.1\",osFamily=\"windows\")");
+         MachineLocation machine = byon.obtain();
+         assertEquals(machine.getConfig(SshMachineLocation.LOCAL_TEMP_DIR), localTempDir);
+     }
+ 
+     @Test
+     public void testMachinesObtainedInOrder() throws Exception {
+         List<String> ips = ImmutableList.of("1.1.1.1", "1.1.1.6", "1.1.1.3", "1.1.1.4", "1.1.1.5");
+         String spec = "byon(hosts=\""+Joiner.on(",").join(ips)+"\")";
+         
+         MachineProvisioningLocation<MachineLocation> ll = resolve(spec);
+ 
+         for (String expected : ips) {
+             MachineLocation obtained = ll.obtain(ImmutableMap.of());
+             assertEquals(obtained.getAddress().getHostAddress(), expected);
+         }
+     }
+     
+     @Test
+     public void testEmptySpec() throws Exception {
+         String spec = "byon";
+         Map<String, ?> flags = ImmutableMap.of(
+                 "hosts", ImmutableList.of("1.1.1.1", "2.2.2.22"),
+                 "name", "foo",
+                 "user", "myuser"
+         );
+         MachineProvisioningLocation<MachineLocation> provisioner = resolve(spec, flags);
+         SshMachineLocation location1 = (SshMachineLocation)provisioner.obtain(ImmutableMap.of());
+         Assert.assertEquals("myuser", location1.getUser());
+         Assert.assertEquals("1.1.1.1", location1.getAddress().getHostAddress());
+     }
+ 
+     @Test
+     public void testNonWindowsMachines() throws Exception {
+         String spec = "byon";
+         Map<String, ?> flags = ImmutableMap.of(
+                 "hosts", ImmutableList.of("1.1.1.1", "2.2.2.2"),
+                 "osFamily", "linux"
+         );
+         MachineProvisioningLocation<MachineLocation> provisioner = resolve(spec, flags);
+         MachineLocation location = provisioner.obtain(ImmutableMap.of());
+         assertTrue(location instanceof SshMachineLocation, "Expected location to be SshMachineLocation, found " + location);
+     }
+ 
+     @Test
+     public void testAdditionalConfig() throws Exception {
+         FixedListMachineProvisioningLocation<MachineLocation> loc = resolve("byon(mykey=myval,hosts=\"1.1.1.1\")");
+         MachineLocation machine = loc.obtain(ImmutableMap.of());
+         assertEquals(machine.getConfig(ConfigKeys.newConfigKey(String.class, "mykey")), "myval");
+     }
+ 
+     private void assertByonClusterEquals(FixedListMachineProvisioningLocation<? extends MachineLocation> cluster, Set<String> expectedHosts) {
+         assertByonClusterEquals(cluster, expectedHosts, defaultNamePredicate);
+     }
+     
+     private void assertByonClusterEquals(FixedListMachineProvisioningLocation<? extends MachineLocation> cluster, Set<String> expectedHosts, String expectedName) {
+         assertByonClusterEquals(cluster, expectedHosts, Predicates.equalTo(expectedName));
+     }
+     
+     private void assertByonClusterEquals(FixedListMachineProvisioningLocation<? extends MachineLocation> cluster, Set<String> expectedHosts, Predicate<? super String> expectedName) {
+         Set<String> actualHosts = ImmutableSet.copyOf(Iterables.transform(cluster.getMachines(), new Function<MachineLocation, String>() {
+             @Override public String apply(MachineLocation input) {
+                 return input.getAddress().getHostName();
+             }}));
+         assertEquals(actualHosts, expectedHosts);
+         assertTrue(expectedName.apply(cluster.getDisplayName()), "name="+cluster.getDisplayName());
+     }
+ 
+     private void assertByonClusterWithUsersEquals(FixedListMachineProvisioningLocation<? extends MachineLocation> cluster, Set<UserAndHostAndPort> expectedHosts) {
+         assertByonClusterWithUsersEquals(cluster, expectedHosts, defaultNamePredicate);
+     }
+     
+     private void assertByonClusterWithUsersEquals(FixedListMachineProvisioningLocation<? extends MachineLocation> cluster, Set<UserAndHostAndPort> expectedHosts, Predicate<? super String> expectedName) {
+         Set<UserAndHostAndPort> actualHosts = ImmutableSet.copyOf(Iterables.transform(cluster.getMachines(), new Function<MachineLocation, UserAndHostAndPort>() {
+             @Override public UserAndHostAndPort apply(MachineLocation input) {
+                 SshMachineLocation machine = (SshMachineLocation) input;
+                 return UserAndHostAndPort.fromParts(machine.getUser(), machine.getAddress().getHostName(), machine.getPort());
+             }}));
+         assertEquals(actualHosts, expectedHosts);
+         assertTrue(expectedName.apply(cluster.getDisplayName()), "name="+cluster.getDisplayName());
+     }
+ 
+     private void assertThrowsNoSuchElement(String val) {
+         try {
+             resolve(val);
+             fail();
+         } catch (NoSuchElementException e) {
+             // success
+         }
+     }
+     
+     private void assertThrowsIllegalArgument(String val) {
+         try {
+             resolve(val);
+             fail();
+         } catch (IllegalArgumentException e) {
+             // success
+         }
+     }
+     
+     @SuppressWarnings("unchecked")
+     private FixedListMachineProvisioningLocation<MachineLocation> resolve(String val) {
+         return (FixedListMachineProvisioningLocation<MachineLocation>) managementContext.getLocationRegistry().resolve(val);
+     }
+     
+     @SuppressWarnings("unchecked")
+     private FixedListMachineProvisioningLocation<MachineLocation> resolve(String val, Map<?, ?> locationFlags) {
+         return (FixedListMachineProvisioningLocation<MachineLocation>) managementContext.getLocationRegistry().resolve(val, locationFlags);
+     }
+ }

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/018a0e15/brooklyn-server/launcher/src/main/java/org/apache/brooklyn/launcher/BrooklynLauncher.java
----------------------------------------------------------------------
diff --cc brooklyn-server/launcher/src/main/java/org/apache/brooklyn/launcher/BrooklynLauncher.java
index 0000000,ca5cda4..5fa0dec
mode 000000,100644..100644
--- a/brooklyn-server/launcher/src/main/java/org/apache/brooklyn/launcher/BrooklynLauncher.java
+++ b/brooklyn-server/launcher/src/main/java/org/apache/brooklyn/launcher/BrooklynLauncher.java
@@@ -1,0 -1,1048 +1,1067 @@@
+ /*
+  * 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.launcher;
+ 
+ import static com.google.common.base.Preconditions.checkNotNull;
+ 
+ import java.io.Closeable;
+ import java.io.File;
++import java.io.IOException;
+ import java.net.InetAddress;
++import java.nio.file.Files;
+ import java.util.ArrayList;
+ import java.util.Arrays;
+ import java.util.LinkedHashMap;
+ import java.util.List;
+ import java.util.Map;
+ import java.util.concurrent.TimeoutException;
 -
+ import javax.annotation.Nullable;
+ 
+ import org.apache.brooklyn.api.entity.Application;
+ import org.apache.brooklyn.api.entity.EntitySpec;
+ import org.apache.brooklyn.api.location.Location;
+ import org.apache.brooklyn.api.location.LocationSpec;
+ import org.apache.brooklyn.api.location.PortRange;
+ import org.apache.brooklyn.api.mgmt.ManagementContext;
+ import org.apache.brooklyn.api.mgmt.ha.HighAvailabilityManager;
+ import org.apache.brooklyn.api.mgmt.ha.HighAvailabilityMode;
+ import org.apache.brooklyn.api.mgmt.ha.ManagementNodeState;
+ import org.apache.brooklyn.api.mgmt.ha.ManagementPlaneSyncRecord;
+ import org.apache.brooklyn.api.mgmt.ha.ManagementPlaneSyncRecordPersister;
+ import org.apache.brooklyn.api.mgmt.rebind.PersistenceExceptionHandler;
+ import org.apache.brooklyn.api.mgmt.rebind.RebindManager;
+ import org.apache.brooklyn.api.mgmt.rebind.mementos.BrooklynMementoRawData;
+ import org.apache.brooklyn.camp.CampPlatform;
+ import org.apache.brooklyn.camp.brooklyn.BrooklynCampPlatformLauncherNoServer;
+ import org.apache.brooklyn.config.ConfigKey;
+ import org.apache.brooklyn.core.catalog.internal.CatalogInitialization;
+ import org.apache.brooklyn.core.config.ConfigPredicates;
+ import org.apache.brooklyn.core.entity.Entities;
+ import org.apache.brooklyn.core.entity.StartableApplication;
+ import org.apache.brooklyn.core.entity.factory.ApplicationBuilder;
+ import org.apache.brooklyn.core.entity.trait.Startable;
+ import org.apache.brooklyn.core.internal.BrooklynProperties;
+ import org.apache.brooklyn.core.location.PortRanges;
+ import org.apache.brooklyn.core.mgmt.EntityManagementUtils;
+ import org.apache.brooklyn.core.mgmt.ha.HighAvailabilityManagerImpl;
+ import org.apache.brooklyn.core.mgmt.ha.ManagementPlaneSyncRecordPersisterToObjectStore;
+ import org.apache.brooklyn.core.mgmt.internal.BrooklynShutdownHooks;
+ import org.apache.brooklyn.core.mgmt.internal.LocalManagementContext;
+ import org.apache.brooklyn.core.mgmt.internal.ManagementContextInternal;
+ import org.apache.brooklyn.core.mgmt.persist.BrooklynMementoPersisterToObjectStore;
+ import org.apache.brooklyn.core.mgmt.persist.BrooklynPersistenceUtils;
+ import org.apache.brooklyn.core.mgmt.persist.PersistMode;
+ import org.apache.brooklyn.core.mgmt.persist.PersistenceObjectStore;
+ import org.apache.brooklyn.core.mgmt.rebind.PersistenceExceptionHandlerImpl;
+ import org.apache.brooklyn.core.mgmt.rebind.RebindManagerImpl;
+ import org.apache.brooklyn.core.mgmt.rebind.transformer.CompoundTransformer;
+ import org.apache.brooklyn.core.server.BrooklynServerConfig;
+ import org.apache.brooklyn.core.server.BrooklynServerPaths;
+ import org.apache.brooklyn.entity.brooklynnode.BrooklynNode;
+ import org.apache.brooklyn.entity.brooklynnode.LocalBrooklynNode;
+ import org.apache.brooklyn.entity.software.base.SoftwareProcess;
+ import org.apache.brooklyn.launcher.config.StopWhichAppsOnShutdown;
+ import org.apache.brooklyn.location.localhost.LocalhostMachineProvisioningLocation.LocalhostMachine;
+ import org.apache.brooklyn.rest.BrooklynWebConfig;
+ import org.apache.brooklyn.rest.filter.BrooklynPropertiesSecurityFilter;
+ import org.apache.brooklyn.rest.security.provider.BrooklynUserWithRandomPasswordSecurityProvider;
+ import org.apache.brooklyn.rest.util.ShutdownHandler;
+ import org.apache.brooklyn.util.exceptions.Exceptions;
+ import org.apache.brooklyn.util.exceptions.FatalConfigurationRuntimeException;
+ import org.apache.brooklyn.util.exceptions.FatalRuntimeException;
+ import org.apache.brooklyn.util.exceptions.RuntimeInterruptedException;
+ import org.apache.brooklyn.util.guava.Maybe;
+ import org.apache.brooklyn.util.io.FileUtil;
+ import org.apache.brooklyn.util.net.Networking;
+ import org.apache.brooklyn.util.os.Os;
+ import org.apache.brooklyn.util.stream.Streams;
+ import org.apache.brooklyn.util.text.Strings;
+ import org.apache.brooklyn.util.time.Duration;
+ import org.apache.brooklyn.util.time.Time;
+ import org.slf4j.Logger;
+ import org.slf4j.LoggerFactory;
+ 
+ import com.google.common.annotations.Beta;
+ import com.google.common.base.Function;
+ import com.google.common.base.Splitter;
+ import com.google.common.base.Stopwatch;
+ import com.google.common.collect.ImmutableList;
+ import com.google.common.collect.Lists;
+ import com.google.common.collect.Maps;
+ 
+ /**
+  * Example usage is:
+  *  * <pre>
+  * {@code
+  * BrooklynLauncher launcher = BrooklynLauncher.newInstance()
+  *     .application(new WebClusterDatabaseExample().appDisplayName("Web-cluster example"))
+  *     .location("localhost")
+  *     .start();
+  * 
+  * Entities.dumpInfo(launcher.getApplications());
+  * </pre>
+  */
+ public class BrooklynLauncher {
+ 
+     private static final Logger LOG = LoggerFactory.getLogger(BrooklynLauncher.class);
+ 
+     /** Creates a configurable (fluent API) launcher for use starting the web console and Brooklyn applications. */
+     public static BrooklynLauncher newInstance() {
+         return new BrooklynLauncher();
+     }
+     
+     private final Map<String,Object> brooklynAdditionalProperties = Maps.newLinkedHashMap();
+     private BrooklynProperties brooklynProperties;
+     private ManagementContext managementContext;
+     
+     private final List<String> locationSpecs = new ArrayList<String>();
+     private final List<Location> locations = new ArrayList<Location>();
+ 
+     private final List<Application> appsToManage = new ArrayList<Application>();
+     private final List<ApplicationBuilder> appBuildersToManage = new ArrayList<ApplicationBuilder>();
+     private final List<String> yamlAppsToManage = new ArrayList<String>();
+     private final List<Application> apps = new ArrayList<Application>();
+     
+     private boolean startWebApps = true;
+     private boolean startBrooklynNode = false;
+     private PortRange port = null;
+     private Boolean useHttps = null;
+     private InetAddress bindAddress = null;
+     private InetAddress publicAddress = null;
+     private Map<String,String> webApps = new LinkedHashMap<String,String>();
+     private Map<String, ?> webconsoleFlags = Maps.newLinkedHashMap();
+     private Boolean skipSecurityFilter = null;
+     
+     private boolean ignoreWebErrors = false;
+     private boolean ignorePersistenceErrors = true;
+     private boolean ignoreCatalogErrors = true;
+     private boolean ignoreAppErrors = true;
+     
+     private StopWhichAppsOnShutdown stopWhichAppsOnShutdown = StopWhichAppsOnShutdown.THESE_IF_NOT_PERSISTED;
+     private ShutdownHandler shutdownHandler;
+     
+     private Function<ManagementContext,Void> customizeManagement = null;
+     private CatalogInitialization catalogInitialization = null;
+     
+     private PersistMode persistMode = PersistMode.DISABLED;
+     private HighAvailabilityMode highAvailabilityMode = HighAvailabilityMode.DISABLED;
+     private String persistenceDir;
+     private String persistenceLocation;
+     private Duration persistPeriod = Duration.ONE_SECOND;
+     // these default values come from config in HighAvailablilityManagerImpl
+     private Duration haHeartbeatTimeoutOverride = null;
+     private Duration haHeartbeatPeriodOverride = null;
+     
+     private volatile BrooklynWebServer webServer;
+     @SuppressWarnings("unused")
+     private CampPlatform campPlatform;
+ 
+     private boolean started;
+     private String globalBrooklynPropertiesFile = Os.mergePaths(Os.home(), ".brooklyn", "brooklyn.properties");
+     private String localBrooklynPropertiesFile;
+ 
+     public List<Application> getApplications() {
+         if (!started) throw new IllegalStateException("Cannot retrieve application until started");
+         return ImmutableList.copyOf(apps);
+     }
+     
+     public BrooklynServerDetails getServerDetails() {
+         if (!started) throw new IllegalStateException("Cannot retrieve server details until started");
+         return new BrooklynServerDetails(webServer, managementContext);
+     }
+     
+     /** 
+      * Specifies that the launcher should manage the given Brooklyn application.
+      * The application must not yet be managed. 
+      * The application will not be started as part of this call (callers can
+      * subsequently call {@link #start()} or {@link #getApplications()}.
+      * 
+      * @see #application(ApplicationBuilder)
+      * 
+      * @deprecated since 0.9.0; instead use {@link #application(String)} for YAML apps, or {@link #application(EntitySpec)}.
+      *             Note that apps are now auto-managed on construction through EntitySpec/YAML.
+      */
+     @Deprecated
+     public BrooklynLauncher application(Application app) {
+         if (Entities.isManaged(app)) throw new IllegalArgumentException("Application must not already be managed");
+         appsToManage.add(checkNotNull(app, "app"));
+         return this;
+     }
+ 
+     /** 
+      * Specifies that the launcher should build and manage the given Brooklyn application.
+      * The application must not yet be managed. 
+      * The application will not be started as part of this call (callers can
+      * subsequently call {@link #start()} or {@link #getApplications()}.
+      * 
+      * @see #application(Application)
+      */
+     public BrooklynLauncher application(ApplicationBuilder appBuilder) {
+         appBuildersToManage.add(checkNotNull(appBuilder, "appBuilder"));
+         return this;
+     }
+ 
+     /** 
+      * Specifies that the launcher should build and manage the Brooklyn application
+      * described by the given spec.
+      * The application will not be started as part of this call (callers can
+      * subsequently call {@link #start()} or {@link #getApplications()}.
+      * 
+      * @see #application(Application)
+      */
+     public BrooklynLauncher application(EntitySpec<? extends StartableApplication> appSpec) {
+         appBuildersToManage.add(new ApplicationBuilder(checkNotNull(appSpec, "appSpec")) {
+                 @Override protected void doBuild() {
+                 }});
+         return this;
+     }
+ 
+     /**
+      * Specifies that the launcher should build and manage the Brooklyn application
+      * described by the given YAML blueprint.
+      * The application will not be started as part of this call (callers can
+      * subsequently call {@link #start()} or {@link #getApplications()}.
+      *
+      * @see #application(Application)
+      */
+     public BrooklynLauncher application(String yaml) {
+         this.yamlAppsToManage.add(yaml);
+         return this;
+     }
+ 
+     /**
+      * Adds a location to be passed in on {@link #start()}, when that calls
+      * {@code application.start(locations)}.
+      */
+     public BrooklynLauncher location(Location location) {
+         locations.add(checkNotNull(location, "location"));
+         return this;
+     }
+ 
+     /**
+      * Give the spec of an application, to be created.
+      * 
+      * @see #location(Location)
+      */
+     public BrooklynLauncher location(String spec) {
+         locationSpecs.add(checkNotNull(spec, "spec"));
+         return this;
+     }
+     
+     public BrooklynLauncher locations(List<String> specs) {
+         locationSpecs.addAll(checkNotNull(specs, "specs"));
+         return this;
+     }
+ 
+     public BrooklynLauncher persistenceLocation(@Nullable String persistenceLocationSpec) {
+         persistenceLocation = persistenceLocationSpec;
+         return this;
+     }
+ 
+     public BrooklynLauncher globalBrooklynPropertiesFile(String file) {
+         globalBrooklynPropertiesFile = file;
+         return this;
+     }
+     
+     public BrooklynLauncher localBrooklynPropertiesFile(String file) {
+         localBrooklynPropertiesFile = file;
+         return this;
+     }
+     
+     /** 
+      * Specifies the management context this launcher should use. 
+      * If not specified a new one is created automatically.
+      */
+     public BrooklynLauncher managementContext(ManagementContext context) {
+         if (brooklynProperties != null) throw new IllegalStateException("Cannot set brooklynProperties and managementContext");
+         this.managementContext = context;
+         return this;
+     }
+ 
+     /**
+      * Specifies the brooklyn properties to be used. 
+      * Must not be set if managementContext is explicitly set.
+      */
+     public BrooklynLauncher brooklynProperties(BrooklynProperties brooklynProperties){
+         if (managementContext != null) throw new IllegalStateException("Cannot set brooklynProperties and managementContext");
+         if (this.brooklynProperties!=null && brooklynProperties!=null && this.brooklynProperties!=brooklynProperties)
+             LOG.warn("Brooklyn properties being reset in "+this+"; set null first if you wish to clear it", new Throwable("Source of brooklyn properties reset"));
+         this.brooklynProperties = brooklynProperties;
+         return this;
+     }
+     
+     /**
+      * Specifies a property to be added to the brooklyn properties
+      */
+     public BrooklynLauncher brooklynProperties(String field, Object value) {
+         brooklynAdditionalProperties.put(checkNotNull(field, "field"), value);
+         return this;
+     }
+     public <T> BrooklynLauncher brooklynProperties(ConfigKey<T> key, T value) {
+         return brooklynProperties(key.getName(), value);
+     }
+ 
+     /** 
+      * Specifies whether the launcher will start the Brooklyn web console 
+      * (and any additional webapps specified); default true.
+      */
+     public BrooklynLauncher webconsole(boolean startWebApps) {
+         this.startWebApps = startWebApps;
+         return this;
+     }
+ 
+     public BrooklynLauncher installSecurityFilter(Boolean val) {
+         this.skipSecurityFilter = val == null ? null : !val;
+         return this;
+     }
+ 
+     /** 
+      * As {@link #webconsolePort(PortRange)} taking a single port
+      */ 
+     public BrooklynLauncher webconsolePort(int port) {
+         return webconsolePort(PortRanges.fromInteger(port));
+     }
+ 
+     /**
+      * As {@link #webconsolePort(PortRange)} taking a string range
+      */
+     public BrooklynLauncher webconsolePort(String port) {
+         if (port==null) return webconsolePort((PortRange)null);
+         return webconsolePort(PortRanges.fromString(port));
+     }
+ 
+     /**
+      * Specifies the port where the web console (and any additional webapps specified) will listen;
+      * default (null) means "8081+" being the first available >= 8081 (or "8443+" for https).
+      */ 
+     public BrooklynLauncher webconsolePort(PortRange port) {
+         this.port = port;
+         return this;
+     }
+ 
+     /**
+      * Specifies whether the webconsole should use https.
+      */ 
+     public BrooklynLauncher webconsoleHttps(Boolean useHttps) {
+         this.useHttps = useHttps;
+         return this;
+     }
+ 
+     /**
+      * Specifies the NIC where the web console (and any additional webapps specified) will be bound;
+      * default 0.0.0.0, unless no security is specified (e.g. users) in which case it is localhost.
+      */ 
+     public BrooklynLauncher bindAddress(InetAddress bindAddress) {
+         this.bindAddress = bindAddress;
+         return this;
+     }
+ 
+     /**
+      * Specifies the address that the management context's REST API will be available on. Defaults
+      * to {@link #bindAddress} if it is not 0.0.0.0.
+      * @see #bindAddress(java.net.InetAddress)
+      */
+     public BrooklynLauncher publicAddress(InetAddress publicAddress) {
+         this.publicAddress = publicAddress;
+         return this;
+     }
+ 
+     /**
+      * Specifies additional flags to be passed to {@link BrooklynWebServer}.
+      */ 
+     public BrooklynLauncher webServerFlags(Map<String,?> webServerFlags) {
+         this.webconsoleFlags  = webServerFlags;
+         return this;
+     }
+ 
+     /** 
+      * Specifies an additional webapp to host on the webconsole port.
+      * @param contextPath The context path (e.g. "/hello", or equivalently just "hello") where the webapp will be hosted.
+      *      "/" will override the brooklyn console webapp.
+      * @param warUrl The URL from which the WAR should be loaded, supporting classpath:// protocol in addition to file:// and http(s)://.
+      */
+     public BrooklynLauncher webapp(String contextPath, String warUrl) {
+         webApps.put(contextPath, warUrl);
+         return this;
+     }
+ 
+     public BrooklynLauncher ignorePersistenceErrors(boolean ignorePersistenceErrors) {
+         this.ignorePersistenceErrors = ignorePersistenceErrors;
+         return this;
+     }
+ 
+     public BrooklynLauncher ignoreCatalogErrors(boolean ignoreCatalogErrors) {
+         this.ignoreCatalogErrors = ignoreCatalogErrors;
+         return this;
+     }
+ 
+     public BrooklynLauncher ignoreWebErrors(boolean ignoreWebErrors) {
+         this.ignoreWebErrors = ignoreWebErrors;
+         return this;
+     }
+ 
+     public BrooklynLauncher ignoreAppErrors(boolean ignoreAppErrors) {
+         this.ignoreAppErrors = ignoreAppErrors;
+         return this;
+     }
+ 
+     public BrooklynLauncher stopWhichAppsOnShutdown(StopWhichAppsOnShutdown stopWhich) {
+         this.stopWhichAppsOnShutdown = stopWhich;
+         return this;
+     }
+ 
+     public BrooklynLauncher customizeManagement(Function<ManagementContext,Void> customizeManagement) {
+         this.customizeManagement = customizeManagement;
+         return this;
+     }
+ 
+     @Beta
+     public BrooklynLauncher catalogInitialization(CatalogInitialization catInit) {
+         if (this.catalogInitialization!=null)
+             throw new IllegalStateException("Initial catalog customization already set.");
+         this.catalogInitialization = catInit;
+         return this;
+     }
+ 
+     public BrooklynLauncher shutdownOnExit(boolean val) {
+         LOG.warn("Call to deprecated `shutdownOnExit`", new Throwable("source of deprecated call"));
+         stopWhichAppsOnShutdown = StopWhichAppsOnShutdown.THESE_IF_NOT_PERSISTED;
+         return this;
+     }
+ 
+     public BrooklynLauncher persistMode(PersistMode persistMode) {
+         this.persistMode = persistMode;
+         return this;
+     }
+ 
+     public BrooklynLauncher highAvailabilityMode(HighAvailabilityMode highAvailabilityMode) {
+         this.highAvailabilityMode = highAvailabilityMode;
+         return this;
+     }
+ 
+     public BrooklynLauncher persistenceDir(@Nullable String persistenceDir) {
+         this.persistenceDir = persistenceDir;
+         return this;
+     }
+ 
+     public BrooklynLauncher persistenceDir(@Nullable File persistenceDir) {
+         if (persistenceDir==null) return persistenceDir((String)null);
+         return persistenceDir(persistenceDir.getAbsolutePath());
+     }
+ 
+     public BrooklynLauncher persistPeriod(Duration persistPeriod) {
+         this.persistPeriod = persistPeriod;
+         return this;
+     }
+ 
+     public BrooklynLauncher haHeartbeatTimeout(Duration val) {
+         this.haHeartbeatTimeoutOverride = val;
+         return this;
+     }
+ 
+     public BrooklynLauncher startBrooklynNode(boolean val) {
+         this.startBrooklynNode = val;
+         return this;
+     }
+ 
+     /**
+      * Controls both the frequency of heartbeats, and the frequency of checking the health of other nodes.
+      */
+     public BrooklynLauncher haHeartbeatPeriod(Duration val) {
+         this.haHeartbeatPeriodOverride = val;
+         return this;
+     }
+ 
+     /**
+      * @param destinationDir Directory for state to be copied to
+      */
+     public void copyPersistedState(String destinationDir) {
+         copyPersistedState(destinationDir, null, null);
+     }
+ 
+     /**
+      * A listener to call when the user requests a shutdown (i.e. through the REST API)
+      */
+     public BrooklynLauncher shutdownHandler(ShutdownHandler shutdownHandler) {
+         this.shutdownHandler = shutdownHandler;
+         return this;
+     }
+ 
+     /**
+      * @param destinationDir Directory for state to be copied to
+      * @param destinationLocation Optional location if target for copied state is a blob store.
+      */
+     public void copyPersistedState(String destinationDir, @Nullable String destinationLocation) {
+         copyPersistedState(destinationDir, destinationLocation, null);
+     }
+ 
+     /**
+      * @param destinationDir Directory for state to be copied to
+      * @param destinationLocationSpec Optional location if target for copied state is a blob store.
+      * @param transformer Optional transformations to apply to retrieved state before it is copied.
+      */
+     public void copyPersistedState(String destinationDir, @Nullable String destinationLocationSpec, @Nullable CompoundTransformer transformer) {
+         initManagementContext();
+         try {
+             highAvailabilityMode = HighAvailabilityMode.HOT_STANDBY;
+             initPersistence();
+         } catch (Exception e) {
+             handleSubsystemStartupError(ignorePersistenceErrors, "persistence", e);
+         }
+         
+         try {
+             BrooklynMementoRawData memento = managementContext.getRebindManager().retrieveMementoRawData();
+             if (transformer != null) memento = transformer.transform(memento);
+             
+             ManagementPlaneSyncRecord planeState = managementContext.getHighAvailabilityManager().loadManagementPlaneSyncRecord(true);
+             
+             LOG.info("Persisting state to "+destinationDir+(destinationLocationSpec!=null ? " @ "+destinationLocationSpec : ""));
+             PersistenceObjectStore destinationObjectStore = BrooklynPersistenceUtils.newPersistenceObjectStore(
+                 managementContext, destinationLocationSpec, destinationDir);
+             BrooklynPersistenceUtils.writeMemento(managementContext, memento, destinationObjectStore);
+             BrooklynPersistenceUtils.writeManagerMemento(managementContext, planeState, destinationObjectStore);
+ 
+         } catch (Exception e) {
+             Exceptions.propagateIfFatal(e);
+             LOG.debug("Error copying persisted state (rethrowing): " + e, e);
+             throw new FatalRuntimeException("Error copying persisted state: " +
+                 Exceptions.collapseText(e), e);
+         }
+     }
+ 
+     /** @deprecated since 0.7.0 use {@link #copyPersistedState} instead */
+     // Make private after deprecation
+     @Deprecated
+     public BrooklynMementoRawData retrieveState() {
+         initManagementContext();
+         initPersistence();
+         return managementContext.getRebindManager().retrieveMementoRawData();
+     }
+ 
+     /**
+      * @param memento The state to copy
+      * @param destinationDir Directory for state to be copied to
 -     * @param destinationLocation Optional location if target for copied state is a blob store.
++     * @param destinationLocationSpec Optional location if target for copied state is a blob store.
+      * @deprecated since 0.7.0 use {@link #copyPersistedState} instead
+      */
+     // Make private after deprecation
+     @Deprecated
+     public void persistState(BrooklynMementoRawData memento, String destinationDir, @Nullable String destinationLocationSpec) {
+         initManagementContext();
+         PersistenceObjectStore destinationObjectStore = BrooklynPersistenceUtils.newPersistenceObjectStore(
+             managementContext, destinationLocationSpec, destinationDir);
+         BrooklynPersistenceUtils.writeMemento(managementContext, memento, destinationObjectStore);
+     }
+ 
+     /**
+      * Starts the web server (with web console) and Brooklyn applications, as per the specifications configured. 
+      * @return An object containing details of the web server and the management context.
+      */
+     public BrooklynLauncher start() {
+         if (started) throw new IllegalStateException("Cannot start() or launch() multiple times");
+         started = true;
+ 
+         // Create the management context
+         initManagementContext();
+ 
+         // Inform catalog initialization that it is starting up
+         CatalogInitialization catInit = ((ManagementContextInternal)managementContext).getCatalogInitialization();
+         catInit.setStartingUp(true);
+ 
+         // Start webapps as soon as mgmt context available -- can use them to detect progress of other processes
+         if (startWebApps) {
+             try {
+                 startWebApps();
+             } catch (Exception e) {
+                 handleSubsystemStartupError(ignoreWebErrors, "core web apps", e);
+             }
+         }
+         
+         // Add a CAMP platform
+         campPlatform = new BrooklynCampPlatformLauncherNoServer()
+                 .useManagementContext(managementContext)
+                 .launch()
+                 .getCampPlatform();
+         // TODO start CAMP rest _server_ in the below (at /camp) ?
+ 
+         try {
+             initPersistence();
+             startPersistence();
+         } catch (Exception e) {
+             handleSubsystemStartupError(ignorePersistenceErrors, "persistence", e);
+         }
+ 
+         try {
+             // run cat init now if it hasn't yet been run; 
+             // will also run if there was an ignored error in catalog above, allowing it to fail startup here if requested
+             if (catInit!=null && !catInit.hasRunOfficialInitialization()) {
+                 if (persistMode==PersistMode.DISABLED) {
+                     LOG.debug("Loading catalog as part of launch sequence (it was not loaded as part of any rebind sequence)");
+                     catInit.populateCatalog(ManagementNodeState.MASTER, true, true, null);
+                 } else {
+                     // should have loaded during rebind
+                     ManagementNodeState state = managementContext.getHighAvailabilityManager().getNodeState();
+                     LOG.warn("Loading catalog for "+state+" as part of launch sequence (it was not loaded as part of the rebind sequence)");
+                     catInit.populateCatalog(state, true, true, null);
+                 }
+             }
+         } catch (Exception e) {
+             handleSubsystemStartupError(ignoreCatalogErrors, "initial catalog", e);
+         }
+         catInit.setStartingUp(false);
+ 
+         // Create the locations. Must happen after persistence is started in case the
+         // management context's catalog is loaded from persisted state. (Location
+         // resolution uses the catalog's classpath to scan for resolvers.)
+         locations.addAll(managementContext.getLocationRegistry().resolve(locationSpecs));
+ 
+         // Already rebinded successfully, so previous apps are now available.
+         // Allow the startup to be visible in console for newly created apps.
+         ((LocalManagementContext)managementContext).noteStartupComplete();
+ 
+         // TODO create apps only after becoming master, analogously to catalog initialization
+         try {
+             createApps();
+             startApps();
+         } catch (Exception e) {
+             handleSubsystemStartupError(ignoreAppErrors, "brooklyn autostart apps", e);
+         }
+ 
+         if (startBrooklynNode) {
+             try {
+                 startBrooklynNode();
+             } catch (Exception e) {
+                 handleSubsystemStartupError(ignoreAppErrors, "brooklyn node / self entity", e);
+             }
+         }
+         
+         if (persistMode != PersistMode.DISABLED) {
+             // Make sure the new apps are persisted in case process exits immediately.
+             managementContext.getRebindManager().forcePersistNow(false, null);
+         }
+         return this;
+     }
+ 
+     private void initManagementContext() {
+         // Create the management context
+         if (managementContext == null) {
+             if (brooklynProperties == null) {
+                 BrooklynProperties.Factory.Builder builder = BrooklynProperties.Factory.builderDefault();
++
+                 if (globalBrooklynPropertiesFile != null) {
 -                    if (fileExists(globalBrooklynPropertiesFile)) {
 -                        LOG.debug("Using global properties file "+globalBrooklynPropertiesFile);
 -                        // brooklyn.properties stores passwords (web-console and cloud credentials), 
++                    File globalProperties = new File(Os.tidyPath(globalBrooklynPropertiesFile));
++                    if (globalProperties.exists()) {
++                        globalProperties = resolveSymbolicLink(globalProperties);
++                        checkFileReadable(globalProperties);
++                        // brooklyn.properties stores passwords (web-console and cloud credentials),
+                         // so ensure it has sensible permissions
 -                        checkFileReadable(globalBrooklynPropertiesFile);
 -                        checkFilePermissionsX00(globalBrooklynPropertiesFile);
++                        checkFilePermissionsX00(globalProperties);
++                        LOG.debug("Using global properties file " + globalProperties);
+                     } else {
 -                        LOG.debug("Global properties file "+globalBrooklynPropertiesFile+" does not exist, will ignore");
++                        LOG.debug("Global properties file " + globalProperties + " does not exist, will ignore");
+                     }
 -                    builder.globalPropertiesFile(globalBrooklynPropertiesFile);
++                    builder.globalPropertiesFile(globalProperties.getAbsolutePath());
+                 } else {
+                     LOG.debug("Global properties file disabled");
+                     builder.globalPropertiesFile(null);
+                 }
+                 
+                 if (localBrooklynPropertiesFile != null) {
 -                    checkFileReadable(localBrooklynPropertiesFile);
 -                    checkFilePermissionsX00(localBrooklynPropertiesFile);
 -                    builder.localPropertiesFile(localBrooklynPropertiesFile);
++                    File localProperties = new File(Os.tidyPath(localBrooklynPropertiesFile));
++                    localProperties = resolveSymbolicLink(localProperties);
++                    checkFileReadable(localProperties);
++                    checkFilePermissionsX00(localProperties);
++                    builder.localPropertiesFile(localProperties.getAbsolutePath());
+                 }
++
+                 managementContext = new LocalManagementContext(builder, brooklynAdditionalProperties);
++
+             } else {
+                 if (globalBrooklynPropertiesFile != null)
+                     LOG.warn("Ignoring globalBrooklynPropertiesFile "+globalBrooklynPropertiesFile+" because explicit brooklynProperties supplied");
+                 if (localBrooklynPropertiesFile != null)
+                     LOG.warn("Ignoring localBrooklynPropertiesFile "+localBrooklynPropertiesFile+" because explicit brooklynProperties supplied");
+                 managementContext = new LocalManagementContext(brooklynProperties, brooklynAdditionalProperties);
+             }
++
+             brooklynProperties = ((ManagementContextInternal)managementContext).getBrooklynProperties();
+             
+             // We created the management context, so we are responsible for terminating it
+             BrooklynShutdownHooks.invokeTerminateOnShutdown(managementContext);
+             
+         } else if (brooklynProperties == null) {
+             brooklynProperties = ((ManagementContextInternal)managementContext).getBrooklynProperties();
+             brooklynProperties.addFromMap(brooklynAdditionalProperties);
+         }
+         
+         if (catalogInitialization!=null) {
+             ((ManagementContextInternal)managementContext).setCatalogInitialization(catalogInitialization);
+         }
+         
+         if (customizeManagement!=null) {
+             customizeManagement.apply(managementContext);
+         }
+     }
+ 
 -    private boolean fileExists(String file) {
 -        return new File(Os.tidyPath(file)).exists();
++    /**
++     * @return The canonical path of the argument.
++     */
++    private File resolveSymbolicLink(File f) {
++        File f2 = f;
++        try {
++            f2 = f.getCanonicalFile();
++            if (Files.isSymbolicLink(f.toPath())) {
++                LOG.debug("Resolved symbolic link: {} -> {}", f, f2);
++            }
++        } catch (IOException e) {
++            LOG.warn("Could not determine canonical name of file "+f+"; returning original file", e);
++        }
++        return f2;
+     }
+ 
 -    private void checkFileReadable(String file) {
 -        File f = new File(Os.tidyPath(file));
++    private void checkFileReadable(File f) {
+         if (!f.exists()) {
 -            throw new FatalRuntimeException("File "+file+" does not exist");
++            throw new FatalRuntimeException("File " + f + " does not exist");
+         }
+         if (!f.isFile()) {
 -            throw new FatalRuntimeException(file+" is not a file");
++            throw new FatalRuntimeException(f + " is not a file");
+         }
+         if (!f.canRead()) {
 -            throw new FatalRuntimeException(file+" is not readable");
++            throw new FatalRuntimeException(f + " is not readable");
+         }
+     }
+     
 -    private void checkFilePermissionsX00(String file) {
 -        File f = new File(Os.tidyPath(file));
 -        
++    private void checkFilePermissionsX00(File f) {
++
+         Maybe<String> permission = FileUtil.getFilePermissions(f);
+         if (permission.isAbsent()) {
+             LOG.debug("Could not determine permissions of file; assuming ok: "+f);
+         } else {
+             if (!permission.get().subSequence(4, 10).equals("------")) {
 -                throw new FatalRuntimeException("Invalid permissions for file "+file+"; expected ?00 but was "+permission.get());
++                throw new FatalRuntimeException("Invalid permissions for file " + f + "; expected ?00 but was " + permission.get());
+             }
+         }
+     }
+     
+     private void handleSubsystemStartupError(boolean ignoreSuchErrors, String system, Exception e) {
+         Exceptions.propagateIfFatal(e);
+         if (ignoreSuchErrors) {
+             LOG.error("Subsystem for "+system+" had startup error (continuing with startup): "+e, e);
+             if (managementContext!=null)
+                 ((ManagementContextInternal)managementContext).errors().add(e);
+         } else {
+             throw Exceptions.propagate(e);
+         }
+     }
+ 
+     protected void startWebApps() {
+         // No security options in properties and no command line options overriding.
+         if (Boolean.TRUE.equals(skipSecurityFilter) && bindAddress==null) {
+             LOG.info("Starting Brooklyn web-console on loopback because security is explicitly disabled and no bind address specified");
+             bindAddress = Networking.LOOPBACK;
+         } else if (BrooklynWebConfig.hasNoSecurityOptions(brooklynProperties)) {
+             LOG.info("No security provider options specified. Define a security provider or users to prevent a random password being created and logged.");
+             
+             if (bindAddress==null) {
+                 LOG.info("Starting Brooklyn web-console with passwordless access on localhost and protected access from any other interfaces (no bind address specified)");
+             } else {
+                 if (Arrays.equals(new byte[] { 127, 0, 0, 1 }, bindAddress.getAddress())) { 
+                     LOG.info("Starting Brooklyn web-console with passwordless access on localhost");
+                 } else if (Arrays.equals(new byte[] { 0, 0, 0, 0 }, bindAddress.getAddress())) { 
+                     LOG.info("Starting Brooklyn web-console with passwordless access on localhost and random password (logged) required from any other interfaces");
+                 } else { 
+                     LOG.info("Starting Brooklyn web-console with passwordless access on localhost (if permitted) and random password (logged) required from any other interfaces");
+                 }
+             }
+             brooklynProperties.put(
+                     BrooklynWebConfig.SECURITY_PROVIDER_INSTANCE,
+                     new BrooklynUserWithRandomPasswordSecurityProvider(managementContext));
+         } else {
+             LOG.debug("Starting Brooklyn using security properties: "+brooklynProperties.submap(ConfigPredicates.startingWith(BrooklynWebConfig.BASE_NAME_SECURITY)).asMapWithStringKeys());
+         }
+         if (bindAddress == null) bindAddress = Networking.ANY_NIC;
+ 
+         LOG.debug("Starting Brooklyn web-console with bindAddress "+bindAddress+" and properties "+brooklynProperties);
+         try {
+             webServer = new BrooklynWebServer(webconsoleFlags, managementContext);
+             webServer.setBindAddress(bindAddress);
+             webServer.setPublicAddress(publicAddress);
+             if (port!=null) webServer.setPort(port);
+             if (useHttps!=null) webServer.setHttpsEnabled(useHttps);
+             webServer.setShutdownHandler(shutdownHandler);
+             webServer.putAttributes(brooklynProperties);
+             if (skipSecurityFilter != Boolean.TRUE) {
+                 webServer.setSecurityFilter(BrooklynPropertiesSecurityFilter.class);
+             }
+             for (Map.Entry<String, String> webapp : webApps.entrySet()) {
+                 webServer.addWar(webapp.getKey(), webapp.getValue());
+             }
+             webServer.start();
+ 
+         } catch (Exception e) {
+             LOG.warn("Failed to start Brooklyn web-console (rethrowing): " + Exceptions.collapseText(e));
+             throw new FatalRuntimeException("Failed to start Brooklyn web-console: " + Exceptions.collapseText(e), e);
+         }
+     }
+ 
+     protected void initPersistence() {
+         // Prepare the rebind directory, and initialise the RebindManager as required
+         final PersistenceObjectStore objectStore;
+         if (persistMode == PersistMode.DISABLED) {
+             LOG.info("Persistence disabled");
+             objectStore = null;
+             
+         } else {
+             try {
+                 if (persistenceLocation == null) {
+                     persistenceLocation = brooklynProperties.getConfig(BrooklynServerConfig.PERSISTENCE_LOCATION_SPEC);
+                 }
+                 persistenceDir = BrooklynServerPaths.newMainPersistencePathResolver(brooklynProperties).location(persistenceLocation).dir(persistenceDir).resolve();
+                 objectStore = BrooklynPersistenceUtils.newPersistenceObjectStore(managementContext, persistenceLocation, persistenceDir, 
+                     persistMode, highAvailabilityMode);
+                     
+                 RebindManager rebindManager = managementContext.getRebindManager();
+                 
+                 BrooklynMementoPersisterToObjectStore persister = new BrooklynMementoPersisterToObjectStore(
+                     objectStore,
+                     ((ManagementContextInternal)managementContext).getBrooklynProperties(),
+                     managementContext.getCatalogClassLoader());
+                 PersistenceExceptionHandler persistenceExceptionHandler = PersistenceExceptionHandlerImpl.builder().build();
+                 ((RebindManagerImpl) rebindManager).setPeriodicPersistPeriod(persistPeriod);
+                 rebindManager.setPersister(persister, persistenceExceptionHandler);
+             } catch (FatalConfigurationRuntimeException e) {
+                 throw e;
+             } catch (Exception e) {
+                 Exceptions.propagateIfFatal(e);
+                 LOG.debug("Error initializing persistence subsystem (rethrowing): "+e, e);
+                 throw new FatalRuntimeException("Error initializing persistence subsystem: "+
+                     Exceptions.collapseText(e), e);
+             }
+         }
+         
+         // Initialise the HA manager as required
+         if (highAvailabilityMode == HighAvailabilityMode.DISABLED) {
+             LOG.info("High availability disabled");
+         } else {
+             if (objectStore==null)
+                 throw new FatalConfigurationRuntimeException("Cannot run in HA mode when no persistence configured.");
+ 
+             HighAvailabilityManager haManager = managementContext.getHighAvailabilityManager();
+             ManagementPlaneSyncRecordPersister persister =
+                 new ManagementPlaneSyncRecordPersisterToObjectStore(managementContext,
+                     objectStore,
+                     managementContext.getCatalogClassLoader());
+             ((HighAvailabilityManagerImpl)haManager).setHeartbeatTimeout(haHeartbeatTimeoutOverride);
+             ((HighAvailabilityManagerImpl)haManager).setPollPeriod(haHeartbeatPeriodOverride);
+             haManager.setPersister(persister);
+         }
+     }
+     
+     protected void startPersistence() {
+         // Now start the HA Manager and the Rebind manager, as required
+         if (highAvailabilityMode == HighAvailabilityMode.DISABLED) {
+             HighAvailabilityManager haManager = managementContext.getHighAvailabilityManager();
+             haManager.disabled();
+ 
+             if (persistMode != PersistMode.DISABLED) {
+                 startPersistenceWithoutHA();
+             }
+             
+         } else {
+             // Let the HA manager decide when objectstore.prepare and rebindmgr.rebind need to be called 
+             // (based on whether other nodes in plane are already running).
+             
+             HighAvailabilityMode startMode=null;
+             switch (highAvailabilityMode) {
+                 case AUTO:
+                 case MASTER:
+                 case STANDBY:
+                 case HOT_STANDBY:
+                 case HOT_BACKUP:
+                     startMode = highAvailabilityMode;
+                     break;
+                 case DISABLED:
+                     throw new IllegalStateException("Unexpected code-branch for high availability mode "+highAvailabilityMode);
+             }
+             if (startMode==null)
+                 throw new IllegalStateException("Unexpected high availability mode "+highAvailabilityMode);
+             
+             LOG.debug("Management node (with HA) starting");
+             HighAvailabilityManager haManager = managementContext.getHighAvailabilityManager();
+             // prepare after HA mode is known, to prevent backups happening in standby mode
+             haManager.start(startMode);
+         }
+     }
+ 
+     private void startPersistenceWithoutHA() {
+         RebindManager rebindManager = managementContext.getRebindManager();
+         if (Strings.isNonBlank(persistenceLocation))
+             LOG.info("Management node (no HA) rebinding to entities at "+persistenceLocation+" in "+persistenceDir);
+         else
+             LOG.info("Management node (no HA) rebinding to entities on file system in "+persistenceDir);
+ 
+         ClassLoader classLoader = managementContext.getCatalogClassLoader();
+         try {
+             rebindManager.rebind(classLoader, null, ManagementNodeState.MASTER);
+         } catch (Exception e) {
+             Exceptions.propagateIfFatal(e);
+             LOG.debug("Error rebinding to persisted state (rethrowing): "+e, e);
+             throw new FatalRuntimeException("Error rebinding to persisted state: "+
+                 Exceptions.collapseText(e), e);
+         }
+         rebindManager.startPersistence();
+     }
+ 
+     protected void createApps() {
+         for (ApplicationBuilder appBuilder : appBuildersToManage) {
+             StartableApplication app = appBuilder.manage(managementContext);
+             apps.add(app);
+         }
+         for (Application app : appsToManage) {
+             Entities.startManagement(app, managementContext);
+             apps.add(app);
+         }
+         for (String blueprint : yamlAppsToManage) {
+             Application app = EntityManagementUtils.createUnstarted(managementContext, blueprint);
+             // Note: BrooklynAssemblyTemplateInstantiator automatically puts applications under management.
+             apps.add(app);
+         }
+     }
+ 
+     protected void startBrooklynNode() {
+         final String classpath = System.getenv("INITIAL_CLASSPATH");
+         if (Strings.isBlank(classpath)) {
+             LOG.warn("Cannot find INITIAL_CLASSPATH environment variable, skipping BrooklynNode entity creation");
+             return;
+         }
+         if (webServer == null || !startWebApps) {
+             LOG.info("Skipping BrooklynNode entity creation, BrooklynWebServer not running");
+             return;
+         }
+         ApplicationBuilder brooklyn = new ApplicationBuilder() {
+             @SuppressWarnings("deprecation")
+             @Override
+             protected void doBuild() {
+                 addChild(EntitySpec.create(LocalBrooklynNode.class)
+                         .configure(SoftwareProcess.ENTITY_STARTED, true)
+                         .configure(SoftwareProcess.RUN_DIR, System.getenv("ROOT"))
+                         .configure(SoftwareProcess.INSTALL_DIR, System.getenv("BROOKLYN_HOME"))
+                         .configure(BrooklynNode.ENABLED_HTTP_PROTOCOLS, ImmutableList.of(webServer.getHttpsEnabled() ? "https" : "http"))
+                         .configure(webServer.getHttpsEnabled() ? BrooklynNode.HTTPS_PORT : BrooklynNode.HTTP_PORT, PortRanges.fromInteger(webServer.getActualPort()))
+                         .configure(BrooklynNode.WEB_CONSOLE_BIND_ADDRESS, bindAddress)
+                         .configure(BrooklynNode.WEB_CONSOLE_PUBLIC_ADDRESS, publicAddress)
+                         .configure(BrooklynNode.CLASSPATH, Splitter.on(":").splitToList(classpath))
+                         .configure(BrooklynNode.NO_WEB_CONSOLE_AUTHENTICATION, Boolean.TRUE.equals(skipSecurityFilter))
+                         .displayName("Brooklyn Console"));
+             }
+         };
+         LocationSpec<?> spec = LocationSpec.create(LocalhostMachine.class).displayName("Local Brooklyn");
+         Location localhost = managementContext.getLocationManager().createLocation(spec);
+         brooklyn.appDisplayName("Brooklyn")
+                 .manage(managementContext)
+                 .start(ImmutableList.of(localhost));
+     }
+ 
+     protected void startApps() {
+         if ((stopWhichAppsOnShutdown==StopWhichAppsOnShutdown.ALL) || 
+             (stopWhichAppsOnShutdown==StopWhichAppsOnShutdown.ALL_IF_NOT_PERSISTED && persistMode==PersistMode.DISABLED)) {
+             BrooklynShutdownHooks.invokeStopAppsOnShutdown(managementContext);
+         }
+ 
+         List<Throwable> appExceptions = Lists.newArrayList();
+         for (Application app : apps) {
+             if (app instanceof Startable) {
+                 
+                 if ((stopWhichAppsOnShutdown==StopWhichAppsOnShutdown.THESE) || 
+                     (stopWhichAppsOnShutdown==StopWhichAppsOnShutdown.THESE_IF_NOT_PERSISTED && persistMode==PersistMode.DISABLED)) {
+                     BrooklynShutdownHooks.invokeStopOnShutdown(app);
+                 }
+                 try {
+                     LOG.info("Starting brooklyn application {} in location{} {}", new Object[] { app, locations.size()!=1?"s":"", locations });
+                     ((Startable)app).start(locations);
+                 } catch (Exception e) {
+                     LOG.error("Error starting "+app+": "+Exceptions.collapseText(e), Exceptions.getFirstInteresting(e));
+                     appExceptions.add(Exceptions.collapse(e));
+                     
+                     if (Thread.currentThread().isInterrupted()) {
+                         LOG.error("Interrupted while starting applications; aborting");
+                         break;
+                     }
+                 }
+             }
+         }
+         if (!appExceptions.isEmpty()) {
+             Throwable t = Exceptions.create(appExceptions);
+             throw new FatalRuntimeException("Error starting applications: "+Exceptions.collapseText(t), t);
+         }
+     }
+     
+     public boolean isStarted() {
+         return started;
+     }
+     
+     /**
+      * Terminates this launch, but does <em>not</em> stop the applications (i.e. external processes
+      * are left running, etc). However, by terminating the management console the brooklyn applications
+      * become unusable.
+      */
+     public void terminate() {
+         if (!started) return; // no-op
+         
+         if (webServer != null) {
+             try {
+                 webServer.stop();
+             } catch (Exception e) {
+                 LOG.warn("Error stopping web-server; continuing with termination", e);
+             }
+         }
+ 
+         // TODO Do we want to do this as part of managementContext.terminate, so after other threads are terminated etc?
+         // Otherwise the app can change between this persist and the terminate.
+         if (persistMode != PersistMode.DISABLED) {
+             try {
+                 Stopwatch stopwatch = Stopwatch.createStarted();
+                 if (managementContext.getHighAvailabilityManager().getPersister() != null) {
+                     managementContext.getHighAvailabilityManager().getPersister().waitForWritesCompleted(Duration.TEN_SECONDS);
+                 }
+                 managementContext.getRebindManager().waitForPendingComplete(Duration.TEN_SECONDS, true);
+                 LOG.info("Finished waiting for persist; took "+Time.makeTimeStringRounded(stopwatch));
+             } catch (RuntimeInterruptedException e) {
+                 Thread.currentThread().interrupt(); // keep going with shutdown
+                 LOG.warn("Persistence interrupted during shutdown: "+e, e);
+             } catch (InterruptedException e) {
+                 Thread.currentThread().interrupt(); // keep going with shutdown
+                 LOG.warn("Persistence interrupted during shutdown: "+e, e);
+             } catch (TimeoutException e) {
+                 LOG.warn("Timeout after 10 seconds waiting for persistence to write all data; continuing");
+             }
+         }
+         
+         if (managementContext instanceof ManagementContextInternal) {
+             ((ManagementContextInternal)managementContext).terminate();
+         }
+         
+         for (Location loc : locations) {
+             if (loc instanceof Closeable) {
+                 Streams.closeQuietly((Closeable)loc);
+             }
+         }
+     }
+ 
+ }


Mime
View raw message