You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@brooklyn.apache.org by he...@apache.org on 2014/11/15 01:05:10 UTC

[01/21] incubator-brooklyn git commit: support for exporting persistent state

Repository: incubator-brooklyn
Updated Branches:
  refs/heads/master e22b5bfb7 -> c4061feaf


support for exporting persistent state


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

Branch: refs/heads/master
Commit: 3e793cd7b67e95db09a68a4e11a16e84e6a117cb
Parents: 0773c64
Author: Alex Heneveld <al...@cloudsoftcorp.com>
Authored: Mon Nov 10 14:18:47 2014 +0000
Committer: Alex Heneveld <al...@cloudsoftcorp.com>
Committed: Thu Nov 13 23:38:51 2014 +0000

----------------------------------------------------------------------
 .../brooklyn/management/ha/MementoCopyMode.java | 29 ++++++++
 .../mementos/BrooklynMementoPersister.java      |  1 +
 .../basic/EntityTransientCopyInternal.java      |  2 +
 .../rebind/ImmediateDeltaChangeListener.java    |  1 +
 .../rebind/RebindExceptionHandlerImpl.java      |  8 +--
 .../entity/rebind/RebindManagerImpl.java        |  4 ++
 .../BrooklynMementoPersisterToObjectStore.java  |  5 +-
 .../persister/BrooklynPersistenceUtils.java     | 71 ++++++++++++++++++++
 .../persister/FileBasedStoreObjectAccessor.java |  1 -
 .../main/java/brooklyn/rest/api/ServerApi.java  | 33 ++++++++-
 .../brooklyn/rest/resources/ServerResource.java | 38 +++++++++++
 11 files changed, 183 insertions(+), 10 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/3e793cd7/api/src/main/java/brooklyn/management/ha/MementoCopyMode.java
----------------------------------------------------------------------
diff --git a/api/src/main/java/brooklyn/management/ha/MementoCopyMode.java b/api/src/main/java/brooklyn/management/ha/MementoCopyMode.java
new file mode 100644
index 0000000..83c37f5
--- /dev/null
+++ b/api/src/main/java/brooklyn/management/ha/MementoCopyMode.java
@@ -0,0 +1,29 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package brooklyn.management.ha;
+
+public enum MementoCopyMode {
+    /** Use items currently managed at this node */ 
+    LOCAL,
+    /** Use items as stored in the remote persistence store */ 
+    REMOTE, 
+    /** Auto-detect whether to use {@link #LOCAL} or {@link #REMOTE} depending on the
+     * HA mode of this management node (usually {@link #LOCAL} for master and {@link #REMOTE} otherwise)*/ 
+    AUTO 
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/3e793cd7/api/src/main/java/brooklyn/mementos/BrooklynMementoPersister.java
----------------------------------------------------------------------
diff --git a/api/src/main/java/brooklyn/mementos/BrooklynMementoPersister.java b/api/src/main/java/brooklyn/mementos/BrooklynMementoPersister.java
index 63a04d1..ed649fa 100644
--- a/api/src/main/java/brooklyn/mementos/BrooklynMementoPersister.java
+++ b/api/src/main/java/brooklyn/mementos/BrooklynMementoPersister.java
@@ -92,6 +92,7 @@ public interface BrooklynMementoPersister {
       */
     BrooklynMemento loadMemento(@Nullable BrooklynMementoRawData mementoData, LookupContext lookupContext, RebindExceptionHandler exceptionHandler) throws IOException;
     
+    // TODO can this be deprecated? not used much, and cumbersome
     void checkpoint(BrooklynMemento memento, PersistenceExceptionHandler exceptionHandler);
 
     void delta(Delta delta, PersistenceExceptionHandler exceptionHandler);

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/3e793cd7/core/src/main/java/brooklyn/entity/basic/EntityTransientCopyInternal.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/entity/basic/EntityTransientCopyInternal.java b/core/src/main/java/brooklyn/entity/basic/EntityTransientCopyInternal.java
index 576e182..4cbe034 100644
--- a/core/src/main/java/brooklyn/entity/basic/EntityTransientCopyInternal.java
+++ b/core/src/main/java/brooklyn/entity/basic/EntityTransientCopyInternal.java
@@ -104,7 +104,9 @@ public interface EntityTransientCopyInternal {
     ManagementContext getManagementContext();
     Effector<?> getEffector(String effectorName);
     FeedSupport getFeedSupport();
+    FeedSupport feeds();
     TagSupport getTagSupport();
+    TagSupport tags();
     RebindSupport<EntityMemento> getRebindSupport();
     // for REST calls on read-only entities which want to resolve values
     ExecutionContext getExecutionContext();

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/3e793cd7/core/src/main/java/brooklyn/entity/rebind/ImmediateDeltaChangeListener.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/entity/rebind/ImmediateDeltaChangeListener.java b/core/src/main/java/brooklyn/entity/rebind/ImmediateDeltaChangeListener.java
index d7ddeea..a668a37 100644
--- a/core/src/main/java/brooklyn/entity/rebind/ImmediateDeltaChangeListener.java
+++ b/core/src/main/java/brooklyn/entity/rebind/ImmediateDeltaChangeListener.java
@@ -92,6 +92,7 @@ public class ImmediateDeltaChangeListener implements ChangeListener {
         if (running && persister != null) {
             PersisterDeltaImpl delta = new PersisterDeltaImpl();
             Memento memento = ((BrooklynObjectInternal)instance).getRebindSupport().getMemento();
+            // XXX use switch statement, and above, and in MementosGenerator
             if (instance instanceof Entity) {
                 delta.entities.add((EntityMemento) memento);
                 addEntityAdjuncts((Entity)instance, delta);

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/3e793cd7/core/src/main/java/brooklyn/entity/rebind/RebindExceptionHandlerImpl.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/entity/rebind/RebindExceptionHandlerImpl.java b/core/src/main/java/brooklyn/entity/rebind/RebindExceptionHandlerImpl.java
index f595e54..e5f3111 100644
--- a/core/src/main/java/brooklyn/entity/rebind/RebindExceptionHandlerImpl.java
+++ b/core/src/main/java/brooklyn/entity/rebind/RebindExceptionHandlerImpl.java
@@ -364,9 +364,6 @@ public class RebindExceptionHandlerImpl implements RebindExceptionHandler {
             allExceptions.add(new IllegalStateException(this+" has already been informed of rebind done"));
         }
         done = true;
-        if (!started) {
-            allExceptions.add(new IllegalStateException(this+" was not informed of start of rebind run"));
-        }
         if (e != null) {
             allExceptions.add(e);
         }
@@ -400,7 +397,10 @@ public class RebindExceptionHandlerImpl implements RebindExceptionHandler {
         if (rebindFailureMode != RebindManager.RebindFailureMode.CONTINUE) {
             allExceptions.addAll(exceptions);
         }
-        
+        if (!started) {
+            allExceptions.add(new IllegalStateException(this+" was not informed of start of rebind run"));
+        }
+
         if (allExceptions.isEmpty()) {
             return; // no errors
         } else {

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/3e793cd7/core/src/main/java/brooklyn/entity/rebind/RebindManagerImpl.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/entity/rebind/RebindManagerImpl.java b/core/src/main/java/brooklyn/entity/rebind/RebindManagerImpl.java
index 9c85e66..67263c7 100644
--- a/core/src/main/java/brooklyn/entity/rebind/RebindManagerImpl.java
+++ b/core/src/main/java/brooklyn/entity/rebind/RebindManagerImpl.java
@@ -396,6 +396,7 @@ public class RebindManagerImpl implements RebindManager {
     @Override
     @VisibleForTesting
     public void forcePersistNow() {
+//        XXX persistenceStoreAccess.checkpoint(memento, exceptionHandler);
         persistenceRealChangeListener.persistNow();
     }
     
@@ -471,6 +472,9 @@ public class RebindManagerImpl implements RebindManager {
      */
     protected BrooklynMementoRawData loadMementoRawData(final RebindExceptionHandler exceptionHandler) {
         try {
+            if (persistenceStoreAccess==null) {
+                throw new IllegalStateException("Persistence not configured; cannot load memento data from persistent backing store");
+            }
             if (!(persistenceStoreAccess instanceof BrooklynMementoPersisterToObjectStore)) {
                 throw new IllegalStateException("Cannot load raw memento with persister "+persistenceStoreAccess);
             }

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/3e793cd7/core/src/main/java/brooklyn/entity/rebind/persister/BrooklynMementoPersisterToObjectStore.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/entity/rebind/persister/BrooklynMementoPersisterToObjectStore.java b/core/src/main/java/brooklyn/entity/rebind/persister/BrooklynMementoPersisterToObjectStore.java
index fdaca78..cbf6ccb 100644
--- a/core/src/main/java/brooklyn/entity/rebind/persister/BrooklynMementoPersisterToObjectStore.java
+++ b/core/src/main/java/brooklyn/entity/rebind/persister/BrooklynMementoPersisterToObjectStore.java
@@ -40,7 +40,6 @@ import org.slf4j.LoggerFactory;
 import brooklyn.basic.BrooklynObject;
 import brooklyn.catalog.CatalogItem;
 import brooklyn.catalog.internal.CatalogUtils;
-import brooklyn.config.BrooklynProperties;
 import brooklyn.config.ConfigKey;
 import brooklyn.config.StringConfigMap;
 import brooklyn.entity.basic.ConfigKeys;
@@ -122,7 +121,7 @@ public class BrooklynMementoPersisterToObjectStore implements BrooklynMementoPer
      */
     private final ReadWriteLock lock = new ReentrantReadWriteLock(true);
 
-    public BrooklynMementoPersisterToObjectStore(PersistenceObjectStore objectStore, BrooklynProperties brooklynProperties, ClassLoader classLoader) {
+    public BrooklynMementoPersisterToObjectStore(PersistenceObjectStore objectStore, StringConfigMap brooklynProperties, ClassLoader classLoader) {
         this.objectStore = checkNotNull(objectStore, "objectStore");
         this.brooklynProperties = brooklynProperties;
         
@@ -505,7 +504,6 @@ public class BrooklynMementoPersisterToObjectStore implements BrooklynMementoPer
     @Beta
     public void checkpoint(BrooklynMementoRawData newMemento, PersistenceExceptionHandler exceptionHandler) {
         checkWritesAllowed();
-        
         try {
             lock.writeLock().lockInterruptibly();
         } catch (InterruptedException e) {
@@ -532,7 +530,6 @@ public class BrooklynMementoPersisterToObjectStore implements BrooklynMementoPer
             } catch (Exception e) {
                 throw Exceptions.propagate(e);
             }
-            
             if (LOG.isDebugEnabled()) LOG.debug("Checkpointed entire memento in {}", Time.makeTimeStringRounded(stopwatch));
         } finally {
             lock.writeLock().unlock();

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/3e793cd7/core/src/main/java/brooklyn/entity/rebind/persister/BrooklynPersistenceUtils.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/entity/rebind/persister/BrooklynPersistenceUtils.java b/core/src/main/java/brooklyn/entity/rebind/persister/BrooklynPersistenceUtils.java
index 1308d62..e8bd336 100644
--- a/core/src/main/java/brooklyn/entity/rebind/persister/BrooklynPersistenceUtils.java
+++ b/core/src/main/java/brooklyn/entity/rebind/persister/BrooklynPersistenceUtils.java
@@ -18,9 +18,18 @@
  */
 package brooklyn.entity.rebind.persister;
 
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import brooklyn.basic.BrooklynObject;
+import brooklyn.catalog.CatalogItem;
 import brooklyn.config.BrooklynServerConfig;
+import brooklyn.entity.Entity;
+import brooklyn.entity.Feed;
+import brooklyn.entity.basic.EntityInternal;
 import brooklyn.entity.rebind.PersistenceExceptionHandler;
 import brooklyn.entity.rebind.PersistenceExceptionHandlerImpl;
+import brooklyn.entity.rebind.dto.MementosGenerators;
 import brooklyn.entity.rebind.transformer.CompoundTransformer;
 import brooklyn.entity.rebind.transformer.CompoundTransformerLoader;
 import brooklyn.location.Location;
@@ -28,15 +37,26 @@ import brooklyn.location.LocationSpec;
 import brooklyn.location.basic.LocalhostMachineProvisioningLocation;
 import brooklyn.management.ManagementContext;
 import brooklyn.management.ha.HighAvailabilityMode;
+import brooklyn.management.ha.ManagementNodeState;
 import brooklyn.management.ha.ManagementPlaneSyncRecord;
 import brooklyn.management.ha.ManagementPlaneSyncRecordPersisterToObjectStore;
+import brooklyn.management.ha.MementoCopyMode;
 import brooklyn.management.internal.ManagementContextInternal;
 import brooklyn.mementos.BrooklynMementoRawData;
+import brooklyn.mementos.Memento;
+import brooklyn.policy.Enricher;
+import brooklyn.policy.Policy;
 import brooklyn.util.ResourceUtils;
 import brooklyn.util.text.Strings;
+import brooklyn.util.time.Duration;
+import brooklyn.util.time.Time;
+
+import com.google.common.base.Stopwatch;
 
 public class BrooklynPersistenceUtils {
 
+    private static final Logger log = LoggerFactory.getLogger(BrooklynPersistenceUtils.class);
+    
     /** Creates a {@link PersistenceObjectStore} for general-purpose use. */
     public static PersistenceObjectStore newPersistenceObjectStore(ManagementContext managementContext,
             String locationSpec, String locationContainer) {
@@ -100,4 +120,55 @@ public class BrooklynPersistenceUtils {
         }
     }
 
+    public static Memento newObjectMemento(BrooklynObject instance) {
+        return MementosGenerators.newMemento(instance);
+    }
+    
+    public static BrooklynMementoRawData newFullMemento(ManagementContext mgmt) {
+        BrooklynMementoRawData.Builder result = BrooklynMementoRawData.builder();
+        MementoSerializer<Object> rawSerializer = new XmlMementoSerializer<Object>(mgmt.getClass().getClassLoader());
+        RetryingMementoSerializer<Object> serializer = new RetryingMementoSerializer<Object>(rawSerializer, 1);
+        
+        for (Location instance: mgmt.getLocationManager().getLocations())
+            result.location(instance.getId(), serializer.toString(newObjectMemento(instance)));
+        for (Entity instance: mgmt.getEntityManager().getEntities()) {
+            result.entity(instance.getId(), serializer.toString(newObjectMemento(instance)));
+            for (Feed instanceAdjunct: ((EntityInternal)instance).feeds().getFeeds())
+                result.feed(instanceAdjunct.getId(), serializer.toString(newObjectMemento(instanceAdjunct)));
+            for (Enricher instanceAdjunct: instance.getEnrichers())
+                result.enricher(instanceAdjunct.getId(), serializer.toString(newObjectMemento(instanceAdjunct)));
+            for (Policy instanceAdjunct: instance.getPolicies())
+                result.policy(instanceAdjunct.getId(), serializer.toString(newObjectMemento(instanceAdjunct)));
+        }
+        for (CatalogItem<?,?> instance: mgmt.getCatalog().getCatalogItems())
+            result.catalogItem(instance.getId(), serializer.toString(newObjectMemento(instance)));
+        
+        return result.build();
+    }
+
+    /** generates and writes mementos for the given mgmt context to the given targetStore;
+     * this may be taken from {@link MementoCopyMode#LOCAL} current state 
+     * or {@link MementoCopyMode#REMOTE} persisted state, or the default {@link MementoCopyMode#AUTO} detected
+     */
+    public static void writeMemento(ManagementContext mgmt, PersistenceObjectStore targetStore, MementoCopyMode preferredOrigin) {
+        if (preferredOrigin==null || preferredOrigin==MementoCopyMode.AUTO) 
+            preferredOrigin = (mgmt.getHighAvailabilityManager().getNodeState()==ManagementNodeState.MASTER ? MementoCopyMode.LOCAL : MementoCopyMode.REMOTE);
+
+        Stopwatch timer = Stopwatch.createStarted();
+        
+        BrooklynMementoRawData dataRecord = null; 
+        switch (preferredOrigin) {
+        case LOCAL: dataRecord = newFullMemento(mgmt); break;
+        case REMOTE: dataRecord = mgmt.getRebindManager().retrieveMementoRawData(); break;
+        case AUTO: throw new IllegalStateException("Should not come here, should have autodetected");
+        }
+
+        ManagementPlaneSyncRecord mgmtRecord = mgmt.getHighAvailabilityManager().getManagementPlaneSyncState();
+        
+        writeMemento(mgmt, dataRecord, targetStore);
+        writeManagerMemento(mgmt, mgmtRecord, targetStore);
+        
+        log.debug("Wrote full memento to "+targetStore+" in "+Time.makeTimeStringRounded(Duration.of(timer)));
+    }
+    
 }

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/3e793cd7/core/src/main/java/brooklyn/entity/rebind/persister/FileBasedStoreObjectAccessor.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/entity/rebind/persister/FileBasedStoreObjectAccessor.java b/core/src/main/java/brooklyn/entity/rebind/persister/FileBasedStoreObjectAccessor.java
index f20c432..1793abd 100644
--- a/core/src/main/java/brooklyn/entity/rebind/persister/FileBasedStoreObjectAccessor.java
+++ b/core/src/main/java/brooklyn/entity/rebind/persister/FileBasedStoreObjectAccessor.java
@@ -71,7 +71,6 @@ public class FileBasedStoreObjectAccessor implements PersistenceObjectStore.Stor
             FileUtil.setFilePermissionsTo600(tmpFile);
             Files.write(val, tmpFile, Charsets.UTF_8);
             FileBasedObjectStore.moveFile(tmpFile, file);
-            
         } catch (IOException e) {
             throw Exceptions.propagate(e);
         } catch (InterruptedException e) {

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/3e793cd7/usage/rest-api/src/main/java/brooklyn/rest/api/ServerApi.java
----------------------------------------------------------------------
diff --git a/usage/rest-api/src/main/java/brooklyn/rest/api/ServerApi.java b/usage/rest-api/src/main/java/brooklyn/rest/api/ServerApi.java
index f77daa4..7977ea8 100644
--- a/usage/rest-api/src/main/java/brooklyn/rest/api/ServerApi.java
+++ b/usage/rest-api/src/main/java/brooklyn/rest/api/ServerApi.java
@@ -25,7 +25,9 @@ import javax.ws.rs.GET;
 import javax.ws.rs.POST;
 import javax.ws.rs.Path;
 import javax.ws.rs.Produces;
+import javax.ws.rs.QueryParam;
 import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
 
 import brooklyn.management.ha.HighAvailabilityMode;
 import brooklyn.management.ha.ManagementNodeState;
@@ -43,6 +45,10 @@ import com.wordnik.swagger.core.ApiParam;
 @Beta
 public interface ServerApi {
 
+    public final String MIME_TYPE_ZIP = "applicaiton/zip";
+    // TODO support TGZ, and check mime type
+    public final String MIME_TYPE_TGZ = "applicaiton/gzip";
+    
     @POST
     @Path("/properties/reload")
     @ApiOperation(value = "Reload brooklyn.properties")
@@ -95,7 +101,7 @@ public interface ServerApi {
     @Path("/ha/state")
     @ApiOperation(value = "Changes the HA state of this management node")
     public ManagementNodeState setHighAvailabilityNodeState(
-            @ApiParam(name = "state", value = "The state to change to")
+            @ApiParam(name = "mode", value = "The state to change to")
             @FormParam("mode") HighAvailabilityMode mode);
 
     @GET
@@ -115,6 +121,31 @@ public interface ServerApi {
     public long setHighAvailabilityPriority(
             @ApiParam(name = "priority", value = "The priority to be set")
             @FormParam("priority") long priority);
+    
+    @GET
+    @Produces(MIME_TYPE_ZIP)
+    @Path("/ha/persist/export")
+    @ApiOperation(value = "Retrieves the persistence store data, as an archive")
+    public Response exportPersistenceData(
+        @ApiParam(name = "origin", value = "Whether to take from LOCAL or REMOTE state; default to AUTO detect, "
+            + "using LOCAL as master and REMOTE for other notes")
+        @QueryParam("origin") @DefaultValue("AUTO") String origin);
+
+    // TODO would be nice to allow setting, as a means to recover / control more easily than messing with persistent stores
+//    @POST
+//    @Consumes({MediaType.APPLICATION_FORM_URLENCODED})
+//    @Path("/ha/persist/import")
+//    @ApiOperation(value = "Causes the supplied persistence data (tgz) to be imported and added "
+//        + "(fails if the node is not master), optionally removing any items not referenced")
+//    public Response importPersistenceData(
+//          // question: do we want the MementoCopyMode, cf export above?
+//        @ApiParam(name = "clearOthers", value = "Whether to clear all existing items before adding these", required = false, defaultValue = "false")
+//        @FormParam("clearOthers") Boolean clearOthers,
+//        @ApiParam(name = "data",
+//        value = "TGZ contents of a persistent directory to be imported", required = true)
+//    @Valid String dataTgz);
+
+    // TODO /ha/persist/backup set of endpoints, to list and retrieve specific backups
 
     @GET
     @Path("/user")

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/3e793cd7/usage/rest-server/src/main/java/brooklyn/rest/resources/ServerResource.java
----------------------------------------------------------------------
diff --git a/usage/rest-server/src/main/java/brooklyn/rest/resources/ServerResource.java b/usage/rest-server/src/main/java/brooklyn/rest/resources/ServerResource.java
index a9cf8a5..803dac4 100644
--- a/usage/rest-server/src/main/java/brooklyn/rest/resources/ServerResource.java
+++ b/usage/rest-server/src/main/java/brooklyn/rest/resources/ServerResource.java
@@ -18,6 +18,7 @@
  */
 package brooklyn.rest.resources;
 
+import java.io.ByteArrayOutputStream;
 import java.io.IOException;
 import java.io.InputStream;
 import java.util.ArrayList;
@@ -27,6 +28,9 @@ import java.util.concurrent.TimeUnit;
 import java.util.concurrent.TimeoutException;
 import java.util.concurrent.atomic.AtomicBoolean;
 
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -35,6 +39,9 @@ import brooklyn.entity.Application;
 import brooklyn.entity.basic.Entities;
 import brooklyn.entity.basic.EntityLocal;
 import brooklyn.entity.basic.StartableApplication;
+import brooklyn.entity.rebind.persister.BrooklynPersistenceUtils;
+import brooklyn.entity.rebind.persister.FileBasedObjectStore;
+import brooklyn.entity.rebind.persister.PersistenceObjectStore;
 import brooklyn.management.Task;
 import brooklyn.management.entitlement.EntitlementContext;
 import brooklyn.management.entitlement.Entitlements;
@@ -42,6 +49,7 @@ import brooklyn.management.ha.HighAvailabilityManager;
 import brooklyn.management.ha.HighAvailabilityMode;
 import brooklyn.management.ha.ManagementNodeState;
 import brooklyn.management.ha.ManagementPlaneSyncRecord;
+import brooklyn.management.ha.MementoCopyMode;
 import brooklyn.management.internal.ManagementContextInternal;
 import brooklyn.rest.api.ServerApi;
 import brooklyn.rest.domain.HighAvailabilitySummary;
@@ -50,6 +58,9 @@ import brooklyn.rest.transform.HighAvailabilityTransformer;
 import brooklyn.rest.util.WebResourceUtils;
 import brooklyn.util.ResourceUtils;
 import brooklyn.util.exceptions.Exceptions;
+import brooklyn.util.file.ArchiveBuilder;
+import brooklyn.util.flags.TypeCoercions;
+import brooklyn.util.text.Identifiers;
 import brooklyn.util.text.Strings;
 import brooklyn.util.time.CountdownTimer;
 import brooklyn.util.time.Duration;
@@ -75,6 +86,10 @@ public class ServerResource extends AbstractBrooklynRestResource implements Serv
     public void shutdown(final boolean stopAppsFirst, final boolean forceShutdownOnError,
             String shutdownTimeoutRaw, String requestTimeoutRaw, String delayForHttpReturnRaw,
             Long delayMillis) {
+        
+        if (!Entitlements.isEntitled(mgmt().getEntitlementManager(), Entitlements.SEE_ALL_SERVER_INFO, null))
+            throw WebResourceUtils.unauthorized("User '%s' is not authorized for this operation", Entitlements.getEntitlementContext().user());
+        
         log.info("REST call to shutdown server, stopAppsFirst="+stopAppsFirst+", delayForHttpReturn="+shutdownTimeoutRaw);
 
         final Duration shutdownTimeout = parseDuration(shutdownTimeoutRaw, Duration.of(20, TimeUnit.SECONDS));
@@ -268,4 +283,27 @@ public class ServerResource extends AbstractBrooklynRestResource implements Serv
         }
     }
 
+    @Override
+    public Response exportPersistenceData(String preferredOrigin) {
+        return exportPersistenceData(TypeCoercions.coerce(preferredOrigin, MementoCopyMode.class));
+    }
+    
+    protected Response exportPersistenceData(MementoCopyMode preferredOrigin) {
+        if (!Entitlements.isEntitled(mgmt().getEntitlementManager(), Entitlements.SEE_ALL_SERVER_INFO, null))
+            throw WebResourceUtils.unauthorized("User '%s' is not authorized for this operation", Entitlements.getEntitlementContext().user());
+
+        try {
+            PersistenceObjectStore targetStore = BrooklynPersistenceUtils.newPersistenceObjectStore(mgmt(), null, 
+                "web-persistence-"+mgmt().getManagementNodeId()+"-"+Time.makeDateStampString()+"-"+Identifiers.makeRandomId(4));
+            BrooklynPersistenceUtils.writeMemento(mgmt(), targetStore, preferredOrigin);            
+            
+            ByteArrayOutputStream baos = new ByteArrayOutputStream();
+            ArchiveBuilder.zip().addDirContentsAt( ((FileBasedObjectStore)targetStore).getBaseDir(), "/" ).stream(baos);
+            return Response.ok(baos.toByteArray(), MediaType.APPLICATION_OCTET_STREAM_TYPE).build();
+        } catch (Exception e) {
+            log.warn("Unable to serve persistence data (rethrowing): "+e, e);
+            throw Exceptions.propagate(e);
+        }
+    }
+
 }


[16/21] incubator-brooklyn git commit: Fix generated zip entries - use supported argument.

Posted by he...@apache.org.
Fix generated zip entries - use supported argument.

Seem the ArchiveBuilder can't create archives without root-level folder, pass an actual folder name.


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

Branch: refs/heads/master
Commit: 1a4e8539dbc89e8d7f2e818c79611f72bc35c334
Parents: 24a3c34
Author: Svetoslav Neykov <sv...@cloudsoftcorp.com>
Authored: Fri Nov 14 16:05:18 2014 +0200
Committer: Svetoslav Neykov <sv...@cloudsoftcorp.com>
Committed: Fri Nov 14 16:05:18 2014 +0200

----------------------------------------------------------------------
 .../src/main/java/brooklyn/rest/resources/ServerResource.java      | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/1a4e8539/usage/rest-server/src/main/java/brooklyn/rest/resources/ServerResource.java
----------------------------------------------------------------------
diff --git a/usage/rest-server/src/main/java/brooklyn/rest/resources/ServerResource.java b/usage/rest-server/src/main/java/brooklyn/rest/resources/ServerResource.java
index b96c1b4..2a4a8c5 100644
--- a/usage/rest-server/src/main/java/brooklyn/rest/resources/ServerResource.java
+++ b/usage/rest-server/src/main/java/brooklyn/rest/resources/ServerResource.java
@@ -310,7 +310,7 @@ public class ServerResource extends AbstractBrooklynRestResource implements Serv
             BrooklynPersistenceUtils.writeMemento(mgmt(), targetStore, preferredOrigin);            
             
             ByteArrayOutputStream baos = new ByteArrayOutputStream();
-            ArchiveBuilder.zip().addDirContentsAt( ((FileBasedObjectStore)targetStore).getBaseDir(), "/" ).stream(baos);
+            ArchiveBuilder.zip().addDirContentsAt( ((FileBasedObjectStore)targetStore).getBaseDir(), ((FileBasedObjectStore)targetStore).getBaseDir().getName() ).stream(baos);
             String filename = "brooklyn-state-"+label+".zip";
             return Response.ok(baos.toByteArray(), MediaType.APPLICATION_OCTET_STREAM_TYPE)
                 .header("Content-Disposition","attachment; filename = "+filename)


[20/21] incubator-brooklyn git commit: This closes #314

Posted by he...@apache.org.
This closes #314


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

Branch: refs/heads/master
Commit: 36b2f6830b86ba6febc23ed59a0daa583f019633
Parents: e22b5bf fa7ba6f
Author: Alex Heneveld <al...@cloudsoftcorp.com>
Authored: Sat Nov 15 00:01:34 2014 +0000
Committer: Alex Heneveld <al...@cloudsoftcorp.com>
Committed: Sat Nov 15 00:01:34 2014 +0000

----------------------------------------------------------------------
 .../entity/rebind/BrooklynObjectType.java       |  38 +-
 .../brooklyn/entity/rebind/RebindContext.java   |  30 +-
 .../entity/rebind/RebindExceptionHandler.java   |   8 +-
 .../brooklyn/entity/rebind/RebindManager.java   |  28 +-
 .../brooklyn/location/LocationRegistry.java     |   4 +-
 .../management/ha/HighAvailabilityManager.java  |  30 +-
 .../management/ha/HighAvailabilityMode.java     |   6 +
 .../management/ha/ManagementNodeState.java      |  35 +-
 .../brooklyn/management/ha/MementoCopyMode.java |  29 ++
 .../java/brooklyn/mementos/BrooklynMemento.java |  16 -
 .../mementos/BrooklynMementoPersister.java      |  19 +-
 .../brooklyn/basic/BrooklynObjectInternal.java  |   6 +-
 .../catalog/internal/CatalogClasspathDo.java    |   2 +-
 .../catalog/internal/CatalogItemDo.java         |  10 +-
 .../brooklyn/config/BrooklynProperties.java     |   4 +-
 .../brooklyn/config/BrooklynServerConfig.java   | 100 +++--
 .../brooklyn/config/BrooklynServerPaths.java    | 239 ++++++++++++
 .../entity/basic/BasicParameterType.java        |  22 +-
 .../java/brooklyn/entity/basic/Entities.java    |   8 +-
 .../basic/EntityTransientCopyInternal.java      |   2 +
 .../entity/rebind/BasicEntityRebindSupport.java |  15 +-
 .../rebind/BasicLocationRebindSupport.java      |   4 +-
 .../rebind/PeriodicDeltaChangeListener.java     | 252 ++++++-------
 .../rebind/PersistenceExceptionHandlerImpl.java |  10 +-
 .../entity/rebind/PersisterDeltaImpl.java       | 147 +++-----
 .../entity/rebind/RebindContextImpl.java        |  44 ++-
 .../rebind/RebindContextLookupContext.java      |   6 +-
 .../rebind/RebindExceptionHandlerImpl.java      |  90 ++++-
 .../entity/rebind/RebindManagerImpl.java        | 136 +++++--
 .../entity/rebind/dto/MementosGenerators.java   |   5 +
 .../AbstractBrooklynMementoPersister.java       |   8 +-
 .../BrooklynMementoPersisterInMemory.java       |   2 +-
 .../BrooklynMementoPersisterToMultiFile.java    |   6 +
 .../BrooklynMementoPersisterToObjectStore.java  |  40 +-
 .../persister/BrooklynPersistenceUtils.java     | 266 +++++++++++++
 .../rebind/persister/FileBasedObjectStore.java  |   8 +-
 .../persister/FileBasedStoreObjectAccessor.java |   1 -
 .../persister/LocationWithObjectStore.java      |  27 ++
 .../persister/PersistenceActivityMetrics.java   |  83 ++++
 .../LocalhostMachineProvisioningLocation.java   |  13 +-
 .../java/brooklyn/location/basic/Locations.java |  48 +++
 .../ha/HighAvailabilityManagerImpl.java         | 374 +++++++++++++------
 .../ha/ManagementPlaneSyncRecordDeltaImpl.java  |   1 -
 ...mentPlaneSyncRecordPersisterToMultiFile.java |   4 -
 ...ntPlaneSyncRecordPersisterToObjectStore.java |  23 +-
 .../internal/AbstractManagementContext.java     |  18 +-
 .../NonDeploymentManagementContext.java         |  23 +-
 .../java/brooklyn/util/flags/TypeCoercions.java |   8 +
 .../entity/rebind/RebindCatalogItemTest.java    |   4 +-
 .../entity/rebind/RebindManagerSorterTest.java  |   2 +-
 .../HighAvailabilityManagerSplitBrainTest.java  |  20 +-
 .../ha/HighAvailabilityManagerTestFixture.java  |  14 +-
 .../brooklyn/management/ha/HotStandbyTest.java  |   2 +-
 .../brooklyn/management/ha/WarmStandbyTest.java |   2 +-
 .../entity/LocalManagementContextForTests.java  |  32 +-
 docs/use/guide/persistence/index.md             |   8 +-
 .../JcloudsBlobStoreBasedObjectStore.java       |  61 ++-
 .../location/jclouds/JcloudsLocation.java       |  11 +-
 .../JcloudsObjectStoreAccessorWriterTest.java   |  81 +++-
 .../software/MachineLifecycleEffectorTasks.java |   3 +-
 usage/cli/src/main/java/brooklyn/cli/Main.java  |  29 +-
 .../brooklyn/launcher/BrooklynLauncher.java     | 169 +++------
 .../brooklyn/launcher/BrooklynWebServer.java    |  11 +-
 .../BrooklynLauncherRebindTestToFiles.java      |   6 +-
 ...lynLauncherRebindToCloudObjectStoreTest.java |   4 +-
 .../main/java/brooklyn/rest/api/ServerApi.java  |  40 +-
 .../brooklyn/rest/resources/ServerResource.java |  55 ++-
 .../brooklyn/util/collections/MutableList.java  |   7 +
 .../brooklyn/util/collections/QuorumCheck.java  | 142 ++++++-
 .../src/main/java/brooklyn/util/time/Time.java  |  25 +-
 .../util/collections/QuorumChecksTest.java      | 105 ++++++
 71 files changed, 2348 insertions(+), 783 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/36b2f683/locations/jclouds/src/main/java/brooklyn/location/jclouds/JcloudsLocation.java
----------------------------------------------------------------------


[07/21] incubator-brooklyn git commit: Add a HOT_BACKUP mode where nodes are read-only copies but NOT willing to be master.

Posted by he...@apache.org.
Add a HOT_BACKUP mode where nodes are read-only copies but NOT willing to be master.


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

Branch: refs/heads/master
Commit: 9dd1a95724a473b59a777fb6d627b6dde2fa0547
Parents: f39c6a3
Author: Alex Heneveld <al...@cloudsoftcorp.com>
Authored: Thu Nov 13 02:05:16 2014 +0000
Committer: Alex Heneveld <al...@cloudsoftcorp.com>
Committed: Thu Nov 13 23:38:53 2014 +0000

----------------------------------------------------------------------
 .../brooklyn/entity/rebind/RebindManager.java   |   6 +-
 .../management/ha/HighAvailabilityManager.java  |   2 +-
 .../management/ha/HighAvailabilityMode.java     |   6 +
 .../management/ha/ManagementNodeState.java      |  25 +++-
 .../entity/rebind/RebindManagerImpl.java        |  22 +--
 .../ha/HighAvailabilityManagerImpl.java         | 140 +++++++++++--------
 .../ha/ManagementPlaneSyncRecordDeltaImpl.java  |   1 -
 ...mentPlaneSyncRecordPersisterToMultiFile.java |   4 -
 .../NonDeploymentManagementContext.java         |   2 +-
 .../entity/rebind/RebindManagerSorterTest.java  |   2 +-
 usage/cli/src/main/java/brooklyn/cli/Main.java  |  18 ++-
 .../brooklyn/launcher/BrooklynLauncher.java     |  20 +--
 12 files changed, 154 insertions(+), 94 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/9dd1a957/api/src/main/java/brooklyn/entity/rebind/RebindManager.java
----------------------------------------------------------------------
diff --git a/api/src/main/java/brooklyn/entity/rebind/RebindManager.java b/api/src/main/java/brooklyn/entity/rebind/RebindManager.java
index e5001a6..fcd71bc 100644
--- a/api/src/main/java/brooklyn/entity/rebind/RebindManager.java
+++ b/api/src/main/java/brooklyn/entity/rebind/RebindManager.java
@@ -69,7 +69,7 @@ public interface RebindManager {
     /** Causes this management context to rebind, loading data from the given backing store.
      * use wisely, as this can cause local entities to be completely lost, or will throw in many other situations.
      * in general it may be invoked for a new node becoming {@link ManagementNodeState#MASTER} 
-     * or periodically for a node in {@link ManagementNodeState#HOT_STANDBY}. */
+     * or periodically for a node in {@link ManagementNodeState#HOT_STANDBY} or {@link ManagementNodeState#HOT_BACKUP}. */
     @Beta
     public List<Application> rebind(ClassLoader classLoader, RebindExceptionHandler exceptionHandler, ManagementNodeState mode);
 
@@ -92,13 +92,13 @@ public interface RebindManager {
      * Perform an initial load of state read-only and starts a background process 
      * reading (mirroring) state periodically.
      */
-    public void startReadOnly();
+    public void startReadOnly(ManagementNodeState mode);
     /** Stops the background reading (mirroring) of state. 
      * Interrupts any current activity and waits for it to cease. */
     public void stopReadOnly();
     
     /** Starts the appropriate background processes, {@link #startPersistence()} if {@link ManagementNodeState#MASTER},
-     * {@link #startReadOnly()} if {@link ManagementNodeState#HOT_STANDBY} */
+     * {@link #startReadOnly()} if {@link ManagementNodeState#HOT_STANDBY} or {@link ManagementNodeState#HOT_BACKUP} */
     public void start();
     /** Stops the appropriate background processes, {@link #stopPersistence()} or {@link #stopReadOnly()},
      * waiting for activity there to cease (interrupting in the case of {@link #stopReadOnly()}). */

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/9dd1a957/api/src/main/java/brooklyn/management/ha/HighAvailabilityManager.java
----------------------------------------------------------------------
diff --git a/api/src/main/java/brooklyn/management/ha/HighAvailabilityManager.java b/api/src/main/java/brooklyn/management/ha/HighAvailabilityManager.java
index c3671d7..de37123 100644
--- a/api/src/main/java/brooklyn/management/ha/HighAvailabilityManager.java
+++ b/api/src/main/java/brooklyn/management/ha/HighAvailabilityManager.java
@@ -72,7 +72,7 @@ public interface HighAvailabilityManager {
      * <p>
      * When this method returns, the status of this node will be set,
      * either {@link ManagementNodeState#MASTER} if appropriate 
-     * or {@link ManagementNodeState#STANDBY} / {@link ManagementNodeState#HOT_STANDBY}.
+     * or {@link ManagementNodeState#STANDBY} / {@link ManagementNodeState#HOT_STANDBY} / {@link ManagementNodeState#HOT_BACKUP}.
      *
      * @param startMode mode to start with
      * @throws IllegalStateException if current state of the management-plane doesn't match that desired by {@code startMode} 

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/9dd1a957/api/src/main/java/brooklyn/management/ha/HighAvailabilityMode.java
----------------------------------------------------------------------
diff --git a/api/src/main/java/brooklyn/management/ha/HighAvailabilityMode.java b/api/src/main/java/brooklyn/management/ha/HighAvailabilityMode.java
index 97fb5bc..2e593b1 100644
--- a/api/src/main/java/brooklyn/management/ha/HighAvailabilityMode.java
+++ b/api/src/main/java/brooklyn/management/ha/HighAvailabilityMode.java
@@ -52,6 +52,12 @@ public enum HighAvailabilityMode {
     HOT_STANDBY,
     
     /**
+     * Means node must be hot backup; do not attempt to become master (but it <i>can</i> start without a master).
+     * See {@link ManagementNodeState#HOT_BACKUP}. 
+     */
+    HOT_BACKUP,
+    
+    /**
      * Means node must be master; if there is already a master then fail fast on startup.
      * See {@link ManagementNodeState#MASTER}.
      */

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/9dd1a957/api/src/main/java/brooklyn/management/ha/ManagementNodeState.java
----------------------------------------------------------------------
diff --git a/api/src/main/java/brooklyn/management/ha/ManagementNodeState.java b/api/src/main/java/brooklyn/management/ha/ManagementNodeState.java
index c8a90f1..4587b57 100644
--- a/api/src/main/java/brooklyn/management/ha/ManagementNodeState.java
+++ b/api/src/main/java/brooklyn/management/ha/ManagementNodeState.java
@@ -18,6 +18,8 @@
  */
 package brooklyn.management.ha;
 
+import brooklyn.util.guava.Maybe;
+
 public enum ManagementNodeState {
     /** @deprecated since 0.7.0 synonym for maintenance (plus, it should have been US English!) */
     UNINITIALISED,
@@ -27,8 +29,10 @@ public enum ManagementNodeState {
     /** Node is in "lukewarm standby" mode, where it is available to be promoted to master,
      * but does not have entities loaded and will require some effort to be promoted */
     STANDBY,
-    /** Node is acting as read-only proxy */
+    /** Node is acting as read-only proxy available to be promoted to master on existing master failure */
     HOT_STANDBY,
+    /** Node is acting as a read-only proxy but not making itself available for promotion to master */
+    HOT_BACKUP,
     /** Node is running as primary/master, able to manage entities and create new ones */
     // the semantics are intended to support multi-master here; we could have multiple master nodes,
     // but we need to look up who is master for any given entity
@@ -38,4 +42,23 @@ public enum ManagementNodeState {
     FAILED,
     /** Node has gone away; maintenance not possible */
     TERMINATED;
+
+    /** Converts a {@link HighAvailabilityMode} to a {@link ManagementNodeState}, if possible */
+    public static Maybe<ManagementNodeState> of(HighAvailabilityMode startMode) {
+        switch (startMode) {
+        case AUTO:
+        case DISABLED:
+            return Maybe.absent("Requested "+HighAvailabilityMode.class+" mode "+startMode+" cannot be converted to "+ManagementNodeState.class);
+        case HOT_BACKUP:
+            return Maybe.of(HOT_BACKUP);
+        case HOT_STANDBY:
+            return Maybe.of(HOT_STANDBY);
+        case MASTER:
+            return Maybe.of(MASTER);
+        case STANDBY:
+            return Maybe.of(STANDBY);
+        }
+        // above should be exhaustive
+        return Maybe.absent("Requested "+HighAvailabilityMode.class+" mode "+startMode+" was not expected");
+    }
 }

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/9dd1a957/core/src/main/java/brooklyn/entity/rebind/RebindManagerImpl.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/entity/rebind/RebindManagerImpl.java b/core/src/main/java/brooklyn/entity/rebind/RebindManagerImpl.java
index ac9f726..4252a25 100644
--- a/core/src/main/java/brooklyn/entity/rebind/RebindManagerImpl.java
+++ b/core/src/main/java/brooklyn/entity/rebind/RebindManagerImpl.java
@@ -298,7 +298,11 @@ public class RebindManagerImpl implements RebindManager {
     
     @SuppressWarnings("unchecked")
     @Override
-    public void startReadOnly() {
+    public void startReadOnly(final ManagementNodeState mode) {
+        if (mode!=ManagementNodeState.HOT_STANDBY && mode!=ManagementNodeState.HOT_BACKUP) {
+            throw new IllegalStateException("Read-only rebind thread only permitted for hot proxy modes; not "+mode);
+        }
+        
         if (persistenceRunning) {
             throw new IllegalStateException("Cannot start read-only when already running with persistence");
         }
@@ -315,7 +319,7 @@ public class RebindManagerImpl implements RebindManager {
         readOnlyRebindCount = 0;
 
         try {
-            rebind(null, null, ManagementNodeState.HOT_STANDBY);
+            rebind(null, null, mode);
         } catch (Exception e) {
             Exceptions.propagate(e);
         }
@@ -325,7 +329,7 @@ public class RebindManagerImpl implements RebindManager {
                 return Tasks.<Void>builder().dynamic(false).name("rebind (periodic run").body(new Callable<Void>() {
                     public Void call() {
                         try {
-                            rebind(null, null, ManagementNodeState.HOT_STANDBY);
+                            rebind(null, null, mode);
                             readOnlyRebindCount++;
                             return null;
                         } catch (RuntimeInterruptedException e) {
@@ -376,8 +380,8 @@ public class RebindManagerImpl implements RebindManager {
     @Override
     public void start() {
         ManagementNodeState target = getRebindMode();
-        if (target==ManagementNodeState.HOT_STANDBY) {
-            startReadOnly();
+        if (target==ManagementNodeState.HOT_STANDBY || target==ManagementNodeState.HOT_BACKUP) {
+            startReadOnly(target);
         } else if (target==ManagementNodeState.MASTER) {
             startPersistence();
         } else {
@@ -466,8 +470,8 @@ public class RebindManagerImpl implements RebindManager {
                 .build();
         final ManagementNodeState mode = modeO!=null ? modeO : getRebindMode();
         
-        if (mode!=ManagementNodeState.HOT_STANDBY && mode!=ManagementNodeState.MASTER)
-            throw new IllegalStateException("Must be either master or read only to rebind (mode "+mode+")");
+        if (mode!=ManagementNodeState.MASTER && mode!=ManagementNodeState.HOT_STANDBY && mode!=ManagementNodeState.HOT_BACKUP)
+            throw new IllegalStateException("Must be either master or hot standby/backup to rebind (mode "+mode+")");
 
         ExecutionContext ec = BasicExecutionContext.getCurrentExecutionContext();
         if (ec == null) {
@@ -533,7 +537,7 @@ public class RebindManagerImpl implements RebindManager {
             
             exceptionHandler.onStart(rebindContext);
             
-            if (mode==ManagementNodeState.HOT_STANDBY) {
+            if (mode==ManagementNodeState.HOT_STANDBY || mode==ManagementNodeState.HOT_BACKUP) {
                 rebindContext.setAllReadOnly();
             } else {
                 Preconditions.checkState(mode==ManagementNodeState.MASTER, "Must be either master or read only to rebind (mode "+mode+")");
@@ -590,7 +594,7 @@ public class RebindManagerImpl implements RebindManager {
             BrooklynMementoManifest mementoManifest = persistenceStoreAccess.loadMementoManifest(mementoRawData, exceptionHandler);
 
             boolean isEmpty = mementoManifest.isEmpty();
-            if (mode!=ManagementNodeState.HOT_STANDBY) {
+            if (mode!=ManagementNodeState.HOT_STANDBY && mode!=ManagementNodeState.HOT_BACKUP) {
                 if (!isEmpty) { 
                     LOG.info("Rebinding from "+getPersister().getBackingStoreDescription()+"...");
                 } else {

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/9dd1a957/core/src/main/java/brooklyn/management/ha/HighAvailabilityManagerImpl.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/management/ha/HighAvailabilityManagerImpl.java b/core/src/main/java/brooklyn/management/ha/HighAvailabilityManagerImpl.java
index 329578e..62b06c0 100644
--- a/core/src/main/java/brooklyn/management/ha/HighAvailabilityManagerImpl.java
+++ b/core/src/main/java/brooklyn/management/ha/HighAvailabilityManagerImpl.java
@@ -98,20 +98,8 @@ import com.google.common.collect.Iterables;
 @Beta
 public class HighAvailabilityManagerImpl implements HighAvailabilityManager {
 
-    // TODO Improve mechanism for injecting configuration options (such as heartbeat and timeouts).
-    // For example, read from brooklyn.properties?
-    // But see BrooklynLauncher.haHeartbeatPeriod and .haHeartbeatTimeout, which are always injected.
-    // So perhaps best to read brooklyn.properties there? Would be nice to avoid the cast to 
-    // HighAvailabilityManagerImpl though.
-    
-    // TODO There is a race if you start multiple nodes simultaneously.
-    // They may not have seen each other's heartbeats yet, so will all claim mastery!
-    // But this should be resolved shortly afterwards.
-
-    // TODO Should we pass in a classloader on construction, so it can be passed to {@link RebindManager#rebind(ClassLoader)}
-
     public final ConfigKey<Duration> POLL_PERIOD = ConfigKeys.newConfigKey(Duration.class, "brooklyn.ha.pollPeriod",
-        "How often nodes should poll to detect whether master is healthy", Duration.seconds(5));
+        "How often nodes should poll to detect whether master is healthy", Duration.seconds(1));
     public final ConfigKey<Duration> HEARTBEAT_TIMEOUT = ConfigKeys.newConfigKey(Duration.class, "brooklyn.ha.heartbeatTimeout",
         "Maximum allowable time for detection of a peer's heartbeat; if no sign of master after this time, "
         + "another node may promote itself", Duration.THIRTY_SECONDS);
@@ -286,8 +274,10 @@ public class HighAvailabilityManagerImpl implements HighAvailabilityManager {
             case DISABLED:
                 // no action needed, will do anything necessary below
                 break;
-            case HOT_STANDBY: demoteToStandby(true); break;
-            case STANDBY: demoteToStandby(false); break;
+            case HOT_STANDBY: 
+            case HOT_BACKUP: 
+            case STANDBY: 
+                demoteTo(ManagementNodeState.of(startMode).get()); break;
             default:
                 throw new IllegalStateException("Unexpected high availability mode "+startMode+" requested for "+this);
             }
@@ -298,8 +288,14 @@ public class HighAvailabilityManagerImpl implements HighAvailabilityManager {
         case AUTO:
             // don't care; let's start and see if we promote ourselves
             publishAndCheck(true);
-            if (nodeState == ManagementNodeState.STANDBY || nodeState == ManagementNodeState.HOT_STANDBY) {
-                ManagementPlaneSyncRecord newState = loadManagementPlaneSyncRecord(true);;
+            switch (nodeState) {
+            case HOT_BACKUP:
+                if (!nodeStateTransitionComplete) throw new IllegalStateException("Cannot switch to AUTO when in the middle of a transition to "+nodeState);
+                // else change us to hot standby and continue to below
+                nodeState = ManagementNodeState.HOT_STANDBY;
+            case HOT_STANDBY:
+            case STANDBY:
+                ManagementPlaneSyncRecord newState = loadManagementPlaneSyncRecord(true);
                 String masterNodeId = newState.getMasterNodeId();
                 ManagementNodeSyncRecord masterNodeDetails = newState.getManagementNodes().get(masterNodeId);
                 LOG.info("Management node "+ownNodeId+" running as HA " + nodeState + " autodetected, " +
@@ -307,9 +303,11 @@ public class HighAvailabilityManagerImpl implements HighAvailabilityManager {
                         + (existingMaster==null ? "(new) " : "")
                         + "is "+masterNodeId +
                         (masterNodeDetails==null || masterNodeDetails.getUri()==null ? " (no url)" : " at "+masterNodeDetails.getUri())));
-            } else if (nodeState == ManagementNodeState.MASTER) {
+                break;
+            case MASTER:
                 LOG.info("Management node "+ownNodeId+" running as HA MASTER autodetected");
-            } else {
+                break;
+            default:
                 throw new IllegalStateException("Management node "+ownNodeId+" set to HA AUTO, encountered unexpected mode "+nodeState);
             }
             break;
@@ -332,7 +330,8 @@ public class HighAvailabilityManagerImpl implements HighAvailabilityManager {
                 LOG.error("Management node "+ownNodeId+" detected no master when "+startMode+" requested and existing master required; failing.");
                 throw new IllegalStateException("No existing master; cannot start as "+startMode);
             }
-            
+            // continue to below (above lines skipped for hot backup)
+        case HOT_BACKUP:
             String message = "Management node "+ownNodeId+" running as HA "+getNodeState()+" (";
             if (getNodeState().toString().equals(startMode.toString()))
                 message += "explicitly requested";
@@ -346,7 +345,8 @@ public class HighAvailabilityManagerImpl implements HighAvailabilityManager {
             } else {
                 ManagementPlaneSyncRecord newState = loadManagementPlaneSyncRecord(true);
                 if (Strings.isBlank(newState.getMasterNodeId())) {
-                    message += "); no master currently (subsequent election may repair)";
+                    message += "); no master currently"; 
+                    if (startMode != HighAvailabilityMode.HOT_BACKUP) message += " (subsequent election may repair)";
                 } else {
                     message += "); master "+newState.getMasterNodeId();
                 }
@@ -356,7 +356,7 @@ public class HighAvailabilityManagerImpl implements HighAvailabilityManager {
         case DISABLED:
             // safe just to run even if we weren't master
             LOG.info("Management node "+ownNodeId+" HA DISABLED (was "+nodeState+")");
-            demoteToFailed();
+            demoteTo(ManagementNodeState.FAILED);
             if (pollingTask!=null) pollingTask.cancel(true);
             break;
         default:
@@ -370,25 +370,28 @@ public class HighAvailabilityManagerImpl implements HighAvailabilityManager {
                 startMode = HighAvailabilityMode.STANDBY;
             }
         }
-        if (nodeState==ManagementNodeState.STANDBY && startMode==HighAvailabilityMode.HOT_STANDBY) {
-            // if it should be hot standby, then we need to promote
+        if ((nodeState==ManagementNodeState.STANDBY && startMode==HighAvailabilityMode.HOT_STANDBY) || 
+                (startMode==HighAvailabilityMode.HOT_BACKUP)) {
             nodeStateTransitionComplete = false;
-            // inform the world that we are transitioning (not eligible for promotion while going in to hot standby)
-            publishHealth();
+            if (startMode==HighAvailabilityMode.HOT_STANDBY) {
+                // if it should be hot standby, then we need to promote
+                // inform the world that we are transitioning (not eligible for promotion while going in to hot standby)
+                publishHealth();
+            }
             try {
-                attemptHotStandby();
+                attemptHotProxy(ManagementNodeState.of(startMode).get());
                 nodeStateTransitionComplete = true;
                 publishHealth();
                 
-                if (getNodeState()==ManagementNodeState.HOT_STANDBY) {
-                    LOG.info("Management node "+ownNodeId+" now running as HA "+ManagementNodeState.HOT_STANDBY+"; "
+                if (getNodeState()==ManagementNodeState.HOT_STANDBY || getNodeState()==ManagementNodeState.HOT_BACKUP) {
+                    LOG.info("Management node "+ownNodeId+" now running as HA "+getNodeState()+"; "
                         + managementContext.getApplications().size()+" application"+Strings.s(managementContext.getApplications().size())+" loaded");
                 } else {
-                    LOG.warn("Management node "+ownNodeId+" unable to promote to "+ManagementNodeState.HOT_STANDBY+" (currently "+getNodeState()+"); "
+                    LOG.warn("Management node "+ownNodeId+" unable to promote to "+startMode+" (currently "+getNodeState()+"); "
                         + "(see log for further details)");
                 }
             } catch (Exception e) {
-                LOG.warn("Management node "+ownNodeId+" unable to promote to "+ManagementNodeState.HOT_STANDBY+" (currently "+getNodeState()+"); rethrowing: "+Exceptions.collapseText(e));
+                LOG.warn("Management node "+ownNodeId+" unable to promote to "+startMode+" (currently "+getNodeState()+"); rethrowing: "+Exceptions.collapseText(e));
                 throw Exceptions.propagate(e);
             }
         } else {
@@ -594,9 +597,8 @@ public class HighAvailabilityManagerImpl implements HighAvailabilityManager {
     protected void checkMaster(boolean initializing) {
         ManagementPlaneSyncRecord memento = loadManagementPlaneSyncRecord(false);
         
-        if (getNodeState() == ManagementNodeState.FAILED) {
-            // if we have failed then no point in checking who is master
-            // (if somehow this node is subsequently clearFailure() then it will resume)
+        if (getNodeState()==ManagementNodeState.FAILED || getNodeState()==ManagementNodeState.HOT_BACKUP) {
+            // if failed or hot backup then we can't promote ourselves, so no point in checking who is master
             return;
         }
         
@@ -642,7 +644,9 @@ public class HighAvailabilityManagerImpl implements HighAvailabilityManager {
         
         if (demotingSelfInFavourOfOtherMaster) {
             LOG.debug("Master-change for this node only, demoting "+ownNodeRecord.toVerboseString()+" in favour of official master "+newMasterNodeRecord.toVerboseString());
-            demoteToStandby(BrooklynFeatureEnablement.isEnabled(BrooklynFeatureEnablement.FEATURE_DEFAULT_STANDBY_IS_HOT_PROPERTY));
+            demoteTo(
+                BrooklynFeatureEnablement.isEnabled(BrooklynFeatureEnablement.FEATURE_DEFAULT_STANDBY_IS_HOT_PROPERTY) ?
+                    ManagementNodeState.HOT_STANDBY : ManagementNodeState.STANDBY);
             return;
         } else {
             LOG.debug("Detected master heartbeat timeout. Initiating a new master election. Master was " + currMasterNodeRecord);
@@ -703,11 +707,11 @@ public class HighAvailabilityManagerImpl implements HighAvailabilityManager {
                 LOG.warn("Problem in promption-listener (continuing)", e);
             }
         }
-        boolean wasHotStandby = nodeState==ManagementNodeState.HOT_STANDBY;
+        boolean wasHot = (nodeState==ManagementNodeState.HOT_STANDBY || nodeState==ManagementNodeState.HOT_BACKUP);
         nodeState = ManagementNodeState.MASTER;
         publishPromotionToMaster();
         try {
-            if (wasHotStandby) {
+            if (wasHot) {
                 // could just promote the standby items; but for now we stop the old read-only and re-load them, to make sure nothing has been missed
                 // TODO ideally there'd be an incremental rebind as well as an incremental persist
                 managementContext.getRebindManager().stopReadOnly();
@@ -716,7 +720,7 @@ public class HighAvailabilityManagerImpl implements HighAvailabilityManager {
             managementContext.getRebindManager().rebind(managementContext.getCatalog().getRootClassLoader(), null, nodeState);
         } catch (Exception e) {
             LOG.error("Management node enountered problem during rebind when promoting self to master; demoting to FAILED and rethrowing: "+e);
-            demoteToFailed();
+            demoteTo(ManagementNodeState.FAILED);
             throw Exceptions.propagate(e);
         }
         managementContext.getRebindManager().start();
@@ -728,19 +732,17 @@ public class HighAvailabilityManagerImpl implements HighAvailabilityManager {
         }
     }
 
+    /** @deprecated since 0.7.0, use {@link #demoteTo(ManagementNodeState)} */ @Deprecated
     protected void demoteToFailed() {
-        // TODO merge this method with the one below
-        boolean wasMaster = nodeState == ManagementNodeState.MASTER;
-        if (wasMaster) backupOnDemotionIfNeeded();
-        ManagementTransitionMode mode = (wasMaster ? ManagementTransitionMode.REBINDING_NO_LONGER_PRIMARY : ManagementTransitionMode.REBINDING_DESTROYED);
-        nodeState = ManagementNodeState.FAILED;
-        onDemotionStopItems(mode);
-        nodeStateTransitionComplete = true;
-        publishDemotion(wasMaster);
+        demoteTo(ManagementNodeState.FAILED);
     }
-    
+    /** @deprecated since 0.7.0, use {@link #demoteTo(ManagementNodeState)} */ @Deprecated
     protected void demoteToStandby(boolean hot) {
-        if (!running) {
+        demoteTo(hot ? ManagementNodeState.HOT_STANDBY : ManagementNodeState.STANDBY);
+    }
+    
+    protected void demoteTo(ManagementNodeState toState) {
+        if (toState!=ManagementNodeState.FAILED && !running) {
             LOG.warn("Ignoring demote-from-master request, as HighAvailabilityManager is no longer running");
             return;
         }
@@ -749,14 +751,24 @@ public class HighAvailabilityManagerImpl implements HighAvailabilityManager {
         ManagementTransitionMode mode = (wasMaster ? ManagementTransitionMode.REBINDING_NO_LONGER_PRIMARY : ManagementTransitionMode.REBINDING_DESTROYED);
 
         nodeStateTransitionComplete = false;
-        nodeState = ManagementNodeState.STANDBY;
+        
+        switch (toState) {
+        case FAILED: 
+        case HOT_BACKUP:
+        case STANDBY:
+            nodeState = toState; break;
+        case HOT_STANDBY:
+            nodeState = ManagementNodeState.STANDBY; break;
+        default:
+            throw new IllegalStateException("Illegal target state: "+toState);
+        }
         onDemotionStopItems(mode);
         nodeStateTransitionComplete = true;
         publishDemotion(wasMaster);
         
-        if (hot) {
+        if (toState==ManagementNodeState.HOT_BACKUP || toState==ManagementNodeState.HOT_STANDBY) {
             nodeStateTransitionComplete = false;
-            attemptHotStandby();
+            attemptHotProxy(toState);
             nodeStateTransitionComplete = true;
             publishHealth();
         }
@@ -795,19 +807,29 @@ public class HighAvailabilityManagerImpl implements HighAvailabilityManager {
         ((BasicBrooklynCatalog)managementContext.getCatalog()).reset(CatalogDto.newEmptyInstance("<reset-by-ha-status-change>"));
     }
     
-    /** starts hot standby, in foreground; the caller is responsible for publishing health afterwards.
-     * @return whether hot standby was possible (if not, errors should be stored elsewhere) */
+    /** @deprecated since 0.7.0, use {@link #attemptHotProxy(ManagementNodeState)} */ @Deprecated
     protected boolean attemptHotStandby() {
+        return attemptHotProxy(ManagementNodeState.HOT_STANDBY);
+    }
+    
+    /** Starts hot standby or hot backup, in foreground
+     * <p>
+     * In the case of the former, the caller is responsible for publishing health afterwards,
+     * but if it fails, this method will {@link #demoteTo(ManagementNodeState)} {@link ManagementNodeState#FAILED}.
+     * <p>
+     * @return whether the requested {@link ManagementNodeState} was possible;
+     * (if not, errors should be stored elsewhere) */
+    protected boolean attemptHotProxy(ManagementNodeState toState) {
         try {
-            Preconditions.checkState(nodeStateTransitionComplete==false, "Must be in transitioning state to go into hot standby");
-            nodeState = ManagementNodeState.HOT_STANDBY;
-            managementContext.getRebindManager().startReadOnly();
+            Preconditions.checkState(nodeStateTransitionComplete==false, "Must be in transitioning state to go into "+toState);
+            nodeState = toState;
+            managementContext.getRebindManager().startReadOnly(toState);
             
             return true;
         } catch (Exception e) {
             Exceptions.propagateIfFatal(e);
-            LOG.warn("Unable to promote "+ownNodeId+" to hot standby, switching to FAILED: "+e, e);
-            demoteToFailed();
+            LOG.warn("Unable to promote "+ownNodeId+" to "+toState+", switching to FAILED: "+e, e);
+            demoteTo(ManagementNodeState.FAILED);
             return false;
         }
     }
@@ -909,7 +931,7 @@ public class HighAvailabilityManagerImpl implements HighAvailabilityManager {
         @Override
         public ManagementNodeSyncRecord apply(@Nullable ManagementNodeSyncRecord input) {
             if (input == null) return null;
-            if (!(input.getStatus() == ManagementNodeState.STANDBY || input.getStatus() == ManagementNodeState.HOT_STANDBY || input.getStatus() == ManagementNodeState.MASTER)) return input;
+            if (!(input.getStatus() == ManagementNodeState.STANDBY || input.getStatus() == ManagementNodeState.HOT_STANDBY || input.getStatus() == ManagementNodeState.MASTER || input.getStatus() == ManagementNodeState.HOT_BACKUP)) return input;
             if (isHeartbeatOk(input, referenceNode)) return input;
             return BasicManagementNodeSyncRecord.builder()
                     .from(input)

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/9dd1a957/core/src/main/java/brooklyn/management/ha/ManagementPlaneSyncRecordDeltaImpl.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/management/ha/ManagementPlaneSyncRecordDeltaImpl.java b/core/src/main/java/brooklyn/management/ha/ManagementPlaneSyncRecordDeltaImpl.java
index 6b026bc..5e07b19 100644
--- a/core/src/main/java/brooklyn/management/ha/ManagementPlaneSyncRecordDeltaImpl.java
+++ b/core/src/main/java/brooklyn/management/ha/ManagementPlaneSyncRecordDeltaImpl.java
@@ -25,7 +25,6 @@ import java.util.Collection;
 
 import brooklyn.management.ha.ManagementPlaneSyncRecordPersister.Delta;
 
-import com.google.api.client.repackaged.com.google.common.base.Objects;
 import com.google.common.annotations.Beta;
 import com.google.common.collect.Sets;
 

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/9dd1a957/core/src/main/java/brooklyn/management/ha/ManagementPlaneSyncRecordPersisterToMultiFile.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/management/ha/ManagementPlaneSyncRecordPersisterToMultiFile.java b/core/src/main/java/brooklyn/management/ha/ManagementPlaneSyncRecordPersisterToMultiFile.java
index d3fa34a..fcade26 100644
--- a/core/src/main/java/brooklyn/management/ha/ManagementPlaneSyncRecordPersisterToMultiFile.java
+++ b/core/src/main/java/brooklyn/management/ha/ManagementPlaneSyncRecordPersisterToMultiFile.java
@@ -274,10 +274,6 @@ public class ManagementPlaneSyncRecordPersisterToMultiFile implements Management
         return new File(dir, "master");
     }
 
-    private File getFileForPlaneId() {
-        return new File(dir, "plane.id");
-    }
-
     private File getFileForChangeLog() {
         return new File(dir, "change.log");
     }

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/9dd1a957/core/src/main/java/brooklyn/management/internal/NonDeploymentManagementContext.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/management/internal/NonDeploymentManagementContext.java b/core/src/main/java/brooklyn/management/internal/NonDeploymentManagementContext.java
index 3d0e4d5..2771475 100644
--- a/core/src/main/java/brooklyn/management/internal/NonDeploymentManagementContext.java
+++ b/core/src/main/java/brooklyn/management/internal/NonDeploymentManagementContext.java
@@ -491,7 +491,7 @@ public class NonDeploymentManagementContext implements ManagementContextInternal
         }
 
         @Override
-        public void startReadOnly() {
+        public void startReadOnly(ManagementNodeState state) {
             throw new IllegalStateException("Non-deployment context "+NonDeploymentManagementContext.this+" is not valid for this operation.");
         }
         

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/9dd1a957/core/src/test/java/brooklyn/entity/rebind/RebindManagerSorterTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/brooklyn/entity/rebind/RebindManagerSorterTest.java b/core/src/test/java/brooklyn/entity/rebind/RebindManagerSorterTest.java
index 991a000..f986c7e 100644
--- a/core/src/test/java/brooklyn/entity/rebind/RebindManagerSorterTest.java
+++ b/core/src/test/java/brooklyn/entity/rebind/RebindManagerSorterTest.java
@@ -142,7 +142,7 @@ public class RebindManagerSorterTest {
     private Map<String, EntityMemento> toMementos(Iterable<? extends Entity> entities) {
         Map<String, EntityMemento> result = Maps.newLinkedHashMap();
         for (Entity entity : entities) {
-            result.put(entity.getId(), MementosGenerators.newEntityMemento(entity));
+            result.put(entity.getId(), MementosGenerators.newEntityMemento(Entities.deproxy(entity)));
         }
         return result;
     }

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/9dd1a957/usage/cli/src/main/java/brooklyn/cli/Main.java
----------------------------------------------------------------------
diff --git a/usage/cli/src/main/java/brooklyn/cli/Main.java b/usage/cli/src/main/java/brooklyn/cli/Main.java
index b1b933c..3eb7fc0 100644
--- a/usage/cli/src/main/java/brooklyn/cli/Main.java
+++ b/usage/cli/src/main/java/brooklyn/cli/Main.java
@@ -313,17 +313,22 @@ public class Main extends AbstractMain {
         protected final static String HA_OPTION_MASTER = "master";
         protected final static String HA_OPTION_STANDBY = "standby";
         protected final static String HA_OPTION_HOT_STANDBY = "hot_standby";
-        static { Enums.checkAllEnumeratedIgnoreCase(HighAvailabilityMode.class, HA_OPTION_AUTO, HA_OPTION_DISABLED, HA_OPTION_MASTER, HA_OPTION_STANDBY, HA_OPTION_HOT_STANDBY); }
+        protected final static String HA_OPTION_HOT_BACKUP = "hot_backup";
+        static { Enums.checkAllEnumeratedIgnoreCase(HighAvailabilityMode.class, HA_OPTION_AUTO, HA_OPTION_DISABLED, HA_OPTION_MASTER, HA_OPTION_STANDBY, HA_OPTION_HOT_STANDBY, HA_OPTION_HOT_BACKUP); }
         
-        @Option(name = { HA_OPTION }, allowedValues = { HA_OPTION_DISABLED, HA_OPTION_AUTO, HA_OPTION_MASTER, HA_OPTION_STANDBY, HA_OPTION_HOT_STANDBY },
+        @Option(name = { HA_OPTION }, allowedValues = { HA_OPTION_DISABLED, HA_OPTION_AUTO, HA_OPTION_MASTER, HA_OPTION_STANDBY, HA_OPTION_HOT_STANDBY, HA_OPTION_HOT_BACKUP },
                 title = "high availability mode",
                 description =
                         "The high availability mode. Possible values are: \n"+
                         "disabled: management node works in isolation - will not cooperate with any other standby/master nodes in management plane; \n"+
                         "auto: will look for other management nodes, and will allocate itself as standby or master based on other nodes' states; \n"+
                         "master: will startup as master - if there is already a master then fails immediately; \n"+
-                        "standby: will start up as lukewarm standby - if there is not already a master then fails immediately; \n"+
-                        "hot_standby: will start up as hot standby - if there is not already a master then fails immediately")
+                        "standby: will start up as lukewarm standby with no state - if there is not already a master then fails immediately, "
+                        + "and if there is a master which subsequently fails, this node can promote itself; \n"+
+                        "hot_standby: will start up as hot standby in read-only mode - if there is not already a master then fails immediately, "
+                        + "and if there is a master which subseuqently fails, this node can promote itself; \n"+
+                        "hot_backup: will start up as hot backup in read-only mode - no master is required, and this node will not become a master"
+                        )
         public String highAvailability = HA_OPTION_AUTO;
 
         @VisibleForTesting
@@ -472,7 +477,10 @@ public class Main extends AbstractMain {
                     if (highAvailabilityMode.get() == HighAvailabilityMode.AUTO)
                         return HighAvailabilityMode.DISABLED;
                     throw new FatalConfigurationRuntimeException("Cannot specify highAvailability when persistence is disabled");
-                } else if (persistMode == PersistMode.CLEAN && (highAvailabilityMode.get() == HighAvailabilityMode.STANDBY || highAvailabilityMode.get() == HighAvailabilityMode.HOT_STANDBY)) {
+                } else if (persistMode == PersistMode.CLEAN && 
+                        (highAvailabilityMode.get() == HighAvailabilityMode.STANDBY 
+                        || highAvailabilityMode.get() == HighAvailabilityMode.HOT_STANDBY
+                        || highAvailabilityMode.get() == HighAvailabilityMode.HOT_BACKUP)) {
                     throw new FatalConfigurationRuntimeException("Cannot specify highAvailability "+highAvailabilityMode.get()+" when persistence is CLEAN");
                 }
             }

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/9dd1a957/usage/launcher/src/main/java/brooklyn/launcher/BrooklynLauncher.java
----------------------------------------------------------------------
diff --git a/usage/launcher/src/main/java/brooklyn/launcher/BrooklynLauncher.java b/usage/launcher/src/main/java/brooklyn/launcher/BrooklynLauncher.java
index 00162a8..976db4c 100644
--- a/usage/launcher/src/main/java/brooklyn/launcher/BrooklynLauncher.java
+++ b/usage/launcher/src/main/java/brooklyn/launcher/BrooklynLauncher.java
@@ -160,8 +160,9 @@ public class BrooklynLauncher {
     private String persistenceDir;
     private String persistenceLocation;
     private Duration persistPeriod = Duration.ONE_SECOND;
-    private Duration haHeartbeatTimeout = Duration.THIRTY_SECONDS;
-    private Duration haHeartbeatPeriod = Duration.ONE_SECOND;
+    // these default values come from config in HighAvailablilityManagerImpl
+    private Duration haHeartbeatTimeoutOverride = null;
+    private Duration haHeartbeatPeriodOverride = null;
     
     private volatile BrooklynWebServer webServer;
     private CampPlatform campPlatform;
@@ -433,7 +434,7 @@ public class BrooklynLauncher {
     }
 
     public BrooklynLauncher haHeartbeatTimeout(Duration val) {
-        this.haHeartbeatTimeout = val;
+        this.haHeartbeatTimeoutOverride = val;
         return this;
     }
 
@@ -446,7 +447,7 @@ public class BrooklynLauncher {
      * Controls both the frequency of heartbeats, and the frequency of checking the health of other nodes.
      */
     public BrooklynLauncher haHeartbeatPeriod(Duration val) {
-        this.haHeartbeatPeriod = val;
+        this.haHeartbeatPeriodOverride = val;
         return this;
     }
 
@@ -770,8 +771,8 @@ public class BrooklynLauncher {
             ManagementPlaneSyncRecordPersister persister =
                 new ManagementPlaneSyncRecordPersisterToObjectStore(managementContext,
                     objectStore, managementContext.getCatalog().getRootClassLoader());
-            ((HighAvailabilityManagerImpl)haManager).setHeartbeatTimeout(haHeartbeatTimeout);
-            ((HighAvailabilityManagerImpl)haManager).setPollPeriod(haHeartbeatPeriod);
+            ((HighAvailabilityManagerImpl)haManager).setHeartbeatTimeout(haHeartbeatTimeoutOverride);
+            ((HighAvailabilityManagerImpl)haManager).setPollPeriod(haHeartbeatPeriodOverride);
             haManager.setPersister(persister);
         }
     }
@@ -790,19 +791,20 @@ public class BrooklynLauncher {
             // 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;
+            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);
-                default:       
-                    throw new IllegalStateException("Unexpected 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();


[02/21] incubator-brooklyn git commit: clean up copyPersistedState code

Posted by he...@apache.org.
clean up copyPersistedState code

move shared code to new `BrooklynPersistenceUtils`,
make a new `LocationWithPersistenceStore.newPersistenceObjectStore` interface,
and add documentation for `copy-state` command


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

Branch: refs/heads/master
Commit: 0773c64fabe89e8af0a48d570a461d2e30ce133e
Parents: 442a769
Author: Alex Heneveld <al...@cloudsoftcorp.com>
Authored: Fri Nov 7 08:38:24 2014 +0000
Committer: Alex Heneveld <al...@cloudsoftcorp.com>
Committed: Thu Nov 13 23:38:51 2014 +0000

----------------------------------------------------------------------
 .../brooklyn/entity/rebind/RebindManager.java   |   2 +-
 .../brooklyn/config/BrooklynServerConfig.java   |   2 +-
 .../entity/rebind/RebindManagerImpl.java        |   5 +-
 .../persister/BrooklynPersistenceUtils.java     | 103 +++++++++++++
 .../persister/LocationWithObjectStore.java      |  27 ++++
 .../LocalhostMachineProvisioningLocation.java   |  13 +-
 docs/use/guide/persistence/index.md             |   8 +-
 .../location/jclouds/JcloudsLocation.java       |  11 +-
 usage/cli/src/main/java/brooklyn/cli/Main.java  |  11 +-
 .../brooklyn/launcher/BrooklynLauncher.java     | 148 ++++++-------------
 10 files changed, 211 insertions(+), 119 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/0773c64f/api/src/main/java/brooklyn/entity/rebind/RebindManager.java
----------------------------------------------------------------------
diff --git a/api/src/main/java/brooklyn/entity/rebind/RebindManager.java b/api/src/main/java/brooklyn/entity/rebind/RebindManager.java
index 5f9ccf8..8995aae 100644
--- a/api/src/main/java/brooklyn/entity/rebind/RebindManager.java
+++ b/api/src/main/java/brooklyn/entity/rebind/RebindManager.java
@@ -72,7 +72,7 @@ public interface RebindManager {
     @Beta
     public List<Application> rebind(ClassLoader classLoader, RebindExceptionHandler exceptionHandler, ManagementNodeState mode);
 
-    public BrooklynMementoRawData retrieveMementoRawData() throws IOException;
+    public BrooklynMementoRawData retrieveMementoRawData();
 
     public ChangeListener getChangeListener();
 

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/0773c64f/core/src/main/java/brooklyn/config/BrooklynServerConfig.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/config/BrooklynServerConfig.java b/core/src/main/java/brooklyn/config/BrooklynServerConfig.java
index ac4a380..a5be8ed 100644
--- a/core/src/main/java/brooklyn/config/BrooklynServerConfig.java
+++ b/core/src/main/java/brooklyn/config/BrooklynServerConfig.java
@@ -152,7 +152,7 @@ public class BrooklynServerConfig {
     public static String resolvePersistencePath(String optionalSuppliedValue, StringConfigMap brooklynProperties, String optionalObjectStoreLocationSpec) {
         String path = optionalSuppliedValue;
         if (path==null) path = brooklynProperties.getConfig(PERSISTENCE_DIR);
-        if (optionalObjectStoreLocationSpec==null) {
+        if (optionalObjectStoreLocationSpec==null || "localhost".equals(optionalObjectStoreLocationSpec)) {
             // file system
             if (path==null) path=DEFAULT_PERSISTENCE_DIR_FOR_FILESYSTEM;
             return resolveAgainstBaseDir(brooklynProperties, path);

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/0773c64f/core/src/main/java/brooklyn/entity/rebind/RebindManagerImpl.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/entity/rebind/RebindManagerImpl.java b/core/src/main/java/brooklyn/entity/rebind/RebindManagerImpl.java
index 5ad0f7d..9c85e66 100644
--- a/core/src/main/java/brooklyn/entity/rebind/RebindManagerImpl.java
+++ b/core/src/main/java/brooklyn/entity/rebind/RebindManagerImpl.java
@@ -20,7 +20,6 @@ package brooklyn.entity.rebind;
 
 import static com.google.common.base.Preconditions.checkNotNull;
 
-import java.io.IOException;
 import java.util.Collection;
 import java.util.List;
 import java.util.Map;
@@ -454,7 +453,7 @@ public class RebindManagerImpl implements RebindManager {
     }
     
     @Override
-    public BrooklynMementoRawData retrieveMementoRawData() throws IOException {
+    public BrooklynMementoRawData retrieveMementoRawData() {
         RebindExceptionHandler exceptionHandler = RebindExceptionHandlerImpl.builder()
                 .danglingRefFailureMode(danglingRefFailureMode)
                 .rebindFailureMode(rebindFailureMode)
@@ -470,7 +469,7 @@ public class RebindManagerImpl implements RebindManager {
      * 
      * In so doing, it instantiates the entities + locations, registering them with the rebindContext.
      */
-    protected BrooklynMementoRawData loadMementoRawData(final RebindExceptionHandler exceptionHandler) throws IOException {
+    protected BrooklynMementoRawData loadMementoRawData(final RebindExceptionHandler exceptionHandler) {
         try {
             if (!(persistenceStoreAccess instanceof BrooklynMementoPersisterToObjectStore)) {
                 throw new IllegalStateException("Cannot load raw memento with persister "+persistenceStoreAccess);

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/0773c64f/core/src/main/java/brooklyn/entity/rebind/persister/BrooklynPersistenceUtils.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/entity/rebind/persister/BrooklynPersistenceUtils.java b/core/src/main/java/brooklyn/entity/rebind/persister/BrooklynPersistenceUtils.java
new file mode 100644
index 0000000..1308d62
--- /dev/null
+++ b/core/src/main/java/brooklyn/entity/rebind/persister/BrooklynPersistenceUtils.java
@@ -0,0 +1,103 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package brooklyn.entity.rebind.persister;
+
+import brooklyn.config.BrooklynServerConfig;
+import brooklyn.entity.rebind.PersistenceExceptionHandler;
+import brooklyn.entity.rebind.PersistenceExceptionHandlerImpl;
+import brooklyn.entity.rebind.transformer.CompoundTransformer;
+import brooklyn.entity.rebind.transformer.CompoundTransformerLoader;
+import brooklyn.location.Location;
+import brooklyn.location.LocationSpec;
+import brooklyn.location.basic.LocalhostMachineProvisioningLocation;
+import brooklyn.management.ManagementContext;
+import brooklyn.management.ha.HighAvailabilityMode;
+import brooklyn.management.ha.ManagementPlaneSyncRecord;
+import brooklyn.management.ha.ManagementPlaneSyncRecordPersisterToObjectStore;
+import brooklyn.management.internal.ManagementContextInternal;
+import brooklyn.mementos.BrooklynMementoRawData;
+import brooklyn.util.ResourceUtils;
+import brooklyn.util.text.Strings;
+
+public class BrooklynPersistenceUtils {
+
+    /** Creates a {@link PersistenceObjectStore} for general-purpose use. */
+    public static PersistenceObjectStore newPersistenceObjectStore(ManagementContext managementContext,
+            String locationSpec, String locationContainer) {
+        
+        return newPersistenceObjectStore(managementContext, locationSpec, locationContainer,
+            PersistMode.AUTO, HighAvailabilityMode.STANDBY);
+    }
+    
+    /** Creates a {@link PersistenceObjectStore} for use with a specified set of modes. */
+    public static PersistenceObjectStore newPersistenceObjectStore(ManagementContext managementContext,
+            String locationSpec, String locationContainer, PersistMode persistMode, HighAvailabilityMode highAvailabilityMode) {
+        PersistenceObjectStore destinationObjectStore;
+        locationContainer = BrooklynServerConfig.resolvePersistencePath(locationContainer, managementContext.getConfig(), locationSpec);
+
+        Location location = null;
+        try {
+            if (Strings.isBlank(locationSpec)) {
+                location = managementContext.getLocationManager().createLocation(LocationSpec.create(LocalhostMachineProvisioningLocation.class));
+            } else {
+                location = managementContext.getLocationRegistry().resolve(locationSpec);
+                if (!(location instanceof LocationWithObjectStore)) {
+                    throw new IllegalArgumentException("Destination location "+location+" does not offer a persistent store");
+                }
+            }
+        } finally {
+            if (location!=null) managementContext.getLocationManager().unmanage(location);
+        }
+        destinationObjectStore = ((LocationWithObjectStore)location).newPersistenceObjectStore(locationContainer);
+        
+        destinationObjectStore.injectManagementContext(managementContext);
+        destinationObjectStore.prepareForSharedUse(persistMode, highAvailabilityMode);
+        return destinationObjectStore;
+    }
+
+    public static void writeMemento(ManagementContext managementContext, BrooklynMementoRawData memento,
+            PersistenceObjectStore destinationObjectStore) {
+        BrooklynMementoPersisterToObjectStore persister = new BrooklynMementoPersisterToObjectStore(
+            destinationObjectStore,
+            ((ManagementContextInternal)managementContext).getBrooklynProperties(),
+            managementContext.getCatalog().getRootClassLoader());
+        PersistenceExceptionHandler exceptionHandler = PersistenceExceptionHandlerImpl.builder().build();
+        persister.enableWriteAccess();
+        persister.checkpoint(memento, exceptionHandler);
+    }
+
+    public static void writeManagerMemento(ManagementContext managementContext, ManagementPlaneSyncRecord optionalPlaneRecord,
+            PersistenceObjectStore destinationObjectStore) {
+        if (optionalPlaneRecord != null) {
+            ManagementPlaneSyncRecordPersisterToObjectStore managementPersister = new ManagementPlaneSyncRecordPersisterToObjectStore(
+                    managementContext, destinationObjectStore, managementContext.getCatalog().getRootClassLoader());
+            managementPersister.checkpoint(optionalPlaneRecord);
+        }
+    }
+
+    public static CompoundTransformer loadTransformer(ResourceUtils resources, String transformationsFileUrl) {
+        if (Strings.isBlank(transformationsFileUrl)) {
+            return CompoundTransformer.NOOP; 
+        } else {
+            String contents = resources.getResourceAsString(transformationsFileUrl);
+            return CompoundTransformerLoader.load(contents);
+        }
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/0773c64f/core/src/main/java/brooklyn/entity/rebind/persister/LocationWithObjectStore.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/entity/rebind/persister/LocationWithObjectStore.java b/core/src/main/java/brooklyn/entity/rebind/persister/LocationWithObjectStore.java
new file mode 100644
index 0000000..81af047
--- /dev/null
+++ b/core/src/main/java/brooklyn/entity/rebind/persister/LocationWithObjectStore.java
@@ -0,0 +1,27 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package brooklyn.entity.rebind.persister;
+
+/** Marker interface for locations which can create a {@link PersistenceObjectStore} */
+public interface LocationWithObjectStore {
+
+    /** Creates a {@link PersistenceObjectStore} pointed at the given container/directory. */
+    public PersistenceObjectStore newPersistenceObjectStore(String container);
+    
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/0773c64f/core/src/main/java/brooklyn/location/basic/LocalhostMachineProvisioningLocation.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/location/basic/LocalhostMachineProvisioningLocation.java b/core/src/main/java/brooklyn/location/basic/LocalhostMachineProvisioningLocation.java
index 113fb67..324f5a0 100644
--- a/core/src/main/java/brooklyn/location/basic/LocalhostMachineProvisioningLocation.java
+++ b/core/src/main/java/brooklyn/location/basic/LocalhostMachineProvisioningLocation.java
@@ -21,6 +21,7 @@ package brooklyn.location.basic;
 import static brooklyn.util.GroovyJavaMethods.elvis;
 import static brooklyn.util.GroovyJavaMethods.truth;
 
+import java.io.File;
 import java.net.InetAddress;
 import java.util.Arrays;
 import java.util.Map;
@@ -33,6 +34,9 @@ import brooklyn.config.ConfigKey;
 import brooklyn.config.ConfigKey.HasConfigKey;
 import brooklyn.entity.basic.BrooklynConfigKeys;
 import brooklyn.entity.basic.ConfigKeys;
+import brooklyn.entity.rebind.persister.FileBasedObjectStore;
+import brooklyn.entity.rebind.persister.LocationWithObjectStore;
+import brooklyn.entity.rebind.persister.PersistenceObjectStore;
 import brooklyn.location.AddressableLocation;
 import brooklyn.location.LocationSpec;
 import brooklyn.location.OsDetails;
@@ -61,7 +65,7 @@ import com.google.common.collect.Sets;
  * By default you can only obtain a single SshMachineLocation for the localhost. Optionally, you can "overload"
  * and choose to allow localhost to be provisioned multiple times, which may be useful in some testing scenarios.
  */
-public class LocalhostMachineProvisioningLocation extends FixedListMachineProvisioningLocation<SshMachineLocation> implements AddressableLocation {
+public class LocalhostMachineProvisioningLocation extends FixedListMachineProvisioningLocation<SshMachineLocation> implements AddressableLocation, LocationWithObjectStore {
 
     public static final Logger LOG = LoggerFactory.getLogger(LocalhostMachineProvisioningLocation.class);
     
@@ -324,4 +328,11 @@ public class LocalhostMachineProvisioningLocation extends FixedListMachineProvis
         return SudoChecker.isSudoAllowed();
     }
 
+    @Override
+    public PersistenceObjectStore newPersistenceObjectStore(String container) {
+        File basedir = new File(container);
+        if (basedir.isFile()) throw new IllegalArgumentException("Destination directory must not be a file");
+        return new FileBasedObjectStore(basedir);
+    }
+    
 }

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/0773c64f/docs/use/guide/persistence/index.md
----------------------------------------------------------------------
diff --git a/docs/use/guide/persistence/index.md b/docs/use/guide/persistence/index.md
index 22ba5ce..a59a042 100644
--- a/docs/use/guide/persistence/index.md
+++ b/docs/use/guide/persistence/index.md
@@ -16,7 +16,7 @@ of your choice.
 Command Line Options
 --------------------
 
-To configure brooklyn, the relevant command line options are:
+To configure brooklyn, the relevant command line options for the `launch` commands are:
 
 * `--persist` <persistence mode>
   The persistence mode.
@@ -34,6 +34,12 @@ For the persistence mode, the possible values are:
 * `auto` means Brooklyn will rebind if there is any existing state, or will start afresh if 
   there is no state.
 
+The persistence directory and location can instead be specified from `brooklyn.properties` using
+the following config keys:
+
+* `brooklyn.persistence.dir`
+* `brooklyn.persistence.location.spec`
+
 
 <a name="file-based-persistence"></a>
 File-based Persistence

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/0773c64f/locations/jclouds/src/main/java/brooklyn/location/jclouds/JcloudsLocation.java
----------------------------------------------------------------------
diff --git a/locations/jclouds/src/main/java/brooklyn/location/jclouds/JcloudsLocation.java b/locations/jclouds/src/main/java/brooklyn/location/jclouds/JcloudsLocation.java
index e77fb81..2a86361 100644
--- a/locations/jclouds/src/main/java/brooklyn/location/jclouds/JcloudsLocation.java
+++ b/locations/jclouds/src/main/java/brooklyn/location/jclouds/JcloudsLocation.java
@@ -87,6 +87,9 @@ import brooklyn.config.ConfigKey;
 import brooklyn.config.ConfigKey.HasConfigKey;
 import brooklyn.config.ConfigUtils;
 import brooklyn.entity.basic.Entities;
+import brooklyn.entity.rebind.persister.LocationWithObjectStore;
+import brooklyn.entity.rebind.persister.PersistenceObjectStore;
+import brooklyn.entity.rebind.persister.jclouds.JcloudsBlobStoreBasedObjectStore;
 import brooklyn.location.LocationSpec;
 import brooklyn.location.MachineLocation;
 import brooklyn.location.MachineManagementMixins.MachineMetadata;
@@ -166,7 +169,7 @@ import com.google.common.primitives.Ints;
  * Configuration flags are defined in {@link JcloudsLocationConfig}.
  */
 @SuppressWarnings("serial")
-public class JcloudsLocation extends AbstractCloudMachineProvisioningLocation implements JcloudsLocationConfig, RichMachineProvisioningLocation<SshMachineLocation> {
+public class JcloudsLocation extends AbstractCloudMachineProvisioningLocation implements JcloudsLocationConfig, RichMachineProvisioningLocation<SshMachineLocation>, LocationWithObjectStore {
 
     // TODO After converting from Groovy to Java, this is now very bad code! It relies entirely on putting 
     // things into and taking them out of maps; it's not type-safe, and it's thus very error-prone.
@@ -2040,4 +2043,10 @@ public class JcloudsLocation extends AbstractCloudMachineProvisioningLocation im
        }
        return iptablesRules;
     }
+    
+    @Override
+    public PersistenceObjectStore newPersistenceObjectStore(String container) {
+        return new JcloudsBlobStoreBasedObjectStore(this, container);
+    }
+    
 }

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/0773c64f/usage/cli/src/main/java/brooklyn/cli/Main.java
----------------------------------------------------------------------
diff --git a/usage/cli/src/main/java/brooklyn/cli/Main.java b/usage/cli/src/main/java/brooklyn/cli/Main.java
index 00bf407..b1b933c 100644
--- a/usage/cli/src/main/java/brooklyn/cli/Main.java
+++ b/usage/cli/src/main/java/brooklyn/cli/Main.java
@@ -55,9 +55,9 @@ import brooklyn.entity.basic.ApplicationBuilder;
 import brooklyn.entity.basic.Entities;
 import brooklyn.entity.basic.StartableApplication;
 import brooklyn.entity.proxying.EntitySpec;
+import brooklyn.entity.rebind.persister.BrooklynPersistenceUtils;
 import brooklyn.entity.rebind.persister.PersistMode;
 import brooklyn.entity.rebind.transformer.CompoundTransformer;
-import brooklyn.entity.rebind.transformer.CompoundTransformerLoader;
 import brooklyn.entity.trait.Startable;
 import brooklyn.launcher.BrooklynLauncher;
 import brooklyn.launcher.BrooklynServerDetails;
@@ -784,13 +784,8 @@ public class Main extends AbstractMain {
             return null;
         }
 
-        protected CompoundTransformer loadTransformer(String transformations) {
-            if (transformations == null) {
-                return CompoundTransformer.NOOP; 
-            } else {
-                String contents = ResourceUtils.create(this).getResourceAsString(transformations);
-                return CompoundTransformerLoader.load(contents);
-            }
+        protected CompoundTransformer loadTransformer(String transformationsFileUrl) {
+            return BrooklynPersistenceUtils.loadTransformer(ResourceUtils.create(this), transformationsFileUrl);
         }
         
         @Override

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/0773c64f/usage/launcher/src/main/java/brooklyn/launcher/BrooklynLauncher.java
----------------------------------------------------------------------
diff --git a/usage/launcher/src/main/java/brooklyn/launcher/BrooklynLauncher.java b/usage/launcher/src/main/java/brooklyn/launcher/BrooklynLauncher.java
index d9cde0c..f28b15f 100644
--- a/usage/launcher/src/main/java/brooklyn/launcher/BrooklynLauncher.java
+++ b/usage/launcher/src/main/java/brooklyn/launcher/BrooklynLauncher.java
@@ -61,10 +61,9 @@ import brooklyn.entity.rebind.PersistenceExceptionHandlerImpl;
 import brooklyn.entity.rebind.RebindManager;
 import brooklyn.entity.rebind.RebindManagerImpl;
 import brooklyn.entity.rebind.persister.BrooklynMementoPersisterToObjectStore;
-import brooklyn.entity.rebind.persister.FileBasedObjectStore;
+import brooklyn.entity.rebind.persister.BrooklynPersistenceUtils;
 import brooklyn.entity.rebind.persister.PersistMode;
 import brooklyn.entity.rebind.persister.PersistenceObjectStore;
-import brooklyn.entity.rebind.persister.jclouds.JcloudsBlobStoreBasedObjectStore;
 import brooklyn.entity.rebind.transformer.CompoundTransformer;
 import brooklyn.entity.trait.Startable;
 import brooklyn.internal.BrooklynFeatureEnablement;
@@ -467,10 +466,10 @@ public class BrooklynLauncher {
 
     /**
      * @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.
      * @param transformer Optional transformations to apply to retrieved state before it is copied.
      */
-    public void copyPersistedState(String destinationDir, @Nullable String destinationLocation, @Nullable CompoundTransformer transformer) {
+    public void copyPersistedState(String destinationDir, @Nullable String destinationLocationSpec, @Nullable CompoundTransformer transformer) {
         initManagementContext();
         try {
             highAvailabilityMode = HighAvailabilityMode.HOT_STANDBY;
@@ -478,45 +477,34 @@ public class BrooklynLauncher {
         } catch (Exception e) {
             handleSubsystemStartupError(ignorePersistenceErrors, "persistence", e);
         }
-        ManagementPlaneSyncRecord planeState = managementContext.getHighAvailabilityManager().getManagementPlaneSyncState();
-        BrooklynMementoRawData memento = retrieveState(false);
-        BrooklynMementoRawData newMemento = memento;
-        if (transformer != null) {
-            try {
-                newMemento = transformer.transform(memento);
-            } catch (Exception e) {
-                throw Exceptions.propagate(e);
-            }
-        }
-        copyPersistedState(newMemento, planeState, destinationDir, destinationLocation);
+        
+        try {
+            BrooklynMementoRawData memento = managementContext.getRebindManager().retrieveMementoRawData();
+            if (transformer != null) memento = transformer.transform(memento);
+            
+            ManagementPlaneSyncRecord planeState = managementContext.getHighAvailabilityManager().getManagementPlaneSyncState();
+            
+            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() {
-        return retrieveState(true);
-    }
-
-    /** To be removed when {@link #retrieveState()} is made private. */
-    private BrooklynMementoRawData retrieveState(boolean initPersistence) {
         initManagementContext();
-        if (initPersistence) {
-            try {
-                initPersistence();
-            } catch (Exception e) {
-                handleSubsystemStartupError(ignorePersistenceErrors, "persistence", e);
-            }
-        }
-        try {
-            return managementContext.getRebindManager().retrieveMementoRawData();
-        } 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);
-        }
+        initPersistence();
+        return managementContext.getRebindManager().retrieveMementoRawData();
     }
 
     /**
@@ -527,49 +515,11 @@ public class BrooklynLauncher {
      */
     // Make private after deprecation
     @Deprecated
-    public void persistState(BrooklynMementoRawData memento, String destinationDir, @Nullable String destinationLocation) {
-        copyPersistedState(memento, null, destinationDir, destinationLocation);
-    }
-
-    private void copyPersistedState(BrooklynMementoRawData memento, ManagementPlaneSyncRecord planeRecord,
-            String destinationDir, String destinationLocation) {
-        LOG.info("Persisting state to "+destinationDir+(Strings.isBlank(destinationLocation) ? "" : " @ "+destinationLocation));
-
+    public void persistState(BrooklynMementoRawData memento, String destinationDir, @Nullable String destinationLocationSpec) {
         initManagementContext();
-        try {
-            destinationDir = BrooklynServerConfig.resolvePersistencePath(destinationDir, brooklynProperties, destinationLocation);
-            PersistenceObjectStore destinationObjectStore;
-
-            if (Strings.isBlank(destinationLocation)) {
-                File persistenceDirF = new File(destinationDir);
-                if (persistenceDirF.isFile()) throw new FatalConfigurationRuntimeException("Destination directory must not be a file");
-                destinationObjectStore = new FileBasedObjectStore(persistenceDirF);
-            } else {
-                destinationObjectStore = new JcloudsBlobStoreBasedObjectStore(destinationLocation, destinationDir);
-            }
-            destinationObjectStore.injectManagementContext(managementContext);
-            destinationObjectStore.prepareForSharedUse(persistMode, highAvailabilityMode);
-
-            BrooklynMementoPersisterToObjectStore persister = new BrooklynMementoPersisterToObjectStore(
-                    destinationObjectStore,
-                    ((ManagementContextInternal)managementContext).getBrooklynProperties(),
-                    managementContext.getCatalog().getRootClassLoader());
-
-            PersistenceExceptionHandler exceptionHandler = PersistenceExceptionHandlerImpl.builder().build();
-            persister.enableWriteAccess();
-            persister.checkpoint(memento, exceptionHandler);
-
-            if (planeRecord != null) {
-                ManagementPlaneSyncRecordPersisterToObjectStore managementPersister = new ManagementPlaneSyncRecordPersisterToObjectStore(
-                        managementContext, destinationObjectStore, managementContext.getCatalog().getRootClassLoader());
-                managementPersister.checkpoint(planeRecord);
-            }
-        } 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);
-        }
+        PersistenceObjectStore destinationObjectStore = BrooklynPersistenceUtils.newPersistenceObjectStore(
+            managementContext, destinationLocationSpec, destinationDir);
+        BrooklynPersistenceUtils.writeMemento(managementContext, memento, destinationObjectStore);
     }
 
     /**
@@ -585,7 +535,7 @@ public class BrooklynLauncher {
         // Create the management context
         initManagementContext();
 
-        // Add a CAMP platform (TODO include a flag for this?)
+        // Add a CAMP platform
         campPlatform = new BrooklynCampPlatformLauncherNoServer()
                 .useManagementContext(managementContext)
                 .launch()
@@ -781,22 +731,23 @@ public class BrooklynLauncher {
             objectStore = null;
             
         } else {
-            if (persistenceLocation == null) {
-                persistenceLocation = brooklynProperties.getConfig(BrooklynServerConfig.PERSISTENCE_LOCATION_SPEC);
-            }
-            
-            persistenceDir = BrooklynServerConfig.resolvePersistencePath(persistenceDir, brooklynProperties, persistenceLocation);
-
-            if (Strings.isBlank(persistenceLocation)) {
-                File persistenceDirF = new File(persistenceDir);
-                if (persistenceDirF.isFile()) throw new FatalConfigurationRuntimeException("Persistence directory must not be a file");
-                objectStore = new FileBasedObjectStore(persistenceDirF);
-            } else {
-                objectStore = new JcloudsBlobStoreBasedObjectStore(persistenceLocation, persistenceDir);
-            }
             try {
-                objectStore.injectManagementContext(managementContext);
-                objectStore.prepareForSharedUse(persistMode, highAvailabilityMode);
+                if (persistenceLocation == null) {
+                    persistenceLocation = brooklynProperties.getConfig(BrooklynServerConfig.PERSISTENCE_LOCATION_SPEC);
+                }
+                persistenceDir = BrooklynServerConfig.resolvePersistencePath(persistenceDir, brooklynProperties, persistenceLocation);
+                objectStore = BrooklynPersistenceUtils.newPersistenceObjectStore(managementContext, persistenceLocation, persistenceDir, 
+                    persistMode, highAvailabilityMode);
+                    
+                RebindManager rebindManager = managementContext.getRebindManager();
+                
+                BrooklynMementoPersisterToObjectStore persister = new BrooklynMementoPersisterToObjectStore(
+                    objectStore,
+                    ((ManagementContextInternal)managementContext).getBrooklynProperties(),
+                    managementContext.getCatalog().getRootClassLoader());
+                PersistenceExceptionHandler persistenceExceptionHandler = PersistenceExceptionHandlerImpl.builder().build();
+                ((RebindManagerImpl) rebindManager).setPeriodicPersistPeriod(persistPeriod);
+                rebindManager.setPersister(persister, persistenceExceptionHandler);
             } catch (FatalConfigurationRuntimeException e) {
                 throw e;
             } catch (Exception e) {
@@ -805,16 +756,6 @@ public class BrooklynLauncher {
                 throw new FatalRuntimeException("Error initializing persistence subsystem: "+
                     Exceptions.collapseText(e), e);
             }
-
-            RebindManager rebindManager = managementContext.getRebindManager();
-
-            BrooklynMementoPersisterToObjectStore persister = new BrooklynMementoPersisterToObjectStore(
-                    objectStore,
-                    ((ManagementContextInternal)managementContext).getBrooklynProperties(),
-                    managementContext.getCatalog().getRootClassLoader());
-            PersistenceExceptionHandler persistenceExceptionHandler = PersistenceExceptionHandlerImpl.builder().build();
-            ((RebindManagerImpl) rebindManager).setPeriodicPersistPeriod(persistPeriod);
-            rebindManager.setPersister(persister, persistenceExceptionHandler);
         }
         
         // Initialise the HA manager as required
@@ -915,6 +856,7 @@ public class BrooklynLauncher {
             return;
         }
         ApplicationBuilder brooklyn = new ApplicationBuilder() {
+            @SuppressWarnings("deprecation")
             @Override
             protected void doBuild() {
                 addChild(EntitySpec.create(LocalBrooklynNode.class)


[04/21] incubator-brooklyn git commit: Use a linear range QuorumCheck to fail if there are too many dangling references.

Posted by he...@apache.org.
Use a linear range QuorumCheck to fail if there are too many dangling references.

This introduces QuorumChecks.newLinearRange which allows specifying points in a place
to define a set of line segments defining the quorum.
This can be TypeCoerced from a string, e.g. `[[5,5],[10,10],[100,70],[200,140]]`
(with things like `all` and `atLeastOne` also supported).

Such a range is then used to cause rebind to fail if there are a lot of dangling references.
The precise number is allowing 2 for small values, scaling to 5% at big values,
but this can be tweaked using RebindManagerImpl.DANGLING_REFERENCES_MIN_REQUIRED_HEALTHY
aka rebind.failureMode.danglingRefs.minRequiredHealthy .  (e.g. set this to all in
brooklyn.properties to cause rebind to fail if there are any dangling references;
but note this can occasionally fail if something is deleted as different items are
not persisted simultaneously).

Also:
* There are also some tidies to how `RebindContext` is used, since many of the
  dangling references weren't being caught
* We are stricter about persisting proxies, since that doesn't work (type written as `$Proxy16`)
* We tolerate remote timestamps in persisted objects (since these can occur in backups, currently);
  we don't *use* those timestamps for anything (except in tests when they can be `prefer`red),
  but there is no harm in having them, if someone copied across backup persistence files


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

Branch: refs/heads/master
Commit: f39c6a3fc3f40cd3469c38b7d61908d317c1d7de
Parents: 7532be8
Author: Alex Heneveld <al...@cloudsoftcorp.com>
Authored: Wed Nov 12 09:02:42 2014 +0000
Committer: Alex Heneveld <al...@cloudsoftcorp.com>
Committed: Thu Nov 13 23:38:52 2014 +0000

----------------------------------------------------------------------
 .../brooklyn/entity/rebind/RebindContext.java   |  30 ++--
 .../entity/rebind/RebindExceptionHandler.java   |   2 +-
 .../brooklyn/config/BrooklynServerPaths.java    |   2 +-
 .../java/brooklyn/entity/basic/Entities.java    |   8 +-
 .../entity/rebind/BasicEntityRebindSupport.java |  15 +-
 .../rebind/BasicLocationRebindSupport.java      |   4 +-
 .../rebind/PersistenceExceptionHandlerImpl.java |   2 +-
 .../entity/rebind/RebindContextImpl.java        |  44 ++++--
 .../rebind/RebindContextLookupContext.java      |   6 +-
 .../rebind/RebindExceptionHandlerImpl.java      |  32 ++++-
 .../entity/rebind/RebindManagerImpl.java        |  26 +++-
 .../entity/rebind/dto/MementosGenerators.java   |   5 +
 .../persister/BrooklynPersistenceUtils.java     |   3 +
 .../ha/HighAvailabilityManagerImpl.java         |   2 +-
 ...ntPlaneSyncRecordPersisterToObjectStore.java |  13 +-
 .../java/brooklyn/util/flags/TypeCoercions.java |   8 ++
 .../HighAvailabilityManagerSplitBrainTest.java  |   2 +-
 .../ha/HighAvailabilityManagerTestFixture.java  |   2 +-
 .../brooklyn/management/ha/HotStandbyTest.java  |   2 +-
 .../brooklyn/management/ha/WarmStandbyTest.java |   2 +-
 .../brooklyn/util/collections/MutableList.java  |   7 +
 .../brooklyn/util/collections/QuorumCheck.java  | 142 ++++++++++++++++++-
 .../util/collections/QuorumChecksTest.java      | 105 ++++++++++++++
 23 files changed, 392 insertions(+), 72 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/f39c6a3f/api/src/main/java/brooklyn/entity/rebind/RebindContext.java
----------------------------------------------------------------------
diff --git a/api/src/main/java/brooklyn/entity/rebind/RebindContext.java b/api/src/main/java/brooklyn/entity/rebind/RebindContext.java
index 1ddd178..17935b3 100644
--- a/api/src/main/java/brooklyn/entity/rebind/RebindContext.java
+++ b/api/src/main/java/brooklyn/entity/rebind/RebindContext.java
@@ -18,41 +18,35 @@
  */
 package brooklyn.entity.rebind;
 
+import java.util.Map;
+
 import brooklyn.basic.BrooklynObject;
-import brooklyn.catalog.CatalogItem;
-import brooklyn.entity.Entity;
-import brooklyn.entity.Feed;
-import brooklyn.location.Location;
-import brooklyn.policy.Enricher;
-import brooklyn.policy.Policy;
+import brooklyn.mementos.BrooklynMementoPersister.LookupContext;
+
+import com.google.common.annotations.Beta;
 
 /**
  * Gives access to things that are being currently rebinding. This is used during a
  * rebind to wire everything back together again, e.g. to find the necessary entity 
  * instances even before they are available through 
  * {@code managementContext.getEntityManager().getEnties()}.
- * 
+ * <p>
  * Users are not expected to implement this class. It is for use by {@link Rebindable} 
  * instances, and will generally be created by the {@link RebindManager}.
+ * <p>
  */
+@Beta
 public interface RebindContext {
 
-    Entity getEntity(String id);
-
-    Location getLocation(String id);
-
-    Policy getPolicy(String id);
-
-    Enricher getEnricher(String id);
-
-    Feed getFeed(String id);
+    /** Returns an unmodifiable view of all objects by ID */ 
+    Map<String,BrooklynObject> getAllBrooklynObjects();
     
-    CatalogItem<?, ?> getCatalogItem(String id);
-
     Class<?> loadClass(String typeName) throws ClassNotFoundException;
     
     RebindExceptionHandler getExceptionHandler();
     
     boolean isReadOnly(BrooklynObject item);
     
+    LookupContext lookup();
+    
 }

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/f39c6a3f/api/src/main/java/brooklyn/entity/rebind/RebindExceptionHandler.java
----------------------------------------------------------------------
diff --git a/api/src/main/java/brooklyn/entity/rebind/RebindExceptionHandler.java b/api/src/main/java/brooklyn/entity/rebind/RebindExceptionHandler.java
index 34c4f05..61bcdaa 100644
--- a/api/src/main/java/brooklyn/entity/rebind/RebindExceptionHandler.java
+++ b/api/src/main/java/brooklyn/entity/rebind/RebindExceptionHandler.java
@@ -96,7 +96,7 @@ public interface RebindExceptionHandler {
     RuntimeException onFailed(Exception e);
 
     /** invoked before the rebind pass */
-    void onStart();
+    void onStart(RebindContext context);
     
     /** invoked after the complete rebind pass, always on success and possibly on failure */
     void onDone();

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/f39c6a3f/core/src/main/java/brooklyn/config/BrooklynServerPaths.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/config/BrooklynServerPaths.java b/core/src/main/java/brooklyn/config/BrooklynServerPaths.java
index 2bcb763..ee294e9 100644
--- a/core/src/main/java/brooklyn/config/BrooklynServerPaths.java
+++ b/core/src/main/java/brooklyn/config/BrooklynServerPaths.java
@@ -205,7 +205,7 @@ public class BrooklynServerPaths {
         }
         @Override
         protected String getDefaultDirForAnyFilesystem() {
-            return backupContainerFor(super.getDefaultDirForAnyFilesystem());
+            return backupContainerFor(DEFAULT_PERSISTENCE_CONTAINER_NAME);
         }
         @Override
         protected String getDefaultContainerForAnyNonFilesystem() {

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/f39c6a3f/core/src/main/java/brooklyn/entity/basic/Entities.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/entity/basic/Entities.java b/core/src/main/java/brooklyn/entity/basic/Entities.java
index 7d17b7a..d073372 100644
--- a/core/src/main/java/brooklyn/entity/basic/Entities.java
+++ b/core/src/main/java/brooklyn/entity/basic/Entities.java
@@ -773,10 +773,14 @@ public class Entities {
 
     /** Unwraps a proxy to retrieve the real item, if available.
      * <p>
-     * Only intended for use in tests. For normal operations, callers should ensure the method is
-     * available on an interface and accessed via the proxy. */
+     * Only intended for use in tests and occasional internal usage, e.g. persistence.
+     * For normal operations, callers should ensure the method is available on an interface and accessed via the proxy. */
     @Beta @VisibleForTesting
     public static AbstractEntity deproxy(Entity e) {
+        if (!(Proxy.isProxyClass(e.getClass()))) {
+            log.warn("Attempt to deproxy non-proxy "+e, new Throwable("Location of attempt to deproxy non-proxy "+e));
+            return (AbstractEntity) e;
+        }
         return (AbstractEntity) ((EntityProxyImpl)Proxy.getInvocationHandler(e)).getDelegate();
     }
     

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/f39c6a3f/core/src/main/java/brooklyn/entity/rebind/BasicEntityRebindSupport.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/entity/rebind/BasicEntityRebindSupport.java b/core/src/main/java/brooklyn/entity/rebind/BasicEntityRebindSupport.java
index bb31a1f..6bb19cb 100644
--- a/core/src/main/java/brooklyn/entity/rebind/BasicEntityRebindSupport.java
+++ b/core/src/main/java/brooklyn/entity/rebind/BasicEntityRebindSupport.java
@@ -118,7 +118,7 @@ public class BasicEntityRebindSupport extends AbstractBrooklynObjectRebindSuppor
     @Override
     public void addPolicies(RebindContext rebindContext, EntityMemento memento) {
         for (String policyId : memento.getPolicies()) {
-            AbstractPolicy policy = (AbstractPolicy) rebindContext.getPolicy(policyId);
+            AbstractPolicy policy = (AbstractPolicy) rebindContext.lookup().lookupPolicy(policyId);
             if (policy != null) {
                 try {
                     entity.addPolicy(policy);
@@ -128,6 +128,7 @@ public class BasicEntityRebindSupport extends AbstractBrooklynObjectRebindSuppor
             } else {
                 LOG.warn("Policy not found; discarding policy {} of entity {}({})",
                         new Object[] {policyId, memento.getType(), memento.getId()});
+                rebindContext.getExceptionHandler().onDanglingPolicyRef(policyId);
             }
         }
     }
@@ -135,7 +136,7 @@ public class BasicEntityRebindSupport extends AbstractBrooklynObjectRebindSuppor
     @Override
     public void addEnrichers(RebindContext rebindContext, EntityMemento memento) {
         for (String enricherId : memento.getEnrichers()) {
-            AbstractEnricher enricher = (AbstractEnricher) rebindContext.getEnricher(enricherId);
+            AbstractEnricher enricher = (AbstractEnricher) rebindContext.lookup().lookupEnricher(enricherId);
             if (enricher != null) {
                 try {
                     entity.addEnricher(enricher);
@@ -152,7 +153,7 @@ public class BasicEntityRebindSupport extends AbstractBrooklynObjectRebindSuppor
     @Override
     public void addFeeds(RebindContext rebindContext, EntityMemento memento) {
         for (String feedId : memento.getFeeds()) {
-            AbstractFeed feed = (AbstractFeed) rebindContext.getFeed(feedId);
+            AbstractFeed feed = (AbstractFeed) rebindContext.lookup().lookupFeed(feedId);
             if (feed != null) {
                 try {
                     ((EntityInternal)entity).feeds().addFeed(feed);
@@ -178,7 +179,7 @@ public class BasicEntityRebindSupport extends AbstractBrooklynObjectRebindSuppor
         if (memento.getMembers().size() > 0) {
             if (entity instanceof AbstractGroupImpl) {
                 for (String memberId : memento.getMembers()) {
-                    Entity member = rebindContext.getEntity(memberId);
+                    Entity member = rebindContext.lookup().lookupEntity(memberId);
                     if (member != null) {
                         ((AbstractGroupImpl)entity).addMemberInternal(member);
                     } else {
@@ -198,7 +199,7 @@ public class BasicEntityRebindSupport extends AbstractBrooklynObjectRebindSuppor
     
     protected void addChildren(RebindContext rebindContext, EntityMemento memento) {
         for (String childId : memento.getChildren()) {
-            Entity child = rebindContext.getEntity(childId);
+            Entity child = rebindContext.lookup().lookupEntity(childId);
             if (child != null) {
                 entity.addChild(proxy(child));
             } else {
@@ -209,7 +210,7 @@ public class BasicEntityRebindSupport extends AbstractBrooklynObjectRebindSuppor
     }
 
     protected void setParent(RebindContext rebindContext, EntityMemento memento) {
-        Entity parent = (memento.getParent() != null) ? rebindContext.getEntity(memento.getParent()) : null;
+        Entity parent = (memento.getParent() != null) ? rebindContext.lookup().lookupEntity(memento.getParent()) : null;
         if (parent != null) {
             entity.setParent(proxy(parent));
         } else if (memento.getParent() != null){
@@ -220,7 +221,7 @@ public class BasicEntityRebindSupport extends AbstractBrooklynObjectRebindSuppor
     
     protected void addLocations(RebindContext rebindContext, EntityMemento memento) {
         for (String id : memento.getLocations()) {
-            Location loc = rebindContext.getLocation(id);
+            Location loc = rebindContext.lookup().lookupLocation(id);
             if (loc != null) {
                 ((EntityInternal)entity).addLocations(ImmutableList.of(loc));
             } else {

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/f39c6a3f/core/src/main/java/brooklyn/entity/rebind/BasicLocationRebindSupport.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/entity/rebind/BasicLocationRebindSupport.java b/core/src/main/java/brooklyn/entity/rebind/BasicLocationRebindSupport.java
index 2ca5d2a..fc7947b 100644
--- a/core/src/main/java/brooklyn/entity/rebind/BasicLocationRebindSupport.java
+++ b/core/src/main/java/brooklyn/entity/rebind/BasicLocationRebindSupport.java
@@ -116,7 +116,7 @@ public class BasicLocationRebindSupport extends AbstractBrooklynObjectRebindSupp
 
     protected void addChildren(RebindContext rebindContext, LocationMemento memento) {
         for (String childId : memento.getChildren()) {
-            Location child = rebindContext.getLocation(childId);
+            Location child = rebindContext.lookup().lookupLocation(childId);
             if (child != null) {
                 location.addChild(child);
             } else {
@@ -126,7 +126,7 @@ public class BasicLocationRebindSupport extends AbstractBrooklynObjectRebindSupp
     }
 
     protected void setParent(RebindContext rebindContext, LocationMemento memento) {
-        Location parent = (memento.getParent() != null) ? rebindContext.getLocation(memento.getParent()) : null;
+        Location parent = (memento.getParent() != null) ? rebindContext.lookup().lookupLocation(memento.getParent()) : null;
         if (parent != null) {
             location.setParent(parent);
         } else if (memento.getParent() != null) {

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/f39c6a3f/core/src/main/java/brooklyn/entity/rebind/PersistenceExceptionHandlerImpl.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/entity/rebind/PersistenceExceptionHandlerImpl.java b/core/src/main/java/brooklyn/entity/rebind/PersistenceExceptionHandlerImpl.java
index bce5c83..a3f7655 100644
--- a/core/src/main/java/brooklyn/entity/rebind/PersistenceExceptionHandlerImpl.java
+++ b/core/src/main/java/brooklyn/entity/rebind/PersistenceExceptionHandlerImpl.java
@@ -90,7 +90,7 @@ public class PersistenceExceptionHandlerImpl implements PersistenceExceptionHand
             if (!isNew) {
                 if (LOG.isDebugEnabled()) LOG.debug("Repeating problem: "+errmsg, e);
             } else {
-                LOG.warn("Problem: "+errmsg, e);
+                LOG.warn("Problem persisting (ignoring): "+errmsg, e);
             }
         } else {
             if (!isNew) {

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/f39c6a3f/core/src/main/java/brooklyn/entity/rebind/RebindContextImpl.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/entity/rebind/RebindContextImpl.java b/core/src/main/java/brooklyn/entity/rebind/RebindContextImpl.java
index bcac4a7..a581c7d 100644
--- a/core/src/main/java/brooklyn/entity/rebind/RebindContextImpl.java
+++ b/core/src/main/java/brooklyn/entity/rebind/RebindContextImpl.java
@@ -28,8 +28,10 @@ import brooklyn.catalog.CatalogItem;
 import brooklyn.entity.Entity;
 import brooklyn.entity.Feed;
 import brooklyn.location.Location;
+import brooklyn.mementos.BrooklynMementoPersister.LookupContext;
 import brooklyn.policy.Enricher;
 import brooklyn.policy.Policy;
+import brooklyn.util.collections.MutableMap;
 
 import com.google.common.collect.Maps;
 
@@ -44,6 +46,7 @@ public class RebindContextImpl implements RebindContext {
     
     private final ClassLoader classLoader;
     private final RebindExceptionHandler exceptionHandler;
+    private LookupContext lookupContext;
     
     private boolean allAreReadOnly = false;
     
@@ -92,69 +95,73 @@ public class RebindContextImpl implements RebindContext {
         catalogItems.remove(item.getId());
     }
 
-    @Override
     public Entity getEntity(String id) {
         return entities.get(id);
     }
 
-    @Override
     public Location getLocation(String id) {
         return locations.get(id);
     }
     
-    @Override
     public Policy getPolicy(String id) {
         return policies.get(id);
     }
     
-    @Override
     public Enricher getEnricher(String id) {
         return enrichers.get(id);
     }
 
-    @Override
     public CatalogItem<?, ?> getCatalogItem(String id) {
         return catalogItems.get(id);
     }
 
-    @Override
     public Feed getFeed(String id) {
         return feeds.get(id);
     }
     
-    @Override
     public Class<?> loadClass(String className) throws ClassNotFoundException {
         return classLoader.loadClass(className);
     }
 
-    @Override
     public RebindExceptionHandler getExceptionHandler() {
         return exceptionHandler;
     }
 
-    protected Collection<Location> getLocations() {
+    public Collection<Location> getLocations() {
         return locations.values();
     }
     
-    protected Collection<Entity> getEntities() {
+    public Collection<Entity> getEntities() {
         return entities.values();
     }
     
-    protected Collection<Policy> getPolicies() {
+    public Collection<Policy> getPolicies() {
         return policies.values();
     }
 
-    protected Collection<Enricher> getEnrichers() {
+    public Collection<Enricher> getEnrichers() {
         return enrichers.values();
     }
     
-    protected Collection<Feed> getFeeds() {
+    public Collection<Feed> getFeeds() {
         return feeds.values();
     }
 
-    protected Collection<CatalogItem<?, ?>> getCatalogItems() {
+    public Collection<CatalogItem<?, ?>> getCatalogItems() {
         return catalogItems.values();
     }
+    
+    @Override
+    public Map<String,BrooklynObject> getAllBrooklynObjects() {
+        MutableMap<String,BrooklynObject> result = MutableMap.of();
+        result.putAll(locations);
+        result.putAll(entities);
+        result.putAll(policies);
+        result.putAll(enrichers);
+        result.putAll(feeds);
+        result.putAll(catalogItems);
+        return result.asUnmodifiable();
+    }
 
     public void setAllReadOnly() {
         allAreReadOnly = true;
@@ -163,5 +170,14 @@ public class RebindContextImpl implements RebindContext {
     public boolean isReadOnly(BrooklynObject item) {
         return allAreReadOnly;
     }
+
+    public void setLookupContext(LookupContext lookupContext) {
+        this.lookupContext = lookupContext;
+    }
+    
+    @Override
+    public LookupContext lookup() {
+        return lookupContext;
+    }
     
 }

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/f39c6a3f/core/src/main/java/brooklyn/entity/rebind/RebindContextLookupContext.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/entity/rebind/RebindContextLookupContext.java b/core/src/main/java/brooklyn/entity/rebind/RebindContextLookupContext.java
index bf80ef9..6392a5f 100644
--- a/core/src/main/java/brooklyn/entity/rebind/RebindContextLookupContext.java
+++ b/core/src/main/java/brooklyn/entity/rebind/RebindContextLookupContext.java
@@ -41,13 +41,13 @@ public class RebindContextLookupContext implements LookupContext {
     @Nullable
     protected final ManagementContext managementContext;
     
-    protected final RebindContext rebindContext;
+    protected final RebindContextImpl rebindContext;
     protected final RebindExceptionHandler exceptionHandler;
     
-    public RebindContextLookupContext(RebindContext rebindContext, RebindExceptionHandler exceptionHandler) {
+    public RebindContextLookupContext(RebindContextImpl rebindContext, RebindExceptionHandler exceptionHandler) {
         this(null, rebindContext, exceptionHandler);
     }
-    public RebindContextLookupContext(ManagementContext managementContext, RebindContext rebindContext, RebindExceptionHandler exceptionHandler) {
+    public RebindContextLookupContext(ManagementContext managementContext, RebindContextImpl rebindContext, RebindExceptionHandler exceptionHandler) {
         this.managementContext = managementContext;
         this.rebindContext = rebindContext;
         this.exceptionHandler = exceptionHandler;

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/f39c6a3f/core/src/main/java/brooklyn/entity/rebind/RebindExceptionHandlerImpl.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/entity/rebind/RebindExceptionHandlerImpl.java b/core/src/main/java/brooklyn/entity/rebind/RebindExceptionHandlerImpl.java
index e5f3111..ac41c8b 100644
--- a/core/src/main/java/brooklyn/entity/rebind/RebindExceptionHandlerImpl.java
+++ b/core/src/main/java/brooklyn/entity/rebind/RebindExceptionHandlerImpl.java
@@ -36,6 +36,8 @@ import brooklyn.entity.rebind.RebindManager.RebindFailureMode;
 import brooklyn.location.Location;
 import brooklyn.policy.Enricher;
 import brooklyn.policy.Policy;
+import brooklyn.util.collections.MutableList;
+import brooklyn.util.collections.QuorumCheck;
 import brooklyn.util.exceptions.Exceptions;
 import brooklyn.util.text.Strings;
 
@@ -51,6 +53,7 @@ public class RebindExceptionHandlerImpl implements RebindExceptionHandler {
     protected final RebindManager.RebindFailureMode rebindFailureMode;
     protected final RebindFailureMode addPolicyFailureMode;
     protected final RebindFailureMode loadPolicyFailureMode;
+    protected final QuorumCheck danglingRefsQuorumRequiredHealthy;
 
     protected final Set<String> missingEntities = Sets.newConcurrentHashSet();
     protected final Set<String> missingLocations = Sets.newConcurrentHashSet();
@@ -63,6 +66,7 @@ public class RebindExceptionHandlerImpl implements RebindExceptionHandler {
     protected final Set<Exception> loadPolicyFailures = Sets.newConcurrentHashSet();
     protected final List<Exception> exceptions = Collections.synchronizedList(Lists.<Exception>newArrayList());
     
+    protected RebindContext context;
     protected boolean started = false;
     protected boolean done = false;
     
@@ -75,6 +79,7 @@ public class RebindExceptionHandlerImpl implements RebindExceptionHandler {
         private RebindManager.RebindFailureMode rebindFailureMode = RebindManager.RebindFailureMode.FAIL_AT_END;
         private RebindManager.RebindFailureMode addPolicyFailureMode = RebindManager.RebindFailureMode.CONTINUE;
         private RebindManager.RebindFailureMode deserializePolicyFailureMode = RebindManager.RebindFailureMode.CONTINUE;
+        private QuorumCheck danglingRefsQuorumRequiredHealthy = RebindManagerImpl.DANGLING_REFERENCES_MIN_REQUIRED_HEALTHY.getDefaultValue();
 
         public Builder danglingRefFailureMode(RebindManager.RebindFailureMode val) {
             danglingRefFailureMode = val;
@@ -92,6 +97,10 @@ public class RebindExceptionHandlerImpl implements RebindExceptionHandler {
             deserializePolicyFailureMode = val;
             return this;
         }
+        public Builder danglingRefQuorumRequiredHealthy(QuorumCheck val) {
+            danglingRefsQuorumRequiredHealthy = val;
+            return this;
+        }
         public RebindExceptionHandler build() {
             return new RebindExceptionHandlerImpl(this);
         }
@@ -102,15 +111,18 @@ public class RebindExceptionHandlerImpl implements RebindExceptionHandler {
         this.rebindFailureMode = checkNotNull(builder.rebindFailureMode, "rebindFailureMode");
         this.addPolicyFailureMode = checkNotNull(builder.addPolicyFailureMode, "addPolicyFailureMode");
         this.loadPolicyFailureMode = checkNotNull(builder.deserializePolicyFailureMode, "deserializePolicyFailureMode");
+        this.danglingRefsQuorumRequiredHealthy = checkNotNull(builder.danglingRefsQuorumRequiredHealthy, "danglingRefsQuorumRequiredHealthy");
     }
 
-    public void onStart() {
+    @Override
+    public void onStart(RebindContext context) {
         if (done) {
             throw new IllegalStateException(this+" has already been used on a finished run");
         }
         if (started) {
             throw new IllegalStateException(this+" has already been used on a started run");
         }
+        this.context = context;
         started = true;
     }
     
@@ -364,6 +376,24 @@ public class RebindExceptionHandlerImpl implements RebindExceptionHandler {
             allExceptions.add(new IllegalStateException(this+" has already been informed of rebind done"));
         }
         done = true;
+        
+        List<String> danglingIds = MutableList.copyOf(missingEntities).appendAll(missingLocations).appendAll(missingPolicies).appendAll(missingEnrichers).appendAll(missingFeeds).appendAll(missingCatalogItems);
+        int totalDangling = danglingIds.size();
+        if (totalDangling>0) {
+            int totalFound = context.getAllBrooklynObjects().size();
+            int totalItems = totalFound + totalDangling;
+            if (context==null) {
+                allExceptions.add(new IllegalStateException("Dangling references ("+totalDangling+" of "+totalItems+") present without rebind context"));
+            } else {
+                if (!danglingRefsQuorumRequiredHealthy.isQuorate(totalFound, totalItems)) {
+                    LOG.warn("Dangling item"+Strings.s(totalDangling)+" ("+totalDangling+" of "+totalItems+") found on rebind exceeds quorum, assuming failed: "+danglingIds);
+                    allExceptions.add(new IllegalStateException("Too many dangling references: "+totalDangling+" of "+totalItems));
+                } else {
+                    LOG.info("Dangling item"+Strings.s(totalDangling)+" ("+totalDangling+" of "+totalItems+") found on rebind, assuming deleted: "+danglingIds);
+                }
+            }
+        }
+        
         if (e != null) {
             allExceptions.add(e);
         }

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/f39c6a3f/core/src/main/java/brooklyn/entity/rebind/RebindManagerImpl.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/entity/rebind/RebindManagerImpl.java b/core/src/main/java/brooklyn/entity/rebind/RebindManagerImpl.java
index 3ba8bf1..ac9f726 100644
--- a/core/src/main/java/brooklyn/entity/rebind/RebindManagerImpl.java
+++ b/core/src/main/java/brooklyn/entity/rebind/RebindManagerImpl.java
@@ -91,6 +91,8 @@ import brooklyn.policy.Enricher;
 import brooklyn.policy.Policy;
 import brooklyn.policy.basic.AbstractPolicy;
 import brooklyn.util.collections.MutableMap;
+import brooklyn.util.collections.QuorumCheck;
+import brooklyn.util.collections.QuorumCheck.QuorumChecks;
 import brooklyn.util.exceptions.Exceptions;
 import brooklyn.util.exceptions.RuntimeInterruptedException;
 import brooklyn.util.flags.FlagUtils;
@@ -136,7 +138,15 @@ public class RebindManagerImpl implements RebindManager {
     public static final ConfigKey<RebindFailureMode> LOAD_POLICY_FAILURE_MODE =
             ConfigKeys.newConfigKey(RebindFailureMode.class, "rebind.failureMode.loadPolicy",
                     "Action to take if a failure occurs when loading a policy or enricher", RebindFailureMode.CONTINUE);
-    
+
+    public static final ConfigKey<QuorumCheck> DANGLING_REFERENCES_MIN_REQUIRED_HEALTHY =
+        ConfigKeys.newConfigKey(QuorumCheck.class, "rebind.failureMode.danglingRefs.minRequiredHealthy",
+                "Number of items which must be rebinded at various sizes; "
+                + "a small number of dangling references is possible if items are in the process of being created or deleted, "
+                + "and that should be resolved on retry; the default set here allows max 2 dangling up to 10 items, "
+                + "then linear regression to allow max 5% at 100 items and above", 
+                QuorumChecks.newLinearRange("[[0,-2],[10,8],[100,95],[200,190]]"));
+
     public static final Logger LOG = LoggerFactory.getLogger(RebindManagerImpl.class);
 
     private final ManagementContextInternal managementContext;
@@ -163,6 +173,7 @@ public class RebindManagerImpl implements RebindManager {
     private RebindFailureMode rebindFailureMode;
     private RebindFailureMode addPolicyFailureMode;
     private RebindFailureMode loadPolicyFailureMode;
+    private QuorumCheck danglingRefsQuorumRequiredHealthy;
 
     /**
      * For tracking if rebinding, for {@link AbstractEnricher#isRebinding()} etc.
@@ -201,6 +212,8 @@ public class RebindManagerImpl implements RebindManager {
         rebindFailureMode = managementContext.getConfig().getConfig(REBIND_FAILURE_MODE);
         addPolicyFailureMode = managementContext.getConfig().getConfig(ADD_POLICY_FAILURE_MODE);
         loadPolicyFailureMode = managementContext.getConfig().getConfig(LOAD_POLICY_FAILURE_MODE);
+        
+        danglingRefsQuorumRequiredHealthy = managementContext.getConfig().getConfig(DANGLING_REFERENCES_MIN_REQUIRED_HEALTHY);
 
         LOG.debug("{} initialized, settings: policies={}, enrichers={}, feeds={}, catalog={}",
                 new Object[]{this, persistPoliciesEnabled, persistEnrichersEnabled, persistFeedsEnabled, persistCatalogItemsEnabled});
@@ -446,6 +459,7 @@ public class RebindManagerImpl implements RebindManager {
         final RebindExceptionHandler exceptionHandler = exceptionHandlerO!=null ? exceptionHandlerO :
             RebindExceptionHandlerImpl.builder()
                 .danglingRefFailureMode(danglingRefFailureMode)
+                .danglingRefQuorumRequiredHealthy(danglingRefsQuorumRequiredHealthy)
                 .rebindFailureMode(rebindFailureMode)
                 .addPolicyFailureMode(addPolicyFailureMode)
                 .loadPolicyFailureMode(loadPolicyFailureMode)
@@ -514,10 +528,11 @@ public class RebindManagerImpl implements RebindManager {
         RebindTracker.setRebinding();
         try {
             Stopwatch timer = Stopwatch.createStarted();
-            exceptionHandler.onStart();
-            
             Reflections reflections = new Reflections(classLoader);
             RebindContextImpl rebindContext = new RebindContextImpl(exceptionHandler, classLoader);
+            
+            exceptionHandler.onStart(rebindContext);
+            
             if (mode==ManagementNodeState.HOT_STANDBY) {
                 rebindContext.setAllReadOnly();
             } else {
@@ -525,6 +540,7 @@ public class RebindManagerImpl implements RebindManager {
             }
             
             LookupContext realLookupContext = new RebindContextLookupContext(managementContext, rebindContext, exceptionHandler);
+            rebindContext.setLookupContext(realLookupContext);
             
             // Mutli-phase deserialization.
             //
@@ -847,7 +863,7 @@ public class RebindManagerImpl implements RebindManager {
             // Reconstruct entities
             logRebindingDebug("RebindManager reconstructing entities");
             for (EntityMemento entityMemento : sortParentFirst(memento.getEntityMementos()).values()) {
-                Entity entity = rebindContext.getEntity(entityMemento.getId());
+                Entity entity = rebindContext.lookup().lookupEntity(entityMemento.getId());
                 logRebindingDebug("RebindManager reconstructing entity {}", entityMemento);
     
                 if (entity == null) {
@@ -1042,7 +1058,7 @@ public class RebindManagerImpl implements RebindManager {
 
     private BrooklynClassLoadingContext getLoadingContextFromCatalogItemId(String catalogItemId, ClassLoader classLoader, RebindContext rebindContext) {
         Preconditions.checkNotNull(catalogItemId, "catalogItemId required (should not be null)");
-        CatalogItem<?, ?> catalogItem = rebindContext.getCatalogItem(catalogItemId);
+        CatalogItem<?, ?> catalogItem = rebindContext.lookup().lookupCatalogItem(catalogItemId);
         if (catalogItem != null) {
             return CatalogUtils.newClassLoadingContext(managementContext, catalogItem);
         } else {

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/f39c6a3f/core/src/main/java/brooklyn/entity/rebind/dto/MementosGenerators.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/entity/rebind/dto/MementosGenerators.java b/core/src/main/java/brooklyn/entity/rebind/dto/MementosGenerators.java
index 94c2b59..7e8d598 100644
--- a/core/src/main/java/brooklyn/entity/rebind/dto/MementosGenerators.java
+++ b/core/src/main/java/brooklyn/entity/rebind/dto/MementosGenerators.java
@@ -21,6 +21,7 @@ package brooklyn.entity.rebind.dto;
 import static com.google.common.base.Preconditions.checkNotNull;
 
 import java.lang.reflect.Modifier;
+import java.lang.reflect.Proxy;
 import java.util.Map;
 import java.util.Set;
 
@@ -375,6 +376,10 @@ public class MementosGenerators {
     }
     
     private static void populateBrooklynObjectMementoBuilder(BrooklynObject instance, AbstractMemento.Builder<?> builder) {
+        if (Proxy.isProxyClass(instance.getClass())) {
+            throw new IllegalStateException("Attempt to create memento from proxy "+instance+" (would fail with wrong type)");
+        }
+        
         builder.id = instance.getId();
         builder.displayName = instance.getDisplayName();
         builder.catalogItemId = instance.getCatalogItemId();

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/f39c6a3f/core/src/main/java/brooklyn/entity/rebind/persister/BrooklynPersistenceUtils.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/entity/rebind/persister/BrooklynPersistenceUtils.java b/core/src/main/java/brooklyn/entity/rebind/persister/BrooklynPersistenceUtils.java
index 356234d..54990b3 100644
--- a/core/src/main/java/brooklyn/entity/rebind/persister/BrooklynPersistenceUtils.java
+++ b/core/src/main/java/brooklyn/entity/rebind/persister/BrooklynPersistenceUtils.java
@@ -29,6 +29,8 @@ import brooklyn.config.BrooklynServerConfig;
 import brooklyn.config.BrooklynServerPaths;
 import brooklyn.entity.Entity;
 import brooklyn.entity.Feed;
+import brooklyn.entity.basic.AbstractEntity;
+import brooklyn.entity.basic.Entities;
 import brooklyn.entity.basic.EntityInternal;
 import brooklyn.entity.rebind.BrooklynObjectType;
 import brooklyn.entity.rebind.PersistenceExceptionHandler;
@@ -169,6 +171,7 @@ public class BrooklynPersistenceUtils {
         for (Location instance: mgmt.getLocationManager().getLocations())
             result.location(instance.getId(), serializer.toString(newObjectMemento(instance)));
         for (Entity instance: mgmt.getEntityManager().getEntities()) {
+            instance = Entities.deproxy(instance);
             result.entity(instance.getId(), serializer.toString(newObjectMemento(instance)));
             for (Feed instanceAdjunct: ((EntityInternal)instance).feeds().getFeeds())
                 result.feed(instanceAdjunct.getId(), serializer.toString(newObjectMemento(instanceAdjunct)));

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/f39c6a3f/core/src/main/java/brooklyn/management/ha/HighAvailabilityManagerImpl.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/management/ha/HighAvailabilityManagerImpl.java b/core/src/main/java/brooklyn/management/ha/HighAvailabilityManagerImpl.java
index c756824..329578e 100644
--- a/core/src/main/java/brooklyn/management/ha/HighAvailabilityManagerImpl.java
+++ b/core/src/main/java/brooklyn/management/ha/HighAvailabilityManagerImpl.java
@@ -211,7 +211,7 @@ public class HighAvailabilityManagerImpl implements HighAvailabilityManager {
      * Defaults to null which means to use the remote timestamp. 
      * Only for testing as this records the remote timestamp in the object.
      * <p>
-     * If this is supplied, one must also set {@link ManagementPlaneSyncRecordPersisterToObjectStore#allowRemoteTimestampInMemento()}. */
+     * If this is supplied, one must also set {@link ManagementPlaneSyncRecordPersisterToObjectStore#useRemoteTimestampInMemento()}. */
     @VisibleForTesting
     public HighAvailabilityManagerImpl setRemoteTicker(Ticker val) {
         this.optionalRemoteTickerUtc = val;

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/f39c6a3f/core/src/main/java/brooklyn/management/ha/ManagementPlaneSyncRecordPersisterToObjectStore.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/management/ha/ManagementPlaneSyncRecordPersisterToObjectStore.java b/core/src/main/java/brooklyn/management/ha/ManagementPlaneSyncRecordPersisterToObjectStore.java
index b9cf465..55b645c 100644
--- a/core/src/main/java/brooklyn/management/ha/ManagementPlaneSyncRecordPersisterToObjectStore.java
+++ b/core/src/main/java/brooklyn/management/ha/ManagementPlaneSyncRecordPersisterToObjectStore.java
@@ -102,7 +102,7 @@ public class ManagementPlaneSyncRecordPersisterToObjectStore implements Manageme
     
     @VisibleForTesting
     /** allows, when testing, to be able to override file times / blobstore times with time from the ticker */
-    private boolean allowRemoteTimestampInMemento = false;
+    private boolean preferRemoteTimestampInMemento = false;
 
     /**
      * @param mgmt not used much at present but handy to ensure we know it so that obj store is prepared
@@ -131,8 +131,8 @@ public class ManagementPlaneSyncRecordPersisterToObjectStore implements Manageme
     }
 
     @VisibleForTesting
-    public void allowRemoteTimestampInMemento() {
-        allowRemoteTimestampInMemento = true;
+    public void preferRemoteTimestampInMemento() {
+        preferRemoteTimestampInMemento = true;
     }
     
     @Override
@@ -210,11 +210,12 @@ public class ManagementPlaneSyncRecordPersisterToObjectStore implements Manageme
                 // shouldn't happen
                 throw Exceptions.propagate(new IllegalStateException("Node record "+nodeFile+" could not be deserialized when "+mgmt.getManagementNodeId()+" was scanning: "+nodeContents, problem));
             } else {
-                if (memento.getRemoteTimestamp()!=null) {
+                if (memento.getRemoteTimestamp()!=null && preferRemoteTimestampInMemento) {
                     // in test mode, the remote timestamp is stored in the file
-                    if (!allowRemoteTimestampInMemento)
-                        throw new IllegalStateException("Remote timestamps not allowed in memento: "+nodeContents);
                 } else {
+                    if (memento.getRemoteTimestamp()!=null) {
+                        LOG.debug("Ignoring remote timestamp in memento file ("+memento+"); looks like this data has been manually copied in");
+                    }
                     Date lastModifiedDate = objectAccessor.getLastModifiedDate();
                     ((BasicManagementNodeSyncRecord)memento).setRemoteTimestamp(lastModifiedDate!=null ? lastModifiedDate.getTime() : null);
                 }

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/f39c6a3f/core/src/main/java/brooklyn/util/flags/TypeCoercions.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/util/flags/TypeCoercions.java b/core/src/main/java/brooklyn/util/flags/TypeCoercions.java
index ec08ad0..26b9dea 100644
--- a/core/src/main/java/brooklyn/util/flags/TypeCoercions.java
+++ b/core/src/main/java/brooklyn/util/flags/TypeCoercions.java
@@ -51,6 +51,8 @@ import brooklyn.entity.basic.EntityFactory;
 import brooklyn.event.AttributeSensor;
 import brooklyn.event.basic.BasicAttributeSensor;
 import brooklyn.util.JavaGroovyEquivalents;
+import brooklyn.util.collections.QuorumCheck;
+import brooklyn.util.collections.QuorumCheck.QuorumChecks;
 import brooklyn.util.exceptions.Exceptions;
 import brooklyn.util.guava.Maybe;
 import brooklyn.util.javalang.Enums;
@@ -696,6 +698,12 @@ public class TypeCoercions {
                 return JavaStringEscapes.unwrapJsonishListIfPossible(input);
             }
         });
+        registerAdapter(String.class, QuorumCheck.class, new Function<String,QuorumCheck>() {
+            @Override
+            public QuorumCheck apply(final String input) {
+                return QuorumChecks.of(input);
+            }
+        });
         registerAdapter(String.class, Map.class, new Function<String,Map>() {
             @Override
             public Map apply(final String input) {

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/f39c6a3f/core/src/test/java/brooklyn/management/ha/HighAvailabilityManagerSplitBrainTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/brooklyn/management/ha/HighAvailabilityManagerSplitBrainTest.java b/core/src/test/java/brooklyn/management/ha/HighAvailabilityManagerSplitBrainTest.java
index 4c736ca..4ead25d 100644
--- a/core/src/test/java/brooklyn/management/ha/HighAvailabilityManagerSplitBrainTest.java
+++ b/core/src/test/java/brooklyn/management/ha/HighAvailabilityManagerSplitBrainTest.java
@@ -101,7 +101,7 @@ public class HighAvailabilityManagerSplitBrainTest {
             objectStore.injectManagementContext(mgmt);
             objectStore.prepareForSharedUse(PersistMode.CLEAN, HighAvailabilityMode.DISABLED);
             persister = new ManagementPlaneSyncRecordPersisterToObjectStore(mgmt, objectStore, classLoader);
-            ((ManagementPlaneSyncRecordPersisterToObjectStore)persister).allowRemoteTimestampInMemento();
+            ((ManagementPlaneSyncRecordPersisterToObjectStore)persister).preferRemoteTimestampInMemento();
             BrooklynMementoPersisterToObjectStore persisterObj = new BrooklynMementoPersisterToObjectStore(objectStore, mgmt.getBrooklynProperties(), classLoader);
             mgmt.getRebindManager().setPersister(persisterObj, PersistenceExceptionHandlerImpl.builder().build());
             ha = ((HighAvailabilityManagerImpl)mgmt.getHighAvailabilityManager())

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/f39c6a3f/core/src/test/java/brooklyn/management/ha/HighAvailabilityManagerTestFixture.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/brooklyn/management/ha/HighAvailabilityManagerTestFixture.java b/core/src/test/java/brooklyn/management/ha/HighAvailabilityManagerTestFixture.java
index 2637ae1..449b2d9 100644
--- a/core/src/test/java/brooklyn/management/ha/HighAvailabilityManagerTestFixture.java
+++ b/core/src/test/java/brooklyn/management/ha/HighAvailabilityManagerTestFixture.java
@@ -83,7 +83,7 @@ public abstract class HighAvailabilityManagerTestFixture {
         objectStore.injectManagementContext(managementContext);
         objectStore.prepareForSharedUse(PersistMode.CLEAN, HighAvailabilityMode.DISABLED);
         persister = new ManagementPlaneSyncRecordPersisterToObjectStore(managementContext, objectStore, classLoader);
-        ((ManagementPlaneSyncRecordPersisterToObjectStore)persister).allowRemoteTimestampInMemento();
+        ((ManagementPlaneSyncRecordPersisterToObjectStore)persister).preferRemoteTimestampInMemento();
         BrooklynMementoPersisterToObjectStore persisterObj = new BrooklynMementoPersisterToObjectStore(
                 objectStore, 
                 managementContext.getBrooklynProperties(), 

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/f39c6a3f/core/src/test/java/brooklyn/management/ha/HotStandbyTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/brooklyn/management/ha/HotStandbyTest.java b/core/src/test/java/brooklyn/management/ha/HotStandbyTest.java
index 3cb6896..76fa635 100644
--- a/core/src/test/java/brooklyn/management/ha/HotStandbyTest.java
+++ b/core/src/test/java/brooklyn/management/ha/HotStandbyTest.java
@@ -90,7 +90,7 @@ public class HotStandbyTest {
             objectStore.injectManagementContext(mgmt);
             objectStore.prepareForSharedUse(PersistMode.CLEAN, HighAvailabilityMode.DISABLED);
             persister = new ManagementPlaneSyncRecordPersisterToObjectStore(mgmt, objectStore, classLoader);
-            ((ManagementPlaneSyncRecordPersisterToObjectStore)persister).allowRemoteTimestampInMemento();
+            ((ManagementPlaneSyncRecordPersisterToObjectStore)persister).preferRemoteTimestampInMemento();
             BrooklynMementoPersisterToObjectStore persisterObj = new BrooklynMementoPersisterToObjectStore(objectStore, mgmt.getBrooklynProperties(), classLoader);
             ((RebindManagerImpl)mgmt.getRebindManager()).setPeriodicPersistPeriod(persistOrRebindPeriod);
             mgmt.getRebindManager().setPersister(persisterObj, PersistenceExceptionHandlerImpl.builder().build());

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/f39c6a3f/core/src/test/java/brooklyn/management/ha/WarmStandbyTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/brooklyn/management/ha/WarmStandbyTest.java b/core/src/test/java/brooklyn/management/ha/WarmStandbyTest.java
index a7b6b8a..29bb45e 100644
--- a/core/src/test/java/brooklyn/management/ha/WarmStandbyTest.java
+++ b/core/src/test/java/brooklyn/management/ha/WarmStandbyTest.java
@@ -74,7 +74,7 @@ public class WarmStandbyTest {
             objectStore.injectManagementContext(mgmt);
             objectStore.prepareForSharedUse(PersistMode.CLEAN, HighAvailabilityMode.DISABLED);
             persister = new ManagementPlaneSyncRecordPersisterToObjectStore(mgmt, objectStore, classLoader);
-            ((ManagementPlaneSyncRecordPersisterToObjectStore)persister).allowRemoteTimestampInMemento();
+            ((ManagementPlaneSyncRecordPersisterToObjectStore)persister).preferRemoteTimestampInMemento();
             BrooklynMementoPersisterToObjectStore persisterObj = new BrooklynMementoPersisterToObjectStore(objectStore, mgmt.getBrooklynProperties(), classLoader);
             mgmt.getRebindManager().setPersister(persisterObj, PersistenceExceptionHandlerImpl.builder().build());
             ha = ((HighAvailabilityManagerImpl)mgmt.getHighAvailabilityManager())

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/f39c6a3f/utils/common/src/main/java/brooklyn/util/collections/MutableList.java
----------------------------------------------------------------------
diff --git a/utils/common/src/main/java/brooklyn/util/collections/MutableList.java b/utils/common/src/main/java/brooklyn/util/collections/MutableList.java
index c467f0e..c5cab8d 100644
--- a/utils/common/src/main/java/brooklyn/util/collections/MutableList.java
+++ b/utils/common/src/main/java/brooklyn/util/collections/MutableList.java
@@ -179,6 +179,13 @@ public class MutableList<V> extends ArrayList<V> {
         public ImmutableList<V> buildImmutable() {
             return ImmutableList.copyOf(result);
         }
+
+        public Builder<V> addLists(Iterable<? extends V> ...items) {
+            for (Iterable<? extends V> item: items) {
+                addAll(item);
+            }
+            return this;
+        }
     }
     
     /** as {@link List#add(Object)} but fluent style */

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/f39c6a3f/utils/common/src/main/java/brooklyn/util/collections/QuorumCheck.java
----------------------------------------------------------------------
diff --git a/utils/common/src/main/java/brooklyn/util/collections/QuorumCheck.java b/utils/common/src/main/java/brooklyn/util/collections/QuorumCheck.java
index 98bfddc..1536f6f 100644
--- a/utils/common/src/main/java/brooklyn/util/collections/QuorumCheck.java
+++ b/utils/common/src/main/java/brooklyn/util/collections/QuorumCheck.java
@@ -19,6 +19,14 @@
 package brooklyn.util.collections;
 
 import java.io.Serializable;
+import java.util.Iterator;
+import java.util.List;
+
+import brooklyn.util.yaml.Yamls;
+
+import com.google.common.base.Objects;
+import com.google.common.base.Preconditions;
+import com.google.common.collect.Iterables;
 
 /**
  * For checking if a group/cluster is quorate. That is, whether the group has sufficient
@@ -44,13 +52,13 @@ public interface QuorumCheck {
          * Checks all members that should be up are up, and that there is at least one such member.
          */
         public static QuorumCheck allAndAtLeastOne() {
-            return new NumericQuorumCheck(1, 1.0, false, "atLeastOne");
+            return new NumericQuorumCheck(1, 1.0, false, "allAndAtLeastOne");
         }
         /**
          * Requires at least one member that should be up is up.
          */
         public static QuorumCheck atLeastOne() {
-            return new NumericQuorumCheck(1, 0.0, false);
+            return new NumericQuorumCheck(1, 0.0, false, "atLeastOne");
         }
         /**
          * Requires at least one member to be up if the total size is non-zero.
@@ -58,17 +66,68 @@ public interface QuorumCheck {
          * "Empty" means that no members are supposed to be up  (e.g. there may be stopped members).
          */
         public static QuorumCheck atLeastOneUnlessEmpty() {
-            return new NumericQuorumCheck(1, 0.0, true);
+            return new NumericQuorumCheck(1, 0.0, true, "atLeastOneUnlessEmpty");
         }
         /**
          * Always "healthy"
          */
         public static QuorumCheck alwaysTrue() {
-            return new NumericQuorumCheck(0, 0.0, true);
+            return new NumericQuorumCheck(0, 0.0, true, "alwaysHealthy");
         }
+        
         public static QuorumCheck newInstance(int minRequiredSize, double minRequiredRatio, boolean allowEmpty) {
             return new NumericQuorumCheck(minRequiredSize, minRequiredRatio, allowEmpty);
         }
+        
+        /** See {@link QuorumChecks#newLinearRange(String,String)} */
+        public static QuorumCheck newLinearRange(String range) {
+            return newLinearRange(range, null);
+        }
+        
+        /** Given a JSON representation of a list of points (where a point is a list of 2 numbers),
+         * with the points in increasing x-coordinate value,
+         * this constructs a quorum check which does linear interpolation on those coordinates,
+         * with extensions to either side.
+         * The x-coordinate is taken as the total size, and the y-coordinate as the minimum required size.
+         * <p>
+         * It sounds complicated but it gives a very easy and powerful way to define quorum checks.
+         * For instance:
+         * <p>
+         * <code>[[0,0],[1,1]]</code> says that if 0 items are expected, at least 0 is required; 
+         *   if 1 is expected, 1 is required; and by extension if 10 are expected, 10 are required.
+         *   In other words, this is the same as {@link #all()}.
+         * <p>
+         * <code>[[0,1],[1,1],[2,2]]</code> is the same as the previous for x (number expected) greater-than or equal to 1;
+         * but if 0 is expected, 1 is required, and so it fails when 0 are present.
+         * In other words, {@link #allAndAtLeastOne()}.
+         * <p>
+         * <code>[[5,5],[10,10],[100,70],[200,140]]</code> has {@link #all()} behavior up to 10 expected 
+         * (line extended to the left, for less than 5); but then gently tapers off to requiring only 70% at 100
+         * (with 30 of 40 = 75% required at that intermediate point along the line [[10,10],[100,70]]);
+         * and then from 100 onwards it is a straight 70%.
+         * <p>
+         * The type of linear regression described in the last example is quite useful in practise, 
+         * to be stricter for smaller clusters (or possibly more lax for small values in some cases,
+         * such as when tolerating dangling references during rebind). 
+         */
+        @SuppressWarnings({ "rawtypes", "unchecked" })
+        public static QuorumCheck newLinearRange(String range, String name) {
+            return LinearRangeQuorumCheck.of(name, (Iterable)Iterables.getOnlyElement( Yamls.parseAll(range) ));
+        }
+        
+        private static final List<QuorumCheck> NAMED_CHECKS = MutableList
+                .of(all(), allAndAtLeastOne(), atLeastOne(), atLeastOneUnlessEmpty(), alwaysTrue());
+        
+        public static QuorumCheck of(String nameOrRange) {
+            if (nameOrRange==null) return null;
+            for (QuorumCheck qc: NAMED_CHECKS) {
+                if (qc instanceof NumericQuorumCheck) {
+                    if (Objects.equal(nameOrRange, ((NumericQuorumCheck)qc).getName()))
+                        return qc;
+                }
+            }
+            return newLinearRange(nameOrRange);
+        }
     }
     
     public static class NumericQuorumCheck implements QuorumCheck, Serializable {
@@ -96,11 +155,82 @@ public interface QuorumCheck {
             if (sizeHealthy < totalSize*minRequiredRatio-0.000000001) return false;
             return true;
         }
+
+        public String getName() {
+            return name;
+        }
         
         @Override
         public String toString() {
-            return "QuorumCheck[require="+minRequiredSize+","+((int)100*minRequiredRatio)+"%"+(allowEmpty ? "|0" : "")+"]";
+            return "QuorumCheck["+(name!=null?name+";":"")+"require="+minRequiredSize+","+((int)100*minRequiredRatio)+"%"+(allowEmpty ? "|0" : "")+"]";
         }
     }
-    
+
+    /** See {@link QuorumChecks#newLinearRange(String,String)} */
+    public static class LinearRangeQuorumCheck implements QuorumCheck, Serializable {
+
+        private static final long serialVersionUID = -6425548115925898645L;
+
+        private static class Point {
+            final double size, minRequiredAtSize;
+            public Point(double size, double minRequiredAtSize) { this.size = size; this.minRequiredAtSize = minRequiredAtSize; }
+            public static Point ofIntegerCoords(Iterable<Integer> coords) {
+                Preconditions.checkNotNull(coords==null, "coords");
+                Preconditions.checkArgument(Iterables.size(coords)==2, "A point must consist of two coordinates; invalid data: "+coords);
+                Iterator<Integer> ci = coords.iterator();
+                return new Point(ci.next(), ci.next());
+            }
+            public static List<Point> listOfIntegerCoords(Iterable<? extends Iterable<Integer>> points) {
+                MutableList<Point> result = MutableList.of();
+                for (Iterable<Integer> point: points) result.add(ofIntegerCoords(point));
+                return result.asUnmodifiable();
+            }
+            @Override
+            public String toString() {
+                return "("+size+","+minRequiredAtSize+")";
+            }
+        }
+        
+        protected final String name;
+        protected final List<Point> points;
+
+        public static LinearRangeQuorumCheck of(String name, Iterable<? extends Iterable<Integer>> points) {
+            return new LinearRangeQuorumCheck(name, Point.listOfIntegerCoords(points));
+        }
+        public static LinearRangeQuorumCheck of(Iterable<? extends Iterable<Integer>> points) {
+            return new LinearRangeQuorumCheck(null, Point.listOfIntegerCoords(points));
+        }
+        
+        protected LinearRangeQuorumCheck(String name, Iterable<Point> points) {
+            Preconditions.checkArgument(Iterables.size(points)>=2, "At least two points must be supplied for "+name+": "+points);
+            this.name = name;
+            this.points = MutableList.copyOf(points).asUnmodifiable();
+            // check valid
+            Point last = null;
+            for (Point p: points) {
+                if (last!=null) {
+                    if (p.size <= last.size) throw new IllegalStateException("Points must be supplied in order of increasing totalSize (x coordinate); instead have "+last+" and "+p);
+                }
+            }
+        }
+
+        @Override
+        public boolean isQuorate(int sizeHealthy, int totalSize) {
+            Point next = points.get(0);
+            Point prev = null;
+            for (int i=1; i<points.size(); i++) {
+                prev = next;
+                next = points.get(i);
+                if (next.size>totalSize) break;
+            }
+            double minRequiredAtSize = (totalSize-prev.size)/(next.size-prev.size) * (next.minRequiredAtSize-prev.minRequiredAtSize) + prev.minRequiredAtSize;
+            return (sizeHealthy > minRequiredAtSize-0.000000001);
+        }
+        
+        @Override
+        public String toString() {
+            return "LinearRangeQuorumCheck["+(name!=null ? name+":" : "")+points+"]";
+        }
+    }
+
 }

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/f39c6a3f/utils/common/src/test/java/brooklyn/util/collections/QuorumChecksTest.java
----------------------------------------------------------------------
diff --git a/utils/common/src/test/java/brooklyn/util/collections/QuorumChecksTest.java b/utils/common/src/test/java/brooklyn/util/collections/QuorumChecksTest.java
new file mode 100644
index 0000000..bf53d68
--- /dev/null
+++ b/utils/common/src/test/java/brooklyn/util/collections/QuorumChecksTest.java
@@ -0,0 +1,105 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package brooklyn.util.collections;
+
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+import brooklyn.util.collections.QuorumCheck.QuorumChecks;
+
+public class QuorumChecksTest {
+
+    @Test
+    public void testAll() {
+        QuorumCheck q = QuorumChecks.all();
+        Assert.assertTrue(q.isQuorate(2, 2));
+        Assert.assertFalse(q.isQuorate(1, 2));
+        Assert.assertTrue(q.isQuorate(0, 0));
+    }
+    
+    @Test
+    public void testAlwaysTrue() {
+        QuorumCheck q = QuorumChecks.alwaysTrue();
+        Assert.assertTrue(q.isQuorate(0, 2));
+        Assert.assertTrue(q.isQuorate(1, 2));
+        Assert.assertTrue(q.isQuorate(0, 0));
+    }
+    
+    @Test
+    public void testAtLeastOne() {
+        QuorumCheck q = QuorumChecks.atLeastOne();
+        Assert.assertTrue(q.isQuorate(2, 2));
+        Assert.assertTrue(q.isQuorate(1, 2));
+        Assert.assertFalse(q.isQuorate(0, 0));
+    }
+    
+    @Test
+    public void testAllAndAtLeastOne() {
+        QuorumCheck q = QuorumChecks.atLeastOne();
+        Assert.assertFalse(q.isQuorate(0, 2));
+        Assert.assertTrue(q.isQuorate(1, 2));
+        Assert.assertFalse(q.isQuorate(0, 0));
+    }
+    
+    @Test
+    public void testAtLeastOneUnlessEmpty() {
+        QuorumCheck q = QuorumChecks.atLeastOneUnlessEmpty();
+        Assert.assertFalse(q.isQuorate(0, 2));
+        Assert.assertTrue(q.isQuorate(1, 2));
+        Assert.assertTrue(q.isQuorate(0, 0));
+    }
+    
+    @Test
+    public void testAtLeastOneUnlessEmptyString() {
+        QuorumCheck q = QuorumChecks.of("atLeastOneUnlessEmpty");
+        Assert.assertFalse(q.isQuorate(0, 2));
+        Assert.assertTrue(q.isQuorate(1, 2));
+        Assert.assertTrue(q.isQuorate(0, 0));
+    }
+    
+    @Test
+    public void testLinearTwoPointsNeedMinTwo() {
+        QuorumCheck q = QuorumChecks.of("[ [0,2], [1,2] ]");
+        Assert.assertTrue(q.isQuorate(2, 2));
+        Assert.assertTrue(q.isQuorate(2, 10));
+        Assert.assertFalse(q.isQuorate(1, 1));
+    }
+    
+    @Test
+    public void testLinearNeedHalfToTenAndTenPercentAtHundred() {
+        QuorumCheck q = QuorumChecks.of("[ [0,0], [10,5], [100,10], [200, 20] ]");
+        Assert.assertTrue(q.isQuorate(2, 2));
+        Assert.assertTrue(q.isQuorate(1, 2));
+        Assert.assertTrue(q.isQuorate(0, 0));
+        Assert.assertFalse(q.isQuorate(1, 10));
+        Assert.assertTrue(q.isQuorate(6, 10));
+        Assert.assertFalse(q.isQuorate(7, 50));
+        Assert.assertTrue(q.isQuorate(8, 50));
+        Assert.assertFalse(q.isQuorate(9, 100));
+        Assert.assertTrue(q.isQuorate(11, 100));
+        Assert.assertFalse(q.isQuorate(19, 200));
+        Assert.assertTrue(q.isQuorate(21, 200));
+        Assert.assertFalse(q.isQuorate(29, 300));
+        Assert.assertTrue(q.isQuorate(31, 300));
+    }
+    
+    
+    
+    
+}


[13/21] incubator-brooklyn git commit: fix use of jclouds blob store wrapper when "container" contains a /, with test, fixing errors with making backups in SL

Posted by he...@apache.org.
fix use of jclouds blob store wrapper when "container" contains a /, with test, fixing errors with making backups in SL


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

Branch: refs/heads/master
Commit: 5060c4f06e80a1b7b8062852a276a8d32bd12237
Parents: a6891c6
Author: Alex Heneveld <al...@cloudsoftcorp.com>
Authored: Thu Nov 13 21:42:36 2014 +0000
Committer: Alex Heneveld <al...@cloudsoftcorp.com>
Committed: Thu Nov 13 23:38:55 2014 +0000

----------------------------------------------------------------------
 .../JcloudsBlobStoreBasedObjectStore.java       | 60 +++++++++++----
 .../JcloudsObjectStoreAccessorWriterTest.java   | 81 +++++++++++++++++++-
 2 files changed, 125 insertions(+), 16 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/5060c4f0/locations/jclouds/src/main/java/brooklyn/entity/rebind/persister/jclouds/JcloudsBlobStoreBasedObjectStore.java
----------------------------------------------------------------------
diff --git a/locations/jclouds/src/main/java/brooklyn/entity/rebind/persister/jclouds/JcloudsBlobStoreBasedObjectStore.java b/locations/jclouds/src/main/java/brooklyn/entity/rebind/persister/jclouds/JcloudsBlobStoreBasedObjectStore.java
index 1e9acf8..93759d6 100644
--- a/locations/jclouds/src/main/java/brooklyn/entity/rebind/persister/jclouds/JcloudsBlobStoreBasedObjectStore.java
+++ b/locations/jclouds/src/main/java/brooklyn/entity/rebind/persister/jclouds/JcloudsBlobStoreBasedObjectStore.java
@@ -40,6 +40,7 @@ import brooklyn.location.jclouds.JcloudsUtil;
 import brooklyn.management.ManagementContext;
 import brooklyn.management.ha.HighAvailabilityMode;
 import brooklyn.util.exceptions.FatalConfigurationRuntimeException;
+import brooklyn.util.text.Strings;
 
 import com.google.common.base.Function;
 import com.google.common.base.Objects;
@@ -53,7 +54,8 @@ public class JcloudsBlobStoreBasedObjectStore implements PersistenceObjectStore
 
     private static final Logger log = LoggerFactory.getLogger(JcloudsBlobStoreBasedObjectStore.class);
 
-    private final String containerName;
+    private final String containerNameFirstPart;
+    private final String containerSubPath;
     
     private String locationSpec;
     private JcloudsLocation location;
@@ -63,17 +65,28 @@ public class JcloudsBlobStoreBasedObjectStore implements PersistenceObjectStore
 
     public JcloudsBlobStoreBasedObjectStore(String locationSpec, String containerName) {
         this.locationSpec = locationSpec;
-        this.containerName = containerName;
+        String[] segments = splitOnce(containerName);
+        this.containerNameFirstPart = segments[0];
+        this.containerSubPath = segments[1];
     }
     
+    private String[] splitOnce(String path) {
+        String separator = subPathSeparator();
+        int index = path.indexOf(separator);
+        if (index<0) return new String[] { path, "" };
+        return new String[] { path.substring(0, index), path.substring(index+separator.length()) };
+    }
+
     public JcloudsBlobStoreBasedObjectStore(JcloudsLocation location, String containerName) {
         this.location = location;
-        this.containerName = containerName;
+        String[] segments = splitOnce(containerName);
+        this.containerNameFirstPart = segments[0];
+        this.containerSubPath = segments[1];
         getBlobStoreContext();
     }
 
     public String getSummaryName() {
-        return (locationSpec!=null ? locationSpec : location)+":"+getContainerName();
+        return (locationSpec!=null ? locationSpec : location)+":"+getContainerNameFull();
     }
     
     public synchronized BlobStoreContext getBlobStoreContext() {
@@ -94,25 +107,40 @@ public class JcloudsBlobStoreBasedObjectStore implements PersistenceObjectStore
             // TODO do we need to get location from region? can't see the jclouds API.
             // doesn't matter in some places because it's already in the endpoint
 //            String region = location.getConfig(CloudLocationConfig.CLOUD_REGION_ID);
-            context.getBlobStore().createContainerInLocation(null, getContainerName());
+            context.getBlobStore().createContainerInLocation(null, getContainerNameFirstPart());
         }
         return context;
     }
 
     @Override
     public void prepareForMasterUse() {
-        // TODO currently backups not supported here, that is all which is needed for master use
-        // (we have already thrown in prepareForSharedUse if backups have been specified as required)
+        // backups not supported here, that is all which is needed for master use
+        // that's now normally done *prior* to calling in to here for writes
+        // (and we have already thrown in prepareForSharedUse if legacy backups have been specified as required)
     }
     
     public String getContainerName() {
-        return containerName;
+        return getContainerNameFull();
+    }
+    
+    protected String getContainerNameFull() {
+        return mergePaths(containerNameFirstPart, containerSubPath);
+    }
+
+    protected String getContainerNameFirstPart() {
+        return containerNameFirstPart;
     }
     
+    protected String getItemInContainerSubPath(String path) {
+        if (Strings.isBlank(containerSubPath)) return path;
+        return mergePaths(containerSubPath, path);
+    }
+
     @Override
     public void createSubPath(String subPath) {
-        // not needed, and buggy on softlayer w swift w jclouds 1.7.2:
-        // throws a "not found" if we're creating an empty directory from scratch
+        // not needed - subpaths are created on demant
+        // (and buggy on softlayer w swift w jclouds 1.7.2:
+        // throws a "not found" if we're creating an empty directory from scratch)
 //        context.getBlobStore().createDirectory(getContainerName(), subPath);
     }
 
@@ -124,7 +152,7 @@ public class JcloudsBlobStoreBasedObjectStore implements PersistenceObjectStore
     @Override
     public StoreObjectAccessor newAccessor(String path) {
         checkPrepared();
-        return new JcloudsStoreObjectAccessor(context.getBlobStore(), getContainerName(), path);
+        return new JcloudsStoreObjectAccessor(context.getBlobStore(), getContainerNameFirstPart(), getItemInContainerSubPath(path));
     }
 
     protected String mergePaths(String basePath, String ...subPaths) {
@@ -146,7 +174,8 @@ public class JcloudsBlobStoreBasedObjectStore implements PersistenceObjectStore
     @Override
     public List<String> listContentsWithSubPath(final String parentSubPath) {
         checkPrepared();
-        return FluentIterable.from(context.getBlobStore().list(getContainerName(), ListContainerOptions.Builder.inDirectory(parentSubPath)))
+        return FluentIterable.from(context.getBlobStore().list(getContainerNameFirstPart(), 
+            ListContainerOptions.Builder.inDirectory(getItemInContainerSubPath(parentSubPath))))
                 .transform(new Function<StorageMetadata, String>() {
                     @Override
                     public String apply(@javax.annotation.Nullable StorageMetadata input) {
@@ -165,7 +194,7 @@ public class JcloudsBlobStoreBasedObjectStore implements PersistenceObjectStore
     public String toString() {
         return Objects.toStringHelper(this)
                 .add("blobStoreContext", context)
-                .add("basedir", containerName)
+                .add("basedir", containerNameFirstPart)
                 .toString();
     }
     
@@ -196,7 +225,10 @@ public class JcloudsBlobStoreBasedObjectStore implements PersistenceObjectStore
 
     @Override
     public void deleteCompletely() {
-        getBlobStoreContext().getBlobStore().deleteContainer(containerName);
+        if (Strings.isBlank(containerSubPath))
+            getBlobStoreContext().getBlobStore().deleteContainer(containerNameFirstPart);
+        else
+            newAccessor(containerSubPath).delete();
     }
     
 }

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/5060c4f0/locations/jclouds/src/test/java/brooklyn/entity/rebind/persister/jclouds/JcloudsObjectStoreAccessorWriterTest.java
----------------------------------------------------------------------
diff --git a/locations/jclouds/src/test/java/brooklyn/entity/rebind/persister/jclouds/JcloudsObjectStoreAccessorWriterTest.java b/locations/jclouds/src/test/java/brooklyn/entity/rebind/persister/jclouds/JcloudsObjectStoreAccessorWriterTest.java
index 8a03501..ef62dee 100644
--- a/locations/jclouds/src/test/java/brooklyn/entity/rebind/persister/jclouds/JcloudsObjectStoreAccessorWriterTest.java
+++ b/locations/jclouds/src/test/java/brooklyn/entity/rebind/persister/jclouds/JcloudsObjectStoreAccessorWriterTest.java
@@ -20,6 +20,8 @@ package brooklyn.entity.rebind.persister.jclouds;
 
 import java.io.IOException;
 
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 import org.testng.annotations.AfterMethod;
 import org.testng.annotations.BeforeMethod;
 import org.testng.annotations.Test;
@@ -32,12 +34,16 @@ import brooklyn.entity.rebind.persister.PersistenceStoreObjectAccessorWriterTest
 import brooklyn.entity.rebind.persister.StoreObjectAccessorLocking;
 import brooklyn.management.ha.HighAvailabilityMode;
 import brooklyn.test.entity.LocalManagementContextForTests;
+import brooklyn.util.exceptions.Exceptions;
+import brooklyn.util.net.Urls;
 import brooklyn.util.text.Identifiers;
 import brooklyn.util.time.Duration;
 
 @Test(groups={"Live", "Live-sanity"})
 public class JcloudsObjectStoreAccessorWriterTest extends PersistenceStoreObjectAccessorWriterTestFixture {
 
+    private static final Logger log = LoggerFactory.getLogger(JcloudsObjectStoreAccessorWriterTest.class);
+    
     private JcloudsBlobStoreBasedObjectStore store;
     private LocalManagementContextForTests mgmt;
 
@@ -58,7 +64,10 @@ public class JcloudsObjectStoreAccessorWriterTest extends PersistenceStoreObject
     }
     
     protected StoreObjectAccessorWithLock newPersistenceStoreObjectAccessor() throws IOException {
-        return new StoreObjectAccessorLocking(store.newAccessor("sample-file-"+Identifiers.makeRandomId(4)));
+        return newPersistenceStoreObjectAccessor(store, "");
+    }
+    protected StoreObjectAccessorWithLock newPersistenceStoreObjectAccessor(JcloudsBlobStoreBasedObjectStore aStore, String prefix) throws IOException {
+        return new StoreObjectAccessorLocking(aStore.newAccessor(prefix+"sample-file-"+Identifiers.makeRandomId(4)));
     }
 
     @Override
@@ -72,7 +81,75 @@ public class JcloudsObjectStoreAccessorWriterTest extends PersistenceStoreObject
         // bit smaller since it's actually uploading here!
         return 10000;
     }
-    
+
+    /** Tests what happen when we ask the store to be in a container with a path, e.g. path1/path2 
+     * and then the accessor to a file within that (path3/file) --
+     * this does it an emulated way, where the store tracks the subpath so we don't have to */
+    @Test(groups={"Live"})
+    public void testNestedPath1() throws IOException {
+        mgmt = new LocalManagementContextForTests(BrooklynProperties.Factory.newDefault());
+        String path1 = BlobStoreTest.CONTAINER_PREFIX+"-"+Identifiers.makeRandomId(4);
+        String path2 = BlobStoreTest.CONTAINER_PREFIX+"-"+Identifiers.makeRandomId(4);
+        String path3 = BlobStoreTest.CONTAINER_PREFIX+"-"+Identifiers.makeRandomId(4);
+        JcloudsBlobStoreBasedObjectStore store0 = null;
+        try {
+            store0 = new JcloudsBlobStoreBasedObjectStore(BlobStoreTest.PERSIST_TO_OBJECT_STORE_FOR_TEST_SPEC, Urls.mergePaths(path1, path2));
+            store0.injectManagementContext(mgmt);
+            store0.prepareForSharedUse(PersistMode.CLEAN, HighAvailabilityMode.DISABLED);
+
+            newPersistenceStoreObjectAccessor(store0, path3+"/").put("hello world");
+        } catch (Exception e) {
+            log.warn("Failed with: "+e, e);
+            throw Exceptions.propagate(e);
+            
+        } finally {
+            store0.deleteCompletely();
+            
+            JcloudsBlobStoreBasedObjectStore storeD = new JcloudsBlobStoreBasedObjectStore(BlobStoreTest.PERSIST_TO_OBJECT_STORE_FOR_TEST_SPEC, path1);
+            storeD.injectManagementContext(mgmt);
+            storeD.prepareForSharedUse(PersistMode.CLEAN, HighAvailabilityMode.DISABLED);
+            storeD.deleteCompletely();
+        }
+    }
+
+    /** Tests what happen when we ask the store to be in a container with a path, e.g. path1/path2 
+     * and then the accessor to a file within that (path3/file) --
+     * this does it the "official" way, where we ask for the store's container
+     * to be the first path segment */
+    @Test(groups={"Live"})
+    public void testNestedPath2() throws IOException {
+        mgmt = new LocalManagementContextForTests(BrooklynProperties.Factory.newDefault());
+        String path1 = BlobStoreTest.CONTAINER_PREFIX+"-"+Identifiers.makeRandomId(4);
+        String path2 = BlobStoreTest.CONTAINER_PREFIX+"-"+Identifiers.makeRandomId(4);
+        String path3 = BlobStoreTest.CONTAINER_PREFIX+"-"+Identifiers.makeRandomId(4);
+        JcloudsBlobStoreBasedObjectStore store1 = null, store2 = null;
+        try {
+            store1 = new JcloudsBlobStoreBasedObjectStore(BlobStoreTest.PERSIST_TO_OBJECT_STORE_FOR_TEST_SPEC, 
+                path1);
+            store1.injectManagementContext(mgmt);
+            store1.prepareForSharedUse(PersistMode.CLEAN, HighAvailabilityMode.DISABLED);
+            store1.createSubPath(path2);
+            newPersistenceStoreObjectAccessor(store1, path2+"/"+path3+"/").put("hello world");
+            
+            store2 = new JcloudsBlobStoreBasedObjectStore(BlobStoreTest.PERSIST_TO_OBJECT_STORE_FOR_TEST_SPEC, 
+                Urls.mergePaths(path1, path2));
+            store2.injectManagementContext(mgmt);
+            store2.prepareForSharedUse(PersistMode.CLEAN, HighAvailabilityMode.DISABLED);
+
+            newPersistenceStoreObjectAccessor(store2, path3+"/").put("hello world");
+            
+        } catch (Exception e) {
+            e.printStackTrace();
+        } finally {
+            // this doesn't work
+//            store2.deleteCompletely();
+            // this is how you have to do it:
+            store1.newAccessor(path2).delete();
+            
+            store1.deleteCompletely();
+        }
+    }
+
     @Test(groups={"Live", "Live-sanity"})
     @Override
     public void testWriteBacklogThenDeleteWillLeaveFileDeleted() throws Exception {


[19/21] incubator-brooklyn git commit: Fix DNS entity reverse lookup domain

Posted by he...@apache.org.
Fix DNS entity reverse lookup domain


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

Branch: refs/heads/master
Commit: 3d3a8f495f82dc40469a96659b61f871a0e096c5
Parents: 8ebeebb
Author: Sam Corbett <sa...@cloudsoftcorp.com>
Authored: Fri Nov 14 21:40:02 2014 +0000
Committer: Sam Corbett <sa...@cloudsoftcorp.com>
Committed: Fri Nov 14 21:40:02 2014 +0000

----------------------------------------------------------------------
 .../main/java/brooklyn/entity/network/bind/BindDnsServerImpl.java | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/3d3a8f49/software/network/src/main/java/brooklyn/entity/network/bind/BindDnsServerImpl.java
----------------------------------------------------------------------
diff --git a/software/network/src/main/java/brooklyn/entity/network/bind/BindDnsServerImpl.java b/software/network/src/main/java/brooklyn/entity/network/bind/BindDnsServerImpl.java
index fc570aa..5eb12a7 100644
--- a/software/network/src/main/java/brooklyn/entity/network/bind/BindDnsServerImpl.java
+++ b/software/network/src/main/java/brooklyn/entity/network/bind/BindDnsServerImpl.java
@@ -164,7 +164,8 @@ public class BindDnsServerImpl extends SoftwareProcessImpl implements BindDnsSer
         String reverse = getConfig(REVERSE_LOOKUP_NETWORK);
         if (Strings.isBlank(reverse)) reverse = getAttribute(ADDRESS);
         setAttribute(REVERSE_LOOKUP_CIDR, new Cidr(reverse + "/24"));
-        String reverseLookupDomain = Iterables.toString(Iterables.skip(Lists.reverse(Lists.newArrayList(Splitter.on('.').split(reverse))), 1)) + ".in-addr.arpa";
+        String reverseLookupDomain = Joiner.on('.').join(Iterables.skip(Lists.reverse(Lists.newArrayList(
+                Splitter.on('.').split(reverse))), 1)) + ".in-addr.arpa";
         setAttribute(REVERSE_LOOKUP_DOMAIN, reverseLookupDomain);
 
         addPolicy(PolicySpec.create(MemberTrackingPolicy.class)


[21/21] incubator-brooklyn git commit: This closes #329

Posted by he...@apache.org.
This closes #329


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

Branch: refs/heads/master
Commit: c4061feaf964c3caacb437a9980406eeea9f091d
Parents: 36b2f68 3d3a8f4
Author: Alex Heneveld <al...@cloudsoftcorp.com>
Authored: Sat Nov 15 00:04:05 2014 +0000
Committer: Alex Heneveld <al...@cloudsoftcorp.com>
Committed: Sat Nov 15 00:04:05 2014 +0000

----------------------------------------------------------------------
 .../entity/network/bind/BindDnsServerImpl.java  |  3 +-
 .../rest/util/json/BidiSerialization.java       |  9 +-
 .../util/json/BrooklynJacksonJsonProvider.java  | 86 +++++++++++---------
 .../rest/util/json/MultimapSerializer.java      | 63 ++++++++++++++
 .../json/BrooklynJacksonSerializerTest.java     | 19 ++++-
 5 files changed, 134 insertions(+), 46 deletions(-)
----------------------------------------------------------------------



[09/21] incubator-brooklyn git commit: specify a filename when people download service state

Posted by he...@apache.org.
specify a filename when people download service state


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

Branch: refs/heads/master
Commit: ed805673797a032d002aa941a3e2eab32aef9881
Parents: f6cd031
Author: Alex Heneveld <al...@cloudsoftcorp.com>
Authored: Thu Nov 13 17:34:24 2014 +0000
Committer: Alex Heneveld <al...@cloudsoftcorp.com>
Committed: Thu Nov 13 23:38:54 2014 +0000

----------------------------------------------------------------------
 .../main/java/brooklyn/rest/resources/ServerResource.java   | 9 ++++++---
 1 file changed, 6 insertions(+), 3 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/ed805673/usage/rest-server/src/main/java/brooklyn/rest/resources/ServerResource.java
----------------------------------------------------------------------
diff --git a/usage/rest-server/src/main/java/brooklyn/rest/resources/ServerResource.java b/usage/rest-server/src/main/java/brooklyn/rest/resources/ServerResource.java
index 50f99e1..ceb688c 100644
--- a/usage/rest-server/src/main/java/brooklyn/rest/resources/ServerResource.java
+++ b/usage/rest-server/src/main/java/brooklyn/rest/resources/ServerResource.java
@@ -58,7 +58,6 @@ import brooklyn.rest.domain.VersionSummary;
 import brooklyn.rest.transform.HighAvailabilityTransformer;
 import brooklyn.rest.util.WebResourceUtils;
 import brooklyn.util.ResourceUtils;
-import brooklyn.util.collections.MutableMap;
 import brooklyn.util.exceptions.Exceptions;
 import brooklyn.util.file.ArchiveBuilder;
 import brooklyn.util.flags.TypeCoercions;
@@ -302,13 +301,17 @@ public class ServerResource extends AbstractBrooklynRestResource implements Serv
             throw WebResourceUtils.unauthorized("User '%s' is not authorized for this operation", Entitlements.getEntitlementContext().user());
 
         try {
+            String label = mgmt().getManagementNodeId()+"-"+Time.makeDateSimpleStampString();
             PersistenceObjectStore targetStore = BrooklynPersistenceUtils.newPersistenceObjectStore(mgmt(), null, 
-                "web-persistence-"+mgmt().getManagementNodeId()+"-"+Time.makeDateStampString()+"-"+Identifiers.makeRandomId(4));
+                "web-persistence-"+label+"-"+Identifiers.makeRandomId(4));
             BrooklynPersistenceUtils.writeMemento(mgmt(), targetStore, preferredOrigin);            
             
             ByteArrayOutputStream baos = new ByteArrayOutputStream();
             ArchiveBuilder.zip().addDirContentsAt( ((FileBasedObjectStore)targetStore).getBaseDir(), "/" ).stream(baos);
-            return Response.ok(baos.toByteArray(), MediaType.APPLICATION_OCTET_STREAM_TYPE).build();
+            String filename = "brooklyn-state-"+label+".zip";
+            return Response.ok(baos.toByteArray(), MediaType.APPLICATION_OCTET_STREAM_TYPE)
+                .header("Content-Disposition","attachment; filename = "+filename)
+                .build();
         } catch (Exception e) {
             log.warn("Unable to serve persistence data (rethrowing): "+e, e);
             throw Exceptions.propagate(e);


[10/21] incubator-brooklyn git commit: code review to #314, use enum and create unmanaged location

Posted by he...@apache.org.
code review to #314, use enum and create unmanaged location


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

Branch: refs/heads/master
Commit: f6cd031428f2f355d56e6eedef14fcabb0abd8e5
Parents: f90faf4
Author: Alex Heneveld <al...@cloudsoftcorp.com>
Authored: Thu Nov 13 15:33:31 2014 +0000
Committer: Alex Heneveld <al...@cloudsoftcorp.com>
Committed: Thu Nov 13 23:38:54 2014 +0000

----------------------------------------------------------------------
 .../entity/rebind/RebindManagerImpl.java        |  3 +-
 .../persister/BrooklynPersistenceUtils.java     | 51 +++++++++++---------
 .../ha/HighAvailabilityManagerImpl.java         |  9 ++--
 3 files changed, 34 insertions(+), 29 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/f6cd0314/core/src/main/java/brooklyn/entity/rebind/RebindManagerImpl.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/entity/rebind/RebindManagerImpl.java b/core/src/main/java/brooklyn/entity/rebind/RebindManagerImpl.java
index e24a818..9dad632 100644
--- a/core/src/main/java/brooklyn/entity/rebind/RebindManagerImpl.java
+++ b/core/src/main/java/brooklyn/entity/rebind/RebindManagerImpl.java
@@ -59,6 +59,7 @@ import brooklyn.entity.proxying.InternalPolicyFactory;
 import brooklyn.entity.rebind.persister.BrooklynMementoPersisterToObjectStore;
 import brooklyn.entity.rebind.persister.BrooklynPersistenceUtils;
 import brooklyn.entity.rebind.persister.PersistenceActivityMetrics;
+import brooklyn.entity.rebind.persister.BrooklynPersistenceUtils.CreateBackupMode;
 import brooklyn.event.feed.AbstractFeed;
 import brooklyn.internal.BrooklynFeatureEnablement;
 import brooklyn.location.Location;
@@ -285,7 +286,7 @@ public class RebindManagerImpl implements RebindManager {
         LOG.debug("Starting persistence ("+this+"), mgmt "+managementContext.getManagementNodeId());
         if (!persistenceRunning) {
             if (managementContext.getBrooklynProperties().getConfig(BrooklynServerConfig.PERSISTENCE_BACKUPS_REQUIRED_ON_PROMOTION)) {
-                BrooklynPersistenceUtils.createBackup(managementContext, "promotion", MementoCopyMode.REMOTE);
+                BrooklynPersistenceUtils.createBackup(managementContext, CreateBackupMode.PROMOTION, MementoCopyMode.REMOTE);
             }
         }
         persistenceRunning = true;

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/f6cd0314/core/src/main/java/brooklyn/entity/rebind/persister/BrooklynPersistenceUtils.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/entity/rebind/persister/BrooklynPersistenceUtils.java b/core/src/main/java/brooklyn/entity/rebind/persister/BrooklynPersistenceUtils.java
index 8b5317d..ea52f36 100644
--- a/core/src/main/java/brooklyn/entity/rebind/persister/BrooklynPersistenceUtils.java
+++ b/core/src/main/java/brooklyn/entity/rebind/persister/BrooklynPersistenceUtils.java
@@ -46,6 +46,7 @@ import brooklyn.management.ha.ManagementNodeState;
 import brooklyn.management.ha.ManagementPlaneSyncRecord;
 import brooklyn.management.ha.ManagementPlaneSyncRecordPersisterToObjectStore;
 import brooklyn.management.ha.MementoCopyMode;
+import brooklyn.management.internal.LocalLocationManager;
 import brooklyn.management.internal.ManagementContextInternal;
 import brooklyn.mementos.BrooklynMementoRawData;
 import brooklyn.mementos.Memento;
@@ -85,17 +86,14 @@ public class BrooklynPersistenceUtils {
         locationContainer = BrooklynServerPaths.newMainPersistencePathResolver(managementContext).location(locationSpec).dir(locationContainer).resolve();
 
         Location location = null;
-        try {
-            if (Strings.isBlank(locationSpec)) {
-                location = managementContext.getLocationManager().createLocation(LocationSpec.create(LocalhostMachineProvisioningLocation.class));
-            } else {
-                location = managementContext.getLocationRegistry().resolve(locationSpec);
-                if (!(location instanceof LocationWithObjectStore)) {
-                    throw new IllegalArgumentException("Destination location "+location+" does not offer a persistent store");
-                }
+        if (Strings.isBlank(locationSpec)) {
+            location = managementContext.getLocationManager().createLocation(LocationSpec.create(LocalhostMachineProvisioningLocation.class)
+                .configure(LocalLocationManager.CREATE_UNMANAGED, true));
+        } else {
+            location = managementContext.getLocationRegistry().resolve(locationSpec, false, null).get();
+            if (!(location instanceof LocationWithObjectStore)) {
+                throw new IllegalArgumentException("Destination location "+location+" does not offer a persistent store");
             }
-        } finally {
-            if (location!=null) managementContext.getLocationManager().unmanage(location);
         }
         destinationObjectStore = ((LocationWithObjectStore)location).newPersistenceObjectStore(locationContainer);
         
@@ -204,35 +202,42 @@ public class BrooklynPersistenceUtils {
         log.debug("Wrote full memento to "+targetStore+" in "+Time.makeTimeStringRounded(Duration.of(timer)));
     }
 
-    public static void createBackup(ManagementContext managementContext, String label, MementoCopyMode source) {
+    public static enum CreateBackupMode { PROMOTION, DEMOTION, CUSTOM;
+        @Override public String toString() { return super.toString().toLowerCase(); }
+    }
+    
+    public static void createBackup(ManagementContext managementContext, CreateBackupMode mode, MementoCopyMode source) {
         if (source==null || source==MementoCopyMode.AUTO) {
-            if ("promotion".equalsIgnoreCase(label)) source = MementoCopyMode.REMOTE;
-            else if ("demotion".equalsIgnoreCase(label)) source = MementoCopyMode.LOCAL;
-            else throw new IllegalArgumentException("Cannot detect copy mode for "+label+"/"+source);
+            switch (mode) {
+            case PROMOTION: source = MementoCopyMode.REMOTE; break;
+            case DEMOTION: source = MementoCopyMode.LOCAL; break;
+            default:
+                throw new IllegalArgumentException("Cannot detect copy mode for "+mode+"/"+source);
+            }
         }
         BrooklynMementoRawData memento = null;
         ManagementPlaneSyncRecord planeState = null;
         
         try {
-            log.debug("Loading persisted state on "+label+" for backup purposes");
+            log.debug("Loading persisted state on "+mode+" for backup purposes");
             memento = newStateMemento(managementContext, source);
             try {
                 planeState = newManagerMemento(managementContext, source);
             } catch (Exception e) {
                 Exceptions.propagateIfFatal(e);
-                log.warn("Unable to access management plane sync state on "+label+" (ignoring): "+e, e);
+                log.warn("Unable to access management plane sync state on "+mode+" (ignoring): "+e, e);
             }
         
             PersistenceObjectStore destinationObjectStore = null;
             String backupSpec = managementContext.getConfig().getConfig(BrooklynServerConfig.PERSISTENCE_BACKUPS_LOCATION_SPEC);
             try {
                 String backupContainer = BrooklynServerPaths.newBackupPersistencePathResolver(managementContext).location(backupSpec)
-                    .resolveWithSubpathFor(managementContext, label);
+                    .resolveWithSubpathFor(managementContext, mode.toString());
                 destinationObjectStore = BrooklynPersistenceUtils.newPersistenceObjectStore(managementContext, backupSpec, backupContainer);
-                log.debug("Backing up persisted state on "+label+", to "+destinationObjectStore.getSummaryName());
+                log.debug("Backing up persisted state on "+mode+", to "+destinationObjectStore.getSummaryName());
                 BrooklynPersistenceUtils.writeMemento(managementContext, memento, destinationObjectStore);
                 BrooklynPersistenceUtils.writeManagerMemento(managementContext, planeState, destinationObjectStore);
-                log.info("Back-up of persisted state created on "+label+", in "+destinationObjectStore.getSummaryName());
+                log.info("Back-up of persisted state created on "+mode+", in "+destinationObjectStore.getSummaryName());
                 
             } catch (Exception e) {
                 Exceptions.propagateIfFatal(e);
@@ -240,19 +245,19 @@ public class BrooklynPersistenceUtils {
                 if (!Strings.isBlank(backupSpec) && !"localhost".equals(backupSpec)) {
                     backupSpec = "localhost";
                     String backupContainer = BrooklynServerPaths.newBackupPersistencePathResolver(managementContext).location(backupSpec)
-                        .resolveWithSubpathFor(managementContext, label);
+                        .resolveWithSubpathFor(managementContext, mode.toString());
                     destinationObjectStore = BrooklynPersistenceUtils.newPersistenceObjectStore(managementContext, backupSpec, backupContainer);
                     log.warn("Persisted state back-up to "+failedStore.getSummaryName()+" failed with "+e, e);
                     
-                    log.debug("Backing up persisted state on "+label+", locally because remote failed, to "+destinationObjectStore.getSummaryName());
+                    log.debug("Backing up persisted state on "+mode+", locally because remote failed, to "+destinationObjectStore.getSummaryName());
                     BrooklynPersistenceUtils.writeMemento(managementContext, memento, destinationObjectStore);
                     BrooklynPersistenceUtils.writeManagerMemento(managementContext, planeState, destinationObjectStore);
-                    log.info("Back-up of persisted state created on "+label+", locally because remote failed, in "+destinationObjectStore.getSummaryName());
+                    log.info("Back-up of persisted state created on "+mode+", locally because remote failed, in "+destinationObjectStore.getSummaryName());
                 }
             }
         } catch (Exception e) {
             Exceptions.propagateIfFatal(e);
-            log.warn("Unable to backup management plane sync state on "+label+" (ignoring): "+e, e);
+            log.warn("Unable to backup management plane sync state on "+mode+" (ignoring): "+e, e);
         }
     }
 }

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/f6cd0314/core/src/main/java/brooklyn/management/ha/HighAvailabilityManagerImpl.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/management/ha/HighAvailabilityManagerImpl.java b/core/src/main/java/brooklyn/management/ha/HighAvailabilityManagerImpl.java
index 73d431d..08c1d06 100644
--- a/core/src/main/java/brooklyn/management/ha/HighAvailabilityManagerImpl.java
+++ b/core/src/main/java/brooklyn/management/ha/HighAvailabilityManagerImpl.java
@@ -44,6 +44,7 @@ import brooklyn.entity.basic.ConfigKeys;
 import brooklyn.entity.basic.EntityInternal;
 import brooklyn.entity.rebind.RebindManager;
 import brooklyn.entity.rebind.persister.BrooklynPersistenceUtils;
+import brooklyn.entity.rebind.persister.BrooklynPersistenceUtils.CreateBackupMode;
 import brooklyn.entity.rebind.persister.PersistenceActivityMetrics;
 import brooklyn.entity.rebind.plane.dto.BasicManagementNodeSyncRecord;
 import brooklyn.entity.rebind.plane.dto.ManagementPlaneSyncRecordImpl;
@@ -174,9 +175,7 @@ public class HighAvailabilityManagerImpl implements HighAvailabilityManager {
      * including e.g. {@link Duration#PRACTICALLY_FOREVER} to disable polling;
      * or <code>null</code> to clear a local override */
     public HighAvailabilityManagerImpl setPollPeriod(Duration val) {
-        synchronized (this) {
-            this.pollPeriodLocalOverride = val;
-        }
+        this.pollPeriodLocalOverride = val;
         if (running) {
             registerPollTask();
         }
@@ -196,7 +195,7 @@ public class HighAvailabilityManagerImpl implements HighAvailabilityManager {
     /** Overrides {@link #HEARTBEAT_TIMEOUT} from brooklyn config, 
      * including e.g. {@link Duration#PRACTICALLY_FOREVER} to prevent failover due to heartbeat absence;
      * or <code>null</code> to clear a local override */
-    public synchronized HighAvailabilityManagerImpl setHeartbeatTimeout(Duration val) {
+    public HighAvailabilityManagerImpl setHeartbeatTimeout(Duration val) {
         this.heartbeatTimeoutOverride = val;
         return this;
     }
@@ -776,7 +775,7 @@ public class HighAvailabilityManagerImpl implements HighAvailabilityManager {
     
     protected void backupOnDemotionIfNeeded() {
         if (managementContext.getBrooklynProperties().getConfig(BrooklynServerConfig.PERSISTENCE_BACKUPS_REQUIRED_ON_DEMOTION)) {
-            BrooklynPersistenceUtils.createBackup(managementContext, "demotion", MementoCopyMode.LOCAL);
+            BrooklynPersistenceUtils.createBackup(managementContext, CreateBackupMode.DEMOTION, MementoCopyMode.LOCAL);
         }
     }
 


[08/21] incubator-brooklyn git commit: Make metrics on rebind, persistence, and HA available through REST API.

Posted by he...@apache.org.
Make metrics on rebind, persistence, and HA available through REST API.

So that a management plane or human can determine server health and historic problems.


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

Branch: refs/heads/master
Commit: b9c1b6fca99165f62b712ec7fd7967f7e4b4e283
Parents: 9dd1a95
Author: Alex Heneveld <al...@cloudsoftcorp.com>
Authored: Thu Nov 13 03:42:54 2014 +0000
Committer: Alex Heneveld <al...@cloudsoftcorp.com>
Committed: Thu Nov 13 23:38:53 2014 +0000

----------------------------------------------------------------------
 .../entity/rebind/RebindExceptionHandler.java   |   6 +
 .../brooklyn/entity/rebind/RebindManager.java   |   5 +
 .../management/ha/HighAvailabilityManager.java  |   6 +
 .../rebind/PeriodicDeltaChangeListener.java     |  23 +--
 .../rebind/RebindExceptionHandlerImpl.java      |  54 +++++--
 .../entity/rebind/RebindManagerImpl.java        |  53 ++++++-
 .../persister/BrooklynPersistenceUtils.java     |   1 -
 .../persister/PersistenceActivityMetrics.java   |  83 ++++++++++
 .../ha/HighAvailabilityManagerImpl.java         | 155 ++++++++++++++-----
 .../NonDeploymentManagementContext.java         |   9 ++
 .../main/java/brooklyn/rest/api/ServerApi.java  |   7 +
 .../brooklyn/rest/resources/ServerResource.java |   7 +
 12 files changed, 346 insertions(+), 63 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/b9c1b6fc/api/src/main/java/brooklyn/entity/rebind/RebindExceptionHandler.java
----------------------------------------------------------------------
diff --git a/api/src/main/java/brooklyn/entity/rebind/RebindExceptionHandler.java b/api/src/main/java/brooklyn/entity/rebind/RebindExceptionHandler.java
index 61bcdaa..ce0af83 100644
--- a/api/src/main/java/brooklyn/entity/rebind/RebindExceptionHandler.java
+++ b/api/src/main/java/brooklyn/entity/rebind/RebindExceptionHandler.java
@@ -18,6 +18,8 @@
  */
 package brooklyn.entity.rebind;
 
+import java.util.List;
+
 import brooklyn.basic.BrooklynObject;
 import brooklyn.catalog.CatalogItem;
 import brooklyn.entity.Entity;
@@ -100,4 +102,8 @@ public interface RebindExceptionHandler {
     
     /** invoked after the complete rebind pass, always on success and possibly on failure */
     void onDone();
+    
+    List<Exception> getExceptions();
+    List<String> getWarnings();
+
 }

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/b9c1b6fc/api/src/main/java/brooklyn/entity/rebind/RebindManager.java
----------------------------------------------------------------------
diff --git a/api/src/main/java/brooklyn/entity/rebind/RebindManager.java b/api/src/main/java/brooklyn/entity/rebind/RebindManager.java
index fcd71bc..8f34c5a 100644
--- a/api/src/main/java/brooklyn/entity/rebind/RebindManager.java
+++ b/api/src/main/java/brooklyn/entity/rebind/RebindManager.java
@@ -19,6 +19,7 @@
 package brooklyn.entity.rebind;
 
 import java.util.List;
+import java.util.Map;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.TimeoutException;
 
@@ -124,4 +125,8 @@ public interface RebindManager {
      * setting if the process fails after the clear!) */
     @VisibleForTesting
     public void forcePersistNow(boolean full, @Nullable PersistenceExceptionHandler exceptionHandler);
+
+    /** Metrics about rebind, last success, etc. */
+    public Map<String,Object> getMetrics();
+    
 }

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/b9c1b6fc/api/src/main/java/brooklyn/management/ha/HighAvailabilityManager.java
----------------------------------------------------------------------
diff --git a/api/src/main/java/brooklyn/management/ha/HighAvailabilityManager.java b/api/src/main/java/brooklyn/management/ha/HighAvailabilityManager.java
index de37123..597e407 100644
--- a/api/src/main/java/brooklyn/management/ha/HighAvailabilityManager.java
+++ b/api/src/main/java/brooklyn/management/ha/HighAvailabilityManager.java
@@ -18,6 +18,8 @@
  */
 package brooklyn.management.ha;
 
+import java.util.Map;
+
 import com.google.common.annotations.Beta;
 import com.google.common.annotations.VisibleForTesting;
 
@@ -118,4 +120,8 @@ public interface HighAvailabilityManager {
     
     @VisibleForTesting
     ManagementPlaneSyncRecordPersister getPersister();
+
+    /** Returns a collection of metrics */
+    Map<String,Object> getMetrics();
+    
 }

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/b9c1b6fc/core/src/main/java/brooklyn/entity/rebind/PeriodicDeltaChangeListener.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/entity/rebind/PeriodicDeltaChangeListener.java b/core/src/main/java/brooklyn/entity/rebind/PeriodicDeltaChangeListener.java
index 08ca53c..da35459 100644
--- a/core/src/main/java/brooklyn/entity/rebind/PeriodicDeltaChangeListener.java
+++ b/core/src/main/java/brooklyn/entity/rebind/PeriodicDeltaChangeListener.java
@@ -38,10 +38,10 @@ import brooklyn.entity.Feed;
 import brooklyn.entity.basic.BrooklynTaskTags;
 import brooklyn.entity.basic.EntityInternal;
 import brooklyn.entity.rebind.persister.BrooklynPersistenceUtils;
+import brooklyn.entity.rebind.persister.PersistenceActivityMetrics;
 import brooklyn.internal.BrooklynFeatureEnablement;
 import brooklyn.location.Location;
 import brooklyn.management.ExecutionContext;
-import brooklyn.management.ExecutionManager;
 import brooklyn.management.Task;
 import brooklyn.mementos.BrooklynMementoPersister;
 import brooklyn.policy.Enricher;
@@ -50,7 +50,6 @@ import brooklyn.util.collections.MutableMap;
 import brooklyn.util.collections.MutableSet;
 import brooklyn.util.exceptions.Exceptions;
 import brooklyn.util.exceptions.RuntimeInterruptedException;
-import brooklyn.util.task.BasicExecutionContext;
 import brooklyn.util.task.ScheduledTask;
 import brooklyn.util.task.Tasks;
 import brooklyn.util.time.CountdownTimer;
@@ -59,6 +58,7 @@ import brooklyn.util.time.Time;
 
 import com.google.api.client.util.Lists;
 import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Stopwatch;
 import com.google.common.collect.Iterables;
 import com.google.common.collect.Sets;
 
@@ -178,17 +178,14 @@ public class PeriodicDeltaChangeListener implements ChangeListener {
     
     private final Semaphore persistingMutex = new Semaphore(1);
     private final Object startMutex = new Object();
+
+    private PersistenceActivityMetrics metrics;
     
-    /** @deprecated since 0.7.0 pass in an {@link ExecutionContext} and a {@link Duration} */
-    @Deprecated
-    public PeriodicDeltaChangeListener(ExecutionManager executionManager, BrooklynMementoPersister persister, PersistenceExceptionHandler exceptionHandler, long periodMillis) {
-        this(new BasicExecutionContext(executionManager), persister, exceptionHandler, Duration.of(periodMillis, TimeUnit.MILLISECONDS));
-    }
-    
-    public PeriodicDeltaChangeListener(ExecutionContext executionContext, BrooklynMementoPersister persister, PersistenceExceptionHandler exceptionHandler, Duration period) {
+    public PeriodicDeltaChangeListener(ExecutionContext executionContext, BrooklynMementoPersister persister, PersistenceExceptionHandler exceptionHandler, PersistenceActivityMetrics metrics, Duration period) {
         this.executionContext = executionContext;
         this.persister = persister;
         this.exceptionHandler = exceptionHandler;
+        this.metrics = metrics;
         this.period = period;
         
         this.persistPoliciesEnabled = BrooklynFeatureEnablement.isEnabled(BrooklynFeatureEnablement.FEATURE_POLICY_PERSISTENCE_PROPERTY);
@@ -210,20 +207,28 @@ public class PeriodicDeltaChangeListener implements ChangeListener {
                 @Override public Task<Void> call() {
                     return Tasks.<Void>builder().dynamic(false).name("periodic-persister").body(new Callable<Void>() {
                         public Void call() {
+                            Stopwatch timer = Stopwatch.createStarted();
                             try {
                                 persistNow();
+                                metrics.noteSuccess(Duration.of(timer));
                                 return null;
                             } catch (RuntimeInterruptedException e) {
                                 LOG.debug("Interrupted persisting change-delta (rethrowing)", e);
+                                metrics.noteFailure(Duration.of(timer));
+                                metrics.noteError(e.toString());
                                 Thread.currentThread().interrupt();
                                 return null;
                             } catch (Exception e) {
                                 // Don't rethrow: the behaviour of executionManager is different from a scheduledExecutorService,
                                 // if we throw an exception, then our task will never get executed again
                                 LOG.error("Problem persisting change-delta", e);
+                                metrics.noteFailure(Duration.of(timer));
+                                metrics.noteError(e.toString());
                                 return null;
                             } catch (Throwable t) {
                                 LOG.warn("Problem persisting change-delta (rethrowing)", t);
+                                metrics.noteFailure(Duration.of(timer));
+                                metrics.noteError(t.toString());
                                 throw Exceptions.propagate(t);
                             }
                         }}).build();

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/b9c1b6fc/core/src/main/java/brooklyn/entity/rebind/RebindExceptionHandlerImpl.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/entity/rebind/RebindExceptionHandlerImpl.java b/core/src/main/java/brooklyn/entity/rebind/RebindExceptionHandlerImpl.java
index ac41c8b..2b61173 100644
--- a/core/src/main/java/brooklyn/entity/rebind/RebindExceptionHandlerImpl.java
+++ b/core/src/main/java/brooklyn/entity/rebind/RebindExceptionHandlerImpl.java
@@ -41,6 +41,7 @@ import brooklyn.util.collections.QuorumCheck;
 import brooklyn.util.exceptions.Exceptions;
 import brooklyn.util.text.Strings;
 
+import com.google.common.collect.ImmutableList;
 import com.google.common.collect.Lists;
 import com.google.common.collect.Sets;
 
@@ -62,9 +63,12 @@ public class RebindExceptionHandlerImpl implements RebindExceptionHandler {
     protected final Set<String> missingFeeds = Sets.newConcurrentHashSet();
     protected final Set<String> missingCatalogItems = Sets.newConcurrentHashSet();
     protected final Set<String> creationFailedIds = Sets.newConcurrentHashSet();
+    
     protected final Set<Exception> addPolicyFailures = Sets.newConcurrentHashSet();
     protected final Set<Exception> loadPolicyFailures = Sets.newConcurrentHashSet();
-    protected final List<Exception> exceptions = Collections.synchronizedList(Lists.<Exception>newArrayList());
+    
+    protected final Set<String> warnings = Collections.synchronizedSet(Sets.<String>newLinkedHashSet());
+    protected final Set<Exception> exceptions = Collections.synchronizedSet(Sets.<Exception>newLinkedHashSet());
     
     protected RebindContext context;
     protected boolean started = false;
@@ -113,6 +117,15 @@ public class RebindExceptionHandlerImpl implements RebindExceptionHandler {
         this.loadPolicyFailureMode = checkNotNull(builder.deserializePolicyFailureMode, "deserializePolicyFailureMode");
         this.danglingRefsQuorumRequiredHealthy = checkNotNull(builder.danglingRefsQuorumRequiredHealthy, "danglingRefsQuorumRequiredHealthy");
     }
+    
+    protected void warn(String message) {
+        warn(message, null);
+    }
+    protected void warn(String message, Throwable optionalError) {
+        if (optionalError==null) LOG.warn(message);
+        else LOG.warn(message, optionalError);
+        warnings.add(message);
+    }
 
     @Override
     public void onStart(RebindContext context) {
@@ -142,7 +155,7 @@ public class RebindExceptionHandlerImpl implements RebindExceptionHandler {
                         loadPolicyFailures.add(new IllegalStateException(errmsg, e));
                         break;
                     case CONTINUE:
-                        LOG.warn(errmsg+"; continuing", e);
+                        warn(errmsg+"; continuing: "+e, e);
                         break;
                     default:
                         throw new IllegalStateException("Unexpected state '"+loadPolicyFailureMode+"' for loadPolicyFailureMode");
@@ -160,7 +173,7 @@ public class RebindExceptionHandlerImpl implements RebindExceptionHandler {
         if (danglingRefFailureMode == RebindManager.RebindFailureMode.FAIL_FAST) {
             throw new IllegalStateException("No entity found with id "+id);
         } else {
-            LOG.warn("No entity found with id "+id+"; returning null");
+            warn("No entity found with id "+id+"; returning null");
             return null;
         }
     }
@@ -171,7 +184,7 @@ public class RebindExceptionHandlerImpl implements RebindExceptionHandler {
         if (danglingRefFailureMode == RebindManager.RebindFailureMode.FAIL_FAST) {
             throw new IllegalStateException("No location found with id "+id);
         } else {
-            LOG.warn("No location found with id "+id+"; returning null");
+            warn("No location found with id "+id+"; returning null");
             return null;
         }
     }
@@ -182,7 +195,7 @@ public class RebindExceptionHandlerImpl implements RebindExceptionHandler {
         if (danglingRefFailureMode == RebindManager.RebindFailureMode.FAIL_FAST) {
             throw new IllegalStateException("No policy found with id "+id);
         } else {
-            LOG.warn("No policy found with id "+id+"; returning null");
+            warn("No policy found with id "+id+"; returning null");
             return null;
         }
     }
@@ -193,7 +206,7 @@ public class RebindExceptionHandlerImpl implements RebindExceptionHandler {
         if (danglingRefFailureMode == RebindManager.RebindFailureMode.FAIL_FAST) {
             throw new IllegalStateException("No enricher found with id "+id);
         } else {
-            LOG.warn("No enricher found with id "+id+"; returning null");
+            warn("No enricher found with id "+id+"; returning null");
             return null;
         }
     }
@@ -204,7 +217,7 @@ public class RebindExceptionHandlerImpl implements RebindExceptionHandler {
         if (danglingRefFailureMode == RebindManager.RebindFailureMode.FAIL_FAST) {
             throw new IllegalStateException("No feed found with id "+id);
         } else {
-            LOG.warn("No feed found with id "+id+"; returning null");
+            warn("No feed found with id "+id+"; returning null");
             return null;
         }
     }
@@ -215,7 +228,7 @@ public class RebindExceptionHandlerImpl implements RebindExceptionHandler {
         if (danglingRefFailureMode == RebindManager.RebindFailureMode.FAIL_FAST) {
             throw new IllegalStateException("No catalog item found with id "+id);
         } else {
-            LOG.warn("No catalog item found with id "+id+"; returning null");
+            warn("No catalog item found with id "+id+"; returning null");
             return null;
         }
     }
@@ -256,7 +269,7 @@ public class RebindExceptionHandlerImpl implements RebindExceptionHandler {
                 addPolicyFailures.add(new IllegalStateException(errmsg, e));
                 break;
             case CONTINUE:
-                LOG.warn(errmsg+"; continuing", e);
+                warn(errmsg+"; continuing", e);
                 creationFailedIds.add(instance.getId());
                 break;
             default:
@@ -282,7 +295,7 @@ public class RebindExceptionHandlerImpl implements RebindExceptionHandler {
             addPolicyFailures.add(new IllegalStateException(errmsg, e));
             break;
         case CONTINUE:
-            LOG.warn(errmsg+"; continuing", e);
+            warn(errmsg+"; continuing", e);
             break;
         default:
             throw new IllegalStateException("Unexpected state '"+addPolicyFailureMode+"' for addPolicyFailureMode");
@@ -301,7 +314,7 @@ public class RebindExceptionHandlerImpl implements RebindExceptionHandler {
             addPolicyFailures.add(new IllegalStateException(errmsg, e));
             break;
         case CONTINUE:
-            LOG.warn(errmsg+"; continuing", e);
+            warn(errmsg+"; continuing", e);
             break;
         default:
             throw new IllegalStateException("Unexpected state '"+addPolicyFailureMode+"' for addPolicyFailureMode");
@@ -320,7 +333,7 @@ public class RebindExceptionHandlerImpl implements RebindExceptionHandler {
             addPolicyFailures.add(new IllegalStateException(errmsg, e));
             break;
         case CONTINUE:
-            LOG.warn(errmsg+"; continuing", e);
+            warn(errmsg+"; continuing", e);
             break;
         default:
             throw new IllegalStateException("Unexpected state '"+addPolicyFailureMode+"' for addPolicyFailureMode");
@@ -349,7 +362,7 @@ public class RebindExceptionHandlerImpl implements RebindExceptionHandler {
                     LOG.debug("Rebind: while interrupted, received "+errmsg+"/"+e+"; throwing interruption", e);
                 throw Exceptions.propagate(new InterruptedException("Detected interruptiong while not sleeping, due to secondary error rebinding: "+errmsg+"/"+e));
             }
-            LOG.warn("Rebind: continuing after "+errmsg, e);
+            warn("Rebind: continuing after "+errmsg, e);
         }
     }
     
@@ -364,6 +377,8 @@ public class RebindExceptionHandlerImpl implements RebindExceptionHandler {
             throw Exceptions.propagate(e);
         
         onDoneImpl(e);
+        exceptions.add(e);
+        
         throw new IllegalStateException("Rebind failed", e); // should have thrown exception above
     }
     
@@ -386,7 +401,7 @@ public class RebindExceptionHandlerImpl implements RebindExceptionHandler {
                 allExceptions.add(new IllegalStateException("Dangling references ("+totalDangling+" of "+totalItems+") present without rebind context"));
             } else {
                 if (!danglingRefsQuorumRequiredHealthy.isQuorate(totalFound, totalItems)) {
-                    LOG.warn("Dangling item"+Strings.s(totalDangling)+" ("+totalDangling+" of "+totalItems+") found on rebind exceeds quorum, assuming failed: "+danglingIds);
+                    warn("Dangling item"+Strings.s(totalDangling)+" ("+totalDangling+" of "+totalItems+") found on rebind exceeds quorum, assuming failed: "+danglingIds);
                     allExceptions.add(new IllegalStateException("Too many dangling references: "+totalDangling+" of "+totalItems));
                 } else {
                     LOG.info("Dangling item"+Strings.s(totalDangling)+" ("+totalDangling+" of "+totalItems+") found on rebind, assuming deleted: "+danglingIds);
@@ -439,4 +454,15 @@ public class RebindExceptionHandlerImpl implements RebindExceptionHandler {
             throw compoundException;
         }
     }
+    
+    @Override
+    public List<Exception> getExceptions() {
+        return ImmutableList.copyOf(exceptions);
+    }
+    
+    @Override
+    public List<String> getWarnings() {
+        return ImmutableList.copyOf(warnings);
+    }
+    
 }

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/b9c1b6fc/core/src/main/java/brooklyn/entity/rebind/RebindManagerImpl.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/entity/rebind/RebindManagerImpl.java b/core/src/main/java/brooklyn/entity/rebind/RebindManagerImpl.java
index 4252a25..e24a818 100644
--- a/core/src/main/java/brooklyn/entity/rebind/RebindManagerImpl.java
+++ b/core/src/main/java/brooklyn/entity/rebind/RebindManagerImpl.java
@@ -58,6 +58,7 @@ import brooklyn.entity.proxying.InternalLocationFactory;
 import brooklyn.entity.proxying.InternalPolicyFactory;
 import brooklyn.entity.rebind.persister.BrooklynMementoPersisterToObjectStore;
 import brooklyn.entity.rebind.persister.BrooklynPersistenceUtils;
+import brooklyn.entity.rebind.persister.PersistenceActivityMetrics;
 import brooklyn.event.feed.AbstractFeed;
 import brooklyn.internal.BrooklynFeatureEnablement;
 import brooklyn.location.Location;
@@ -90,6 +91,7 @@ import brooklyn.mementos.TreeNode;
 import brooklyn.policy.Enricher;
 import brooklyn.policy.Policy;
 import brooklyn.policy.basic.AbstractPolicy;
+import brooklyn.util.collections.MutableList;
 import brooklyn.util.collections.MutableMap;
 import brooklyn.util.collections.QuorumCheck;
 import brooklyn.util.collections.QuorumCheck.QuorumChecks;
@@ -174,7 +176,12 @@ public class RebindManagerImpl implements RebindManager {
     private RebindFailureMode addPolicyFailureMode;
     private RebindFailureMode loadPolicyFailureMode;
     private QuorumCheck danglingRefsQuorumRequiredHealthy;
+    
+    private PersistenceActivityMetrics rebindMetrics = new PersistenceActivityMetrics();
+    private PersistenceActivityMetrics persistMetrics = new PersistenceActivityMetrics();
 
+    Integer firstRebindAppCount, firstRebindEntityCount, firstRebindItemCount;
+    
     /**
      * For tracking if rebinding, for {@link AbstractEnricher#isRebinding()} etc.
      *  
@@ -256,7 +263,7 @@ public class RebindManagerImpl implements RebindManager {
         }
         this.persistenceStoreAccess = checkNotNull(val, "persister");
         
-        this.persistenceRealChangeListener = new PeriodicDeltaChangeListener(managementContext.getServerExecutionContext(), persistenceStoreAccess, exceptionHandler, periodicPersistPeriod);
+        this.persistenceRealChangeListener = new PeriodicDeltaChangeListener(managementContext.getServerExecutionContext(), persistenceStoreAccess, exceptionHandler, persistMetrics, periodicPersistPeriod);
         this.persistencePublicChangeListener = new SafeChangeListener(persistenceRealChangeListener);
         
         if (persistenceRunning) {
@@ -530,8 +537,8 @@ public class RebindManagerImpl implements RebindManager {
             rebindActive.acquire();
         } catch (InterruptedException e1) { Exceptions.propagate(e1); }
         RebindTracker.setRebinding();
+        Stopwatch timer = Stopwatch.createStarted();
         try {
-            Stopwatch timer = Stopwatch.createStarted();
             Reflections reflections = new Reflections(classLoader);
             RebindContextImpl rebindContext = new RebindContextImpl(exceptionHandler, classLoader);
             
@@ -970,6 +977,14 @@ public class RebindManagerImpl implements RebindManager {
             }
 
             exceptionHandler.onDone();
+            
+            rebindMetrics.noteSuccess(Duration.of(timer));
+            noteErrors(exceptionHandler, null);
+            if (firstRebindAppCount==null) {
+                firstRebindAppCount = apps.size();
+                firstRebindEntityCount = rebindContext.getEntities().size();
+                firstRebindItemCount = rebindContext.getAllBrooklynObjects().size();
+            }
 
             if (!isEmpty) {
                 BrooklynLogging.log(LOG, shouldLogRebinding() ? LoggingLevel.INFO : LoggingLevel.DEBUG, 
@@ -990,13 +1005,30 @@ public class RebindManagerImpl implements RebindManager {
             return apps;
 
         } catch (Exception e) {
+            rebindMetrics.noteFailure(Duration.of(timer));
+            
+            Exceptions.propagateIfFatal(e);
+            noteErrors(exceptionHandler, e);
             throw exceptionHandler.onFailed(e);
+            
         } finally {
             rebindActive.release();
             RebindTracker.reset();
         }
     }
 
+    private void noteErrors(final RebindExceptionHandler exceptionHandler, Exception primaryException) {
+        List<Exception> exceptions = exceptionHandler.getExceptions();
+        List<String> warnings = exceptionHandler.getWarnings();
+        if (primaryException!=null || !exceptions.isEmpty() || !warnings.isEmpty()) {
+            List<String> messages = MutableList.<String>of();
+            if (primaryException!=null) messages.add(primaryException.toString());
+            for (Exception e: exceptions) messages.add(e.toString());
+            for (String w: warnings) messages.add(w);
+            rebindMetrics.noteError(messages);
+        }
+    }
+    
     private String findCatalogItemId(ClassLoader cl, Map<String, EntityMementoManifest> entityIdToManifest, EntityMementoManifest entityManifest) {
         if (entityManifest.getCatalogItemId() != null) {
             return entityManifest.getCatalogItemId();
@@ -1418,7 +1450,24 @@ public class RebindManagerImpl implements RebindManager {
     }
 
     @Override
+    public Map<String, Object> getMetrics() {
+        Map<String,Object> result = MutableMap.of();
+
+        result.put("rebind", rebindMetrics.asMap());
+        result.put("persist", persistMetrics.asMap());
+        
+        // include first rebind counts, so we know whether we rebinded or not
+        result.put("firstRebindCounts", MutableMap.of(
+            "applications", firstRebindAppCount,
+            "entities", firstRebindEntityCount,
+            "allItems", firstRebindItemCount));
+        
+        return result;
+    }
+
+    @Override
     public String toString() {
         return super.toString()+"[mgmt="+managementContext.getManagementNodeId()+"]";
     }
+
 }

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/b9c1b6fc/core/src/main/java/brooklyn/entity/rebind/persister/BrooklynPersistenceUtils.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/entity/rebind/persister/BrooklynPersistenceUtils.java b/core/src/main/java/brooklyn/entity/rebind/persister/BrooklynPersistenceUtils.java
index 54990b3..8b5317d 100644
--- a/core/src/main/java/brooklyn/entity/rebind/persister/BrooklynPersistenceUtils.java
+++ b/core/src/main/java/brooklyn/entity/rebind/persister/BrooklynPersistenceUtils.java
@@ -29,7 +29,6 @@ import brooklyn.config.BrooklynServerConfig;
 import brooklyn.config.BrooklynServerPaths;
 import brooklyn.entity.Entity;
 import brooklyn.entity.Feed;
-import brooklyn.entity.basic.AbstractEntity;
 import brooklyn.entity.basic.Entities;
 import brooklyn.entity.basic.EntityInternal;
 import brooklyn.entity.rebind.BrooklynObjectType;

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/b9c1b6fc/core/src/main/java/brooklyn/entity/rebind/persister/PersistenceActivityMetrics.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/entity/rebind/persister/PersistenceActivityMetrics.java b/core/src/main/java/brooklyn/entity/rebind/persister/PersistenceActivityMetrics.java
new file mode 100644
index 0000000..8315156
--- /dev/null
+++ b/core/src/main/java/brooklyn/entity/rebind/persister/PersistenceActivityMetrics.java
@@ -0,0 +1,83 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package brooklyn.entity.rebind.persister;
+
+import java.util.List;
+import java.util.Map;
+
+import brooklyn.util.collections.MutableList;
+import brooklyn.util.collections.MutableMap;
+import brooklyn.util.time.Duration;
+
+public class PersistenceActivityMetrics {
+    
+    final static int MAX_ERRORS = 200;
+    
+    long count=0, failureCount=0;
+    Long lastSuccessTime, lastDuration, lastFailureTime;
+    List<Map<String,Object>> errorMessages = MutableList.of();
+
+    public void noteSuccess(Duration duration) {
+        count++;
+        lastSuccessTime = System.currentTimeMillis();
+        lastDuration = duration.toMilliseconds();
+    }
+    
+    public void noteFailure(Duration duration) {
+        count++;
+        failureCount++;
+        lastFailureTime = System.currentTimeMillis();
+        lastDuration = duration.toMilliseconds();
+    }
+
+    public void noteError(String error) {
+        noteErrorObject(error);
+    }
+    
+    public void noteError(List<?> error) {
+        noteErrorObject(error);
+    }
+    
+    /** error should be json-serializable; exceptions can be problematic */
+    protected synchronized void noteErrorObject(Object error) {
+        errorMessages.add(0, MutableMap.<String,Object>of("error", error, "timestamp", System.currentTimeMillis()));
+        while (errorMessages.size() > MAX_ERRORS) {
+            errorMessages.remove(errorMessages.size()-1);
+        }
+    }
+    
+    public synchronized Map<String,Object> asMap() {
+        Map<String,Object> result = MutableMap.of();
+        result.put("count", count);
+        result.put("lastSuccessTimeUtc", lastSuccessTime);
+        result.put("lastSuccessTimeMillisSince", since(lastSuccessTime));
+        result.put("lastDuration", lastDuration);
+        result.put("failureCount", failureCount);
+        result.put("lastFailureTimeUtc", lastFailureTime);
+        result.put("lastFailureTimeMillisSince", since(lastFailureTime));
+        result.put("errorMessages", MutableList.copyOf(errorMessages));
+        return result;
+    }
+
+    private Long since(Long time) {
+        if (time==null) return null;
+        return System.currentTimeMillis() - time;
+    }
+    
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/b9c1b6fc/core/src/main/java/brooklyn/management/ha/HighAvailabilityManagerImpl.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/management/ha/HighAvailabilityManagerImpl.java b/core/src/main/java/brooklyn/management/ha/HighAvailabilityManagerImpl.java
index 62b06c0..73d431d 100644
--- a/core/src/main/java/brooklyn/management/ha/HighAvailabilityManagerImpl.java
+++ b/core/src/main/java/brooklyn/management/ha/HighAvailabilityManagerImpl.java
@@ -23,6 +23,8 @@ import static com.google.common.base.Preconditions.checkState;
 
 import java.io.IOException;
 import java.net.URI;
+import java.util.List;
+import java.util.Map;
 import java.util.concurrent.Callable;
 
 import javax.annotation.Nullable;
@@ -42,6 +44,7 @@ import brooklyn.entity.basic.ConfigKeys;
 import brooklyn.entity.basic.EntityInternal;
 import brooklyn.entity.rebind.RebindManager;
 import brooklyn.entity.rebind.persister.BrooklynPersistenceUtils;
+import brooklyn.entity.rebind.persister.PersistenceActivityMetrics;
 import brooklyn.entity.rebind.plane.dto.BasicManagementNodeSyncRecord;
 import brooklyn.entity.rebind.plane.dto.ManagementPlaneSyncRecordImpl;
 import brooklyn.entity.rebind.plane.dto.ManagementPlaneSyncRecordImpl.Builder;
@@ -54,6 +57,7 @@ import brooklyn.management.internal.LocalEntityManager;
 import brooklyn.management.internal.LocationManagerInternal;
 import brooklyn.management.internal.ManagementContextInternal;
 import brooklyn.management.internal.ManagementTransitionInfo.ManagementTransitionMode;
+import brooklyn.util.collections.MutableList;
 import brooklyn.util.collections.MutableMap;
 import brooklyn.util.exceptions.Exceptions;
 import brooklyn.util.task.ScheduledTask;
@@ -66,6 +70,7 @@ import com.google.common.annotations.Beta;
 import com.google.common.annotations.VisibleForTesting;
 import com.google.common.base.Function;
 import com.google.common.base.Preconditions;
+import com.google.common.base.Stopwatch;
 import com.google.common.base.Ticker;
 import com.google.common.collect.Iterables;
 
@@ -132,13 +137,21 @@ public class HighAvailabilityManagerImpl implements HighAvailabilityManager {
     private volatile boolean nodeStateTransitionComplete = false;
     private volatile long priority = 0;
     
+    private final static int MAX_NODE_STATE_HISTORY = 200;
+    private final List<Map<String,Object>> nodeStateHistory = MutableList.of();
+    
     private volatile transient Duration pollPeriodLocalOverride;
     private volatile transient Duration heartbeatTimeoutOverride;
 
     private volatile ManagementPlaneSyncRecord lastSyncRecord;
     
+    private volatile PersistenceActivityMetrics managementStateWritePersistenceMetrics = new PersistenceActivityMetrics();
+    private volatile PersistenceActivityMetrics managementStateReadPersistenceMetrics = new PersistenceActivityMetrics();
+    private final long startTimeUtc;
+    
     public HighAvailabilityManagerImpl(ManagementContextInternal managementContext) {
         this.managementContext = managementContext;
+        startTimeUtc = localTickerUtc.read();
     }
 
     @Override
@@ -228,9 +241,9 @@ public class HighAvailabilityManagerImpl implements HighAvailabilityManager {
     @Override
     public void start(HighAvailabilityMode startMode) {
         nodeStateTransitionComplete = true;
-        // always start in standby; it may get promoted to master or hot_standby in this method
+        // always start in standby, unless hot backup; it may get promoted to master or hot_standby in this method
         // (depending on startMode; but for startMode STANDBY or HOT_STANDBY it will not promote until the next election)
-        nodeState = ManagementNodeState.STANDBY;
+        setInternalNodeState(startMode==HighAvailabilityMode.HOT_BACKUP ? ManagementNodeState.HOT_BACKUP : ManagementNodeState.STANDBY);
         disabled = false;
         running = true;
         changeMode(startMode, true, true);
@@ -246,13 +259,13 @@ public class HighAvailabilityManagerImpl implements HighAvailabilityManager {
     public void changeMode(HighAvailabilityMode startMode, boolean preventElectionOnExplicitStandbyMode, boolean failOnExplicitStandbyModeIfNoMaster) {
         if (!running) {
             // if was not running then start as disabled mode, then proceed as normal
-            LOG.info("HA changing mode to "+startMode+" from "+nodeState+" when not running, forcing an intermediate start as DISABLED then will convert to "+startMode);
+            LOG.info("HA changing mode to "+startMode+" from "+getInternalNodeState()+" when not running, forcing an intermediate start as DISABLED then will convert to "+startMode);
             start(HighAvailabilityMode.DISABLED);
         }
         if (getNodeState()==ManagementNodeState.FAILED || getNodeState()==ManagementNodeState.INITIALIZING) {
             if (startMode!=HighAvailabilityMode.DISABLED) {
                 // if coming from FAILED (or INITIALIZING because we skipped start call) then treat as cold standby
-                nodeState = ManagementNodeState.STANDBY; 
+                setInternalNodeState(ManagementNodeState.STANDBY);
             }
         }
         
@@ -272,7 +285,7 @@ public class HighAvailabilityManagerImpl implements HighAvailabilityManager {
             case MASTER:
             case AUTO:
             case DISABLED:
-                // no action needed, will do anything necessary below
+                // no action needed, will do anything necessary below (or above)
                 break;
             case HOT_STANDBY: 
             case HOT_BACKUP: 
@@ -288,17 +301,18 @@ public class HighAvailabilityManagerImpl implements HighAvailabilityManager {
         case AUTO:
             // don't care; let's start and see if we promote ourselves
             publishAndCheck(true);
-            switch (nodeState) {
+            switch (getInternalNodeState()) {
             case HOT_BACKUP:
-                if (!nodeStateTransitionComplete) throw new IllegalStateException("Cannot switch to AUTO when in the middle of a transition to "+nodeState);
-                // else change us to hot standby and continue to below
-                nodeState = ManagementNodeState.HOT_STANDBY;
+                if (!nodeStateTransitionComplete) throw new IllegalStateException("Cannot switch to AUTO when in the middle of a transition to "+getInternalNodeState());
+                // else change us to standby, desiring to go to hot standby, and continue to below
+                setInternalNodeState(ManagementNodeState.STANDBY);
+                startMode = HighAvailabilityMode.HOT_STANDBY;
             case HOT_STANDBY:
             case STANDBY:
                 ManagementPlaneSyncRecord newState = loadManagementPlaneSyncRecord(true);
                 String masterNodeId = newState.getMasterNodeId();
                 ManagementNodeSyncRecord masterNodeDetails = newState.getManagementNodes().get(masterNodeId);
-                LOG.info("Management node "+ownNodeId+" running as HA " + nodeState + " autodetected, " +
+                LOG.info("Management node "+ownNodeId+" running as HA " + getInternalNodeState() + " autodetected, " +
                     (Strings.isBlank(masterNodeId) ? "no master currently (other node should promote itself soon)" : "master "
                         + (existingMaster==null ? "(new) " : "")
                         + "is "+masterNodeId +
@@ -308,7 +322,7 @@ public class HighAvailabilityManagerImpl implements HighAvailabilityManager {
                 LOG.info("Management node "+ownNodeId+" running as HA MASTER autodetected");
                 break;
             default:
-                throw new IllegalStateException("Management node "+ownNodeId+" set to HA AUTO, encountered unexpected mode "+nodeState);
+                throw new IllegalStateException("Management node "+ownNodeId+" set to HA AUTO, encountered unexpected mode "+getInternalNodeState());
             }
             break;
         case MASTER:
@@ -322,16 +336,23 @@ public class HighAvailabilityManagerImpl implements HighAvailabilityManager {
                 LOG.info("Management node "+ownNodeId+" already running as HA MASTER, when set explicitly");
             }
             break;
+        case HOT_BACKUP:
         case STANDBY:
         case HOT_STANDBY:
-            if (!preventElectionOnExplicitStandbyMode)
-                publishAndCheck(true);
-            if (failOnExplicitStandbyModeIfNoMaster && existingMaster==null) {
-                LOG.error("Management node "+ownNodeId+" detected no master when "+startMode+" requested and existing master required; failing.");
-                throw new IllegalStateException("No existing master; cannot start as "+startMode);
+            if (getInternalNodeState()==ManagementNodeState.STANDBY || getInternalNodeState()==ManagementNodeState.HOT_STANDBY) {
+                if (!preventElectionOnExplicitStandbyMode)
+                    publishAndCheck(true);
+                if (failOnExplicitStandbyModeIfNoMaster && existingMaster==null) {
+                    LOG.error("Management node "+ownNodeId+" detected no master when "+startMode+" requested and existing master required; failing.");
+                    throw new IllegalStateException("No existing master; cannot start as "+startMode);
+                }
+            }
+            if (startMode==HighAvailabilityMode.HOT_BACKUP) {
+                setInternalNodeState(ManagementNodeState.HOT_BACKUP);
+            } else {
+                setInternalNodeState(ManagementNodeState.STANDBY);
+                // might jump to hot_standby next
             }
-            // continue to below (above lines skipped for hot backup)
-        case HOT_BACKUP:
             String message = "Management node "+ownNodeId+" running as HA "+getNodeState()+" (";
             if (getNodeState().toString().equals(startMode.toString()))
                 message += "explicitly requested";
@@ -355,7 +376,7 @@ public class HighAvailabilityManagerImpl implements HighAvailabilityManager {
             break;
         case DISABLED:
             // safe just to run even if we weren't master
-            LOG.info("Management node "+ownNodeId+" HA DISABLED (was "+nodeState+")");
+            LOG.info("Management node "+ownNodeId+" HA DISABLED (was "+getInternalNodeState()+")");
             demoteTo(ManagementNodeState.FAILED);
             if (pollingTask!=null) pollingTask.cancel(true);
             break;
@@ -370,7 +391,7 @@ public class HighAvailabilityManagerImpl implements HighAvailabilityManager {
                 startMode = HighAvailabilityMode.STANDBY;
             }
         }
-        if ((nodeState==ManagementNodeState.STANDBY && startMode==HighAvailabilityMode.HOT_STANDBY) || 
+        if ((getInternalNodeState()==ManagementNodeState.STANDBY && startMode==HighAvailabilityMode.HOT_STANDBY) || 
                 (startMode==HighAvailabilityMode.HOT_BACKUP)) {
             nodeStateTransitionComplete = false;
             if (startMode==HighAvailabilityMode.HOT_STANDBY) {
@@ -422,7 +443,7 @@ public class HighAvailabilityManagerImpl implements HighAvailabilityManager {
         boolean wasRunning = running;
         
         running = false;
-        nodeState = newState;
+        setInternalNodeState(newState);
         if (pollingTask != null) pollingTask.cancel(true);
         
         if (wasRunning) {
@@ -437,22 +458,40 @@ public class HighAvailabilityManagerImpl implements HighAvailabilityManager {
     
     /** returns the node state this node is trying to be in */
     public ManagementNodeState getTransitionTargetNodeState() {
+        return getInternalNodeState();
+    }
+    
+    protected ManagementNodeState getInternalNodeState() {
         return nodeState;
     }
     
+    protected void setInternalNodeState(ManagementNodeState newState) {
+        synchronized (nodeStateHistory) {
+            if (this.nodeState != newState) {
+                nodeStateHistory.add(0, MutableMap.<String,Object>of("state", newState, "timestamp", currentTimeMillis()));
+                while (nodeStateHistory.size()>MAX_NODE_STATE_HISTORY) {
+                    nodeStateHistory.remove(nodeStateHistory.size()-1);
+                }
+            }
+            
+            this.nodeState = newState;
+        }
+    }
+
     @SuppressWarnings("deprecation")
     @Override
     public ManagementNodeState getNodeState() {
-        if (nodeState==ManagementNodeState.FAILED) return nodeState;
+        ManagementNodeState myNodeState = getInternalNodeState();
+        if (myNodeState==ManagementNodeState.FAILED) return getInternalNodeState();
         // if target is master then we claim already being master, to prevent other nodes from taking it
         // (we may fail subsequently of course)
-        if (nodeState==ManagementNodeState.MASTER) return nodeState;
+        if (myNodeState==ManagementNodeState.MASTER) return myNodeState;
         
         // for backwards compatibility; remove in 0.8.0
-        if (nodeState==ManagementNodeState.UNINITIALISED) return ManagementNodeState.INITIALIZING;
+        if (myNodeState==ManagementNodeState.UNINITIALISED) return ManagementNodeState.INITIALIZING;
         
         if (!nodeStateTransitionComplete) return ManagementNodeState.INITIALIZING;
-        return nodeState;
+        return myNodeState;
     }
 
     public ManagementPlaneSyncRecord getLastManagementPlaneSyncRecord() {
@@ -516,10 +555,19 @@ public class HighAvailabilityManagerImpl implements HighAvailabilityManager {
             return;
         }
         
-        ManagementNodeSyncRecord memento = createManagementNodeSyncRecord(false);
-        Delta delta = ManagementPlaneSyncRecordDeltaImpl.builder().node(memento).build();
-        persister.delta(delta);
-        if (LOG.isTraceEnabled()) LOG.trace("Published management-node health: {}", memento);
+        Stopwatch timer = Stopwatch.createStarted();
+        try {
+            ManagementNodeSyncRecord memento = createManagementNodeSyncRecord(false);
+            Delta delta = ManagementPlaneSyncRecordDeltaImpl.builder().node(memento).build();
+            persister.delta(delta);
+            managementStateWritePersistenceMetrics.noteSuccess(Duration.of(timer));
+            if (LOG.isTraceEnabled()) LOG.trace("Published management-node health: {}", memento);
+        } catch (Throwable t) {
+            managementStateWritePersistenceMetrics.noteFailure(Duration.of(timer));
+            managementStateWritePersistenceMetrics.noteError(t.toString());
+            LOG.debug("Error publishing management-node health (rethrowing): "+t);
+            throw Exceptions.propagate(t);
+        }
     }
     
     protected synchronized void publishDemotion(boolean demotingFromMaster) {
@@ -707,8 +755,8 @@ public class HighAvailabilityManagerImpl implements HighAvailabilityManager {
                 LOG.warn("Problem in promption-listener (continuing)", e);
             }
         }
-        boolean wasHot = (nodeState==ManagementNodeState.HOT_STANDBY || nodeState==ManagementNodeState.HOT_BACKUP);
-        nodeState = ManagementNodeState.MASTER;
+        boolean wasHot = (getInternalNodeState()==ManagementNodeState.HOT_STANDBY || getInternalNodeState()==ManagementNodeState.HOT_BACKUP);
+        setInternalNodeState(ManagementNodeState.MASTER);
         publishPromotionToMaster();
         try {
             if (wasHot) {
@@ -717,7 +765,7 @@ public class HighAvailabilityManagerImpl implements HighAvailabilityManager {
                 managementContext.getRebindManager().stopReadOnly();
                 clearManagedItems(ManagementTransitionMode.REBINDING_DESTROYED);
             }
-            managementContext.getRebindManager().rebind(managementContext.getCatalog().getRootClassLoader(), null, nodeState);
+            managementContext.getRebindManager().rebind(managementContext.getCatalog().getRootClassLoader(), null, getInternalNodeState());
         } catch (Exception e) {
             LOG.error("Management node enountered problem during rebind when promoting self to master; demoting to FAILED and rethrowing: "+e);
             demoteTo(ManagementNodeState.FAILED);
@@ -746,7 +794,7 @@ public class HighAvailabilityManagerImpl implements HighAvailabilityManager {
             LOG.warn("Ignoring demote-from-master request, as HighAvailabilityManager is no longer running");
             return;
         }
-        boolean wasMaster = nodeState == ManagementNodeState.MASTER;
+        boolean wasMaster = (getInternalNodeState() == ManagementNodeState.MASTER);
         if (wasMaster) backupOnDemotionIfNeeded();
         ManagementTransitionMode mode = (wasMaster ? ManagementTransitionMode.REBINDING_NO_LONGER_PRIMARY : ManagementTransitionMode.REBINDING_DESTROYED);
 
@@ -756,9 +804,9 @@ public class HighAvailabilityManagerImpl implements HighAvailabilityManager {
         case FAILED: 
         case HOT_BACKUP:
         case STANDBY:
-            nodeState = toState; break;
+            setInternalNodeState(toState); break;
         case HOT_STANDBY:
-            nodeState = ManagementNodeState.STANDBY; break;
+            setInternalNodeState(ManagementNodeState.STANDBY); break;
         default:
             throw new IllegalStateException("Illegal target state: "+toState);
         }
@@ -822,7 +870,7 @@ public class HighAvailabilityManagerImpl implements HighAvailabilityManager {
     protected boolean attemptHotProxy(ManagementNodeState toState) {
         try {
             Preconditions.checkState(nodeStateTransitionComplete==false, "Must be in transitioning state to go into "+toState);
-            nodeState = toState;
+            setInternalNodeState(toState);
             managementContext.getRebindManager().startReadOnly(toState);
             
             return true;
@@ -859,6 +907,8 @@ public class HighAvailabilityManagerImpl implements HighAvailabilityManager {
         
         int maxLoadAttempts = 5;
         Exception lastException = null;
+        Stopwatch timer = Stopwatch.createStarted();
+
         for (int i = 0; i < maxLoadAttempts; i++) {
             try {
                 ManagementPlaneSyncRecord result = persister.loadSyncRecord();
@@ -881,6 +931,11 @@ public class HighAvailabilityManagerImpl implements HighAvailabilityManager {
                     }
                     result = builder.build();
                 }
+                
+                if (i>0) {
+                    managementStateReadPersistenceMetrics.noteError("Succeeded only on attempt "+(i+1)+": "+lastException);
+                }
+                managementStateReadPersistenceMetrics.noteSuccess(Duration.of(timer));
                 return result;
             } catch (IOException e) {
                 if (i < (maxLoadAttempts - 1)) {
@@ -889,7 +944,11 @@ public class HighAvailabilityManagerImpl implements HighAvailabilityManager {
                 lastException = e;
             }
         }
-        throw new IllegalStateException("Failed to load mangement-plane memento "+maxLoadAttempts+" consecutive times", lastException);
+        String message = "Failed to load mangement-plane memento "+maxLoadAttempts+" consecutive times";
+        managementStateReadPersistenceMetrics.noteError(message+": "+lastException);
+        managementStateReadPersistenceMetrics.noteFailure(Duration.of(timer));
+
+        throw new IllegalStateException(message, lastException);
     }
 
     protected ManagementNodeSyncRecord createManagementNodeSyncRecord(boolean useLocalTimestampAsRemoteTimestamp) {
@@ -944,4 +1003,26 @@ public class HighAvailabilityManagerImpl implements HighAvailabilityManager {
     public String toString() {
         return super.toString()+"[node:"+ownNodeId+";running="+running+"]";
     }
+    
+    @Override
+    public Map<String,Object> getMetrics() {
+        Map<String,Object> result = MutableMap.of();
+        
+        result.put("state", getNodeState());
+        result.put("uptime", Time.makeTimeStringRounded(Duration.millis(currentTimeMillis()-startTimeUtc)));
+        result.put("currentTimeUtc", currentTimeMillis());
+        result.put("startTimeUtc", startTimeUtc);
+        result.put("highAvailability", MutableMap.<String,Object>of(
+            "priority", getPriority(),
+            "pollPeriod", getPollPeriod().toMilliseconds(),
+            "heartbeatTimeout", getHeartbeatTimeout().toMilliseconds(),
+            "history", nodeStateHistory));
+        
+        result.putAll(managementContext.getRebindManager().getMetrics());
+        result.put("managementStatePersistence", 
+            MutableMap.of("read", managementStateReadPersistenceMetrics, "write", managementStateWritePersistenceMetrics));
+        
+        return result;
+    }
+    
 }

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/b9c1b6fc/core/src/main/java/brooklyn/management/internal/NonDeploymentManagementContext.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/management/internal/NonDeploymentManagementContext.java b/core/src/main/java/brooklyn/management/internal/NonDeploymentManagementContext.java
index 2771475..c04a31c 100644
--- a/core/src/main/java/brooklyn/management/internal/NonDeploymentManagementContext.java
+++ b/core/src/main/java/brooklyn/management/internal/NonDeploymentManagementContext.java
@@ -530,6 +530,11 @@ public class NonDeploymentManagementContext implements ManagementContextInternal
         public BrooklynMementoRawData retrieveMementoRawData() {
             throw new IllegalStateException("Non-deployment context "+NonDeploymentManagementContext.this+" is not valid for this operation.");
         }
+
+        @Override
+        public Map<String, Object> getMetrics() {
+            throw new IllegalStateException("Non-deployment context "+NonDeploymentManagementContext.this+" is not valid for this operation.");
+        }
     }
 
     /**
@@ -590,6 +595,10 @@ public class NonDeploymentManagementContext implements ManagementContextInternal
         public long getPriority() {
             throw new IllegalStateException("Non-deployment context "+NonDeploymentManagementContext.this+" is not valid for this operation.");
         }
+        @Override
+        public Map<String, Object> getMetrics() {
+            throw new IllegalStateException("Non-deployment context "+NonDeploymentManagementContext.this+" is not valid for this operation.");
+        }
     }
     
 }

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/b9c1b6fc/usage/rest-api/src/main/java/brooklyn/rest/api/ServerApi.java
----------------------------------------------------------------------
diff --git a/usage/rest-api/src/main/java/brooklyn/rest/api/ServerApi.java b/usage/rest-api/src/main/java/brooklyn/rest/api/ServerApi.java
index 7977ea8..246e31a 100644
--- a/usage/rest-api/src/main/java/brooklyn/rest/api/ServerApi.java
+++ b/usage/rest-api/src/main/java/brooklyn/rest/api/ServerApi.java
@@ -18,6 +18,8 @@
  */
 package brooklyn.rest.api;
 
+import java.util.Map;
+
 import javax.ws.rs.Consumes;
 import javax.ws.rs.DefaultValue;
 import javax.ws.rs.FormParam;
@@ -97,6 +99,11 @@ public interface ServerApi {
     @ApiOperation(value = "Returns the HA state of this management node")
     public ManagementNodeState getHighAvailabilityNodeState();
     
+    @GET
+    @Path("/ha/metrics")
+    @ApiOperation(value = "Returns a collection of HA metrics")
+    public Map<String,Object> getHighAvailabilityMetrics();
+    
     @POST
     @Path("/ha/state")
     @ApiOperation(value = "Changes the HA state of this management node")

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/b9c1b6fc/usage/rest-server/src/main/java/brooklyn/rest/resources/ServerResource.java
----------------------------------------------------------------------
diff --git a/usage/rest-server/src/main/java/brooklyn/rest/resources/ServerResource.java b/usage/rest-server/src/main/java/brooklyn/rest/resources/ServerResource.java
index 87fce42..50f99e1 100644
--- a/usage/rest-server/src/main/java/brooklyn/rest/resources/ServerResource.java
+++ b/usage/rest-server/src/main/java/brooklyn/rest/resources/ServerResource.java
@@ -23,6 +23,7 @@ import java.io.IOException;
 import java.io.InputStream;
 import java.util.ArrayList;
 import java.util.List;
+import java.util.Map;
 import java.util.Properties;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.TimeoutException;
@@ -57,6 +58,7 @@ import brooklyn.rest.domain.VersionSummary;
 import brooklyn.rest.transform.HighAvailabilityTransformer;
 import brooklyn.rest.util.WebResourceUtils;
 import brooklyn.util.ResourceUtils;
+import brooklyn.util.collections.MutableMap;
 import brooklyn.util.exceptions.Exceptions;
 import brooklyn.util.file.ArchiveBuilder;
 import brooklyn.util.flags.TypeCoercions;
@@ -255,6 +257,11 @@ public class ServerResource extends AbstractBrooklynRestResource implements Serv
     }
 
     @Override
+    public Map<String, Object> getHighAvailabilityMetrics() {
+        return mgmt().getHighAvailabilityManager().getMetrics();
+    }
+    
+    @Override
     public long getHighAvailabitlityPriority() {
         return mgmt().getHighAvailabilityManager().getPriority();
     }


[06/21] incubator-brooklyn git commit: more persistence, mainly tidying code to treat all BrooklynObject instances the same

Posted by he...@apache.org.
more persistence, mainly tidying code to treat all BrooklynObject instances the same


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

Branch: refs/heads/master
Commit: 38a756b2bb08f2baa80dddb53faa51cc645f988b
Parents: 3e793cd
Author: Alex Heneveld <al...@cloudsoftcorp.com>
Authored: Mon Nov 10 21:26:42 2014 +0000
Committer: Alex Heneveld <al...@cloudsoftcorp.com>
Committed: Thu Nov 13 23:38:52 2014 +0000

----------------------------------------------------------------------
 .../entity/rebind/BrooklynObjectType.java       |  38 ++-
 .../brooklyn/entity/rebind/RebindManager.java   |  15 +-
 .../java/brooklyn/mementos/BrooklynMemento.java |  16 --
 .../mementos/BrooklynMementoPersister.java      |  20 +-
 .../brooklyn/basic/BrooklynObjectInternal.java  |   6 +-
 .../catalog/internal/CatalogItemDo.java         |  10 +-
 .../rebind/ImmediateDeltaChangeListener.java    |   1 -
 .../rebind/PeriodicDeltaChangeListener.java     | 229 ++++++++-----------
 .../rebind/PersistenceExceptionHandlerImpl.java |   8 +-
 .../entity/rebind/PersisterDeltaImpl.java       | 147 +++++-------
 .../entity/rebind/RebindManagerImpl.java        |  17 +-
 .../AbstractBrooklynMementoPersister.java       |   8 +-
 .../BrooklynMementoPersisterInMemory.java       |   2 +-
 .../BrooklynMementoPersisterToMultiFile.java    |   6 +
 .../BrooklynMementoPersisterToObjectStore.java  |  35 ++-
 .../persister/BrooklynPersistenceUtils.java     |  10 +
 .../NonDeploymentManagementContext.java         |   4 +
 .../entity/rebind/RebindCatalogItemTest.java    |   4 +-
 18 files changed, 296 insertions(+), 280 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/38a756b2/api/src/main/java/brooklyn/entity/rebind/BrooklynObjectType.java
----------------------------------------------------------------------
diff --git a/api/src/main/java/brooklyn/entity/rebind/BrooklynObjectType.java b/api/src/main/java/brooklyn/entity/rebind/BrooklynObjectType.java
index b00d3b3..ce30c75 100644
--- a/api/src/main/java/brooklyn/entity/rebind/BrooklynObjectType.java
+++ b/api/src/main/java/brooklyn/entity/rebind/BrooklynObjectType.java
@@ -18,22 +18,32 @@
  */
 package brooklyn.entity.rebind;
 
+import brooklyn.basic.BrooklynObject;
+import brooklyn.catalog.CatalogItem;
+import brooklyn.entity.Entity;
+import brooklyn.entity.Feed;
+import brooklyn.location.Location;
+import brooklyn.policy.Enricher;
+import brooklyn.policy.Policy;
+
 import com.google.common.annotations.Beta;
 import com.google.common.base.CaseFormat;
 
 @Beta
 public enum BrooklynObjectType {
-    ENTITY("entities"),
-    LOCATION("locations"),
-    POLICY("policies"),
-    ENRICHER("enrichers"),
-    FEED("feeds"),
-    CATALOG_ITEM("catalog"),
-    UNKNOWN("unknown");
+    ENTITY(Entity.class, "entities"),
+    LOCATION(Location.class, "locations"),
+    POLICY(Policy.class, "policies"),
+    ENRICHER(Enricher.class, "enrichers"),
+    FEED(Feed.class, "feeds"),
+    CATALOG_ITEM(CatalogItem.class, "catalog"),
+    UNKNOWN(null, "unknown");
     
+    private Class<? extends BrooklynObject> interfaceType;
     private final String subPathName;
     
-    BrooklynObjectType(String subPathName) {
+    BrooklynObjectType(Class<? extends BrooklynObject> interfaceType, String subPathName) {
+        this.interfaceType = interfaceType;
         this.subPathName = subPathName;
     }
     public String toCamelCase() {
@@ -43,4 +53,16 @@ public enum BrooklynObjectType {
     public String getSubPathName() {
         return subPathName;
     }
+    
+    public Class<? extends BrooklynObject> getInterfaceType() {
+        return interfaceType;
+    }
+    
+    public static BrooklynObjectType of(BrooklynObject instance) {
+        for (BrooklynObjectType t: values()) {
+            if (t.getInterfaceType()!=null && t.getInterfaceType().isInstance(instance))
+                return t;
+        }
+        return UNKNOWN;
+    }
 }
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/38a756b2/api/src/main/java/brooklyn/entity/rebind/RebindManager.java
----------------------------------------------------------------------
diff --git a/api/src/main/java/brooklyn/entity/rebind/RebindManager.java b/api/src/main/java/brooklyn/entity/rebind/RebindManager.java
index 8995aae..e5001a6 100644
--- a/api/src/main/java/brooklyn/entity/rebind/RebindManager.java
+++ b/api/src/main/java/brooklyn/entity/rebind/RebindManager.java
@@ -18,11 +18,12 @@
  */
 package brooklyn.entity.rebind;
 
-import java.io.IOException;
 import java.util.List;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.TimeoutException;
 
+import javax.annotation.Nullable;
+
 import brooklyn.entity.Application;
 import brooklyn.management.ha.ManagementNodeState;
 import brooklyn.mementos.BrooklynMementoPersister;
@@ -110,7 +111,17 @@ public interface RebindManager {
     /** waits for any needed or pending writes to complete */
     @VisibleForTesting
     public void waitForPendingComplete(Duration duration) throws InterruptedException, TimeoutException;
-    /** Forcibly performs persistence, in the foreground */
+    /** Forcibly performs persistence, in the foreground 
+     * @deprecated since 0.7.0; use {@link #forcePersistNow(boolean)}, 
+     * default parameter here is false to mean incremental, with  */
     @VisibleForTesting
     public void forcePersistNow();
+    /** Forcibly performs persistence, in the foreground, either full (all entities) or incremental;
+     * if no exception handler specified, the default one from the persister is used.
+     * <p>
+     * Note that full persistence does *not* delete items; incremental should normally be sufficient.
+     * (A clear then full persistence would have the same effect, but that is risky in a production
+     * setting if the process fails after the clear!) */
+    @VisibleForTesting
+    public void forcePersistNow(boolean full, @Nullable PersistenceExceptionHandler exceptionHandler);
 }

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/38a756b2/api/src/main/java/brooklyn/mementos/BrooklynMemento.java
----------------------------------------------------------------------
diff --git a/api/src/main/java/brooklyn/mementos/BrooklynMemento.java b/api/src/main/java/brooklyn/mementos/BrooklynMemento.java
index 538c670..4aa36c6 100644
--- a/api/src/main/java/brooklyn/mementos/BrooklynMemento.java
+++ b/api/src/main/java/brooklyn/mementos/BrooklynMemento.java
@@ -38,43 +38,27 @@ import java.util.Map;
 public interface BrooklynMemento extends Serializable {
 
     public EntityMemento getEntityMemento(String id);
-
     public LocationMemento getLocationMemento(String id);
-    
     public PolicyMemento getPolicyMemento(String id);
-    
     public EnricherMemento getEnricherMemento(String id);
-
     public FeedMemento getFeedMemento(String id);
-    
     public CatalogItemMemento getCatalogItemMemento(String id);
 
     public Collection<String> getApplicationIds();
-    
     public Collection<String> getTopLevelLocationIds();
 
     public Collection<String> getEntityIds();
-    
     public Collection<String> getLocationIds();
-
     public Collection<String> getPolicyIds();
-
     public Collection<String> getEnricherIds();
-
     public Collection<String> getFeedIds();
-    
     public Collection<String> getCatalogItemIds();
 
     public Map<String, EntityMemento> getEntityMementos();
-
     public Map<String, LocationMemento> getLocationMementos();
-
     public Map<String, PolicyMemento> getPolicyMementos();
-
     public Map<String, EnricherMemento> getEnricherMementos();
-
     public Map<String, FeedMemento> getFeedMementos();
-    
     public Map<String, CatalogItemMemento> getCatalogItemMementos();
 
 }

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/38a756b2/api/src/main/java/brooklyn/mementos/BrooklynMementoPersister.java
----------------------------------------------------------------------
diff --git a/api/src/main/java/brooklyn/mementos/BrooklynMementoPersister.java b/api/src/main/java/brooklyn/mementos/BrooklynMementoPersister.java
index ed649fa..a9eb65f 100644
--- a/api/src/main/java/brooklyn/mementos/BrooklynMementoPersister.java
+++ b/api/src/main/java/brooklyn/mementos/BrooklynMementoPersister.java
@@ -20,6 +20,7 @@ package brooklyn.mementos;
 
 import java.io.IOException;
 import java.util.Collection;
+import java.util.Set;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.TimeoutException;
 
@@ -39,6 +40,7 @@ import brooklyn.policy.Enricher;
 import brooklyn.policy.Policy;
 import brooklyn.util.time.Duration;
 
+import com.google.common.annotations.Beta;
 import com.google.common.annotations.VisibleForTesting;
 
 /**
@@ -91,9 +93,12 @@ public interface BrooklynMementoPersister {
       * Note that this method is *not* thread safe.
       */
     BrooklynMemento loadMemento(@Nullable BrooklynMementoRawData mementoData, LookupContext lookupContext, RebindExceptionHandler exceptionHandler) throws IOException;
-    
-    // TODO can this be deprecated? not used much, and cumbersome
+
+    /** @deprecated since 0.7.0, use {@link #checkpoint(BrooklynMementoRawData, PersistenceExceptionHandler)} 
+     * and javadoc on implementations of that */ @Deprecated  // pretty sure this is not used outwith deprecated code
     void checkpoint(BrooklynMemento memento, PersistenceExceptionHandler exceptionHandler);
+    
+    void checkpoint(BrooklynMementoRawData newMemento, PersistenceExceptionHandler exceptionHandler);
 
     void delta(Delta delta, PersistenceExceptionHandler exceptionHandler);
 
@@ -113,6 +118,8 @@ public interface BrooklynMementoPersister {
 
     String getBackingStoreDescription();
     
+    /** All methods on this interface are unmodifiable by the caller. Sub-interfaces may introduce modifiers. */
+    // NB: the type-specific methods aren't actually used anymore; we could remove them to simplify the impl (and use a multiset there)
     public interface Delta {
         Collection<LocationMemento> locations();
         Collection<EntityMemento> entities();
@@ -129,7 +136,14 @@ public interface BrooklynMementoPersister {
         Collection<String> removedCatalogItemIds();
         
         Collection<? extends Memento> getObjectsOfType(BrooklynObjectType type);
-        Collection<String> getRemovedObjectsOfType(BrooklynObjectType type);
+        Collection<String> getRemovedIdsOfType(BrooklynObjectType type);
+    }
+    
+    @Beta
+    public interface MutableDelta extends Delta {
+        void add(BrooklynObjectType type, Memento memento);
+        void addAll(BrooklynObjectType type, Iterable<? extends Memento> memento);
+        void removed(BrooklynObjectType type, Set<String> removedIdsOfType);
     }
 
 }

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/38a756b2/core/src/main/java/brooklyn/basic/BrooklynObjectInternal.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/basic/BrooklynObjectInternal.java b/core/src/main/java/brooklyn/basic/BrooklynObjectInternal.java
index 84a0a58..26e75c9 100644
--- a/core/src/main/java/brooklyn/basic/BrooklynObjectInternal.java
+++ b/core/src/main/java/brooklyn/basic/BrooklynObjectInternal.java
@@ -18,10 +18,14 @@
  */
 package brooklyn.basic;
 
+import brooklyn.entity.rebind.RebindSupport;
 import brooklyn.entity.rebind.Rebindable;
 
 public interface BrooklynObjectInternal extends BrooklynObject, Rebindable {
     
-    public void setCatalogItemId(String id);
+    void setCatalogItemId(String id);
+    
+    @SuppressWarnings("rawtypes")  // subclasses typically apply stronger typing
+    RebindSupport getRebindSupport();
     
 }

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/38a756b2/core/src/main/java/brooklyn/catalog/internal/CatalogItemDo.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/catalog/internal/CatalogItemDo.java b/core/src/main/java/brooklyn/catalog/internal/CatalogItemDo.java
index f40a49b..fdac692 100644
--- a/core/src/main/java/brooklyn/catalog/internal/CatalogItemDo.java
+++ b/core/src/main/java/brooklyn/catalog/internal/CatalogItemDo.java
@@ -23,6 +23,7 @@ import java.util.Collection;
 import javax.annotation.Nonnull;
 import javax.annotation.Nullable;
 
+import brooklyn.basic.BrooklynObjectInternal;
 import brooklyn.catalog.CatalogItem;
 import brooklyn.entity.rebind.RebindSupport;
 import brooklyn.management.ManagementContext;
@@ -30,7 +31,7 @@ import brooklyn.mementos.CatalogItemMemento;
 
 import com.google.common.base.Preconditions;
 
-public class CatalogItemDo<T,SpecT> implements CatalogItem<T,SpecT> {
+public class CatalogItemDo<T,SpecT> implements CatalogItem<T,SpecT>, BrooklynObjectInternal {
 
     protected final CatalogDo catalog;
     protected final CatalogItemDtoAbstract<T,SpecT> itemDto;
@@ -63,7 +64,12 @@ public class CatalogItemDo<T,SpecT> implements CatalogItem<T,SpecT> {
 
     @Override
     public String getCatalogItemId() {
-        return null;
+        return itemDto.getCatalogItemId();
+    }
+    
+    @Override
+    public void setCatalogItemId(String id) {
+        itemDto.setCatalogItemId(id);
     }
 
     @Override

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/38a756b2/core/src/main/java/brooklyn/entity/rebind/ImmediateDeltaChangeListener.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/entity/rebind/ImmediateDeltaChangeListener.java b/core/src/main/java/brooklyn/entity/rebind/ImmediateDeltaChangeListener.java
index a668a37..d7ddeea 100644
--- a/core/src/main/java/brooklyn/entity/rebind/ImmediateDeltaChangeListener.java
+++ b/core/src/main/java/brooklyn/entity/rebind/ImmediateDeltaChangeListener.java
@@ -92,7 +92,6 @@ public class ImmediateDeltaChangeListener implements ChangeListener {
         if (running && persister != null) {
             PersisterDeltaImpl delta = new PersisterDeltaImpl();
             Memento memento = ((BrooklynObjectInternal)instance).getRebindSupport().getMemento();
-            // XXX use switch statement, and above, and in MementosGenerator
             if (instance instanceof Entity) {
                 delta.entities.add((EntityMemento) memento);
                 addEntityAdjuncts((Entity)instance, delta);

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/38a756b2/core/src/main/java/brooklyn/entity/rebind/PeriodicDeltaChangeListener.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/entity/rebind/PeriodicDeltaChangeListener.java b/core/src/main/java/brooklyn/entity/rebind/PeriodicDeltaChangeListener.java
index b104365..08ca53c 100644
--- a/core/src/main/java/brooklyn/entity/rebind/PeriodicDeltaChangeListener.java
+++ b/core/src/main/java/brooklyn/entity/rebind/PeriodicDeltaChangeListener.java
@@ -31,14 +31,15 @@ import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 import brooklyn.basic.BrooklynObject;
+import brooklyn.basic.BrooklynObjectInternal;
 import brooklyn.catalog.CatalogItem;
 import brooklyn.entity.Entity;
 import brooklyn.entity.Feed;
 import brooklyn.entity.basic.BrooklynTaskTags;
 import brooklyn.entity.basic.EntityInternal;
+import brooklyn.entity.rebind.persister.BrooklynPersistenceUtils;
 import brooklyn.internal.BrooklynFeatureEnablement;
 import brooklyn.location.Location;
-import brooklyn.location.basic.LocationInternal;
 import brooklyn.management.ExecutionContext;
 import brooklyn.management.ExecutionManager;
 import brooklyn.management.Task;
@@ -79,18 +80,19 @@ public class PeriodicDeltaChangeListener implements ChangeListener {
     private static final Logger LOG = LoggerFactory.getLogger(PeriodicDeltaChangeListener.class);
 
     private static class DeltaCollector {
-        Set<Location> locations = Sets.newLinkedHashSet();
-        Set<Entity> entities = Sets.newLinkedHashSet();
-        Set<Policy> policies = Sets.newLinkedHashSet();
-        Set<Enricher> enrichers = Sets.newLinkedHashSet();
-        Set<Feed> feeds = Sets.newLinkedHashSet();
-        Set<CatalogItem<?, ?>> catalogItems = Sets.newLinkedHashSet();
-        Set<String> removedLocationIds = Sets.newLinkedHashSet();
-        Set<String> removedEntityIds = Sets.newLinkedHashSet();
-        Set<String> removedPolicyIds = Sets.newLinkedHashSet();
-        Set<String> removedEnricherIds = Sets.newLinkedHashSet();
-        Set<String> removedFeedIds = Sets.newLinkedHashSet();
-        Set<String> removedCatalogItemIds = Sets.newLinkedHashSet();
+        private Set<Location> locations = Sets.newLinkedHashSet();
+        private Set<Entity> entities = Sets.newLinkedHashSet();
+        private Set<Policy> policies = Sets.newLinkedHashSet();
+        private Set<Enricher> enrichers = Sets.newLinkedHashSet();
+        private Set<Feed> feeds = Sets.newLinkedHashSet();
+        private Set<CatalogItem<?, ?>> catalogItems = Sets.newLinkedHashSet();
+        
+        private Set<String> removedLocationIds = Sets.newLinkedHashSet();
+        private Set<String> removedEntityIds = Sets.newLinkedHashSet();
+        private Set<String> removedPolicyIds = Sets.newLinkedHashSet();
+        private Set<String> removedEnricherIds = Sets.newLinkedHashSet();
+        private Set<String> removedFeedIds = Sets.newLinkedHashSet();
+        private Set<String> removedCatalogItemIds = Sets.newLinkedHashSet();
 
         public boolean isEmpty() {
             return locations.isEmpty() && entities.isEmpty() && policies.isEmpty() && 
@@ -100,6 +102,56 @@ public class PeriodicDeltaChangeListener implements ChangeListener {
                     removedEnricherIds.isEmpty() && removedFeedIds.isEmpty() &&
                     removedCatalogItemIds.isEmpty();
         }
+        
+        public void add(BrooklynObject instance) {
+            BrooklynObjectType type = BrooklynObjectType.of(instance);
+            getUnsafeCollectionOfType(type).add(instance);
+        }
+        
+        public void addIfNotRemoved(BrooklynObject instance) {
+            BrooklynObjectType type = BrooklynObjectType.of(instance);
+            if (!getRemovedIdsOfType(type).contains(instance.getId())) {
+                getUnsafeCollectionOfType(type).add(instance);
+            }
+        }
+
+        public void remove(BrooklynObject instance) {
+            BrooklynObjectType type = BrooklynObjectType.of(instance);
+            getUnsafeCollectionOfType(type).remove(instance);
+            getRemovedIdsOfType(type).add(instance.getId());
+        }
+
+        @SuppressWarnings("unchecked")
+        private Set<BrooklynObject> getUnsafeCollectionOfType(BrooklynObjectType type) {
+            return (Set<BrooklynObject>)getCollectionOfType(type);
+        }
+
+        private Set<? extends BrooklynObject> getCollectionOfType(BrooklynObjectType type) {
+            switch (type) {
+            case ENTITY: return entities;
+            case LOCATION: return locations;
+            case ENRICHER: return enrichers;
+            case FEED: return feeds;
+            case POLICY: return policies;
+            case CATALOG_ITEM: return catalogItems;
+            case UNKNOWN: break;
+            }
+            throw new IllegalStateException("No collection for type "+type);
+        }
+        
+        private Set<String> getRemovedIdsOfType(BrooklynObjectType type) {
+            switch (type) {
+            case ENTITY: return removedEntityIds;
+            case LOCATION: return removedLocationIds;
+            case ENRICHER: return removedEnricherIds;
+            case FEED: return removedFeedIds;
+            case POLICY: return removedPolicyIds;
+            case CATALOG_ITEM: return removedCatalogItemIds;
+            case UNKNOWN: break;
+            }
+            throw new IllegalStateException("No removed ids for type "+type);
+        }
+
     }
     
     private final ExecutionContext executionContext;
@@ -262,47 +314,28 @@ public class PeriodicDeltaChangeListener implements ChangeListener {
     }
     
     private void addReferencedObjects(DeltaCollector deltaCollector) {
-        Set<Location> referencedLocations = Sets.newLinkedHashSet();
-        Set<Policy> referencedPolicies = Sets.newLinkedHashSet();
-        Set<Enricher> referencedEnrichers = Sets.newLinkedHashSet();
-        Set<Feed> referencedFeeds = Sets.newLinkedHashSet();
+        Set<BrooklynObject> referencedObjects = Sets.newLinkedHashSet();
         
+        // collect references
         for (Entity entity : deltaCollector.entities) {
             // FIXME How to let the policy/location tell us about changes? Don't do this every time!
             for (Location location : entity.getLocations()) {
                 Collection<Location> findLocationsInHierarchy = TreeUtils.findLocationsInHierarchy(location);
-                referencedLocations.addAll(findLocationsInHierarchy);
+                referencedObjects.addAll(findLocationsInHierarchy);
             }
             if (persistPoliciesEnabled) {
-                referencedPolicies.addAll(entity.getPolicies());
+                referencedObjects.addAll(entity.getPolicies());
             }
             if (persistEnrichersEnabled) {
-                referencedEnrichers.addAll(entity.getEnrichers());
+                referencedObjects.addAll(entity.getEnrichers());
             }
             if (persistFeedsEnabled) {
-                referencedFeeds.addAll(((EntityInternal)entity).feeds().getFeeds());
+                referencedObjects.addAll(((EntityInternal)entity).feeds().getFeeds());
             }
         }
         
-        for (Location loc : referencedLocations) {
-            if (!deltaCollector.removedLocationIds.contains(loc.getId())) {
-                deltaCollector.locations.add(loc);
-            }
-        }
-        for (Policy pol : referencedPolicies) {
-            if (!deltaCollector.removedPolicyIds.contains(pol.getId())) {
-                deltaCollector.policies.add(pol);
-            }
-        }
-        for (Enricher enr : referencedEnrichers) {
-            if (!deltaCollector.removedEnricherIds.contains(enr.getId())) {
-                deltaCollector.enrichers.add(enr);
-            }
-        }
-        for (Feed feed : referencedFeeds) {
-            if (!deltaCollector.removedFeedIds.contains(feed.getId())) {
-                deltaCollector.feeds.add(feed);
-            }
+        for (BrooklynObject instance : referencedObjects) {
+            deltaCollector.addIfNotRemoved(instance);
         }
     }
     
@@ -344,54 +377,19 @@ public class PeriodicDeltaChangeListener implements ChangeListener {
                 if (LOG.isTraceEnabled()) LOG.trace("No changes to persist since last delta");
             } else {
                 PersisterDeltaImpl persisterDelta = new PersisterDeltaImpl();
-                for (Location location : prevDeltaCollector.locations) {
-                    try {
-                        persisterDelta.locations.add(((LocationInternal)location).getRebindSupport().getMemento());
-                    } catch (Exception e) {
-                        exceptionHandler.onGenerateMementoFailed(BrooklynObjectType.LOCATION, location, e);
-                    }
-                }
-                for (Entity entity : prevDeltaCollector.entities) {
-                    try {
-                        persisterDelta.entities.add(((EntityInternal)entity).getRebindSupport().getMemento());
-                    } catch (Exception e) {
-                        exceptionHandler.onGenerateMementoFailed(BrooklynObjectType.ENTITY, entity, e);
-                    }
-                }
-                for (Policy policy : prevDeltaCollector.policies) {
-                    try {
-                        persisterDelta.policies.add(policy.getRebindSupport().getMemento());
-                    } catch (Exception e) {
-                        exceptionHandler.onGenerateMementoFailed(BrooklynObjectType.POLICY, policy, e);
-                    }
-                }
-                for (Enricher enricher : prevDeltaCollector.enrichers) {
-                    try {
-                        persisterDelta.enrichers.add(enricher.getRebindSupport().getMemento());
-                    } catch (Exception e) {
-                        exceptionHandler.onGenerateMementoFailed(BrooklynObjectType.ENRICHER, enricher, e);
-                    }
-                }
-                for (Feed feed : prevDeltaCollector.feeds) {
-                    try {
-                        persisterDelta.feeds.add(feed.getRebindSupport().getMemento());
-                    } catch (Exception e) {
-                        exceptionHandler.onGenerateMementoFailed(BrooklynObjectType.FEED, feed, e);
+                
+                for (BrooklynObjectType type: BrooklynPersistenceUtils.STANDARD_BROOKLYN_OBJECT_TYPE_PERSISTENCE_ORDER) {
+                    for (BrooklynObject instance: prevDeltaCollector.getCollectionOfType(type)) {
+                        try {
+                            persisterDelta.add(type, ((BrooklynObjectInternal)instance).getRebindSupport().getMemento());
+                        } catch (Exception e) {
+                            exceptionHandler.onGenerateMementoFailed(type, instance, e);
+                        }
                     }
                 }
-                for (CatalogItem<?, ?> catalogItem : prevDeltaCollector.catalogItems) {
-                    try {
-                        persisterDelta.catalogItems.add(catalogItem.getRebindSupport().getMemento());
-                    } catch (Exception e) {
-                        exceptionHandler.onGenerateMementoFailed(BrooklynObjectType.CATALOG_ITEM, catalogItem, e);
-                    }
+                for (BrooklynObjectType type: BrooklynPersistenceUtils.STANDARD_BROOKLYN_OBJECT_TYPE_PERSISTENCE_ORDER) {
+                    persisterDelta.removed(type, prevDeltaCollector.getRemovedIdsOfType(type));
                 }
-                persisterDelta.removedLocationIds = prevDeltaCollector.removedLocationIds;
-                persisterDelta.removedEntityIds = prevDeltaCollector.removedEntityIds;
-                persisterDelta.removedPolicyIds = prevDeltaCollector.removedPolicyIds;
-                persisterDelta.removedEnricherIds = prevDeltaCollector.removedEnricherIds;
-                persisterDelta.removedFeedIds = prevDeltaCollector.removedFeedIds;
-                persisterDelta.removedCatalogItemIds = prevDeltaCollector.removedCatalogItemIds;
 
                 /*
                  * Need to guarantee "happens before", with any thread that subsequently reads
@@ -441,63 +439,30 @@ public class PeriodicDeltaChangeListener implements ChangeListener {
     public synchronized void onUnmanaged(BrooklynObject instance) {
         if (LOG.isTraceEnabled()) LOG.trace("onUnmanaged: {}", instance);
         if (!isStopped()) {
+            removeFromCollector(instance);
             if (instance instanceof Entity) {
                 Entity entity = (Entity) instance;
-                deltaCollector.removedEntityIds.add(entity.getId());
-                deltaCollector.entities.remove(entity);
-                
-                for (Policy policy : entity.getPolicies()) {
-                    deltaCollector.removedPolicyIds.add(policy.getId());
-                    deltaCollector.policies.remove(policy);
-                }
-                for (Enricher enricher : entity.getEnrichers()) {
-                    deltaCollector.removedEnricherIds.add(enricher.getId());
-                    deltaCollector.enrichers.remove(enricher);
-                }
-                for (Feed feed : ((EntityInternal)entity).feeds().getFeeds()) {
-                    deltaCollector.removedFeedIds.add(feed.getId());
-                    deltaCollector.feeds.remove(feed);
-                }
-            } else if (instance instanceof Location) {
-                deltaCollector.removedLocationIds.add(instance.getId());
-                deltaCollector.locations.remove(instance);
-            } else if (instance instanceof Policy) {
-                deltaCollector.removedPolicyIds.add(instance.getId());
-                deltaCollector.policies.remove(instance);
-            } else if (instance instanceof Enricher) {
-                deltaCollector.removedEnricherIds.add(instance.getId());
-                deltaCollector.enrichers.remove(instance);
-            } else if (instance instanceof Feed) {
-                deltaCollector.removedFeedIds.add(instance.getId());
-                deltaCollector.feeds.remove(instance);
-            } else if (instance instanceof CatalogItem) {
-                deltaCollector.removedCatalogItemIds.add(instance.getId());
-                deltaCollector.catalogItems.remove(instance);
-            } else {
-                throw new IllegalStateException("Unexpected brooklyn type: "+instance);
+                for (BrooklynObject adjunct : entity.getPolicies()) removeFromCollector(adjunct);
+                for (BrooklynObject adjunct : entity.getEnrichers()) removeFromCollector(adjunct);
+                for (BrooklynObject adjunct : ((EntityInternal)entity).feeds().getFeeds()) removeFromCollector(adjunct);
             }
         }
     }
+    
+    private void removeFromCollector(BrooklynObject instance) {
+        deltaCollector.remove(instance);
+    }
 
     @Override
     public synchronized void onChanged(BrooklynObject instance) {
         if (LOG.isTraceEnabled()) LOG.trace("onChanged: {}", instance);
         if (!isStopped()) {
-            if (instance instanceof Entity) {
-                deltaCollector.entities.add((Entity)instance);
-            } else if (instance instanceof Location) {
-                deltaCollector.locations.add((Location) instance);
-            } else if (instance instanceof Policy) {
-                deltaCollector.policies.add((Policy) instance);
-            } else if (instance instanceof Enricher) {
-                deltaCollector.enrichers.add((Enricher) instance);
-            } else if (instance instanceof Feed) {
-                deltaCollector.feeds.add((Feed) instance);
-            } else if (instance instanceof CatalogItem) {
-                deltaCollector.catalogItems.add((CatalogItem<?,?>) instance);
-            } else {
-                throw new IllegalStateException("Unexpected brooklyn type: "+instance);
-            }
+            deltaCollector.add(instance);
         }
     }
+    
+    public PersistenceExceptionHandler getExceptionHandler() {
+        return exceptionHandler;
+    }
+    
 }

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/38a756b2/core/src/main/java/brooklyn/entity/rebind/PersistenceExceptionHandlerImpl.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/entity/rebind/PersistenceExceptionHandlerImpl.java b/core/src/main/java/brooklyn/entity/rebind/PersistenceExceptionHandlerImpl.java
index aa16710..bce5c83 100644
--- a/core/src/main/java/brooklyn/entity/rebind/PersistenceExceptionHandlerImpl.java
+++ b/core/src/main/java/brooklyn/entity/rebind/PersistenceExceptionHandlerImpl.java
@@ -82,16 +82,18 @@ public class PersistenceExceptionHandlerImpl implements PersistenceExceptionHand
         onErrorImpl(errmsg, e, prevFailedPersisters.add(id));
     }
     
-    protected void onErrorImpl(String errmsg, Exception e, boolean isRepeat) {
+    protected void onErrorImpl(String errmsg, Exception e, boolean isNew) {
+        // TODO the default behaviour is simply to warn; we should have a "fail_at_end" behaviour,
+        // and a way for other subsystems to tune in to such failures
         Exceptions.propagateIfFatal(e);
         if (isActive()) {
-            if (isRepeat) {
+            if (!isNew) {
                 if (LOG.isDebugEnabled()) LOG.debug("Repeating problem: "+errmsg, e);
             } else {
                 LOG.warn("Problem: "+errmsg, e);
             }
         } else {
-            if (isRepeat) {
+            if (!isNew) {
                 if (LOG.isTraceEnabled()) LOG.trace("Repeating problem: "+errmsg+"; but no longer active (ignoring)", e);
             } else {
                 if (LOG.isDebugEnabled()) LOG.debug("Problem: "+errmsg+"; but no longer active (ignoring)", e);

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/38a756b2/core/src/main/java/brooklyn/entity/rebind/PersisterDeltaImpl.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/entity/rebind/PersisterDeltaImpl.java b/core/src/main/java/brooklyn/entity/rebind/PersisterDeltaImpl.java
index c971a2f..891e9db 100644
--- a/core/src/main/java/brooklyn/entity/rebind/PersisterDeltaImpl.java
+++ b/core/src/main/java/brooklyn/entity/rebind/PersisterDeltaImpl.java
@@ -19,8 +19,11 @@
 package brooklyn.entity.rebind;
 
 import java.util.Collection;
+import java.util.Collections;
+import java.util.Set;
 
 import brooklyn.mementos.BrooklynMementoPersister.Delta;
+import brooklyn.mementos.BrooklynMementoPersister.MutableDelta;
 import brooklyn.mementos.CatalogItemMemento;
 import brooklyn.mementos.EnricherMemento;
 import brooklyn.mementos.EntityMemento;
@@ -29,69 +32,13 @@ import brooklyn.mementos.LocationMemento;
 import brooklyn.mementos.Memento;
 import brooklyn.mementos.PolicyMemento;
 
+import com.google.common.annotations.Beta;
+import com.google.common.collect.Iterables;
 import com.google.common.collect.Sets;
 
-public class PersisterDeltaImpl implements Delta {
+public class PersisterDeltaImpl implements Delta, MutableDelta {
     
-    public static Builder builder() {
-        return new Builder();
-    }
-    
-    public static class Builder {
-        private final PersisterDeltaImpl delta = new PersisterDeltaImpl();
-
-        public Builder locations(Collection<? extends LocationMemento> vals) {
-            delta.locations.addAll(vals);
-            return this;
-        }
-        public Builder entities(Collection<? extends EntityMemento> vals) {
-            delta.entities.addAll(vals);
-            return this;
-        }
-        public Builder policies(Collection<? extends PolicyMemento> vals) {
-            delta.policies.addAll(vals);
-            return this;
-        }
-        public Builder enrichers(Collection<? extends EnricherMemento> vals) {
-            delta.enrichers.addAll(vals);
-            return this;
-        }
-        public Builder feeds(Collection<? extends FeedMemento> vals) {
-            delta.feeds.addAll(vals);
-            return this;
-        }
-        public Builder catalogItems(Collection<? extends CatalogItemMemento> vals) {
-            delta.catalogItems.addAll(vals);
-            return this;
-        }
-        public Builder removedLocationIds(Collection<String> vals) {
-            delta.removedLocationIds.addAll(vals);
-            return this;
-        }
-        public Builder removedEntityIds(Collection<String> vals) {
-            delta.removedEntityIds.addAll(vals);
-            return this;
-        }
-        public Builder removedPolicyIds(Collection<String> vals) {
-            delta.removedPolicyIds.addAll(vals);
-            return this;
-        }
-        public Builder removedEnricherIds(Collection<String> vals) {
-            delta.removedEnricherIds.addAll(vals);
-            return this;
-        }
-        public Builder removedFeedIds(Collection<String> vals) {
-            delta.removedFeedIds.addAll(vals);
-            return this;
-        }
-        public Builder removedCatalogItemIds(Collection<String> vals) {
-            delta.removedCatalogItemIds.addAll(vals);
-            return this;
-        }
-        public Delta build() {
-            return delta;
-        }
-    }
+    // use multiset?
     
     Collection<LocationMemento> locations = Sets.newLinkedHashSet();
     Collection<EntityMemento> entities = Sets.newLinkedHashSet();
@@ -99,6 +46,7 @@ public class PersisterDeltaImpl implements Delta {
     Collection<EnricherMemento> enrichers = Sets.newLinkedHashSet();
     Collection<FeedMemento> feeds = Sets.newLinkedHashSet();
     Collection<CatalogItemMemento> catalogItems = Sets.newLinkedHashSet();
+    
     Collection<String> removedLocationIds = Sets.newLinkedHashSet();
     Collection<String> removedEntityIds = Sets.newLinkedHashSet();
     Collection<String> removedPolicyIds = Sets.newLinkedHashSet();
@@ -108,73 +56,82 @@ public class PersisterDeltaImpl implements Delta {
 
     @Override
     public Collection<LocationMemento> locations() {
-        return locations;
+        return Collections.unmodifiableCollection(locations);
     }
 
     @Override
     public Collection<EntityMemento> entities() {
-        return entities;
+        return Collections.unmodifiableCollection(entities);
     }
 
     @Override
     public Collection<PolicyMemento> policies() {
-        return policies;
+        return Collections.unmodifiableCollection(policies);
     }
 
     @Override
     public Collection<EnricherMemento> enrichers() {
-        return enrichers;
+        return Collections.unmodifiableCollection(enrichers);
     }
     
     @Override
     public Collection<FeedMemento> feeds() {
-        return feeds;
+        return Collections.unmodifiableCollection(feeds);
     }
 
     @Override
     public Collection<CatalogItemMemento> catalogItems() {
-        return catalogItems;
+        return Collections.unmodifiableCollection(catalogItems);
     }
 
     @Override
     public Collection<String> removedLocationIds() {
-        return removedLocationIds;
+        return Collections.unmodifiableCollection(removedLocationIds);
     }
 
     @Override
     public Collection<String> removedEntityIds() {
-        return removedEntityIds;
+        return Collections.unmodifiableCollection(removedEntityIds);
     }
     
     @Override
     public Collection<String> removedPolicyIds() {
-        return removedPolicyIds;
+        return Collections.unmodifiableCollection(removedPolicyIds);
     }
     
     @Override
     public Collection<String> removedEnricherIds() {
-        return removedEnricherIds;
+        return Collections.unmodifiableCollection(removedEnricherIds);
     }
     
     @Override
     public Collection<String> removedFeedIds() {
-        return removedFeedIds;
+        return Collections.unmodifiableCollection(removedFeedIds);
     }
 
     @Override
     public Collection<String> removedCatalogItemIds() {
-        return removedCatalogItemIds;
+        return Collections.unmodifiableCollection(removedCatalogItemIds);
     }
-    
+
     @Override
     public Collection<? extends Memento> getObjectsOfType(BrooklynObjectType type) {
+        return Collections.unmodifiableCollection(getMutableObjectsOfType(type));
+    }
+    
+    @SuppressWarnings("unchecked")
+    @Beta
+    private Collection<Memento> getMutableUncheckedObjectsOfType(BrooklynObjectType type) {
+        return (Collection<Memento>)getMutableObjectsOfType(type);
+    }
+    private Collection<? extends Memento> getMutableObjectsOfType(BrooklynObjectType type) {
         switch (type) {
-        case ENTITY: return entities();
-        case LOCATION: return locations();
-        case POLICY: return policies();
-        case ENRICHER: return enrichers();
-        case FEED: return feeds();
-        case CATALOG_ITEM: return catalogItems();
+        case ENTITY: return entities;
+        case LOCATION: return locations;
+        case POLICY: return policies;
+        case ENRICHER: return enrichers;
+        case FEED: return feeds;
+        case CATALOG_ITEM: return catalogItems;
         case UNKNOWN: 
         default:
             throw new IllegalArgumentException(type+" not supported");
@@ -182,17 +139,35 @@ public class PersisterDeltaImpl implements Delta {
     }
     
     @Override
-    public Collection<String> getRemovedObjectsOfType(BrooklynObjectType type) {
+    public Collection<String> getRemovedIdsOfType(BrooklynObjectType type) {
+        return Collections.unmodifiableCollection(getRemovedIdsOfTypeMutable(type));
+    }
+    
+    private Collection<String> getRemovedIdsOfTypeMutable(BrooklynObjectType type) {
         switch (type) {
-        case ENTITY: return removedEntityIds();
-        case LOCATION: return removedLocationIds();
-        case POLICY: return removedPolicyIds();
-        case ENRICHER: return removedEnricherIds();
-        case FEED: return removedFeedIds();
-        case CATALOG_ITEM: return removedCatalogItemIds();
+        case ENTITY: return removedEntityIds;
+        case LOCATION: return removedLocationIds;
+        case POLICY: return removedPolicyIds;
+        case ENRICHER: return removedEnricherIds;
+        case FEED: return removedFeedIds;
+        case CATALOG_ITEM: return removedCatalogItemIds;
         case UNKNOWN: 
         default:
             throw new IllegalArgumentException(type+" not supported");
         }
     }
+
+    public void add(BrooklynObjectType type, Memento memento) {
+        getMutableUncheckedObjectsOfType(type).add(memento);
+    }
+
+    @Override
+    public void addAll(BrooklynObjectType type, Iterable<? extends Memento> mementos) {
+        Iterables.addAll(getMutableUncheckedObjectsOfType(type), mementos);
+    }
+    
+    public void removed(BrooklynObjectType type, Set<String> removedIdsOfType) {
+        getRemovedIdsOfTypeMutable(type).addAll(removedIdsOfType);    
+    }
+
 }

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/38a756b2/core/src/main/java/brooklyn/entity/rebind/RebindManagerImpl.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/entity/rebind/RebindManagerImpl.java b/core/src/main/java/brooklyn/entity/rebind/RebindManagerImpl.java
index 67263c7..23bbf26 100644
--- a/core/src/main/java/brooklyn/entity/rebind/RebindManagerImpl.java
+++ b/core/src/main/java/brooklyn/entity/rebind/RebindManagerImpl.java
@@ -57,6 +57,7 @@ import brooklyn.entity.proxying.InternalFactory;
 import brooklyn.entity.proxying.InternalLocationFactory;
 import brooklyn.entity.proxying.InternalPolicyFactory;
 import brooklyn.entity.rebind.persister.BrooklynMementoPersisterToObjectStore;
+import brooklyn.entity.rebind.persister.BrooklynPersistenceUtils;
 import brooklyn.event.feed.AbstractFeed;
 import brooklyn.internal.BrooklynFeatureEnablement;
 import brooklyn.location.Location;
@@ -396,8 +397,20 @@ public class RebindManagerImpl implements RebindManager {
     @Override
     @VisibleForTesting
     public void forcePersistNow() {
-//        XXX persistenceStoreAccess.checkpoint(memento, exceptionHandler);
-        persistenceRealChangeListener.persistNow();
+        forcePersistNow(false, null);
+    }
+    @Override
+    @VisibleForTesting
+    public void forcePersistNow(boolean full, PersistenceExceptionHandler exceptionHandler) {
+        if (full) {
+            BrooklynMementoRawData memento = BrooklynPersistenceUtils.newFullMemento(managementContext);
+            if (exceptionHandler==null) {
+                exceptionHandler = persistenceRealChangeListener.getExceptionHandler();
+            }
+            persistenceStoreAccess.checkpoint(memento, exceptionHandler);
+        } else {
+            persistenceRealChangeListener.persistNow();
+        }
     }
     
     @Override

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/38a756b2/core/src/main/java/brooklyn/entity/rebind/persister/AbstractBrooklynMementoPersister.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/entity/rebind/persister/AbstractBrooklynMementoPersister.java b/core/src/main/java/brooklyn/entity/rebind/persister/AbstractBrooklynMementoPersister.java
index 42643ab..d296e35 100644
--- a/core/src/main/java/brooklyn/entity/rebind/persister/AbstractBrooklynMementoPersister.java
+++ b/core/src/main/java/brooklyn/entity/rebind/persister/AbstractBrooklynMementoPersister.java
@@ -34,8 +34,7 @@ import brooklyn.mementos.LocationMemento;
 import brooklyn.mementos.PolicyMemento;
 
 /**
- * @deprecated since 0.7.0 for production use {@link BrooklynMementoPersisterToMultiFile} instead; 
- *             this class will be merged with {@link BrooklynMementoPersisterInMemory} in test code.
+ * @deprecated since 0.7.0 for production use {@link BrooklynMementoPersisterToObjectStore} instead 
  */
 @Deprecated
 public abstract class AbstractBrooklynMementoPersister implements BrooklynMementoPersister {
@@ -92,6 +91,11 @@ public abstract class AbstractBrooklynMementoPersister implements BrooklynMement
     public void checkpoint(BrooklynMemento newMemento, PersistenceExceptionHandler exceptionHandler) {
         memento.reset(checkNotNull(newMemento, "memento"));
     }
+    
+    public void checkpoint(BrooklynMementoRawData newMemento, PersistenceExceptionHandler exceptionHandler) {
+        throw new IllegalStateException("Not supported; use "+BrooklynMementoPersisterToObjectStore.class);
+    }
+    
 
     @Override
     public void delta(Delta delta, PersistenceExceptionHandler exceptionHanlder) {

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/38a756b2/core/src/main/java/brooklyn/entity/rebind/persister/BrooklynMementoPersisterInMemory.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/entity/rebind/persister/BrooklynMementoPersisterInMemory.java b/core/src/main/java/brooklyn/entity/rebind/persister/BrooklynMementoPersisterInMemory.java
index 902b57e..dbb1e52 100644
--- a/core/src/main/java/brooklyn/entity/rebind/persister/BrooklynMementoPersisterInMemory.java
+++ b/core/src/main/java/brooklyn/entity/rebind/persister/BrooklynMementoPersisterInMemory.java
@@ -59,7 +59,7 @@ import com.google.common.base.Optional;
 import com.google.common.base.Throwables;
 
 /**
- * @deprecated since 0.7.0 for production use {@link BrooklynMementoPersisterToMultiFile} instead; class be moved to tests
+ * @deprecated since 0.7.0 for production use {@link BrooklynMementoPersisterToObjectStore} instead; class be moved to tests
  * <code>
  * new BrooklynMementoPersisterToObjectStore(new InMemoryObjectStore(), classLoader)
  * </code>

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/38a756b2/core/src/main/java/brooklyn/entity/rebind/persister/BrooklynMementoPersisterToMultiFile.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/entity/rebind/persister/BrooklynMementoPersisterToMultiFile.java b/core/src/main/java/brooklyn/entity/rebind/persister/BrooklynMementoPersisterToMultiFile.java
index 74817c5..d3318b3 100644
--- a/core/src/main/java/brooklyn/entity/rebind/persister/BrooklynMementoPersisterToMultiFile.java
+++ b/core/src/main/java/brooklyn/entity/rebind/persister/BrooklynMementoPersisterToMultiFile.java
@@ -390,6 +390,12 @@ public class BrooklynMementoPersisterToMultiFile implements BrooklynMementoPersi
         for (CatalogItemMemento m : newMemento.getCatalogItemMementos().values()) {
             persist(m);
         }
+        LOG.warn("Using legacy persister; feeds will not be persisted");
+    }
+    
+    @Override
+    public void checkpoint(BrooklynMementoRawData newMemento, PersistenceExceptionHandler exceptionHandler) {
+        throw new IllegalStateException("Not supported; use "+BrooklynMementoPersisterToObjectStore.class);
     }
     
     @Override

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/38a756b2/core/src/main/java/brooklyn/entity/rebind/persister/BrooklynMementoPersisterToObjectStore.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/entity/rebind/persister/BrooklynMementoPersisterToObjectStore.java b/core/src/main/java/brooklyn/entity/rebind/persister/BrooklynMementoPersisterToObjectStore.java
index cbf6ccb..115fecc 100644
--- a/core/src/main/java/brooklyn/entity/rebind/persister/BrooklynMementoPersisterToObjectStore.java
+++ b/core/src/main/java/brooklyn/entity/rebind/persister/BrooklynMementoPersisterToObjectStore.java
@@ -71,7 +71,6 @@ import com.google.common.annotations.Beta;
 import com.google.common.annotations.VisibleForTesting;
 import com.google.common.base.Objects;
 import com.google.common.base.Stopwatch;
-import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Lists;
 import com.google.common.util.concurrent.Futures;
@@ -100,10 +99,6 @@ public class BrooklynMementoPersisterToObjectStore implements BrooklynMementoPer
             "Maximum number of attempts to serialize a memento (e.g. if first attempts fail because of concurrent modifications of an entity)", 
             5);
 
-    static final List<BrooklynObjectType> PERSISTED_OBJECT_TYPES_IN_ORDER = ImmutableList.of( 
-        BrooklynObjectType.ENTITY, BrooklynObjectType.LOCATION, BrooklynObjectType.POLICY,
-        BrooklynObjectType.ENRICHER, BrooklynObjectType.FEED, BrooklynObjectType.CATALOG_ITEM);
-
     private final PersistenceObjectStore objectStore;
     private final MementoSerializer<Object> serializerWithStandardClassLoader;
 
@@ -251,7 +246,7 @@ public class BrooklynMementoPersisterToObjectStore implements BrooklynMementoPer
 
         Stopwatch stopwatch = Stopwatch.createStarted();
         try {
-            for (BrooklynObjectType type: PERSISTED_OBJECT_TYPES_IN_ORDER)
+            for (BrooklynObjectType type: BrooklynPersistenceUtils.STANDARD_BROOKLYN_OBJECT_TYPE_PERSISTENCE_ORDER)
                 subPathDataBuilder.putAll(type, makeIdSubPathMap(objectStore.listContentsWithSubPath(type.getSubPathName())));
             
         } catch (Exception e) {
@@ -459,7 +454,7 @@ public class BrooklynMementoPersisterToObjectStore implements BrooklynMementoPer
             }
         }
         
-        for (BrooklynObjectType type: PERSISTED_OBJECT_TYPES_IN_ORDER) {
+        for (BrooklynObjectType type: BrooklynPersistenceUtils.STANDARD_BROOKLYN_OBJECT_TYPE_PERSISTENCE_ORDER) {
             for (final Map.Entry<String,String> entry : rawData.getObjectsOfType(type).entrySet()) {
                 futures.add(executor.submit(new VisitorWrapper(type, entry)));
             }
@@ -501,6 +496,8 @@ public class BrooklynMementoPersisterToObjectStore implements BrooklynMementoPer
         }
     }
     
+    /** See {@link BrooklynPersistenceUtils} for conveniences for using this method. */
+    @Override
     @Beta
     public void checkpoint(BrooklynMementoRawData newMemento, PersistenceExceptionHandler exceptionHandler) {
         checkWritesAllowed();
@@ -516,7 +513,7 @@ public class BrooklynMementoPersisterToObjectStore implements BrooklynMementoPer
             Stopwatch stopwatch = Stopwatch.createStarted();
             List<ListenableFuture<?>> futures = Lists.newArrayList();
             
-            for (BrooklynObjectType type: PERSISTED_OBJECT_TYPES_IN_ORDER) {
+            for (BrooklynObjectType type: BrooklynPersistenceUtils.STANDARD_BROOKLYN_OBJECT_TYPE_PERSISTENCE_ORDER) {
                 for (Map.Entry<String, String> entry : newMemento.getObjectsOfType(type).entrySet()) {
                     futures.add(asyncPersist(type.getSubPathName(), type, entry.getKey(), entry.getValue(), exceptionHandler));
                 }
@@ -541,14 +538,14 @@ public class BrooklynMementoPersisterToObjectStore implements BrooklynMementoPer
     public void checkpoint(BrooklynMemento newMemento, PersistenceExceptionHandler exceptionHandler) {
         checkWritesAllowed();
 
-        Delta delta = PersisterDeltaImpl.builder()
-                .entities(newMemento.getEntityMementos().values())
-                .locations(newMemento.getLocationMementos().values())
-                .policies(newMemento.getPolicyMementos().values())
-                .enrichers(newMemento.getEnricherMementos().values())
-                .feeds(newMemento.getFeedMementos().values())
-                .catalogItems(newMemento.getCatalogItemMementos().values())
-                .build();
+        MutableDelta delta = new PersisterDeltaImpl();
+        delta.addAll(BrooklynObjectType.ENTITY, newMemento.getEntityMementos().values());
+        delta.addAll(BrooklynObjectType.LOCATION, newMemento.getLocationMementos().values());
+        delta.addAll(BrooklynObjectType.POLICY, newMemento.getPolicyMementos().values());
+        delta.addAll(BrooklynObjectType.ENRICHER, newMemento.getEnricherMementos().values());
+        delta.addAll(BrooklynObjectType.FEED, newMemento.getFeedMementos().values());
+        delta.addAll(BrooklynObjectType.CATALOG_ITEM, newMemento.getCatalogItemMementos().values());
+        
         Stopwatch stopwatch = deltaImpl(delta, exceptionHandler);
         
         if (LOG.isDebugEnabled()) LOG.debug("Checkpointed entire memento in {}", Time.makeTimeStringRounded(stopwatch));
@@ -587,13 +584,13 @@ public class BrooklynMementoPersisterToObjectStore implements BrooklynMementoPer
             Stopwatch stopwatch = Stopwatch.createStarted();
             List<ListenableFuture<?>> futures = Lists.newArrayList();
             
-            for (BrooklynObjectType type: PERSISTED_OBJECT_TYPES_IN_ORDER) {
+            for (BrooklynObjectType type: BrooklynPersistenceUtils.STANDARD_BROOKLYN_OBJECT_TYPE_PERSISTENCE_ORDER) {
                 for (Memento entity : delta.getObjectsOfType(type)) {
                     futures.add(asyncPersist(type.getSubPathName(), entity, exceptionHandler));
                 }
             }
-            for (BrooklynObjectType type: PERSISTED_OBJECT_TYPES_IN_ORDER) {
-                for (String id : delta.getRemovedObjectsOfType(type)) {
+            for (BrooklynObjectType type: BrooklynPersistenceUtils.STANDARD_BROOKLYN_OBJECT_TYPE_PERSISTENCE_ORDER) {
+                for (String id : delta.getRemovedIdsOfType(type)) {
                     futures.add(asyncDelete(type.getSubPathName(), id, exceptionHandler));
                 }
             }

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/38a756b2/core/src/main/java/brooklyn/entity/rebind/persister/BrooklynPersistenceUtils.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/entity/rebind/persister/BrooklynPersistenceUtils.java b/core/src/main/java/brooklyn/entity/rebind/persister/BrooklynPersistenceUtils.java
index e8bd336..373ce1d 100644
--- a/core/src/main/java/brooklyn/entity/rebind/persister/BrooklynPersistenceUtils.java
+++ b/core/src/main/java/brooklyn/entity/rebind/persister/BrooklynPersistenceUtils.java
@@ -18,6 +18,8 @@
  */
 package brooklyn.entity.rebind.persister;
 
+import java.util.List;
+
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -27,6 +29,7 @@ import brooklyn.config.BrooklynServerConfig;
 import brooklyn.entity.Entity;
 import brooklyn.entity.Feed;
 import brooklyn.entity.basic.EntityInternal;
+import brooklyn.entity.rebind.BrooklynObjectType;
 import brooklyn.entity.rebind.PersistenceExceptionHandler;
 import brooklyn.entity.rebind.PersistenceExceptionHandlerImpl;
 import brooklyn.entity.rebind.dto.MementosGenerators;
@@ -51,12 +54,19 @@ import brooklyn.util.text.Strings;
 import brooklyn.util.time.Duration;
 import brooklyn.util.time.Time;
 
+import com.google.common.annotations.Beta;
 import com.google.common.base.Stopwatch;
+import com.google.common.collect.ImmutableList;
 
 public class BrooklynPersistenceUtils {
 
     private static final Logger log = LoggerFactory.getLogger(BrooklynPersistenceUtils.class);
     
+    @Beta
+    public static final List<BrooklynObjectType> STANDARD_BROOKLYN_OBJECT_TYPE_PERSISTENCE_ORDER = ImmutableList.of( 
+        BrooklynObjectType.ENTITY, BrooklynObjectType.LOCATION, BrooklynObjectType.POLICY,
+        BrooklynObjectType.ENRICHER, BrooklynObjectType.FEED, BrooklynObjectType.CATALOG_ITEM);
+
     /** Creates a {@link PersistenceObjectStore} for general-purpose use. */
     public static PersistenceObjectStore newPersistenceObjectStore(ManagementContext managementContext,
             String locationSpec, String locationContainer) {

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/38a756b2/core/src/main/java/brooklyn/management/internal/NonDeploymentManagementContext.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/management/internal/NonDeploymentManagementContext.java b/core/src/main/java/brooklyn/management/internal/NonDeploymentManagementContext.java
index 1343e90..20755dd 100644
--- a/core/src/main/java/brooklyn/management/internal/NonDeploymentManagementContext.java
+++ b/core/src/main/java/brooklyn/management/internal/NonDeploymentManagementContext.java
@@ -523,6 +523,10 @@ public class NonDeploymentManagementContext implements ManagementContextInternal
             throw new IllegalStateException("Non-deployment context "+NonDeploymentManagementContext.this+" is not valid for this operation.");
         }
         @Override
+        public void forcePersistNow(boolean full, PersistenceExceptionHandler exceptionHandler) {
+            throw new IllegalStateException("Non-deployment context "+NonDeploymentManagementContext.this+" is not valid for this operation.");
+        }
+        @Override
         public BrooklynMementoRawData retrieveMementoRawData() {
             throw new IllegalStateException("Non-deployment context "+NonDeploymentManagementContext.this+" is not valid for this operation.");
         }

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/38a756b2/core/src/test/java/brooklyn/entity/rebind/RebindCatalogItemTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/brooklyn/entity/rebind/RebindCatalogItemTest.java b/core/src/test/java/brooklyn/entity/rebind/RebindCatalogItemTest.java
index 8d44e3d..93460dc 100644
--- a/core/src/test/java/brooklyn/entity/rebind/RebindCatalogItemTest.java
+++ b/core/src/test/java/brooklyn/entity/rebind/RebindCatalogItemTest.java
@@ -56,9 +56,9 @@ public class RebindCatalogItemTest extends RebindTestFixtureWithApp {
     @BeforeMethod(alwaysRun = true)
     @Override
     public void setUp() throws Exception {
-        super.setUp();
         catalogPersistenceWasEnabled = BrooklynFeatureEnablement.isEnabled(BrooklynFeatureEnablement.FEATURE_CATALOG_PERSISTENCE_PROPERTY);
         BrooklynFeatureEnablement.enable(BrooklynFeatureEnablement.FEATURE_CATALOG_PERSISTENCE_PROPERTY);
+        super.setUp();
         BasicCampPlatform platform = new CampPlatformWithJustBrooklynMgmt(origManagementContext);
         MockWebPlatform.populate(platform, TestAppAssemblyInstantiator.class);
         origApp.createAndManageChild(EntitySpec.create(TestEntity.class));
@@ -149,7 +149,7 @@ public class RebindCatalogItemTest extends RebindTestFixtureWithApp {
         // Must make sure that the original catalogue item is not managed and unmanaged in the same
         // persistence window. Because BrooklynMementoPersisterToObjectStore applies writes/deletes
         // asynchronously the winner is down to a race and the test might pass or fail.
-        origManagementContext.getRebindManager().forcePersistNow();
+        origManagementContext.getRebindManager().forcePersistNow(false, null);
         origManagementContext.getCatalog().deleteCatalogItem(toRemove.getSymbolicName(), toRemove.getVersion());
         assertEquals(Iterables.size(origManagementContext.getCatalog().getCatalogItems()), 0);
         rebindAndAssertCatalogsAreEqual();


[05/21] incubator-brooklyn git commit: Create backup of persistent stores at key times

Posted by he...@apache.org.
Create backup of persistent stores at key times

Created on master promotion (technically just before persistence starts) by reading from the store,
and on demotion by HA subsystem by reading from local in-memory model.

Includes new BrooklynServerPaths cleaning up logic from BrooklynServerConfig, and some cleanups to the utils in BrooklynPersistenceUtils.
Relevant config keys are the *PERSISTENCE*BACKUP* options in BrooklynServerConfig.

Logging now looks like this:

     2014-11-11 13:46:06,153 INFO  Rebinding from /Users/alex/.brooklyn/brooklyn-persisted-state/data...
     2014-11-11 13:46:06,409 INFO  Rebind VanillaSoftwareProcessImpl{id=wGwL3QXG} connecting to pre-running service
     2014-11-11 13:46:06,606 INFO  Rebind complete (MASTER) in 669ms: 1 app, 2 entities, 14 locations, 0 policies, 9 enrichers, 0 feeds, 0 catalog items
    *2014-11-11 13:46:06,697 INFO  Back-up of persisted state created on promotion, in /Users/alex/.brooklyn/brooklyn-persisted-state/data/backups/2014-11-11-1346-oZyOou3V-promotion-s5kE
     2014-11-11 13:46:26,362 INFO  REST using security provider brooklyn.rest.security.provider.BrooklynUserWithRandomPasswordSecurityProvider
     2014-11-11 13:46:26,363 INFO  Allowing access to web console from localhost or with brooklyn:Ao3m5nZoe9
    *2014-11-11 13:46:46,825 INFO  Back-up of persisted state created on demotion, in /Users/alex/.brooklyn/brooklyn-persisted-state/data/backups/2014-11-11-1346-oZyOou3V-demotion-XYlN
     2014-11-11 13:46:47,045 WARN  Management node oZyOou3V detected master change, from  (?) to orZg7gai (1415713606000 / 1.05s ago) http://almacretin.local:8082/
     2014-11-11 13:46:47,059 INFO  Rebind complete (HOT_STANDBY, iteration 0) in 220ms: 1 app, 2 entities, 14 locations, 0 policies, 9 enrichers, 0 feeds, 0 catalog items

In future it would be nice to have periodic backups and some automatic clean-out support ... but for another day!


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

Branch: refs/heads/master
Commit: 7532be8c87e2791a6e3b056067ce309cd146641d
Parents: 38a756b
Author: Alex Heneveld <al...@cloudsoftcorp.com>
Authored: Tue Nov 11 10:41:03 2014 +0000
Committer: Alex Heneveld <al...@cloudsoftcorp.com>
Committed: Thu Nov 13 23:38:52 2014 +0000

----------------------------------------------------------------------
 .../management/ha/HighAvailabilityManager.java  |  22 +-
 .../brooklyn/config/BrooklynProperties.java     |   4 +-
 .../brooklyn/config/BrooklynServerConfig.java   | 100 ++++----
 .../brooklyn/config/BrooklynServerPaths.java    | 235 +++++++++++++++++++
 .../entity/rebind/RebindManagerImpl.java        |  10 +-
 .../persister/BrooklynPersistenceUtils.java     | 100 ++++++--
 .../rebind/persister/FileBasedObjectStore.java  |   8 +-
 .../ha/HighAvailabilityManagerImpl.java         |  39 ++-
 .../NonDeploymentManagementContext.java         |   8 +
 .../HighAvailabilityManagerSplitBrainTest.java  |  18 +-
 .../ha/HighAvailabilityManagerTestFixture.java  |  12 +-
 .../entity/LocalManagementContextForTests.java  |  32 ++-
 .../JcloudsBlobStoreBasedObjectStore.java       |   1 +
 .../brooklyn/launcher/BrooklynLauncher.java     |   5 +-
 .../brooklyn/launcher/BrooklynWebServer.java    |  11 +-
 .../BrooklynLauncherRebindTestToFiles.java      |   6 +-
 ...lynLauncherRebindToCloudObjectStoreTest.java |   4 +-
 .../brooklyn/rest/resources/ServerResource.java |   4 +-
 .../src/main/java/brooklyn/util/time/Time.java  |  25 +-
 19 files changed, 506 insertions(+), 138 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/7532be8c/api/src/main/java/brooklyn/management/ha/HighAvailabilityManager.java
----------------------------------------------------------------------
diff --git a/api/src/main/java/brooklyn/management/ha/HighAvailabilityManager.java b/api/src/main/java/brooklyn/management/ha/HighAvailabilityManager.java
index 175e8df..c3671d7 100644
--- a/api/src/main/java/brooklyn/management/ha/HighAvailabilityManager.java
+++ b/api/src/main/java/brooklyn/management/ha/HighAvailabilityManager.java
@@ -53,7 +53,7 @@ public interface HighAvailabilityManager {
      * and will not persist HA meta-information (meaning other nodes cannot join). 
      * <p>
      * Subsequently can expect {@link #getNodeState()} to be {@link ManagementNodeState#MASTER} 
-     * and {@link #getManagementPlaneSyncState()} to show just this one node --
+     * and {@link #loadManagementPlaneSyncRecord(boolean)} to show just this one node --
      * as if it were running HA with just one node --
      * but {@link #isRunning()} will return false.
      * <p>
@@ -97,13 +97,25 @@ public interface HighAvailabilityManager {
     long getPriority();
     
     /**
-     * Returns a snapshot of the management-plane's current / most-recently-known status.
-     * <p>
-     * This is mainly the nodes and their {@link ManagementNodeSyncRecord} instances, 
-     * as known (for this node) or last read (other nodes).  
+     * Returns a snapshot of the management-plane's current / most-recently-known status,
+     * as last read from {@link #loadManagementPlaneSyncRecord(boolean)}, or null if none read.
+     */
+    ManagementPlaneSyncRecord getLastManagementPlaneSyncRecord();
+    
+    /**
+     * @deprecated since 0.7.0 use {@link #getLastManagementPlaneSyncRecord()} or {@link #loadManagementPlaneSyncRecord(boolean)}
+     * to be explicit about the source.  
      */
     ManagementPlaneSyncRecord getManagementPlaneSyncState();
     
+    /**
+     * @param useLocalKnowledgeForThisNode - if true, the record for this mgmt node will be replaced with the
+     * actual current status known in this JVM (may be more recent than what is persisted);
+     * for most purposes there is little difference but in some cases the local node being updated
+     * may be explicitly wanted or not wanted
+     */
+    ManagementPlaneSyncRecord loadManagementPlaneSyncRecord(boolean useLocalKnowledgeForThisNode);
+    
     @VisibleForTesting
     ManagementPlaneSyncRecordPersister getPersister();
 }

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/7532be8c/core/src/main/java/brooklyn/config/BrooklynProperties.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/config/BrooklynProperties.java b/core/src/main/java/brooklyn/config/BrooklynProperties.java
index 63c2289..2c76b21 100644
--- a/core/src/main/java/brooklyn/config/BrooklynProperties.java
+++ b/core/src/main/java/brooklyn/config/BrooklynProperties.java
@@ -33,8 +33,6 @@ import java.util.Map;
 import java.util.NoSuchElementException;
 import java.util.Properties;
 
-import javax.annotation.Nullable;
-
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -51,7 +49,6 @@ import brooklyn.util.text.Strings;
 
 import com.google.common.annotations.Beta;
 import com.google.common.base.CharMatcher;
-import com.google.common.base.Function;
 import com.google.common.base.Objects;
 import com.google.common.base.Predicate;
 import com.google.common.base.Throwables;
@@ -247,6 +244,7 @@ public class BrooklynProperties extends LinkedHashMap implements StringConfigMap
         return this;
     }
 
+    @SuppressWarnings("unchecked")
     public BrooklynProperties addFrom(Map map) {
         putAll(Maps.transformValues(map, StringFunctions.trim()));
         return this;

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/7532be8c/core/src/main/java/brooklyn/config/BrooklynServerConfig.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/config/BrooklynServerConfig.java b/core/src/main/java/brooklyn/config/BrooklynServerConfig.java
index a5be8ed..c240d7c 100644
--- a/core/src/main/java/brooklyn/config/BrooklynServerConfig.java
+++ b/core/src/main/java/brooklyn/config/BrooklynServerConfig.java
@@ -25,7 +25,6 @@ import java.io.File;
 import java.net.URI;
 import java.util.Map;
 
-import org.apache.commons.io.FileUtils;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -34,13 +33,13 @@ import brooklyn.entity.basic.ConfigKeys;
 import brooklyn.event.AttributeSensor;
 import brooklyn.event.basic.Sensors;
 import brooklyn.management.ManagementContext;
-import brooklyn.util.exceptions.Exceptions;
 import brooklyn.util.guava.Maybe;
 import brooklyn.util.os.Os;
 
 /** Config keys for the brooklyn server */
 public class BrooklynServerConfig {
 
+    @SuppressWarnings("unused")
     private static final Logger log = LoggerFactory.getLogger(BrooklynServerConfig.class);
 
     /**
@@ -55,31 +54,50 @@ public class BrooklynServerConfig {
     public static final ConfigKey<String> BROOKLYN_DATA_DIR = newStringConfigKey(
             "brooklyn.datadir", "Directory for writing all brooklyn data");
 
-    public static final String DEFAULT_PERSISTENCE_CONTAINER_NAME = "brooklyn-persisted-state";
-    /** on file system, the 'data' subdir is used so that there is an obvious place to put backup dirs */ 
-    public static final String DEFAULT_PERSISTENCE_DIR_FOR_FILESYSTEM = Os.mergePaths(DEFAULT_PERSISTENCE_CONTAINER_NAME, "data");
-    
     /**
      * Provided for setting; consumers should query the management context persistence subsystem
-     * for the actual target, or use {@link #resolvePersistencePath(String, StringConfigMap, String)}
+     * for the actual target, or use {@link BrooklynServerPaths#newMainPersistencePathResolver(ManagementContext)}
      * if trying to resolve the value
      */
     public static final ConfigKey<String> PERSISTENCE_DIR = newStringConfigKey(
         "brooklyn.persistence.dir", 
-        "Directory or container name for writing brooklyn persisted state");
+        "Directory or container name for writing persisted state");
 
     public static final ConfigKey<String> PERSISTENCE_LOCATION_SPEC = newStringConfigKey(
         "brooklyn.persistence.location.spec", 
-        "Optional location spec string for an object store (e.g. jclouds:swift:URL) where persisted state should be kept;"
+        "Optional location spec string for an object store (e.g. jclouds:swift:URL) where persisted state should be kept; "
         + "if blank or not supplied, the file system is used"); 
 
+    public static final ConfigKey<String> PERSISTENCE_BACKUPS_DIR = newStringConfigKey(
+        "brooklyn.persistence.backups.dir", 
+        "Directory or container name for writing backups of persisted state; "
+        + "defaults to 'backups' inside the default persistence directory");
+    
+    public static final ConfigKey<String> PERSISTENCE_BACKUPS_LOCATION_SPEC = newStringConfigKey(
+        "brooklyn.persistence.backups.location.spec", 
+        "Location spec string for an object store (e.g. jclouds:swift:URL) where backups of persisted state should be kept; "
+        + "defaults to the same location spec as regular persisted state, failing back to local file system");
+    
+    public static final ConfigKey<Boolean> PERSISTENCE_BACKUPS_REQUIRED_ON_PROMOTION =
+        ConfigKeys.newBooleanConfigKey("brooklyn.persistence.backups.required.promotion",
+            "Whether a backup should be made of the persisted state from the persistence location to the backup location on node promotion, "
+            + "before any writes from this node", true);
+    
+    public static final ConfigKey<Boolean> PERSISTENCE_BACKUPS_REQUIRED_ON_DEMOTION =
+        ConfigKeys.newBooleanConfigKey("brooklyn.persistence.backups.required.promotion",
+            "Whether a backup of in-memory state should be made to the backup persistence location on node demotion, "
+            + "in case other nodes might write conflicting state", true);
+
+    /** @deprecated since 0.7.0, use {@link #PERSISTENCE_BACKUPS_ON_PROMOTION} and {@link #PERSISTENCE_BACKUPS_ON_DEMOTION},
+     * which allow using a different target location and are supported on more environments (and now default to true) */
+    @Deprecated
     public static final ConfigKey<Boolean> PERSISTENCE_BACKUPS_REQUIRED =
         ConfigKeys.newBooleanConfigKey("brooklyn.persistence.backups.required",
             "Whether a backup should always be made of the persistence directory; "
             + "if true, it will fail if this operation is not permitted (e.g. jclouds-based cloud object stores); "
             + "if false, the persistence store will be overwritten with changes (but files not removed if they are unreadable); "
-            + "if null or not set, the legacy beahviour of creating backups where possible (e.g. file system) is currently used, "
-            + "but this may be changed in future versions");
+            + "if null or not set, the legacy beahviour of creating backups where possible (e.g. file system) is currently used; "
+            + "this key is DEPRECATED in favor of promotion and demotion specific flags now defaulting to true");
 
     public static final ConfigKey<String> BROOKLYN_CATALOG_URL = ConfigKeys.newStringConfigKey("brooklyn.catalog.url",
         "The URL of a catalog.xml descriptor; absent for default (~/.brooklyn/catalog.xml), " +
@@ -100,37 +118,24 @@ public class BrooklynServerConfig {
     public static final AttributeSensor<ManagementContext.PropertiesReloadListener> PROPERTIES_RELOAD_LISTENER = Sensors.newSensor(
             ManagementContext.PropertiesReloadListener.class, "brooklyn.management.propertiesReloadListenet", "Properties reload listener");
 
+    /** @see BrooklynServerPaths#getMgmtBaseDir(ManagementContext) */
     public static String getMgmtBaseDir(ManagementContext mgmt) {
-        return getMgmtBaseDir(mgmt.getConfig());
+        return BrooklynServerPaths.getMgmtBaseDir(mgmt);
     }
-    
+    /** @see BrooklynServerPaths#getMgmtBaseDir(ManagementContext) */
     public static String getMgmtBaseDir(StringConfigMap brooklynProperties) {
-        String base = (String) brooklynProperties.getConfigRaw(MGMT_BASE_DIR, true).orNull();
-        if (base==null) {
-            base = brooklynProperties.getConfig(BROOKLYN_DATA_DIR);
-            if (base!=null)
-                log.warn("Using deprecated "+BROOKLYN_DATA_DIR.getName()+": use "+MGMT_BASE_DIR.getName()+" instead; value: "+base);
-        }
-        if (base==null) base = brooklynProperties.getConfig(MGMT_BASE_DIR);
-        return Os.tidyPath(base)+File.separator;
+        return BrooklynServerPaths.getMgmtBaseDir(brooklynProperties);
     }
+    /** @see BrooklynServerPaths#getMgmtBaseDir(ManagementContext) */
     public static String getMgmtBaseDir(Map<String,?> brooklynProperties) {
-        String base = (String) brooklynProperties.get(MGMT_BASE_DIR.getName());
-        if (base==null) base = (String) brooklynProperties.get(BROOKLYN_DATA_DIR.getName());
-        if (base==null) base = MGMT_BASE_DIR.getDefaultValue();
-        return Os.tidyPath(base)+File.separator;
+        return BrooklynServerPaths.getMgmtBaseDir(brooklynProperties);
     }
     
-    protected static String resolveAgainstBaseDir(StringConfigMap brooklynProperties, String path) {
-        if (!Os.isAbsolutish(path)) path = Os.mergePaths(getMgmtBaseDir(brooklynProperties), path);
-        return Os.tidyPath(path);
-    }
-    
-    /** @deprecated since 0.7.0 use {@link #resolvePersistencePath(String, StringConfigMap, String)} */
+    /** @deprecated since 0.7.0 use {@link BrooklynServerPaths#newMainPersistencePathResolver(ManagementContext)} */
     public static String getPersistenceDir(ManagementContext mgmt) {
         return getPersistenceDir(mgmt.getConfig());
     }
-    /** @deprecated since 0.7.0 use {@link #resolvePersistencePath(String, StringConfigMap, String)} */ 
+    /** @deprecated since 0.7.0 use {@link BrooklynServerPaths#newMainPersistencePathResolver(ManagementContext)} */ 
     public static String getPersistenceDir(StringConfigMap brooklynProperties) {
         return resolvePersistencePath(null, brooklynProperties, null);
     }
@@ -148,34 +153,15 @@ public class BrooklynServerConfig {
      *     will return a full file system path, relative to the brooklyn.base.dir if the
      *     configured brooklyn.persistence.dir is not absolute
      * @return The container name or full path for where persist state should be kept
-     */
+     * @deprecated since 0.7.0 use {@link BrooklynServerPaths#newMainPersistencePathResolver(ManagementContext)} */
     public static String resolvePersistencePath(String optionalSuppliedValue, StringConfigMap brooklynProperties, String optionalObjectStoreLocationSpec) {
-        String path = optionalSuppliedValue;
-        if (path==null) path = brooklynProperties.getConfig(PERSISTENCE_DIR);
-        if (optionalObjectStoreLocationSpec==null || "localhost".equals(optionalObjectStoreLocationSpec)) {
-            // file system
-            if (path==null) path=DEFAULT_PERSISTENCE_DIR_FOR_FILESYSTEM;
-            return resolveAgainstBaseDir(brooklynProperties, path);
-        } else {
-            // obj store
-            if (path==null) path=DEFAULT_PERSISTENCE_CONTAINER_NAME;
-            return path;
-        }
+        return BrooklynServerPaths.newMainPersistencePathResolver(brooklynProperties).location(optionalObjectStoreLocationSpec).dir(optionalSuppliedValue).resolve();
     }
-
+    
+    
+    /** @deprecated since 0.7.0 use {@link BrooklynServerPaths#getBrooklynWebTmpDir(ManagementContext)} */
     public static File getBrooklynWebTmpDir(ManagementContext mgmt) {
-        String brooklynMgmtBaseDir = getMgmtBaseDir(mgmt);
-        File webappTempDir = new File(Os.mergePaths(brooklynMgmtBaseDir, "planes", mgmt.getManagementPlaneId(), mgmt.getManagementNodeId(), "jetty"));
-        try {
-            FileUtils.forceMkdir(webappTempDir);
-            Os.deleteOnExitRecursivelyAndEmptyParentsUpTo(webappTempDir, new File(brooklynMgmtBaseDir)); 
-            return webappTempDir;
-        } catch (Exception e) {
-            Exceptions.propagateIfFatal(e);
-            IllegalStateException e2 = new IllegalStateException("Cannot create working directory "+webappTempDir+" for embedded jetty server: "+e, e);
-            log.warn(e2.getMessage()+" (rethrowing)");
-            throw e2;
-        }
+        return BrooklynServerPaths.getBrooklynWebTmpDir(mgmt);
     }
 
     /**

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/7532be8c/core/src/main/java/brooklyn/config/BrooklynServerPaths.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/config/BrooklynServerPaths.java b/core/src/main/java/brooklyn/config/BrooklynServerPaths.java
new file mode 100644
index 0000000..2bcb763
--- /dev/null
+++ b/core/src/main/java/brooklyn/config/BrooklynServerPaths.java
@@ -0,0 +1,235 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package brooklyn.config;
+
+import java.io.File;
+import java.util.Map;
+
+import javax.annotation.Nullable;
+
+import org.apache.commons.io.FileUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import brooklyn.management.ManagementContext;
+import brooklyn.management.internal.ManagementContextInternal;
+import brooklyn.util.exceptions.Exceptions;
+import brooklyn.util.guava.Maybe;
+import brooklyn.util.net.Urls;
+import brooklyn.util.os.Os;
+import brooklyn.util.text.Identifiers;
+import brooklyn.util.text.Strings;
+import brooklyn.util.time.Time;
+
+import com.google.common.base.Objects;
+
+public class BrooklynServerPaths {
+
+    private static final Logger log = LoggerFactory.getLogger(BrooklynServerPaths.class);
+    
+    /** Computes the base dir where brooklyn should read and write configuration.
+     * Defaults to <code>~/.brooklyn/</code>. 
+     * <p>
+     * Also see other variants of this method if a {@link ManagementContext} is not yet available. */
+    public static String getMgmtBaseDir(ManagementContext mgmt) {
+        return getMgmtBaseDir(mgmt.getConfig());
+    }
+    
+    /** @see BrooklynServerPaths#getMgmtBaseDir(ManagementContext) */
+    @SuppressWarnings("deprecation")
+    public static String getMgmtBaseDir(StringConfigMap brooklynProperties) {
+        String base = (String) brooklynProperties.getConfigRaw(BrooklynServerConfig.MGMT_BASE_DIR, true).orNull();
+        if (base==null) {
+            base = brooklynProperties.getConfig(BrooklynServerConfig.BROOKLYN_DATA_DIR);
+            if (base!=null)
+                log.warn("Using deprecated "+BrooklynServerConfig.BROOKLYN_DATA_DIR.getName()+": use "+BrooklynServerConfig.MGMT_BASE_DIR.getName()+" instead; value: "+base);
+        }
+        if (base==null) base = brooklynProperties.getConfig(BrooklynServerConfig.MGMT_BASE_DIR);
+        return Os.tidyPath(base)+File.separator;
+    }
+    /** @see BrooklynServerPaths#getMgmtBaseDir(ManagementContext) */
+    @SuppressWarnings("deprecation")
+    public static String getMgmtBaseDir(Map<String,?> brooklynProperties) {
+        String base = (String) brooklynProperties.get(BrooklynServerConfig.MGMT_BASE_DIR.getName());
+        if (base==null) base = (String) brooklynProperties.get(BrooklynServerConfig.BROOKLYN_DATA_DIR.getName());
+        if (base==null) base = BrooklynServerConfig.MGMT_BASE_DIR.getDefaultValue();
+        return Os.tidyPath(base)+File.separator;
+    }
+    
+    protected static String resolveAgainstBaseDir(StringConfigMap brooklynProperties, String path) {
+        if (!Os.isAbsolutish(path)) path = Os.mergePaths(getMgmtBaseDir(brooklynProperties), path);
+        return Os.tidyPath(path);
+    }
+
+    // ---------- persistence
+    
+    public static final String DEFAULT_PERSISTENCE_CONTAINER_NAME = "brooklyn-persisted-state";
+    /** on file system, the 'data' subdir is used so that there is an obvious place to put backup dirs */ 
+    public static final String DEFAULT_PERSISTENCE_DIR_FOR_FILESYSTEM = Os.mergePaths(DEFAULT_PERSISTENCE_CONTAINER_NAME, "data");
+
+    /** @see PersistencePathResolver */
+    public static PersistencePathResolver newMainPersistencePathResolver(StringConfigMap brooklynProperties) {
+        return new PersistencePathResolver(brooklynProperties);
+    }
+    /** @see PersistencePathResolver */
+    public static PersistencePathResolver newMainPersistencePathResolver(ManagementContext mgmt) {
+        return new PersistencePathResolver(mgmt.getConfig());
+    }
+    
+    /** @see PersistenceBackupPathResolver */
+    public static PersistenceBackupPathResolver newBackupPersistencePathResolver(ManagementContext mgmt) {
+        return new PersistenceBackupPathResolver(mgmt.getConfig());
+    }
+    
+    /**
+     * Utility for computing the path (dir or container name) to use for persistence.
+     * <p>
+     * Usage is to invoke {@link BrooklynServerPaths#newMainPersistencePathResolver(ManagementContext)}
+     * then to set {@link #location(String)} and {@link #dir(String)} as needed, and then to {@link #resolve()}.
+     */
+    public static class PersistencePathResolver {
+        protected final StringConfigMap brooklynProperties;
+        protected String locationSpec;
+        protected String dirOrContainer;
+        private PersistencePathResolver(StringConfigMap brooklynProperties) {
+            this.brooklynProperties = brooklynProperties;
+        }
+        /** 
+         * Optional location spec. If supplied, the {@link #resolve()} will return a container name suitable for use
+         * with the store, based on the {@link #dir(String)}; 
+         * if not supplied, or blank, or localhost this will cause resolution to give a full file system path, 
+         * if relative taken with respect to the {@link BrooklynServerPaths#getMgmtBaseDir(ManagementContext)}. 
+         * Config is <b>not</b> looked up for resolving a location spec. */
+        public PersistencePathResolver location(@Nullable String locationSpec) {
+            this.locationSpec = locationSpec;
+            return this;
+        }
+        /** 
+         * Optional directory (for localhost/filesystem) or container name. 
+         * If null or not supplied, config <b>is</b> looked up (because a value is always needed),
+         * followed by defaults for {@link BrooklynServerPaths#DEFAULT_PERSISTENCE_DIR_FOR_FILESYSTEM} and 
+         * {@link BrooklynServerPaths#DEFAULT_PERSISTENCE_CONTAINER_NAME} are used. */
+        public PersistencePathResolver dir(@Nullable String dirOrContainer) {
+            this.dirOrContainer = dirOrContainer;
+            return this;
+        }
+        
+        public String resolve() {
+            String path = dirOrContainer;
+            if (path==null) path = getDefaultPathFromConfig();
+            if (Strings.isBlank(locationSpec) || "localhost".equals(locationSpec)) {
+                // file system
+                if (Strings.isBlank(path)) path=getDefaultDirForAnyFilesystem();
+                return resolveAgainstBaseDir(brooklynProperties, path);
+            } else {
+                // obj store
+                if (path==null) path=getDefaultContainerForAnyNonFilesystem();
+                return path;
+            }
+        }
+        
+        protected String getDefaultPathFromConfig() {
+            return brooklynProperties.getConfig(BrooklynServerConfig.PERSISTENCE_DIR);
+        }
+        protected String getDefaultDirForAnyFilesystem() {
+            return DEFAULT_PERSISTENCE_DIR_FOR_FILESYSTEM;
+        }
+        protected String getDefaultContainerForAnyNonFilesystem() {
+            return DEFAULT_PERSISTENCE_CONTAINER_NAME;
+        }
+    }
+    
+    /**
+     * Similar to {@link PersistencePathResolver}, but designed for use for backup subpaths.
+     * If the container is not explicitly specified, "backups" is appended to the defaults from {@link PersistencePathResolver}.
+     * <p>
+     * It also includes conveniences for resolving further subpaths, cf {@link PersistenceBackupPathResolver#resolveWithSubpathFor(ManagementContextInternal, String)}.
+     */
+    public static class PersistenceBackupPathResolver extends PersistencePathResolver {
+        protected String nonBackuplocationSpec;
+        private PersistenceBackupPathResolver(StringConfigMap brooklynProperties) {
+            super(brooklynProperties);
+        }
+        @Override
+        public PersistenceBackupPathResolver location(@Nullable String locationSpec) {
+            this.nonBackuplocationSpec = locationSpec;
+            return this;
+        }
+        @Override
+        public PersistenceBackupPathResolver dir(String dirOrContainer) {
+            super.dir(dirOrContainer);
+            return this;
+        }
+        protected boolean isBackupSameLocation() {
+            return Objects.equal(locationSpec, nonBackuplocationSpec);
+        }
+        /** Appends a sub-path to the path returned by {@link #resolve()} */
+        public String resolveWithSubpath(String subpath) {
+            return Urls.mergePaths(super.resolve(), subpath);
+        }
+        /** Appends a standard format subpath sub-path to the path returned by {@link #resolve()}.
+         * <p>
+         * For example, this might write to:
+         * <code>~/.brooklyn/brooklyn-persisted-state/backups/2014-11-13-1201-n0deId-promotion-sA1t */
+        public String resolveWithSubpathFor(ManagementContext managementContext, String label) {
+            return resolveWithSubpath(Time.makeDateSimpleStampString()+"-"+managementContext.getManagementNodeId()+"-"+label+"-"+Identifiers.makeRandomId(4));
+        }
+        @Override
+        protected String getDefaultPathFromConfig() {
+            Maybe<Object> result = brooklynProperties.getConfigRaw(BrooklynServerConfig.PERSISTENCE_BACKUPS_DIR, true);
+            if (result.isPresent()) return Strings.toString(result.get());
+            if (isBackupSameLocation()) {
+                return backupContainerFor(brooklynProperties.getConfig(BrooklynServerConfig.PERSISTENCE_DIR));
+            }
+            return null;
+        }
+        protected String backupContainerFor(String nonBackupContainer) {
+            if (nonBackupContainer==null) return null;
+            return Urls.mergePaths(nonBackupContainer, "backups");
+        }
+        @Override
+        protected String getDefaultDirForAnyFilesystem() {
+            return backupContainerFor(super.getDefaultDirForAnyFilesystem());
+        }
+        @Override
+        protected String getDefaultContainerForAnyNonFilesystem() {
+            return backupContainerFor(super.getDefaultContainerForAnyNonFilesystem());
+        }
+    }
+    
+    // ------ web
+    
+    public static File getBrooklynWebTmpDir(ManagementContext mgmt) {
+        String brooklynMgmtBaseDir = getMgmtBaseDir(mgmt);
+        File webappTempDir = new File(Os.mergePaths(brooklynMgmtBaseDir, "planes", mgmt.getManagementPlaneId(), mgmt.getManagementNodeId(), "jetty"));
+        try {
+            FileUtils.forceMkdir(webappTempDir);
+            Os.deleteOnExitRecursivelyAndEmptyParentsUpTo(webappTempDir, new File(brooklynMgmtBaseDir)); 
+            return webappTempDir;
+        } catch (Exception e) {
+            Exceptions.propagateIfFatal(e);
+            IllegalStateException e2 = new IllegalStateException("Cannot create working directory "+webappTempDir+" for embedded jetty server: "+e, e);
+            log.warn(e2.getMessage()+" (rethrowing)");
+            throw e2;
+        }
+    }
+
+    // TODO OSGi
+    
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/7532be8c/core/src/main/java/brooklyn/entity/rebind/RebindManagerImpl.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/entity/rebind/RebindManagerImpl.java b/core/src/main/java/brooklyn/entity/rebind/RebindManagerImpl.java
index 23bbf26..3ba8bf1 100644
--- a/core/src/main/java/brooklyn/entity/rebind/RebindManagerImpl.java
+++ b/core/src/main/java/brooklyn/entity/rebind/RebindManagerImpl.java
@@ -68,6 +68,7 @@ import brooklyn.management.Task;
 import brooklyn.management.classloading.BrooklynClassLoadingContext;
 import brooklyn.management.ha.HighAvailabilityManagerImpl;
 import brooklyn.management.ha.ManagementNodeState;
+import brooklyn.management.ha.MementoCopyMode;
 import brooklyn.management.internal.EntityManagerInternal;
 import brooklyn.management.internal.LocationManagerInternal;
 import brooklyn.management.internal.ManagementContextInternal;
@@ -262,12 +263,17 @@ public class RebindManagerImpl implements RebindManager {
             throw new IllegalStateException("Cannot start read-only when already running with persistence");
         }
         LOG.debug("Starting persistence ("+this+"), mgmt "+managementContext.getManagementNodeId());
+        if (!persistenceRunning) {
+            if (managementContext.getBrooklynProperties().getConfig(BrooklynServerConfig.PERSISTENCE_BACKUPS_REQUIRED_ON_PROMOTION)) {
+                BrooklynPersistenceUtils.createBackup(managementContext, "promotion", MementoCopyMode.REMOTE);
+            }
+        }
         persistenceRunning = true;
         readOnlyRebindCount = Integer.MIN_VALUE;
         persistenceStoreAccess.enableWriteAccess();
         if (persistenceRealChangeListener != null) persistenceRealChangeListener.start();
     }
-    
+
     @Override
     public void stopPersistence() {
         LOG.debug("Stopping persistence ("+this+"), mgmt "+managementContext.getManagementNodeId());
@@ -403,7 +409,7 @@ public class RebindManagerImpl implements RebindManager {
     @VisibleForTesting
     public void forcePersistNow(boolean full, PersistenceExceptionHandler exceptionHandler) {
         if (full) {
-            BrooklynMementoRawData memento = BrooklynPersistenceUtils.newFullMemento(managementContext);
+            BrooklynMementoRawData memento = BrooklynPersistenceUtils.newStateMemento(managementContext, MementoCopyMode.LOCAL);
             if (exceptionHandler==null) {
                 exceptionHandler = persistenceRealChangeListener.getExceptionHandler();
             }

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/7532be8c/core/src/main/java/brooklyn/entity/rebind/persister/BrooklynPersistenceUtils.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/entity/rebind/persister/BrooklynPersistenceUtils.java b/core/src/main/java/brooklyn/entity/rebind/persister/BrooklynPersistenceUtils.java
index 373ce1d..356234d 100644
--- a/core/src/main/java/brooklyn/entity/rebind/persister/BrooklynPersistenceUtils.java
+++ b/core/src/main/java/brooklyn/entity/rebind/persister/BrooklynPersistenceUtils.java
@@ -26,6 +26,7 @@ import org.slf4j.LoggerFactory;
 import brooklyn.basic.BrooklynObject;
 import brooklyn.catalog.CatalogItem;
 import brooklyn.config.BrooklynServerConfig;
+import brooklyn.config.BrooklynServerPaths;
 import brooklyn.entity.Entity;
 import brooklyn.entity.Feed;
 import brooklyn.entity.basic.EntityInternal;
@@ -50,6 +51,7 @@ import brooklyn.mementos.Memento;
 import brooklyn.policy.Enricher;
 import brooklyn.policy.Policy;
 import brooklyn.util.ResourceUtils;
+import brooklyn.util.exceptions.Exceptions;
 import brooklyn.util.text.Strings;
 import brooklyn.util.time.Duration;
 import brooklyn.util.time.Time;
@@ -79,7 +81,7 @@ public class BrooklynPersistenceUtils {
     public static PersistenceObjectStore newPersistenceObjectStore(ManagementContext managementContext,
             String locationSpec, String locationContainer, PersistMode persistMode, HighAvailabilityMode highAvailabilityMode) {
         PersistenceObjectStore destinationObjectStore;
-        locationContainer = BrooklynServerConfig.resolvePersistencePath(locationContainer, managementContext.getConfig(), locationSpec);
+        locationContainer = BrooklynServerPaths.newMainPersistencePathResolver(managementContext).location(locationSpec).dir(locationContainer).resolve();
 
         Location location = null;
         try {
@@ -134,7 +136,32 @@ public class BrooklynPersistenceUtils {
         return MementosGenerators.newMemento(instance);
     }
     
-    public static BrooklynMementoRawData newFullMemento(ManagementContext mgmt) {
+    public static BrooklynMementoRawData newStateMemento(ManagementContext mgmt, MementoCopyMode source) {
+        switch (source) {
+        case LOCAL: 
+            return newStateMementoFromLocal(mgmt); 
+        case REMOTE: 
+            return mgmt.getRebindManager().retrieveMementoRawData(); 
+        case AUTO: 
+            throw new IllegalStateException("Copy mode AUTO not supported here");
+        }
+        throw new IllegalStateException("Should not come here, unknown mode "+source);
+    }
+    
+    public static ManagementPlaneSyncRecord newManagerMemento(ManagementContext mgmt, MementoCopyMode source) {
+        switch (source) {
+        case LOCAL: 
+            return mgmt.getHighAvailabilityManager().getLastManagementPlaneSyncRecord();
+        case REMOTE: 
+            return mgmt.getHighAvailabilityManager().loadManagementPlaneSyncRecord(true);
+        case AUTO: 
+            throw new IllegalStateException("Copy mode AUTO not supported here");
+        }
+        throw new IllegalStateException("Should not come here, unknown mode "+source);
+    }
+    
+
+    private static BrooklynMementoRawData newStateMementoFromLocal(ManagementContext mgmt) {
         BrooklynMementoRawData.Builder result = BrooklynMementoRawData.builder();
         MementoSerializer<Object> rawSerializer = new XmlMementoSerializer<Object>(mgmt.getClass().getClassLoader());
         RetryingMementoSerializer<Object> serializer = new RetryingMementoSerializer<Object>(rawSerializer, 1);
@@ -160,25 +187,70 @@ public class BrooklynPersistenceUtils {
      * this may be taken from {@link MementoCopyMode#LOCAL} current state 
      * or {@link MementoCopyMode#REMOTE} persisted state, or the default {@link MementoCopyMode#AUTO} detected
      */
-    public static void writeMemento(ManagementContext mgmt, PersistenceObjectStore targetStore, MementoCopyMode preferredOrigin) {
-        if (preferredOrigin==null || preferredOrigin==MementoCopyMode.AUTO) 
-            preferredOrigin = (mgmt.getHighAvailabilityManager().getNodeState()==ManagementNodeState.MASTER ? MementoCopyMode.LOCAL : MementoCopyMode.REMOTE);
+    public static void writeMemento(ManagementContext mgmt, PersistenceObjectStore targetStore, MementoCopyMode source) {
+        if (source==null || source==MementoCopyMode.AUTO) 
+            source = (mgmt.getHighAvailabilityManager().getNodeState()==ManagementNodeState.MASTER ? MementoCopyMode.LOCAL : MementoCopyMode.REMOTE);
 
         Stopwatch timer = Stopwatch.createStarted();
         
-        BrooklynMementoRawData dataRecord = null; 
-        switch (preferredOrigin) {
-        case LOCAL: dataRecord = newFullMemento(mgmt); break;
-        case REMOTE: dataRecord = mgmt.getRebindManager().retrieveMementoRawData(); break;
-        case AUTO: throw new IllegalStateException("Should not come here, should have autodetected");
-        }
+        BrooklynMementoRawData dataRecord = newStateMemento(mgmt, source); 
+        ManagementPlaneSyncRecord mgmtRecord = newManagerMemento(mgmt, source);
 
-        ManagementPlaneSyncRecord mgmtRecord = mgmt.getHighAvailabilityManager().getManagementPlaneSyncState();
-        
         writeMemento(mgmt, dataRecord, targetStore);
         writeManagerMemento(mgmt, mgmtRecord, targetStore);
         
         log.debug("Wrote full memento to "+targetStore+" in "+Time.makeTimeStringRounded(Duration.of(timer)));
     }
-    
+
+    public static void createBackup(ManagementContext managementContext, String label, MementoCopyMode source) {
+        if (source==null || source==MementoCopyMode.AUTO) {
+            if ("promotion".equalsIgnoreCase(label)) source = MementoCopyMode.REMOTE;
+            else if ("demotion".equalsIgnoreCase(label)) source = MementoCopyMode.LOCAL;
+            else throw new IllegalArgumentException("Cannot detect copy mode for "+label+"/"+source);
+        }
+        BrooklynMementoRawData memento = null;
+        ManagementPlaneSyncRecord planeState = null;
+        
+        try {
+            log.debug("Loading persisted state on "+label+" for backup purposes");
+            memento = newStateMemento(managementContext, source);
+            try {
+                planeState = newManagerMemento(managementContext, source);
+            } catch (Exception e) {
+                Exceptions.propagateIfFatal(e);
+                log.warn("Unable to access management plane sync state on "+label+" (ignoring): "+e, e);
+            }
+        
+            PersistenceObjectStore destinationObjectStore = null;
+            String backupSpec = managementContext.getConfig().getConfig(BrooklynServerConfig.PERSISTENCE_BACKUPS_LOCATION_SPEC);
+            try {
+                String backupContainer = BrooklynServerPaths.newBackupPersistencePathResolver(managementContext).location(backupSpec)
+                    .resolveWithSubpathFor(managementContext, label);
+                destinationObjectStore = BrooklynPersistenceUtils.newPersistenceObjectStore(managementContext, backupSpec, backupContainer);
+                log.debug("Backing up persisted state on "+label+", to "+destinationObjectStore.getSummaryName());
+                BrooklynPersistenceUtils.writeMemento(managementContext, memento, destinationObjectStore);
+                BrooklynPersistenceUtils.writeManagerMemento(managementContext, planeState, destinationObjectStore);
+                log.info("Back-up of persisted state created on "+label+", in "+destinationObjectStore.getSummaryName());
+                
+            } catch (Exception e) {
+                Exceptions.propagateIfFatal(e);
+                PersistenceObjectStore failedStore = destinationObjectStore;
+                if (!Strings.isBlank(backupSpec) && !"localhost".equals(backupSpec)) {
+                    backupSpec = "localhost";
+                    String backupContainer = BrooklynServerPaths.newBackupPersistencePathResolver(managementContext).location(backupSpec)
+                        .resolveWithSubpathFor(managementContext, label);
+                    destinationObjectStore = BrooklynPersistenceUtils.newPersistenceObjectStore(managementContext, backupSpec, backupContainer);
+                    log.warn("Persisted state back-up to "+failedStore.getSummaryName()+" failed with "+e, e);
+                    
+                    log.debug("Backing up persisted state on "+label+", locally because remote failed, to "+destinationObjectStore.getSummaryName());
+                    BrooklynPersistenceUtils.writeMemento(managementContext, memento, destinationObjectStore);
+                    BrooklynPersistenceUtils.writeManagerMemento(managementContext, planeState, destinationObjectStore);
+                    log.info("Back-up of persisted state created on "+label+", locally because remote failed, in "+destinationObjectStore.getSummaryName());
+                }
+            }
+        } catch (Exception e) {
+            Exceptions.propagateIfFatal(e);
+            log.warn("Unable to backup management plane sync state on "+label+" (ignoring): "+e, e);
+        }
+    }
 }

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/7532be8c/core/src/main/java/brooklyn/entity/rebind/persister/FileBasedObjectStore.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/entity/rebind/persister/FileBasedObjectStore.java b/core/src/main/java/brooklyn/entity/rebind/persister/FileBasedObjectStore.java
index a760a73..c3ce92a 100644
--- a/core/src/main/java/brooklyn/entity/rebind/persister/FileBasedObjectStore.java
+++ b/core/src/main/java/brooklyn/entity/rebind/persister/FileBasedObjectStore.java
@@ -200,8 +200,14 @@ public class FileBasedObjectStore implements PersistenceObjectStore {
             return;
         }
         
+        @SuppressWarnings("deprecation")
         Boolean backups = mgmt.getConfig().getConfig(BrooklynServerConfig.PERSISTENCE_BACKUPS_REQUIRED);
-        if (backups==null) backups = true; // for file system
+        if (Boolean.TRUE.equals(backups)) {
+            log.warn("Using legacy backup for "+this+"; functionality will be removed in future versions, in favor of promotion/demotion-specific backups to a configurable backup location.");
+        }
+        // default backups behaviour here changed to false, Nov 2014, because these backups are now legacy;
+        // we prefer the made when persistence is enabled, using routines in BrooklynPersistenceUtils
+        if (backups==null) backups = false; 
 
         File dir = getBaseDir();
         try {

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/7532be8c/core/src/main/java/brooklyn/management/ha/HighAvailabilityManagerImpl.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/management/ha/HighAvailabilityManagerImpl.java b/core/src/main/java/brooklyn/management/ha/HighAvailabilityManagerImpl.java
index 93cf574..c756824 100644
--- a/core/src/main/java/brooklyn/management/ha/HighAvailabilityManagerImpl.java
+++ b/core/src/main/java/brooklyn/management/ha/HighAvailabilityManagerImpl.java
@@ -33,6 +33,7 @@ import org.slf4j.LoggerFactory;
 import brooklyn.BrooklynVersion;
 import brooklyn.catalog.internal.BasicBrooklynCatalog;
 import brooklyn.catalog.internal.CatalogDto;
+import brooklyn.config.BrooklynServerConfig;
 import brooklyn.config.ConfigKey;
 import brooklyn.entity.Application;
 import brooklyn.entity.Entity;
@@ -40,6 +41,7 @@ import brooklyn.entity.basic.BrooklynTaskTags;
 import brooklyn.entity.basic.ConfigKeys;
 import brooklyn.entity.basic.EntityInternal;
 import brooklyn.entity.rebind.RebindManager;
+import brooklyn.entity.rebind.persister.BrooklynPersistenceUtils;
 import brooklyn.entity.rebind.plane.dto.BasicManagementNodeSyncRecord;
 import brooklyn.entity.rebind.plane.dto.ManagementPlaneSyncRecordImpl;
 import brooklyn.entity.rebind.plane.dto.ManagementPlaneSyncRecordImpl.Builder;
@@ -145,6 +147,8 @@ public class HighAvailabilityManagerImpl implements HighAvailabilityManager {
     private volatile transient Duration pollPeriodLocalOverride;
     private volatile transient Duration heartbeatTimeoutOverride;
 
+    private volatile ManagementPlaneSyncRecord lastSyncRecord;
+    
     public HighAvailabilityManagerImpl(ManagementContextInternal managementContext) {
         this.managementContext = managementContext;
     }
@@ -295,7 +299,7 @@ public class HighAvailabilityManagerImpl implements HighAvailabilityManager {
             // don't care; let's start and see if we promote ourselves
             publishAndCheck(true);
             if (nodeState == ManagementNodeState.STANDBY || nodeState == ManagementNodeState.HOT_STANDBY) {
-                ManagementPlaneSyncRecord newState = getManagementPlaneSyncState();
+                ManagementPlaneSyncRecord newState = loadManagementPlaneSyncRecord(true);;
                 String masterNodeId = newState.getMasterNodeId();
                 ManagementNodeSyncRecord masterNodeDetails = newState.getManagementNodes().get(masterNodeId);
                 LOG.info("Management node "+ownNodeId+" running as HA " + nodeState + " autodetected, " +
@@ -340,7 +344,7 @@ public class HighAvailabilityManagerImpl implements HighAvailabilityManager {
             if (getNodeState()==ManagementNodeState.MASTER) {
                 message += " but election re-promoted this node)";
             } else {
-                ManagementPlaneSyncRecord newState = getManagementPlaneSyncState();
+                ManagementPlaneSyncRecord newState = loadManagementPlaneSyncRecord(true);
                 if (Strings.isBlank(newState.getMasterNodeId())) {
                     message += "); no master currently (subsequent election may repair)";
                 } else {
@@ -448,6 +452,10 @@ public class HighAvailabilityManagerImpl implements HighAvailabilityManager {
         return nodeState;
     }
 
+    public ManagementPlaneSyncRecord getLastManagementPlaneSyncRecord() {
+        return lastSyncRecord;
+    }
+    
     @Override
     public ManagementPlaneSyncRecord getManagementPlaneSyncState() {
         return loadManagementPlaneSyncRecord(true);
@@ -714,22 +722,30 @@ public class HighAvailabilityManagerImpl implements HighAvailabilityManager {
         managementContext.getRebindManager().start();
     }
     
+    protected void backupOnDemotionIfNeeded() {
+        if (managementContext.getBrooklynProperties().getConfig(BrooklynServerConfig.PERSISTENCE_BACKUPS_REQUIRED_ON_DEMOTION)) {
+            BrooklynPersistenceUtils.createBackup(managementContext, "demotion", MementoCopyMode.LOCAL);
+        }
+    }
+
     protected void demoteToFailed() {
         // TODO merge this method with the one below
         boolean wasMaster = nodeState == ManagementNodeState.MASTER;
+        if (wasMaster) backupOnDemotionIfNeeded();
         ManagementTransitionMode mode = (wasMaster ? ManagementTransitionMode.REBINDING_NO_LONGER_PRIMARY : ManagementTransitionMode.REBINDING_DESTROYED);
         nodeState = ManagementNodeState.FAILED;
         onDemotionStopItems(mode);
         nodeStateTransitionComplete = true;
         publishDemotion(wasMaster);
     }
-
+    
     protected void demoteToStandby(boolean hot) {
         if (!running) {
             LOG.warn("Ignoring demote-from-master request, as HighAvailabilityManager is no longer running");
             return;
         }
         boolean wasMaster = nodeState == ManagementNodeState.MASTER;
+        if (wasMaster) backupOnDemotionIfNeeded();
         ManagementTransitionMode mode = (wasMaster ? ManagementTransitionMode.REBINDING_NO_LONGER_PRIMARY : ManagementTransitionMode.REBINDING_DESTROYED);
 
         nodeStateTransitionComplete = false;
@@ -796,13 +812,14 @@ public class HighAvailabilityManagerImpl implements HighAvailabilityManager {
         }
     }
     
-    /**
-     * @param reportCleanedState - if true, the record for this mgmt node will be replaced with the
-     * actual current status known in this JVM (may be more recent than what is persisted);
-     * for most purposes there is little difference but in some cases the local node being updated
-     * may be explicitly wanted or not wanted
-     */
-    protected ManagementPlaneSyncRecord loadManagementPlaneSyncRecord(boolean reportCleanedState) {
+    @Override
+    public ManagementPlaneSyncRecord loadManagementPlaneSyncRecord(boolean useLocalKnowledgeForThisNode) {
+        ManagementPlaneSyncRecord record = loadManagementPlaneSyncRecordInternal(useLocalKnowledgeForThisNode);
+        lastSyncRecord = record;
+        return record; 
+    }
+    
+    private ManagementPlaneSyncRecord loadManagementPlaneSyncRecordInternal(boolean useLocalKnowledgeForThisNode) {
         if (disabled) {
             // if HA is disabled, then we are the only node - no persistence; just load a memento to describe this node
             Builder builder = ManagementPlaneSyncRecordImpl.builder()
@@ -824,7 +841,7 @@ public class HighAvailabilityManagerImpl implements HighAvailabilityManager {
             try {
                 ManagementPlaneSyncRecord result = persister.loadSyncRecord();
                 
-                if (reportCleanedState) {
+                if (useLocalKnowledgeForThisNode) {
                     // Report this node's most recent state, and detect AWOL nodes
                     ManagementNodeSyncRecord me = BasicManagementNodeSyncRecord.builder()
                         .from(result.getManagementNodes().get(ownNodeId), true)

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/7532be8c/core/src/main/java/brooklyn/management/internal/NonDeploymentManagementContext.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/management/internal/NonDeploymentManagementContext.java b/core/src/main/java/brooklyn/management/internal/NonDeploymentManagementContext.java
index 20755dd..3d0e4d5 100644
--- a/core/src/main/java/brooklyn/management/internal/NonDeploymentManagementContext.java
+++ b/core/src/main/java/brooklyn/management/internal/NonDeploymentManagementContext.java
@@ -571,6 +571,14 @@ public class NonDeploymentManagementContext implements ManagementContextInternal
             throw new IllegalStateException("Non-deployment context "+NonDeploymentManagementContext.this+" is not valid for this operation.");
         }
         @Override
+        public ManagementPlaneSyncRecord getLastManagementPlaneSyncRecord() {
+            throw new IllegalStateException("Non-deployment context "+NonDeploymentManagementContext.this+" is not valid for this operation.");
+        }
+        @Override
+        public ManagementPlaneSyncRecord loadManagementPlaneSyncRecord(boolean x) {
+            throw new IllegalStateException("Non-deployment context "+NonDeploymentManagementContext.this+" is not valid for this operation.");
+        }
+        @Override
         public void changeMode(HighAvailabilityMode startMode) {
             throw new IllegalStateException("Non-deployment context "+NonDeploymentManagementContext.this+" is not valid for this operation.");
         }

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/7532be8c/core/src/test/java/brooklyn/management/ha/HighAvailabilityManagerSplitBrainTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/brooklyn/management/ha/HighAvailabilityManagerSplitBrainTest.java b/core/src/test/java/brooklyn/management/ha/HighAvailabilityManagerSplitBrainTest.java
index a6f6ec6..4c736ca 100644
--- a/core/src/test/java/brooklyn/management/ha/HighAvailabilityManagerSplitBrainTest.java
+++ b/core/src/test/java/brooklyn/management/ha/HighAvailabilityManagerSplitBrainTest.java
@@ -205,7 +205,7 @@ public class HighAvailabilityManagerSplitBrainTest {
                 EntitySpec.create(TestApplication.class).impl(TestEntityFailingRebind.class), n1.mgmt);
         app.start(ImmutableList.<Location>of());
 
-        n1.mgmt.getRebindManager().forcePersistNow();
+        n1.mgmt.getRebindManager().forcePersistNow(false, null);
 
         //don't publish state for a while (i.e. long store delays, failures)
         sharedTickerAdvance(Duration.ONE_MINUTE);
@@ -224,7 +224,7 @@ public class HighAvailabilityManagerSplitBrainTest {
             assertNestedRebindException(e);
         }
 
-        ManagementPlaneSyncRecord memento = n1.ha.getManagementPlaneSyncState();
+        ManagementPlaneSyncRecord memento = n1.ha.loadManagementPlaneSyncRecord(true);
         assertEquals(memento.getManagementNodes().get(n1.ownNodeId).getStatus(), ManagementNodeState.FAILED);
         assertEquals(memento.getManagementNodes().get(n2.ownNodeId).getStatus(), ManagementNodeState.FAILED);
     }
@@ -243,7 +243,7 @@ public class HighAvailabilityManagerSplitBrainTest {
                 EntitySpec.create(TestApplication.class).impl(TestEntityFailingRebind.class), n1.mgmt);
         app.start(ImmutableList.<Location>of());
 
-        n1.mgmt.getRebindManager().forcePersistNow();
+        n1.mgmt.getRebindManager().forcePersistNow(false, null);
 
         //don't publish state for a while (i.e. long store delays, failures)
         sharedTickerAdvance(Duration.ONE_MINUTE);
@@ -258,7 +258,7 @@ public class HighAvailabilityManagerSplitBrainTest {
         TestEntityFailingRebind.setThrowOnRebind(false);
         n1.ha.publishAndCheck(false);
 
-        ManagementPlaneSyncRecord memento = n1.ha.getManagementPlaneSyncState();
+        ManagementPlaneSyncRecord memento = n1.ha.loadManagementPlaneSyncRecord(true);
         assertEquals(memento.getManagementNodes().get(n1.ownNodeId).getStatus(), ManagementNodeState.MASTER);
         assertEquals(memento.getManagementNodes().get(n2.ownNodeId).getStatus(), ManagementNodeState.FAILED);
     }
@@ -284,7 +284,7 @@ public class HighAvailabilityManagerSplitBrainTest {
         
         // first auto should become master
         n1.ha.start(HighAvailabilityMode.AUTO);
-        ManagementPlaneSyncRecord memento1 = n1.ha.getManagementPlaneSyncState();
+        ManagementPlaneSyncRecord memento1 = n1.ha.loadManagementPlaneSyncRecord(true);
         
         log.info(n1+" HA: "+memento1);
         assertEquals(memento1.getMasterNodeId(), n1.ownNodeId);
@@ -294,7 +294,7 @@ public class HighAvailabilityManagerSplitBrainTest {
 
         // second - make explicit hot; that's a strictly more complex case than cold standby, so provides pretty good coverage
         n2.ha.start(HighAvailabilityMode.HOT_STANDBY);
-        ManagementPlaneSyncRecord memento2 = n2.ha.getManagementPlaneSyncState();
+        ManagementPlaneSyncRecord memento2 = n2.ha.loadManagementPlaneSyncRecord(true);
         
         log.info(n2+" HA: "+memento2);
         assertEquals(memento2.getMasterNodeId(), n1.ownNodeId);
@@ -315,7 +315,7 @@ public class HighAvailabilityManagerSplitBrainTest {
         assertEquals(n1.mgmt.getApplications().size(), 1);
         assertEquals(n2.mgmt.getApplications().size(), 0);
         log.info("persisting "+n1.ownNodeId);
-        n1.mgmt.getRebindManager().forcePersistNow();
+        n1.mgmt.getRebindManager().forcePersistNow(false, null);
         
         n1.objectStore.setWritesFailSilently(true);
         log.info(n1+" writes off");
@@ -325,7 +325,7 @@ public class HighAvailabilityManagerSplitBrainTest {
         
         log.info("publish "+n2.ownNodeId);
         n2.ha.publishAndCheck(false);
-        ManagementPlaneSyncRecord memento2b = n2.ha.getManagementPlaneSyncState();
+        ManagementPlaneSyncRecord memento2b = n2.ha.loadManagementPlaneSyncRecord(true);
         log.info(n2+" HA now: "+memento2b);
         
         // n2 infers n1 as failed 
@@ -414,7 +414,7 @@ public class HighAvailabilityManagerSplitBrainTest {
             final Stopwatch timer = Stopwatch.createStarted();
             Asserts.succeedsEventually(new Runnable() {
                 @Override public void run() {
-                    ManagementPlaneSyncRecord memento = nodes.get(0).ha.getManagementPlaneSyncState();
+                    ManagementPlaneSyncRecord memento = nodes.get(0).ha.loadManagementPlaneSyncRecord(true);
                     List<ManagementNodeState> counts = MutableList.of(), savedCounts = MutableList.of();
                     for (HaMgmtNode n: nodes) {
                         counts.add(n.ha.getNodeState());

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/7532be8c/core/src/test/java/brooklyn/management/ha/HighAvailabilityManagerTestFixture.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/brooklyn/management/ha/HighAvailabilityManagerTestFixture.java b/core/src/test/java/brooklyn/management/ha/HighAvailabilityManagerTestFixture.java
index eae5bdb..2637ae1 100644
--- a/core/src/test/java/brooklyn/management/ha/HighAvailabilityManagerTestFixture.java
+++ b/core/src/test/java/brooklyn/management/ha/HighAvailabilityManagerTestFixture.java
@@ -34,8 +34,6 @@ import org.testng.annotations.BeforeMethod;
 import org.testng.annotations.Test;
 
 import brooklyn.BrooklynVersion;
-import brooklyn.config.BrooklynProperties;
-import brooklyn.config.BrooklynServerConfig;
 import brooklyn.entity.basic.Entities;
 import brooklyn.entity.rebind.PersistenceExceptionHandlerImpl;
 import brooklyn.entity.rebind.persister.BrooklynMementoPersisterToObjectStore;
@@ -105,9 +103,7 @@ public abstract class HighAvailabilityManagerTestFixture {
     }
 
     protected ManagementContextInternal newLocalManagementContext() {
-        BrooklynProperties props = BrooklynProperties.Factory.newEmpty();
-        props.put(BrooklynServerConfig.PERSISTENCE_BACKUPS_REQUIRED, false);
-        return new LocalManagementContextForTests(props);
+        return LocalManagementContextForTests.newInstance();
     }
 
     protected abstract PersistenceObjectStore newPersistenceObjectStore();
@@ -190,7 +186,7 @@ public abstract class HighAvailabilityManagerTestFixture {
         tickerAdvance(Duration.FIVE_SECONDS);
         
         manager.start(HighAvailabilityMode.AUTO);
-        ManagementPlaneSyncRecord memento = manager.getManagementPlaneSyncState();
+        ManagementPlaneSyncRecord memento = manager.loadManagementPlaneSyncRecord(true);
         
         // Note can assert timestamp because not "real" time; it's using our own Ticker
         assertEquals(memento.getMasterNodeId(), ownNodeId);
@@ -218,7 +214,7 @@ public abstract class HighAvailabilityManagerTestFixture {
         
         manager.start(HighAvailabilityMode.HOT_STANDBY);
         
-        ManagementPlaneSyncRecord state = manager.getManagementPlaneSyncState();
+        ManagementPlaneSyncRecord state = manager.loadManagementPlaneSyncRecord(true);
         assertEquals(state.getManagementNodes().get("node1").getStatus(), ManagementNodeState.MASTER);
         assertEquals(state.getManagementNodes().get(ownNodeId).getStatus(), ManagementNodeState.HOT_STANDBY);
         
@@ -226,7 +222,7 @@ public abstract class HighAvailabilityManagerTestFixture {
         // its own heartbeat with the new time; but node1's record is now out-of-date.
         tickerAdvance(Duration.seconds(31));
         
-        ManagementPlaneSyncRecord state2 = manager.getManagementPlaneSyncState();
+        ManagementPlaneSyncRecord state2 = manager.loadManagementPlaneSyncRecord(true);
         assertEquals(state2.getManagementNodes().get("node1").getStatus(), ManagementNodeState.FAILED);
         assertNotEquals(state.getManagementNodes().get(ownNodeId).getStatus(), ManagementNodeState.FAILED);
     }

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/7532be8c/core/src/test/java/brooklyn/test/entity/LocalManagementContextForTests.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/brooklyn/test/entity/LocalManagementContextForTests.java b/core/src/test/java/brooklyn/test/entity/LocalManagementContextForTests.java
index 5bc962b..455b9af 100644
--- a/core/src/test/java/brooklyn/test/entity/LocalManagementContextForTests.java
+++ b/core/src/test/java/brooklyn/test/entity/LocalManagementContextForTests.java
@@ -20,6 +20,7 @@ package brooklyn.test.entity;
 
 import brooklyn.config.BrooklynProperties;
 import brooklyn.config.BrooklynServerConfig;
+import brooklyn.config.ConfigKey;
 import brooklyn.management.internal.LocalManagementContext;
 
 /** management context which allows disabling common time-consuming tasks.
@@ -60,27 +61,37 @@ public class LocalManagementContextForTests extends LocalManagementContext {
     
     public static BrooklynProperties disableOsgi(BrooklynProperties brooklynProperties) {
         if (brooklynProperties==null) return null;
-        brooklynProperties.putIfAbsent(BrooklynServerConfig.USE_OSGI, false);
+        setFailingIfConflicting(brooklynProperties, BrooklynServerConfig.USE_OSGI, false);
         return brooklynProperties;
     }
     
-    public static BrooklynProperties disablePersistentStoreBackup(BrooklynProperties brooklynProperties) {
+    @SuppressWarnings("deprecation")
+    public static BrooklynProperties disablePersistenceBackups(BrooklynProperties brooklynProperties) {
         if (brooklynProperties==null) return null;
-        brooklynProperties.putIfAbsent(BrooklynServerConfig.PERSISTENCE_BACKUPS_REQUIRED, false);
+        setFailingIfConflicting(brooklynProperties, BrooklynServerConfig.PERSISTENCE_BACKUPS_REQUIRED_ON_DEMOTION, false);
+        setFailingIfConflicting(brooklynProperties, BrooklynServerConfig.PERSISTENCE_BACKUPS_REQUIRED_ON_PROMOTION, false);
+        setFailingIfConflicting(brooklynProperties, BrooklynServerConfig.PERSISTENCE_BACKUPS_REQUIRED, false);
         return brooklynProperties;
     }
     
+    private static <T> void setFailingIfConflicting(BrooklynProperties brooklynProperties, ConfigKey<T> key, T value) {
+        Object old = brooklynProperties.put(key, value);
+        if (old!=null && !old.equals(value)) {
+            throw new IllegalStateException("Not supported to override value for '"+key+"'; was "+old+", setting "+value);
+        }
+    }
+
     public static class Builder {
-        boolean disablePersistence = false;
+        boolean disablePersistenceBackups = false;
         boolean disableOsgi = false;
         boolean emptyCatalog = false;
         BrooklynProperties properties = null;
         
-        public Builder disablePersistence() { return disablePersistence(true); }
+        public Builder disablePersistenceBackups() { return disablePersistenceBackups(true); }
         public Builder disableOsgi() { return disableOsgi(true); }
         public Builder emptyCatalog() { return emptyCatalog(true); }
 
-        public Builder disablePersistence(boolean disablePersistence) { this.disablePersistence = disablePersistence; return this; }
+        public Builder disablePersistenceBackups(boolean disablePersistenceBackups) { this.disablePersistenceBackups = disablePersistenceBackups; return this; }
         public Builder disableOsgi(boolean disableOsgi) { this.disableOsgi = disableOsgi; return this; }
         public Builder emptyCatalog(boolean emptyCatalog) { this.emptyCatalog = emptyCatalog; return this; }
 
@@ -91,7 +102,7 @@ public class LocalManagementContextForTests extends LocalManagementContext {
         }
         
         public Builder minimal() {
-            disablePersistence();
+            disablePersistenceBackups();
             disableOsgi();
             emptyCatalog();
             properties = null;
@@ -107,7 +118,7 @@ public class LocalManagementContextForTests extends LocalManagementContext {
         
         public BrooklynProperties buildProperties() {
             BrooklynProperties result = emptyIfNull(properties);
-            if (disablePersistence) LocalManagementContextForTests.disablePersistentStoreBackup(result);
+            if (disablePersistenceBackups) LocalManagementContextForTests.disablePersistenceBackups(result);
             if (disableOsgi) LocalManagementContextForTests.disableOsgi(result);
             if (emptyCatalog) LocalManagementContextForTests.setEmptyCatalogAsDefault(result);
             return result;
@@ -122,19 +133,22 @@ public class LocalManagementContextForTests extends LocalManagementContext {
         }
     }
     
-    /** create a new builder, defaulting to empty properties, and with the parameter determining whether 
+    /** Create a new builder, defaulting to empty properties, and with the parameter determining whether 
      * by default to disable common things disabled in tests (and the caller can re-enable selected ones individually)
      * or (if false) leaving everything enabled (so the caller turns things off) */
     public static Builder builder(boolean minimal) { return new Builder().minimal(minimal); }
     
+    /** Creates a new minimal instance */
     public static LocalManagementContext newInstance() {
         return builder(true).build();
     }
 
+    /** Creates a new minimal instance with the given properties then installed. */
     public static LocalManagementContext newInstance(BrooklynProperties properties) {
         return builder(true).useProperties(properties).build();
     }
 
+    /** Creates a new minimal instance with OSGi then enabled. */
     public static LocalManagementContext newInstanceWithOsgi() {
         return builder(true).disableOsgi(false).build();
     }

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/7532be8c/locations/jclouds/src/main/java/brooklyn/entity/rebind/persister/jclouds/JcloudsBlobStoreBasedObjectStore.java
----------------------------------------------------------------------
diff --git a/locations/jclouds/src/main/java/brooklyn/entity/rebind/persister/jclouds/JcloudsBlobStoreBasedObjectStore.java b/locations/jclouds/src/main/java/brooklyn/entity/rebind/persister/jclouds/JcloudsBlobStoreBasedObjectStore.java
index 7796d1b..1e9acf8 100644
--- a/locations/jclouds/src/main/java/brooklyn/entity/rebind/persister/jclouds/JcloudsBlobStoreBasedObjectStore.java
+++ b/locations/jclouds/src/main/java/brooklyn/entity/rebind/persister/jclouds/JcloudsBlobStoreBasedObjectStore.java
@@ -189,6 +189,7 @@ public class JcloudsBlobStoreBasedObjectStore implements PersistenceObjectStore
         
         Boolean backups = mgmt.getConfig().getConfig(BrooklynServerConfig.PERSISTENCE_BACKUPS_REQUIRED);
         if (Boolean.TRUE.equals(backups)) {
+            log.warn("Using legacy backup for "+this+"; functionality will be removed in future versions, in favor of promotion/demotion-specific backups to a configurable backup location.");
             throw new FatalConfigurationRuntimeException("Backups not supported for object store ("+this+")");
         }
     }

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/7532be8c/usage/launcher/src/main/java/brooklyn/launcher/BrooklynLauncher.java
----------------------------------------------------------------------
diff --git a/usage/launcher/src/main/java/brooklyn/launcher/BrooklynLauncher.java b/usage/launcher/src/main/java/brooklyn/launcher/BrooklynLauncher.java
index f28b15f..00162a8 100644
--- a/usage/launcher/src/main/java/brooklyn/launcher/BrooklynLauncher.java
+++ b/usage/launcher/src/main/java/brooklyn/launcher/BrooklynLauncher.java
@@ -44,6 +44,7 @@ import org.slf4j.LoggerFactory;
 import brooklyn.catalog.CatalogLoadMode;
 import brooklyn.config.BrooklynProperties;
 import brooklyn.config.BrooklynServerConfig;
+import brooklyn.config.BrooklynServerPaths;
 import brooklyn.config.BrooklynServiceAttributes;
 import brooklyn.config.ConfigKey;
 import brooklyn.config.ConfigPredicates;
@@ -482,7 +483,7 @@ public class BrooklynLauncher {
             BrooklynMementoRawData memento = managementContext.getRebindManager().retrieveMementoRawData();
             if (transformer != null) memento = transformer.transform(memento);
             
-            ManagementPlaneSyncRecord planeState = managementContext.getHighAvailabilityManager().getManagementPlaneSyncState();
+            ManagementPlaneSyncRecord planeState = managementContext.getHighAvailabilityManager().loadManagementPlaneSyncRecord(true);
             
             LOG.info("Persisting state to "+destinationDir+(destinationLocationSpec!=null ? " @ "+destinationLocationSpec : ""));
             PersistenceObjectStore destinationObjectStore = BrooklynPersistenceUtils.newPersistenceObjectStore(
@@ -735,7 +736,7 @@ public class BrooklynLauncher {
                 if (persistenceLocation == null) {
                     persistenceLocation = brooklynProperties.getConfig(BrooklynServerConfig.PERSISTENCE_LOCATION_SPEC);
                 }
-                persistenceDir = BrooklynServerConfig.resolvePersistencePath(persistenceDir, brooklynProperties, persistenceLocation);
+                persistenceDir = BrooklynServerPaths.newMainPersistencePathResolver(brooklynProperties).location(persistenceLocation).dir(persistenceDir).resolve();
                 objectStore = BrooklynPersistenceUtils.newPersistenceObjectStore(managementContext, persistenceLocation, persistenceDir, 
                     persistMode, highAvailabilityMode);
                     

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/7532be8c/usage/launcher/src/main/java/brooklyn/launcher/BrooklynWebServer.java
----------------------------------------------------------------------
diff --git a/usage/launcher/src/main/java/brooklyn/launcher/BrooklynWebServer.java b/usage/launcher/src/main/java/brooklyn/launcher/BrooklynWebServer.java
index 0d44b30..30a4c63 100644
--- a/usage/launcher/src/main/java/brooklyn/launcher/BrooklynWebServer.java
+++ b/usage/launcher/src/main/java/brooklyn/launcher/BrooklynWebServer.java
@@ -46,7 +46,7 @@ import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 import brooklyn.BrooklynVersion;
-import brooklyn.config.BrooklynServerConfig;
+import brooklyn.config.BrooklynServerPaths;
 import brooklyn.config.BrooklynServiceAttributes;
 import brooklyn.config.ConfigKey;
 import brooklyn.launcher.config.CustomResourceLocator;
@@ -78,15 +78,14 @@ import brooklyn.util.text.Identifiers;
 import brooklyn.util.text.Strings;
 import brooklyn.util.web.ContextHandlerCollectionHotSwappable;
 
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Throwables;
+import com.google.common.collect.Maps;
 import com.sun.jersey.api.container.filter.GZIPContentEncodingFilter;
 import com.sun.jersey.api.core.DefaultResourceConfig;
 import com.sun.jersey.api.core.ResourceConfig;
 import com.sun.jersey.spi.container.servlet.ServletContainer;
 
-import com.google.common.annotations.VisibleForTesting;
-import com.google.common.base.Throwables;
-import com.google.common.collect.Maps;
-
 /**
  * Starts the web-app running, connected to the given management context
  */
@@ -192,7 +191,7 @@ public class BrooklynWebServer {
         if (!leftovers.isEmpty())
             log.warn("Ignoring unknown flags " + leftovers);
         
-        webappTempDir = BrooklynServerConfig.getBrooklynWebTmpDir(managementContext);
+        webappTempDir = BrooklynServerPaths.getBrooklynWebTmpDir(managementContext);
     }
 
     public BrooklynWebServer(ManagementContext managementContext, int port) {

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/7532be8c/usage/launcher/src/test/java/brooklyn/launcher/BrooklynLauncherRebindTestToFiles.java
----------------------------------------------------------------------
diff --git a/usage/launcher/src/test/java/brooklyn/launcher/BrooklynLauncherRebindTestToFiles.java b/usage/launcher/src/test/java/brooklyn/launcher/BrooklynLauncherRebindTestToFiles.java
index 1dce905..57b8682 100644
--- a/usage/launcher/src/test/java/brooklyn/launcher/BrooklynLauncherRebindTestToFiles.java
+++ b/usage/launcher/src/test/java/brooklyn/launcher/BrooklynLauncherRebindTestToFiles.java
@@ -19,7 +19,6 @@
 package brooklyn.launcher;
 
 import static org.testng.Assert.assertEquals;
-import static org.testng.Assert.assertFalse;
 import static org.testng.Assert.assertNotEquals;
 import static org.testng.Assert.assertTrue;
 
@@ -28,14 +27,13 @@ import java.io.File;
 import org.testng.annotations.Test;
 
 import brooklyn.config.BrooklynProperties;
-import brooklyn.config.BrooklynServerConfig;
+import brooklyn.config.BrooklynServerPaths;
 import brooklyn.entity.proxying.EntitySpec;
 import brooklyn.entity.rebind.persister.BrooklynMementoPersisterToObjectStore;
 import brooklyn.entity.rebind.persister.FileBasedObjectStore;
 import brooklyn.entity.rebind.persister.PersistMode;
 import brooklyn.management.ManagementContext;
 import brooklyn.management.ha.HighAvailabilityMode;
-import brooklyn.mementos.BrooklynMementoRawData;
 import brooklyn.test.entity.TestApplication;
 import brooklyn.util.javalang.JavaClassNames;
 import brooklyn.util.os.Os;
@@ -68,7 +66,7 @@ public class BrooklynLauncherRebindTestToFiles extends BrooklynLauncherRebindTes
     }
 
     protected void checkPersistenceContainerNameIsDefault() {
-        String expected = BrooklynServerConfig.resolvePersistencePath(null, BrooklynProperties.Factory.newEmpty(), null);
+        String expected = BrooklynServerPaths.newMainPersistencePathResolver(BrooklynProperties.Factory.newEmpty()).location(null).dir(null).resolve();
         checkPersistenceContainerNameIs(expected);
     }
 

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/7532be8c/usage/launcher/src/test/java/brooklyn/launcher/BrooklynLauncherRebindToCloudObjectStoreTest.java
----------------------------------------------------------------------
diff --git a/usage/launcher/src/test/java/brooklyn/launcher/BrooklynLauncherRebindToCloudObjectStoreTest.java b/usage/launcher/src/test/java/brooklyn/launcher/BrooklynLauncherRebindToCloudObjectStoreTest.java
index b044ba0..258ba5b 100644
--- a/usage/launcher/src/test/java/brooklyn/launcher/BrooklynLauncherRebindToCloudObjectStoreTest.java
+++ b/usage/launcher/src/test/java/brooklyn/launcher/BrooklynLauncherRebindToCloudObjectStoreTest.java
@@ -24,7 +24,7 @@ import org.testng.annotations.BeforeMethod;
 import org.testng.annotations.Test;
 
 import brooklyn.config.BrooklynProperties;
-import brooklyn.config.BrooklynServerConfig;
+import brooklyn.config.BrooklynServerPaths;
 import brooklyn.entity.proxying.EntitySpec;
 import brooklyn.entity.rebind.persister.BrooklynMementoPersisterToObjectStore;
 import brooklyn.entity.rebind.persister.PersistMode;
@@ -88,7 +88,7 @@ public class BrooklynLauncherRebindToCloudObjectStoreTest extends BrooklynLaunch
     }
 
     protected void checkPersistenceContainerNameIsDefault() {
-        checkPersistenceContainerNameIs(BrooklynServerConfig.DEFAULT_PERSISTENCE_CONTAINER_NAME);
+        checkPersistenceContainerNameIs(BrooklynServerPaths.DEFAULT_PERSISTENCE_CONTAINER_NAME);
     }
 
     @Override @Test(groups="Live")

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/7532be8c/usage/rest-server/src/main/java/brooklyn/rest/resources/ServerResource.java
----------------------------------------------------------------------
diff --git a/usage/rest-server/src/main/java/brooklyn/rest/resources/ServerResource.java b/usage/rest-server/src/main/java/brooklyn/rest/resources/ServerResource.java
index 803dac4..87fce42 100644
--- a/usage/rest-server/src/main/java/brooklyn/rest/resources/ServerResource.java
+++ b/usage/rest-server/src/main/java/brooklyn/rest/resources/ServerResource.java
@@ -269,7 +269,9 @@ public class ServerResource extends AbstractBrooklynRestResource implements Serv
 
     @Override
     public HighAvailabilitySummary getHighAvailabilityPlaneStates() {
-        ManagementPlaneSyncRecord memento = mgmt().getHighAvailabilityManager().getManagementPlaneSyncState();
+        ManagementPlaneSyncRecord memento = mgmt().getHighAvailabilityManager().getLastManagementPlaneSyncRecord();
+        if (memento==null) memento = mgmt().getHighAvailabilityManager().loadManagementPlaneSyncRecord(true);
+        if (memento==null) return null;
         return HighAvailabilityTransformer.highAvailabilitySummary(mgmt().getManagementNodeId(), memento);
     }
 

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/7532be8c/utils/common/src/main/java/brooklyn/util/time/Time.java
----------------------------------------------------------------------
diff --git a/utils/common/src/main/java/brooklyn/util/time/Time.java b/utils/common/src/main/java/brooklyn/util/time/Time.java
index 7c0612f..2059223 100644
--- a/utils/common/src/main/java/brooklyn/util/time/Time.java
+++ b/utils/common/src/main/java/brooklyn/util/time/Time.java
@@ -39,6 +39,7 @@ public class Time {
 
     public static final String DATE_FORMAT_PREFERRED = "yyyy-MM-dd HH:mm:ss.SSS";
     public static final String DATE_FORMAT_STAMP = "yyyyMMdd-HHmmssSSS";
+    public static final String DATE_FORMAT_SIMPLE_STAMP = "yyyy-MM-dd-HHmm";
 
     public static final long MILLIS_IN_SECOND = 1000;
     public static final long MILLIS_IN_MINUTE = 60*MILLIS_IN_SECOND;
@@ -46,12 +47,13 @@ public class Time {
     public static final long MILLIS_IN_DAY = 24*MILLIS_IN_HOUR;
     public static final long MILLIS_IN_YEAR = 365*MILLIS_IN_DAY;
     
-    /** returns the current time in YYYY-MM-DD HH:MM:SS.mss format */
+    /** returns the current time in {@value #DATE_FORMAT_PREFERRED} format,
+     * numeric big-endian but otherwise optimized for people to read, with spaces and colons and dots */
     public static String makeDateString() {
         return makeDateString(System.currentTimeMillis());
     }
 
-    /** returns the time in YYYY-MM-DD HH:MM:SS.mss format, given a long (e.g. returned by System.currentTimeMillis) */
+    /** returns the time in {@value #DATE_FORMAT_PREFERRED} format, given a long (e.g. returned by System.currentTimeMillis) */
     public static String makeDateString(long date) {
         return new SimpleDateFormat(DATE_FORMAT_PREFERRED).format(new Date(date));
     }
@@ -66,16 +68,31 @@ public class Time {
             }
         };
 
-    /** returns the current time in YYYYMMDD-HHMMSSmss format */
+    /** returns the current time in {@value #DATE_FORMAT_STAMP} format,
+     * suitable for machines to read with only numbers and dashes and quite precise (ms) */
     public static String makeDateStampString() {
         return makeDateStampString(System.currentTimeMillis());
     }
 
-    /** returns the time in YYYY-MM-DD HH:MM:SS.mss format, given a long (e.g. returned by System.currentTimeMillis) */
+    /** returns the time in {@value #DATE_FORMAT_STAMP} format, given a long (e.g. returned by System.currentTimeMillis);
+     * cf {@link #makeDateStampString()} */
     public static String makeDateStampString(long date) {
         return new SimpleDateFormat(DATE_FORMAT_STAMP).format(new Date(date));
     }
 
+    /** returns the current time in {@value #DATE_FORMAT_SIMPLE_STAMP} format, 
+     * suitable for machines to read but easier for humans too, 
+     * like {@link #makeDateStampString()} but not as precise */
+    public static String makeDateSimpleStampString() {
+        return makeDateSimpleStampString(System.currentTimeMillis());
+    }
+
+    /** returns the time in {@value #DATE_FORMAT_SIMPLE_STAMP} format, given a long (e.g. returned by System.currentTimeMillis);
+     * cf {@link #makeDateSimpleStampString()} */
+    public static String makeDateSimpleStampString(long date) {
+        return new SimpleDateFormat(DATE_FORMAT_SIMPLE_STAMP).format(new Date(date));
+    }
+
     public static Function<Long, String> toDateStampString() { return dateStampString; }
     private static Function<Long, String> dateStampString = new Function<Long, String>() {
             @Override


[15/21] incubator-brooklyn git commit: correct description and tidied code for determining backup location spec v non-backup location spec, fixing NPE's

Posted by he...@apache.org.
correct description and tidied code for determining backup location spec v non-backup location spec, fixing NPE's


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

Branch: refs/heads/master
Commit: a6891c691a3ead9ef0dac4e978527e034a83e576
Parents: 9edd58d
Author: Alex Heneveld <al...@cloudsoftcorp.com>
Authored: Thu Nov 13 21:42:04 2014 +0000
Committer: Alex Heneveld <al...@cloudsoftcorp.com>
Committed: Thu Nov 13 23:38:55 2014 +0000

----------------------------------------------------------------------
 .../java/brooklyn/config/BrooklynServerConfig.java    |  2 +-
 .../java/brooklyn/config/BrooklynServerPaths.java     | 14 +++++++++-----
 .../rebind/persister/BrooklynPersistenceUtils.java    | 13 ++++++++-----
 3 files changed, 18 insertions(+), 11 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/a6891c69/core/src/main/java/brooklyn/config/BrooklynServerConfig.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/config/BrooklynServerConfig.java b/core/src/main/java/brooklyn/config/BrooklynServerConfig.java
index c240d7c..4fdf4c4 100644
--- a/core/src/main/java/brooklyn/config/BrooklynServerConfig.java
+++ b/core/src/main/java/brooklyn/config/BrooklynServerConfig.java
@@ -76,7 +76,7 @@ public class BrooklynServerConfig {
     public static final ConfigKey<String> PERSISTENCE_BACKUPS_LOCATION_SPEC = newStringConfigKey(
         "brooklyn.persistence.backups.location.spec", 
         "Location spec string for an object store (e.g. jclouds:swift:URL) where backups of persisted state should be kept; "
-        + "defaults to the same location spec as regular persisted state, failing back to local file system");
+        + "defaults to the local file system");
     
     public static final ConfigKey<Boolean> PERSISTENCE_BACKUPS_REQUIRED_ON_PROMOTION =
         ConfigKeys.newBooleanConfigKey("brooklyn.persistence.backups.required.promotion",

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/a6891c69/core/src/main/java/brooklyn/config/BrooklynServerPaths.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/config/BrooklynServerPaths.java b/core/src/main/java/brooklyn/config/BrooklynServerPaths.java
index ee294e9..06c8033 100644
--- a/core/src/main/java/brooklyn/config/BrooklynServerPaths.java
+++ b/core/src/main/java/brooklyn/config/BrooklynServerPaths.java
@@ -162,13 +162,12 @@ public class BrooklynServerPaths {
      * It also includes conveniences for resolving further subpaths, cf {@link PersistenceBackupPathResolver#resolveWithSubpathFor(ManagementContextInternal, String)}.
      */
     public static class PersistenceBackupPathResolver extends PersistencePathResolver {
-        protected String nonBackuplocationSpec;
+        private String nonBackupLocationSpec;
         private PersistenceBackupPathResolver(StringConfigMap brooklynProperties) {
             super(brooklynProperties);
         }
-        @Override
-        public PersistenceBackupPathResolver location(@Nullable String locationSpec) {
-            this.nonBackuplocationSpec = locationSpec;
+        public PersistenceBackupPathResolver nonBackupLocation(@Nullable String locationSpec) {
+            this.nonBackupLocationSpec = locationSpec;
             return this;
         }
         @Override
@@ -176,8 +175,13 @@ public class BrooklynServerPaths {
             super.dir(dirOrContainer);
             return this;
         }
+        @Override
+        public PersistenceBackupPathResolver location(String backupLocationSpec) {
+            super.location(backupLocationSpec);
+            return this;
+        }
         protected boolean isBackupSameLocation() {
-            return Objects.equal(locationSpec, nonBackuplocationSpec);
+            return Objects.equal(locationSpec, nonBackupLocationSpec);
         }
         /** Appends a sub-path to the path returned by {@link #resolve()} */
         public String resolveWithSubpath(String subpath) {

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/a6891c69/core/src/main/java/brooklyn/entity/rebind/persister/BrooklynPersistenceUtils.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/entity/rebind/persister/BrooklynPersistenceUtils.java b/core/src/main/java/brooklyn/entity/rebind/persister/BrooklynPersistenceUtils.java
index ea52f36..9d2721d 100644
--- a/core/src/main/java/brooklyn/entity/rebind/persister/BrooklynPersistenceUtils.java
+++ b/core/src/main/java/brooklyn/entity/rebind/persister/BrooklynPersistenceUtils.java
@@ -230,9 +230,10 @@ public class BrooklynPersistenceUtils {
         
             PersistenceObjectStore destinationObjectStore = null;
             String backupSpec = managementContext.getConfig().getConfig(BrooklynServerConfig.PERSISTENCE_BACKUPS_LOCATION_SPEC);
+            String nonBackupSpec = managementContext.getConfig().getConfig(BrooklynServerConfig.PERSISTENCE_LOCATION_SPEC);
             try {
-                String backupContainer = BrooklynServerPaths.newBackupPersistencePathResolver(managementContext).location(backupSpec)
-                    .resolveWithSubpathFor(managementContext, mode.toString());
+                String backupContainer = BrooklynServerPaths.newBackupPersistencePathResolver(managementContext)
+                    .location(backupSpec).nonBackupLocation(nonBackupSpec).resolveWithSubpathFor(managementContext, mode.toString());
                 destinationObjectStore = BrooklynPersistenceUtils.newPersistenceObjectStore(managementContext, backupSpec, backupContainer);
                 log.debug("Backing up persisted state on "+mode+", to "+destinationObjectStore.getSummaryName());
                 BrooklynPersistenceUtils.writeMemento(managementContext, memento, destinationObjectStore);
@@ -243,11 +244,13 @@ public class BrooklynPersistenceUtils {
                 Exceptions.propagateIfFatal(e);
                 PersistenceObjectStore failedStore = destinationObjectStore;
                 if (!Strings.isBlank(backupSpec) && !"localhost".equals(backupSpec)) {
+                    String failedSpec = backupSpec;
                     backupSpec = "localhost";
-                    String backupContainer = BrooklynServerPaths.newBackupPersistencePathResolver(managementContext).location(backupSpec)
-                        .resolveWithSubpathFor(managementContext, mode.toString());
+                    String backupContainer = BrooklynServerPaths.newBackupPersistencePathResolver(managementContext)
+                        .location(backupSpec).nonBackupLocation(nonBackupSpec).resolveWithSubpathFor(managementContext, mode.toString());
                     destinationObjectStore = BrooklynPersistenceUtils.newPersistenceObjectStore(managementContext, backupSpec, backupContainer);
-                    log.warn("Persisted state back-up to "+failedStore.getSummaryName()+" failed with "+e, e);
+                    log.warn("Persisted state back-up to "+(failedStore!=null ? failedStore.getSummaryName() : failedSpec)
+                        +" failed with "+e, e);
                     
                     log.debug("Backing up persisted state on "+mode+", locally because remote failed, to "+destinationObjectStore.getSummaryName());
                     BrooklynPersistenceUtils.writeMemento(managementContext, memento, destinationObjectStore);


[18/21] incubator-brooklyn git commit: Adds MultimapSerializer

Posted by he...@apache.org.
Adds MultimapSerializer

Should eventually be replaced with the Guava serialisers in the
jackson-datatype-guava module.


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

Branch: refs/heads/master
Commit: 8ebeebb3d8828a8d4bcbb3142790bff5d8a5509b
Parents: c77f9ff
Author: Sam Corbett <sa...@cloudsoftcorp.com>
Authored: Fri Nov 14 18:49:42 2014 +0000
Committer: Sam Corbett <sa...@cloudsoftcorp.com>
Committed: Fri Nov 14 18:49:42 2014 +0000

----------------------------------------------------------------------
 .../rest/util/json/BidiSerialization.java       |  9 +-
 .../util/json/BrooklynJacksonJsonProvider.java  | 86 +++++++++++---------
 .../rest/util/json/MultimapSerializer.java      | 63 ++++++++++++++
 .../json/BrooklynJacksonSerializerTest.java     | 19 ++++-
 4 files changed, 132 insertions(+), 45 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/8ebeebb3/usage/rest-server/src/main/java/brooklyn/rest/util/json/BidiSerialization.java
----------------------------------------------------------------------
diff --git a/usage/rest-server/src/main/java/brooklyn/rest/util/json/BidiSerialization.java b/usage/rest-server/src/main/java/brooklyn/rest/util/json/BidiSerialization.java
index 262d202..682fd63 100644
--- a/usage/rest-server/src/main/java/brooklyn/rest/util/json/BidiSerialization.java
+++ b/usage/rest-server/src/main/java/brooklyn/rest/util/json/BidiSerialization.java
@@ -40,18 +40,21 @@ public class BidiSerialization {
 
     protected final static ThreadLocal<Boolean> STRICT_SERIALIZATION = new ThreadLocal<Boolean>(); 
 
-    /** sets strict serialization on, or off (the default), for the current thread;
-     * recommended to be used in a <code>try { ... } finally { ... }</code> block
+    /**
+     * Sets strict serialization on, or off (the default), for the current thread.
+     * Recommended to be used in a <code>try { ... } finally { ... }</code> block
      * with {@link #clearStrictSerialization()} at the end.
      * <p>
-     * with strict serialization, classes must have public fields or annotated fields, else they will not be serialized
+     * With strict serialization, classes must have public fields or annotated fields, else they will not be serialized.
      */
     public static void setStrictSerialization(Boolean value) {
         STRICT_SERIALIZATION.set(value);
     }
+
     public static void clearStrictSerialization() {
         STRICT_SERIALIZATION.remove();
     }
+
     public static boolean isStrictSerialization() {
         Boolean result = STRICT_SERIALIZATION.get();
         if (result!=null) return result;

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/8ebeebb3/usage/rest-server/src/main/java/brooklyn/rest/util/json/BrooklynJacksonJsonProvider.java
----------------------------------------------------------------------
diff --git a/usage/rest-server/src/main/java/brooklyn/rest/util/json/BrooklynJacksonJsonProvider.java b/usage/rest-server/src/main/java/brooklyn/rest/util/json/BrooklynJacksonJsonProvider.java
index 88a2792..b79de3f 100644
--- a/usage/rest-server/src/main/java/brooklyn/rest/util/json/BrooklynJacksonJsonProvider.java
+++ b/usage/rest-server/src/main/java/brooklyn/rest/util/json/BrooklynJacksonJsonProvider.java
@@ -41,40 +41,40 @@ import brooklyn.management.ManagementContextInjectable;
 public class BrooklynJacksonJsonProvider extends JacksonJsonProvider implements ManagementContextInjectable {
 
     private static final Logger log = LoggerFactory.getLogger(BrooklynJacksonJsonProvider.class);
-    
+
     public static final String BROOKLYN_REST_OBJECT_MAPPER = BrooklynServiceAttributes.BROOKLYN_REST_OBJECT_MAPPER;
-    
+
     @Context protected ServletContext servletContext;
-    
+
     protected ObjectMapper ourMapper;
     protected boolean notFound = false;
 
     private ManagementContext mgmt;
-    
+
     public ObjectMapper locateMapper(Class<?> type, MediaType mediaType) {
-        if (ourMapper!=null) 
+        if (ourMapper != null)
             return ourMapper;
-        
+
         findSharedMapper();
-        
-        if (ourMapper!=null)
+
+        if (ourMapper != null)
             return ourMapper;
-        
+
         if (!notFound) {
             log.warn("Management context not available; using default ObjectMapper in "+this);
             notFound = true;
         }
-        
+
         return super.locateMapper(Object.class, MediaType.APPLICATION_JSON_TYPE);
     }
-    
+
     protected synchronized ObjectMapper findSharedMapper() {
-        if (ourMapper!=null || notFound) 
+        if (ourMapper != null || notFound)
             return ourMapper;
-                
+
         ourMapper = findSharedObjectMapper(servletContext, mgmt);
-        if (ourMapper==null) return null;
-        
+        if (ourMapper == null) return null;
+
         if (notFound) {
             notFound = false;
         }
@@ -82,25 +82,27 @@ public class BrooklynJacksonJsonProvider extends JacksonJsonProvider implements
 
         return ourMapper;
     }
-    
-    /** finds a shared {@link ObjectMapper} or makes a new one, stored against the servlet context;
-     * returns null if a shared instance cannot be created */
+
+    /**
+     * Finds a shared {@link ObjectMapper} or makes a new one, stored against the servlet context;
+     * returns null if a shared instance cannot be created.
+     */
     public static ObjectMapper findSharedObjectMapper(ServletContext servletContext, ManagementContext mgmt) {
-        if (servletContext!=null) {
+        if (servletContext != null) {
             synchronized (servletContext) {
                 ObjectMapper mapper = (ObjectMapper) servletContext.getAttribute(BROOKLYN_REST_OBJECT_MAPPER);
-                if (mapper!=null) return mapper;
+                if (mapper != null) return mapper;
 
                 mapper = newPrivateObjectMapper(getManagementContext(servletContext));
                 servletContext.setAttribute(BROOKLYN_REST_OBJECT_MAPPER, mapper);
                 return mapper;
             }
         }
-        if (mgmt!=null) {
+        if (mgmt != null) {
             synchronized (mgmt) {
                 ConfigKey<ObjectMapper> key = ConfigKeys.newConfigKey(ObjectMapper.class, BROOKLYN_REST_OBJECT_MAPPER);
                 ObjectMapper mapper = mgmt.getConfig().getConfig(key);
-                if (mapper!=null) return mapper;
+                if (mapper != null) return mapper;
 
                 mapper = newPrivateObjectMapper(mgmt);
                 log.debug("Storing new ObjectMapper against "+mgmt+" because no ServletContext available: "+mapper);
@@ -111,49 +113,55 @@ public class BrooklynJacksonJsonProvider extends JacksonJsonProvider implements
         return null;
     }
 
-    /** like {@link #findSharedObjectMapper(ServletContext)} but will create a private one if it can,
-     * from the servlet context and/or the management context, or else fail */
+    /**
+     * Like {@link #findSharedObjectMapper(ServletContext, ManagementContext)} but will create a private
+     * ObjectMapper if it can, from the servlet context and/or the management context, or else fail
+     */
     public static ObjectMapper findAnyObjectMapper(ServletContext servletContext, ManagementContext mgmt) {
         ObjectMapper mapper = findSharedObjectMapper(servletContext, mgmt);
-        if (mapper!=null) return mapper;
-        
-        if (mgmt==null && servletContext!=null) {
+        if (mapper != null) return mapper;
+
+        if (mgmt == null && servletContext != null) {
             mgmt = getManagementContext(servletContext);
         }
         return newPrivateObjectMapper(mgmt);
     }
 
-    /** creates a new Brooklyn-specific ObjectMapper; normally {@link #findSharedObjectMapper(ServletContext)} is preferred */
+    /**
+     * @return A new Brooklyn-specific ObjectMapper.
+     *   Normally {@link #findSharedObjectMapper(ServletContext, ManagementContext)} is preferred
+     */
     public static ObjectMapper newPrivateObjectMapper(ManagementContext mgmt) {
-        if (mgmt==null) {
+        if (mgmt == null) {
             throw new IllegalStateException("No management context available for creating ObjectMapper");
         }
-        
+
         SerializationConfig defaultConfig = new ObjectMapper().getSerializationConfig();
         SerializationConfig sc = new SerializationConfig(
-            defaultConfig.getClassIntrospector() /* ObjectMapper.DEFAULT_INTROSPECTOR */, 
-            defaultConfig.getAnnotationIntrospector() /* ObjectMapper.DEFAULT_ANNOTATION_INTROSPECTOR */, 
+            defaultConfig.getClassIntrospector() /* ObjectMapper.DEFAULT_INTROSPECTOR */,
+            defaultConfig.getAnnotationIntrospector() /* ObjectMapper.DEFAULT_ANNOTATION_INTROSPECTOR */,
             new PossiblyStrictPreferringFieldsVisibilityChecker(),
             null, null, TypeFactory.defaultInstance(), null);
-        
+
         ConfigurableSerializerProvider sp = new ConfigurableSerializerProvider();
         sp.setUnknownTypeSerializer(new ErrorAndToStringUnknownTypeSerializer());
-        
+
         ObjectMapper mapper = new ObjectMapper(null, sp, null, sc, null);
         SimpleModule mapperModule = new SimpleModule("Brooklyn", new Version(0, 0, 0, "ignored"));
-        
+
         new BidiSerialization.ManagementContextSerialization(mgmt).install(mapperModule);
         new BidiSerialization.EntitySerialization(mgmt).install(mapperModule);
         new BidiSerialization.LocationSerialization(mgmt).install(mapperModule);
-        
+        mapperModule.addSerializer(new MultimapSerializer());
+
         mapper.registerModule(mapperModule);
         return mapper;
     }
 
     public static ManagementContext getManagementContext(ServletContext servletContext) {
-        if (servletContext==null)
+        if (servletContext == null)
             return null;
-        
+
         return (ManagementContext) servletContext.getAttribute(BrooklynServiceAttributes.BROOKLYN_MANAGEMENT_CONTEXT);
     }
 
@@ -161,5 +169,5 @@ public class BrooklynJacksonJsonProvider extends JacksonJsonProvider implements
     public void injectManagementContext(ManagementContext mgmt) {
         this.mgmt = mgmt;
     }
-    
+
 }

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/8ebeebb3/usage/rest-server/src/main/java/brooklyn/rest/util/json/MultimapSerializer.java
----------------------------------------------------------------------
diff --git a/usage/rest-server/src/main/java/brooklyn/rest/util/json/MultimapSerializer.java b/usage/rest-server/src/main/java/brooklyn/rest/util/json/MultimapSerializer.java
new file mode 100644
index 0000000..294f771
--- /dev/null
+++ b/usage/rest-server/src/main/java/brooklyn/rest/util/json/MultimapSerializer.java
@@ -0,0 +1,63 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package brooklyn.rest.util.json;
+
+import java.io.IOException;
+import java.util.Collection;
+import java.util.Map;
+
+import org.codehaus.jackson.JsonGenerator;
+import org.codehaus.jackson.map.JsonSerializer;
+import org.codehaus.jackson.map.SerializerProvider;
+import org.codehaus.jackson.map.ser.std.SerializerBase;
+
+import com.google.api.client.util.Lists;
+import com.google.common.annotations.Beta;
+import com.google.common.collect.Multimap;
+
+/**
+ * Provides a serializer for {@link Multimap} instances.
+ * <p>
+ * When Brooklyn's Jackson dependency is updated from org.codehaus.jackson:1.9.13 to
+ * com.fasterxml.jackson:2.3+ then this class should be replaced with a dependency on
+ * jackson-datatype-guava and a GuavaModule registered with Brooklyn's ObjectMapper.
+ */
+@Beta
+public class MultimapSerializer extends SerializerBase<Multimap<?, ?>> {
+
+    protected MultimapSerializer() {
+        super((Class<Multimap<?, ?>>) (Class) Multimap.class);
+    }
+
+    @Override
+    public void serialize(Multimap<?, ?> value, JsonGenerator jgen, SerializerProvider provider) throws IOException {
+        jgen.writeStartObject();
+        writeEntries(value, jgen, provider);
+        jgen.writeEndObject();
+    }
+
+    private void writeEntries(Multimap<?, ?> value, JsonGenerator jgen, SerializerProvider provider) throws IOException {
+        for (Map.Entry<?, ? extends Collection<?>> entry : value.asMap().entrySet()) {
+            provider.findKeySerializer(provider.constructType(String.class), null)
+                    .serialize(entry.getKey(), jgen, provider);
+            provider.defaultSerializeValue(Lists.newArrayList(entry.getValue()), jgen);
+        }
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/8ebeebb3/usage/rest-server/src/test/java/brooklyn/rest/util/json/BrooklynJacksonSerializerTest.java
----------------------------------------------------------------------
diff --git a/usage/rest-server/src/test/java/brooklyn/rest/util/json/BrooklynJacksonSerializerTest.java b/usage/rest-server/src/test/java/brooklyn/rest/util/json/BrooklynJacksonSerializerTest.java
index e4a47de..552999f 100644
--- a/usage/rest-server/src/test/java/brooklyn/rest/util/json/BrooklynJacksonSerializerTest.java
+++ b/usage/rest-server/src/test/java/brooklyn/rest/util/json/BrooklynJacksonSerializerTest.java
@@ -54,6 +54,8 @@ import brooklyn.util.stream.Streams;
 import brooklyn.util.text.Strings;
 
 import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Multimap;
+import com.google.common.collect.MultimapBuilder;
 import com.google.gson.Gson;
 
 public class BrooklynJacksonSerializerTest {
@@ -198,11 +200,22 @@ public class BrooklynJacksonSerializerTest {
         ll.add(1); ll.add("two");
         String result = checkSerializesAs(ll, null);
         log.info("LLIST json is: "+result);
-        Assert.assertFalse(result.toString().contains("error"), "Shouldn't have had an error, instead got: "+result);
+        Assert.assertFalse(result.contains("error"), "Shouldn't have had an error, instead got: "+result);
         Assert.assertEquals(Strings.collapseWhitespace(result, ""), "[1,\"two\"]");
     }
 
     @Test
+    public void testMultiMapSerialization() throws Exception {
+        Multimap<String, Integer> m = MultimapBuilder.hashKeys().arrayListValues().build();
+        m.put("bob", 24);
+        m.put("bob", 25);
+        String result = checkSerializesAs(m, null);
+        log.info("multimap serialized as: " + result);
+        Assert.assertFalse(result.contains("error"), "Shouldn't have had an error, instead got: "+result);
+        Assert.assertEquals(Strings.collapseWhitespace(result, ""), "{\"bob\":[24,25]}");
+    }
+
+    @Test
     public void testSupplierSerialization() throws Exception {
         String result = checkSerializesAs(Strings.toStringSupplier(Streams.byteArrayOfString("x")), null);
         log.info("SUPPLIER json is: "+result);
@@ -235,7 +248,7 @@ public class BrooklynJacksonSerializerTest {
     protected Map<?,?> checkSerializesAsMapWithErrorAndToString(Object x) {
         Map<?,?> rt = checkSerializesAs(x, Map.class);
         Assert.assertEquals(rt.get("toString"), x.toString());
-        Assert.assertEquals(rt.get("error"), true);
+        Assert.assertEquals(rt.get("error"), Boolean.TRUE);
         return rt;
     }
     protected void checkNonSerializableWhenStrict(Object x) {
@@ -357,7 +370,7 @@ public class BrooklynJacksonSerializerTest {
             // (but with our custom VisibilityChecker server just gives us the nicer error!)
             log.info("CONFIG SERVER is:\n"+content);
             assertErrorObjectMatchingToString(content, server);
-            Assert.assertTrue(content.indexOf(NotSerializableException.class.getCanonicalName())>=0, "server should have contained things which are not serializable");
+            Assert.assertTrue(content.contains(NotSerializableException.class.getCanonicalName()), "server should have contained things which are not serializable");
             Assert.assertTrue(content.length() < 1024, "content should not have been very long; instead was: "+content.length());
             
         } finally {


[17/21] incubator-brooklyn git commit: Use supported names for object store

Posted by he...@apache.org.
Use supported names for object store

Fixes:
  * Files are now accessible (not 404) from Cyberduck & Softlayer UI
  * There's no longer a phantom folder with the same name as the parent for backups.


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

Branch: refs/heads/master
Commit: fa7ba6f838a488ed782d2cacf99a04687a55821f
Parents: 1a4e853
Author: Svetoslav Neykov <sv...@cloudsoftcorp.com>
Authored: Fri Nov 14 16:06:45 2014 +0200
Committer: Svetoslav Neykov <sv...@cloudsoftcorp.com>
Committed: Fri Nov 14 17:11:00 2014 +0200

----------------------------------------------------------------------
 .../ManagementPlaneSyncRecordPersisterToObjectStore.java  | 10 +++++++++-
 1 file changed, 9 insertions(+), 1 deletion(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/fa7ba6f8/core/src/main/java/brooklyn/management/ha/ManagementPlaneSyncRecordPersisterToObjectStore.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/management/ha/ManagementPlaneSyncRecordPersisterToObjectStore.java b/core/src/main/java/brooklyn/management/ha/ManagementPlaneSyncRecordPersisterToObjectStore.java
index 55b645c..c481cbe 100644
--- a/core/src/main/java/brooklyn/management/ha/ManagementPlaneSyncRecordPersisterToObjectStore.java
+++ b/core/src/main/java/brooklyn/management/ha/ManagementPlaneSyncRecordPersisterToObjectStore.java
@@ -125,8 +125,16 @@ public class ManagementPlaneSyncRecordPersisterToObjectStore implements Manageme
     protected synchronized void init() {
         if (!started) {
             started = true;
+            //Leading slash causes problems in SL, it's not a correct file name so remove it.
+            //But once removed we can't load the master file from existing persistence stores.
+            //Try to detect if the old file exists, if so use old-style names, otherwise use the correct names.
             masterWriter = new StoreObjectAccessorLocking(objectStore.newAccessor("/master"));
-            changeLogWriter = new StoreObjectAccessorLocking(objectStore.newAccessor("/change.log"));
+            if (masterWriter.get() != null) {
+                changeLogWriter = new StoreObjectAccessorLocking(objectStore.newAccessor("/change.log"));
+            } else {
+                masterWriter = new StoreObjectAccessorLocking(objectStore.newAccessor("master"));
+                changeLogWriter = new StoreObjectAccessorLocking(objectStore.newAccessor("change.log"));
+            }
         }
     }
 


[03/21] incubator-brooklyn git commit: make heartbeat timeout (to trigger failover) and poll period configurable from brooklyn.properties

Posted by he...@apache.org.
make heartbeat timeout (to trigger failover) and poll period configurable from brooklyn.properties


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

Branch: refs/heads/master
Commit: 442a769bac089e199b120f296f9b186040667243
Parents: 8e0e1b4
Author: Alex Heneveld <al...@cloudsoftcorp.com>
Authored: Thu Nov 6 11:06:10 2014 +0000
Committer: Alex Heneveld <al...@cloudsoftcorp.com>
Committed: Thu Nov 13 23:38:51 2014 +0000

----------------------------------------------------------------------
 .../ha/HighAvailabilityManagerImpl.java         | 52 +++++++++++++++-----
 1 file changed, 39 insertions(+), 13 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/442a769b/core/src/main/java/brooklyn/management/ha/HighAvailabilityManagerImpl.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/management/ha/HighAvailabilityManagerImpl.java b/core/src/main/java/brooklyn/management/ha/HighAvailabilityManagerImpl.java
index 852885a..93cf574 100644
--- a/core/src/main/java/brooklyn/management/ha/HighAvailabilityManagerImpl.java
+++ b/core/src/main/java/brooklyn/management/ha/HighAvailabilityManagerImpl.java
@@ -24,7 +24,6 @@ import static com.google.common.base.Preconditions.checkState;
 import java.io.IOException;
 import java.net.URI;
 import java.util.concurrent.Callable;
-import java.util.concurrent.TimeUnit;
 
 import javax.annotation.Nullable;
 
@@ -34,9 +33,11 @@ import org.slf4j.LoggerFactory;
 import brooklyn.BrooklynVersion;
 import brooklyn.catalog.internal.BasicBrooklynCatalog;
 import brooklyn.catalog.internal.CatalogDto;
+import brooklyn.config.ConfigKey;
 import brooklyn.entity.Application;
 import brooklyn.entity.Entity;
 import brooklyn.entity.basic.BrooklynTaskTags;
+import brooklyn.entity.basic.ConfigKeys;
 import brooklyn.entity.basic.EntityInternal;
 import brooklyn.entity.rebind.RebindManager;
 import brooklyn.entity.rebind.plane.dto.BasicManagementNodeSyncRecord;
@@ -105,7 +106,13 @@ public class HighAvailabilityManagerImpl implements HighAvailabilityManager {
     // They may not have seen each other's heartbeats yet, so will all claim mastery!
     // But this should be resolved shortly afterwards.
 
-    // TODO Should we pass in a classloader on construction, so it can be passed to {@link RebindManager#rebind(ClassLoader)} 
+    // TODO Should we pass in a classloader on construction, so it can be passed to {@link RebindManager#rebind(ClassLoader)}
+
+    public final ConfigKey<Duration> POLL_PERIOD = ConfigKeys.newConfigKey(Duration.class, "brooklyn.ha.pollPeriod",
+        "How often nodes should poll to detect whether master is healthy", Duration.seconds(5));
+    public final ConfigKey<Duration> HEARTBEAT_TIMEOUT = ConfigKeys.newConfigKey(Duration.class, "brooklyn.ha.heartbeatTimeout",
+        "Maximum allowable time for detection of a peer's heartbeat; if no sign of master after this time, "
+        + "another node may promote itself", Duration.THIRTY_SECONDS);
     
     @VisibleForTesting /* only used in tests currently */
     public static interface PromotionListener {
@@ -119,8 +126,6 @@ public class HighAvailabilityManagerImpl implements HighAvailabilityManager {
     private volatile ManagementPlaneSyncRecordPersister persister;
     private volatile PromotionListener promotionListener;
     private volatile MasterChooser masterChooser = new AlphabeticMasterChooser();
-    private volatile Duration pollPeriod = Duration.of(5, TimeUnit.SECONDS);
-    private volatile Duration heartbeatTimeout = Duration.THIRTY_SECONDS;
     private volatile Ticker localTickerUtc = new Ticker() {
         // strictly not a ticker because returns millis UTC, but it works fine even so
         @Override
@@ -136,6 +141,9 @@ public class HighAvailabilityManagerImpl implements HighAvailabilityManager {
     private volatile ManagementNodeState nodeState = ManagementNodeState.INITIALIZING;
     private volatile boolean nodeStateTransitionComplete = false;
     private volatile long priority = 0;
+    
+    private volatile transient Duration pollPeriodLocalOverride;
+    private volatile transient Duration heartbeatTimeoutOverride;
 
     public HighAvailabilityManagerImpl(ManagementContextInternal managementContext) {
         this.managementContext = managementContext;
@@ -152,8 +160,18 @@ public class HighAvailabilityManagerImpl implements HighAvailabilityManager {
         return persister;
     }
     
+    protected synchronized Duration getPollPeriod() {
+        if (pollPeriodLocalOverride!=null) return pollPeriodLocalOverride;
+        return managementContext.getBrooklynProperties().getConfig(POLL_PERIOD);
+    }
+    
+    /** Overrides {@link #POLL_PERIOD} from brooklyn config, 
+     * including e.g. {@link Duration#PRACTICALLY_FOREVER} to disable polling;
+     * or <code>null</code> to clear a local override */
     public HighAvailabilityManagerImpl setPollPeriod(Duration val) {
-        this.pollPeriod = checkNotNull(val, "pollPeriod");
+        synchronized (this) {
+            this.pollPeriodLocalOverride = val;
+        }
         if (running) {
             registerPollTask();
         }
@@ -165,8 +183,16 @@ public class HighAvailabilityManagerImpl implements HighAvailabilityManager {
         return this;
     }
 
-    public HighAvailabilityManagerImpl setHeartbeatTimeout(Duration val) {
-        this.heartbeatTimeout = checkNotNull(val, "heartbeatTimeout");
+    public synchronized Duration getHeartbeatTimeout() {
+        if (heartbeatTimeoutOverride!=null) return heartbeatTimeoutOverride;
+        return managementContext.getBrooklynProperties().getConfig(HEARTBEAT_TIMEOUT);
+    }
+    
+    /** Overrides {@link #HEARTBEAT_TIMEOUT} from brooklyn config, 
+     * including e.g. {@link Duration#PRACTICALLY_FOREVER} to prevent failover due to heartbeat absence;
+     * or <code>null</code> to clear a local override */
+    public synchronized HighAvailabilityManagerImpl setHeartbeatTimeout(Duration val) {
+        this.heartbeatTimeoutOverride = val;
         return this;
     }
 
@@ -452,8 +478,8 @@ public class HighAvailabilityManagerImpl implements HighAvailabilityManager {
             }
         };
         
-        LOG.debug("Registering poll task for "+this+", period "+pollPeriod);
-        if (pollPeriod==null || pollPeriod.equals(Duration.PRACTICALLY_FOREVER)) {
+        LOG.debug("Registering poll task for "+this+", period "+getPollPeriod());
+        if (getPollPeriod().equals(Duration.PRACTICALLY_FOREVER)) {
             // don't schedule - used for tests
             // (scheduling fires off one initial task in the background before the delay, 
             // which affects tests that want to know exactly when publishing happens;
@@ -461,7 +487,7 @@ public class HighAvailabilityManagerImpl implements HighAvailabilityManager {
         } else {
             if (pollingTask!=null) pollingTask.cancel(true);
             
-            ScheduledTask task = new ScheduledTask(MutableMap.of("period", pollPeriod, "displayName", "scheduled:[HA poller task]"), taskFactory);
+            ScheduledTask task = new ScheduledTask(MutableMap.of("period", getPollPeriod(), "displayName", "scheduled:[HA poller task]"), taskFactory);
             pollingTask = managementContext.getExecutionManager().submit(task);
         }
     }
@@ -534,7 +560,7 @@ public class HighAvailabilityManagerImpl implements HighAvailabilityManager {
         Long timestampMaster = masterNode.getRemoteTimestamp();
         Long timestampMe = meNode.getRemoteTimestamp();
         if (timestampMaster==null || timestampMe==null) return false;
-        return (timestampMe - timestampMaster) <= heartbeatTimeout.toMilliseconds();
+        return (timestampMe - timestampMaster) <= getHeartbeatTimeout().toMilliseconds();
     }
     
     protected ManagementNodeSyncRecord hasHealthyMaster() {
@@ -615,7 +641,7 @@ public class HighAvailabilityManagerImpl implements HighAvailabilityManager {
         }
         
         // Need to choose a new master
-        newMasterNodeRecord = masterChooser.choose(memento, heartbeatTimeout, ownNodeId);
+        newMasterNodeRecord = masterChooser.choose(memento, getHeartbeatTimeout(), ownNodeId);
         
         String newMasterNodeId = (newMasterNodeRecord == null) ? null : newMasterNodeRecord.getNodeId();
         URI newMasterNodeUri = (newMasterNodeRecord == null) ? null : newMasterNodeRecord.getUri();
@@ -628,7 +654,7 @@ public class HighAvailabilityManagerImpl implements HighAvailabilityManager {
                     (currMasterNodeRecord == null ? currMasterNodeId+" (no memento)": currMasterNodeRecord.toVerboseString()),
                     memento,
                     ownNodeRecord.toVerboseString(), 
-                    heartbeatTimeout
+                    getHeartbeatTimeout()
                 });
         }
         if (!initializing) {


[11/21] incubator-brooklyn git commit: a few more goodies, coercing strings and maps to location and other fixes

Posted by he...@apache.org.
a few more goodies, coercing strings and maps to location and other fixes

where new Object() was used in persistence, it would not serialize nicely, and also it would not deserialize to the same (NONE instance) value; fix the logic for BasicParameterType defaults including null, and then when testing discovered problems if we passed Strings in, so fix that


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

Branch: refs/heads/master
Commit: f90faf4b1a9838b726d1ccec76fc2acb2304df47
Parents: b9c1b6f
Author: Alex Heneveld <al...@cloudsoftcorp.com>
Authored: Thu Nov 13 04:50:23 2014 +0000
Committer: Alex Heneveld <al...@cloudsoftcorp.com>
Committed: Thu Nov 13 23:38:54 2014 +0000

----------------------------------------------------------------------
 .../brooklyn/location/LocationRegistry.java     |  4 +-
 .../entity/basic/BasicParameterType.java        | 22 ++++++---
 .../java/brooklyn/location/basic/Locations.java | 48 ++++++++++++++++++++
 .../software/MachineLifecycleEffectorTasks.java |  3 +-
 4 files changed, 69 insertions(+), 8 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/f90faf4b/api/src/main/java/brooklyn/location/LocationRegistry.java
----------------------------------------------------------------------
diff --git a/api/src/main/java/brooklyn/location/LocationRegistry.java b/api/src/main/java/brooklyn/location/LocationRegistry.java
index eea5ce4..cc2c251 100644
--- a/api/src/main/java/brooklyn/location/LocationRegistry.java
+++ b/api/src/main/java/brooklyn/location/LocationRegistry.java
@@ -22,6 +22,8 @@ import java.util.List;
 import java.util.Map;
 import java.util.NoSuchElementException;
 
+import javax.annotation.Nullable;
+
 import brooklyn.util.guava.Maybe;
 
 import com.google.common.annotations.Beta;
@@ -98,7 +100,7 @@ public interface LocationRegistry {
     
     /** As {@link #resolve(String, Boolean, Map)}, but unwrapped
      * @throws NoSuchElementException if the spec cannot be resolved */
-    public Location resolve(String spec, Map locationFlags);
+    public Location resolve(String spec, @Nullable Map locationFlags);
     
     /** as {@link #resolve(String)} but returning null (never throwing)
      * @deprecated since 0.7.0 use {@link #resolve(String, Boolean, Map)} */

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/f90faf4b/core/src/main/java/brooklyn/entity/basic/BasicParameterType.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/entity/basic/BasicParameterType.java b/core/src/main/java/brooklyn/entity/basic/BasicParameterType.java
index 81c5d24..76846f5 100644
--- a/core/src/main/java/brooklyn/entity/basic/BasicParameterType.java
+++ b/core/src/main/java/brooklyn/entity/basic/BasicParameterType.java
@@ -31,12 +31,14 @@ public class BasicParameterType<T> implements ParameterType<T> {
     private String name;
     private Class<T> type;
     private String description;
-    private T defaultValue = (T) NONE;
+    private Boolean hasDefaultValue = null;
+    private T defaultValue = null;
 
     public BasicParameterType() {
         this(Collections.emptyMap());
     }
     
+    @SuppressWarnings("unchecked")
     public BasicParameterType(Map<?, ?> arguments) {
         if (arguments.containsKey("name")) name = (String) arguments.get("name");
         if (arguments.containsKey("type")) type = (Class<T>) arguments.get("type");
@@ -45,22 +47,29 @@ public class BasicParameterType<T> implements ParameterType<T> {
     }
 
     public BasicParameterType(String name, Class<T> type) {
-        this(name, type, null, (T) NONE);
+        this(name, type, null, null, false);
     }
     
     public BasicParameterType(String name, Class<T> type, String description) {
-        this(name, type, description, (T) NONE);
+        this(name, type, description, null, false);
     }
     
     public BasicParameterType(String name, Class<T> type, String description, T defaultValue) {
+        this(name, type, description, defaultValue, true);
+    }
+    
+    public BasicParameterType(String name, Class<T> type, String description, T defaultValue, boolean hasDefaultValue) {
         this.name = name;
         this.type = type;
         this.description = description;
         this.defaultValue = defaultValue;
+        if (defaultValue!=null && !defaultValue.getClass().equals(Object.class)) {
+            // if default value is null (or is an Object, which is ambiguous on resolution to to rebind), 
+            // don't bother to set this as it creates noise in the persistence files
+            this.hasDefaultValue = hasDefaultValue;
+        }
     }
 
-    private static Object NONE = new Object();
-    
     @Override
     public String getName() { return name; }
 
@@ -79,7 +88,8 @@ public class BasicParameterType<T> implements ParameterType<T> {
     }
 
     public boolean hasDefaultValue() {
-        return defaultValue != NONE;
+        // a new Object() was previously used to indicate no default value, but that doesn't work well across serialization boundaries!
+        return hasDefaultValue!=null ? hasDefaultValue : defaultValue!=null && !defaultValue.getClass().equals(Object.class);
     }
     
     @Override

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/f90faf4b/core/src/main/java/brooklyn/location/basic/Locations.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/location/basic/Locations.java b/core/src/main/java/brooklyn/location/basic/Locations.java
index a096168..c74db74 100644
--- a/core/src/main/java/brooklyn/location/basic/Locations.java
+++ b/core/src/main/java/brooklyn/location/basic/Locations.java
@@ -20,7 +20,9 @@ package brooklyn.location.basic;
 
 import java.io.Serializable;
 import java.util.Collection;
+import java.util.Collections;
 import java.util.List;
+import java.util.Map;
 
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -32,7 +34,9 @@ import brooklyn.location.LocationSpec;
 import brooklyn.location.MachineLocation;
 import brooklyn.management.LocationManager;
 import brooklyn.management.ManagementContext;
+import brooklyn.util.collections.MutableList;
 import brooklyn.util.guava.Maybe;
+import brooklyn.util.yaml.Yamls;
 
 import com.google.common.collect.ImmutableList;
 
@@ -105,4 +109,48 @@ public class Locations {
             managementContext.getLocationManager().manage(loc);
         }
     }
+
+    public static Location coerce(ManagementContext mgmt, Object rawO) {
+        if (rawO instanceof Location)
+            return (Location)rawO;
+        
+        Object raw = rawO;
+        if (raw instanceof String)
+            raw = Yamls.parseAll((String)raw).iterator().next();
+        
+        String name;
+        Map<?, ?> flags = null;
+        if (raw instanceof Map) {
+            // for yaml, take the key, and merge with locationFlags
+            Map<?,?> tm = ((Map<?,?>)raw);
+            if (tm.size()!=1) {
+                throw new IllegalArgumentException("Location "+rawO+" is invalid; maps must have only one key, being the location spec string");
+            }
+            name = (String) tm.keySet().iterator().next();
+            flags = (Map<?, ?>) tm.values().iterator().next();
+            
+        } else if (raw instanceof String) {
+            name = (String)raw;
+            
+        } else {
+            throw new IllegalArgumentException("Location "+rawO+" is invalid; can only parse strings or maps");
+        }
+        return mgmt.getLocationRegistry().resolve(name, flags);
+    }
+    
+    public static Collection<? extends Location> coerceToCollection(ManagementContext mgmt, Object rawO) {
+        Object raw = rawO;
+        if (raw instanceof Collection) {
+            List<Location> result = MutableList.<Location>of();
+            for (Object o: (Collection<?>)raw)
+                result.add(coerce(mgmt, o));
+            return result;
+        }
+        if (raw instanceof String) {
+            raw = Yamls.parseAll((String)raw).iterator().next();
+            if (raw instanceof Collection)
+                return coerceToCollection(mgmt, raw);
+        }
+        return Collections.singletonList( coerce(mgmt, raw) );
+    }
 }

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/f90faf4b/software/base/src/main/java/brooklyn/entity/software/MachineLifecycleEffectorTasks.java
----------------------------------------------------------------------
diff --git a/software/base/src/main/java/brooklyn/entity/software/MachineLifecycleEffectorTasks.java b/software/base/src/main/java/brooklyn/entity/software/MachineLifecycleEffectorTasks.java
index 3aac2a7..65b0428 100644
--- a/software/base/src/main/java/brooklyn/entity/software/MachineLifecycleEffectorTasks.java
+++ b/software/base/src/main/java/brooklyn/entity/software/MachineLifecycleEffectorTasks.java
@@ -152,7 +152,8 @@ public abstract class MachineLifecycleEffectorTasks {
         return new EffectorBody<Void>() {
             @Override
             public Void call(ConfigBag parameters) {
-                Collection<? extends Location> locations = parameters.get(LOCATIONS);
+                Object locationsRaw = parameters.getStringKey(LOCATIONS.getName());
+                Collection<? extends Location> locations = Locations.coerceToCollection(entity().getManagementContext(), locationsRaw);
                 if (locations==null) {
                     // null/empty will mean to inherit from parent
                     locations = Collections.emptyList();


[12/21] incubator-brooklyn git commit: fail fast if invalid mode given via REST API

Posted by he...@apache.org.
fail fast if invalid mode given via REST API


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

Branch: refs/heads/master
Commit: 9edd58db490863e3e34758b31a19e3cb1ed8deae
Parents: ed80567
Author: Alex Heneveld <al...@cloudsoftcorp.com>
Authored: Thu Nov 13 17:44:00 2014 +0000
Committer: Alex Heneveld <al...@cloudsoftcorp.com>
Committed: Thu Nov 13 23:38:55 2014 +0000

----------------------------------------------------------------------
 .../src/main/java/brooklyn/rest/resources/ServerResource.java     | 3 +++
 1 file changed, 3 insertions(+)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/9edd58db/usage/rest-server/src/main/java/brooklyn/rest/resources/ServerResource.java
----------------------------------------------------------------------
diff --git a/usage/rest-server/src/main/java/brooklyn/rest/resources/ServerResource.java b/usage/rest-server/src/main/java/brooklyn/rest/resources/ServerResource.java
index ceb688c..b96c1b4 100644
--- a/usage/rest-server/src/main/java/brooklyn/rest/resources/ServerResource.java
+++ b/usage/rest-server/src/main/java/brooklyn/rest/resources/ServerResource.java
@@ -249,6 +249,9 @@ public class ServerResource extends AbstractBrooklynRestResource implements Serv
 
     @Override
     public ManagementNodeState setHighAvailabilityNodeState(HighAvailabilityMode mode) {
+        if (mode==null)
+            throw new IllegalStateException("Missing parameter: mode");
+        
         HighAvailabilityManager haMgr = mgmt().getHighAvailabilityManager();
         ManagementNodeState existingState = haMgr.getNodeState();
         haMgr.changeMode(mode);


[14/21] incubator-brooklyn git commit: final fixes from code review of #314, hot_backup

Posted by he...@apache.org.
final fixes from code review of #314, hot_backup

fixes state transitions (master -> hot_backup etc) and some better logging around catalog


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

Branch: refs/heads/master
Commit: 24a3c3417f0ad2c60d3acc2328b6a437d2dfdacf
Parents: 5060c4f
Author: Alex Heneveld <al...@cloudsoftcorp.com>
Authored: Thu Nov 13 23:35:15 2014 +0000
Committer: Alex Heneveld <al...@cloudsoftcorp.com>
Committed: Thu Nov 13 23:38:55 2014 +0000

----------------------------------------------------------------------
 .../management/ha/ManagementNodeState.java      | 10 ++++
 .../catalog/internal/CatalogClasspathDo.java    |  2 +-
 .../entity/rebind/RebindManagerImpl.java        |  4 +-
 .../ha/HighAvailabilityManagerImpl.java         | 51 ++++++++++++--------
 .../internal/AbstractManagementContext.java     | 18 +++++--
 5 files changed, 58 insertions(+), 27 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/24a3c341/api/src/main/java/brooklyn/management/ha/ManagementNodeState.java
----------------------------------------------------------------------
diff --git a/api/src/main/java/brooklyn/management/ha/ManagementNodeState.java b/api/src/main/java/brooklyn/management/ha/ManagementNodeState.java
index 4587b57..d7608bd 100644
--- a/api/src/main/java/brooklyn/management/ha/ManagementNodeState.java
+++ b/api/src/main/java/brooklyn/management/ha/ManagementNodeState.java
@@ -61,4 +61,14 @@ public enum ManagementNodeState {
         // above should be exhaustive
         return Maybe.absent("Requested "+HighAvailabilityMode.class+" mode "+startMode+" was not expected");
     }
+
+    /** true for hot non-master modes, where we are proxying the data from the persistent store */
+    public static boolean isHotProxy(ManagementNodeState state) {
+        return state==HOT_BACKUP || state==HOT_STANDBY;
+    }
+
+    /** true for non-master modes which can be promoted to master */
+    public static boolean isStandby(ManagementNodeState state) {
+        return state==ManagementNodeState.STANDBY || state==ManagementNodeState.HOT_STANDBY;
+    }
 }

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/24a3c341/core/src/main/java/brooklyn/catalog/internal/CatalogClasspathDo.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/catalog/internal/CatalogClasspathDo.java b/core/src/main/java/brooklyn/catalog/internal/CatalogClasspathDo.java
index f6569d7..6ec073f 100644
--- a/core/src/main/java/brooklyn/catalog/internal/CatalogClasspathDo.java
+++ b/core/src/main/java/brooklyn/catalog/internal/CatalogClasspathDo.java
@@ -149,7 +149,7 @@ public class CatalogClasspathDo {
                     baseCP = ((ManagementContextInternal)catalog.mgmt).getBaseClassPathForScanning();
                     scanner = new ReflectionScanner(baseCP, prefix, baseCL, catalog.getRootClassLoader());
                 } catch (Exception e) {
-                    log.info("Catalog scan is empty, and unable to use java.class.path (base classpath is "+baseCP+")");
+                    log.info("Catalog scan is empty, and unable to use java.class.path (base classpath is "+baseCP+"): "+e);
                     Exceptions.propagateIfFatal(e);
                 }
             }

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/24a3c341/core/src/main/java/brooklyn/entity/rebind/RebindManagerImpl.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/entity/rebind/RebindManagerImpl.java b/core/src/main/java/brooklyn/entity/rebind/RebindManagerImpl.java
index 9dad632..ed59bcc 100644
--- a/core/src/main/java/brooklyn/entity/rebind/RebindManagerImpl.java
+++ b/core/src/main/java/brooklyn/entity/rebind/RebindManagerImpl.java
@@ -307,7 +307,7 @@ public class RebindManagerImpl implements RebindManager {
     @SuppressWarnings("unchecked")
     @Override
     public void startReadOnly(final ManagementNodeState mode) {
-        if (mode!=ManagementNodeState.HOT_STANDBY && mode!=ManagementNodeState.HOT_BACKUP) {
+        if (!ManagementNodeState.isHotProxy(mode)) {
             throw new IllegalStateException("Read-only rebind thread only permitted for hot proxy modes; not "+mode);
         }
         
@@ -1065,6 +1065,7 @@ public class RebindManagerImpl implements RebindManager {
             while (ptr != null) {
                 CatalogItem<?, ?> catalogItem = catalog.getCatalogItem(ptr.getType(), BrooklynCatalog.DEFAULT_VERSION);
                 if (catalogItem != null) {
+                    LOG.debug("Inferred catalog item ID "+catalogItem.getId()+" for "+entityManifest+" from ancestor "+ptr);
                     return catalogItem.getId();
                 }
                 if (ptr.getParent() != null) {
@@ -1086,6 +1087,7 @@ public class RebindManagerImpl implements RebindManager {
                 BrooklynClassLoadingContext loader = CatalogUtils.newClassLoadingContext(managementContext, item);
                 boolean canLoadClass = loader.tryLoadClass(entityManifest.getType()).isPresent();
                 if (canLoadClass) {
+                    LOG.warn("Missing catalog item for "+entityManifest.getId()+", inferring as "+item.getId()+" because that is able to load the item");
                     return item.getId();
                 }
             }

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/24a3c341/core/src/main/java/brooklyn/management/ha/HighAvailabilityManagerImpl.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/management/ha/HighAvailabilityManagerImpl.java b/core/src/main/java/brooklyn/management/ha/HighAvailabilityManagerImpl.java
index 08c1d06..e44f30e 100644
--- a/core/src/main/java/brooklyn/management/ha/HighAvailabilityManagerImpl.java
+++ b/core/src/main/java/brooklyn/management/ha/HighAvailabilityManagerImpl.java
@@ -295,6 +295,8 @@ public class HighAvailabilityManagerImpl implements HighAvailabilityManager {
             }
         }
         
+        ManagementNodeState oldState = getInternalNodeState();
+        
         // now do election
         switch (startMode) {
         case AUTO:
@@ -336,22 +338,28 @@ public class HighAvailabilityManagerImpl implements HighAvailabilityManager {
             }
             break;
         case HOT_BACKUP:
+            setInternalNodeState(ManagementNodeState.HOT_BACKUP);
+            // then continue into next block
         case STANDBY:
         case HOT_STANDBY:
-            if (getInternalNodeState()==ManagementNodeState.STANDBY || getInternalNodeState()==ManagementNodeState.HOT_STANDBY) {
-                if (!preventElectionOnExplicitStandbyMode)
+            if (startMode!=HighAvailabilityMode.HOT_BACKUP) {
+                if (ManagementNodeState.isHotProxy(getInternalNodeState()) && startMode==HighAvailabilityMode.HOT_STANDBY) {
+                    // if was hot_backup, we can immediately go hot_standby
+                    setInternalNodeState(ManagementNodeState.HOT_STANDBY);
+                } else {
+                    // from any other state, set standby, then perhaps switch to hot_standby later on (or might become master in the next block)
+                    setInternalNodeState(ManagementNodeState.STANDBY);
+                }
+            }
+            if (ManagementNodeState.isStandby(getInternalNodeState())) {
+                if (!preventElectionOnExplicitStandbyMode) {
                     publishAndCheck(true);
+                }
                 if (failOnExplicitStandbyModeIfNoMaster && existingMaster==null) {
                     LOG.error("Management node "+ownNodeId+" detected no master when "+startMode+" requested and existing master required; failing.");
                     throw new IllegalStateException("No existing master; cannot start as "+startMode);
                 }
             }
-            if (startMode==HighAvailabilityMode.HOT_BACKUP) {
-                setInternalNodeState(ManagementNodeState.HOT_BACKUP);
-            } else {
-                setInternalNodeState(ManagementNodeState.STANDBY);
-                // might jump to hot_standby next
-            }
             String message = "Management node "+ownNodeId+" running as HA "+getNodeState()+" (";
             if (getNodeState().toString().equals(startMode.toString()))
                 message += "explicitly requested";
@@ -391,11 +399,13 @@ public class HighAvailabilityManagerImpl implements HighAvailabilityManager {
             }
         }
         if ((getInternalNodeState()==ManagementNodeState.STANDBY && startMode==HighAvailabilityMode.HOT_STANDBY) || 
-                (startMode==HighAvailabilityMode.HOT_BACKUP)) {
+                (startMode==HighAvailabilityMode.HOT_BACKUP && !ManagementNodeState.isHotProxy(oldState))) {
+            // now transition to hot proxy
             nodeStateTransitionComplete = false;
             if (startMode==HighAvailabilityMode.HOT_STANDBY) {
-                // if it should be hot standby, then we need to promote
-                // inform the world that we are transitioning (not eligible for promotion while going in to hot standby)
+                // if it should be hot standby, then we may need to promote
+                // inform the world that we are transitioning (but not eligible for promotion while going in to hot standby)
+                // (no harm in doing this twice)
                 publishHealth();
             }
             try {
@@ -465,6 +475,7 @@ public class HighAvailabilityManagerImpl implements HighAvailabilityManager {
     }
     
     protected void setInternalNodeState(ManagementNodeState newState) {
+        ManagementNodeState oldState = getInternalNodeState();
         synchronized (nodeStateHistory) {
             if (this.nodeState != newState) {
                 nodeStateHistory.add(0, MutableMap.<String,Object>of("state", newState, "timestamp", currentTimeMillis()));
@@ -472,9 +483,15 @@ public class HighAvailabilityManagerImpl implements HighAvailabilityManager {
                     nodeStateHistory.remove(nodeStateHistory.size()-1);
                 }
             }
-            
             this.nodeState = newState;
         }
+        
+        if (ManagementNodeState.isHotProxy(oldState) && !ManagementNodeState.isHotProxy(newState)) {
+            // could perhaps promote standby items on some transitions; but for now we stop the old read-only and re-load them
+            // TODO ideally there'd be an incremental rebind as well as an incremental persist
+            managementContext.getRebindManager().stopReadOnly();
+            clearManagedItems(ManagementTransitionMode.REBINDING_DESTROYED);
+        }
     }
 
     @SuppressWarnings("deprecation")
@@ -719,8 +736,9 @@ public class HighAvailabilityManagerImpl implements HighAvailabilityManager {
         if (!initializing) {
             String message = "Management node "+ownNodeId+" detected ";
             if (weAreNewMaster) message += "we should be master, changing from ";
+            else if (currMasterNodeRecord==null && newMasterNodeId==null) message += "master change attempted but no candidates ";
             else message += "master change, from ";
-            message +=currMasterNodeId + " (" + (currMasterNodeRecord==null ? "?" : timestampString(currMasterNodeRecord.getRemoteTimestamp())) + ")"
+            message +=currMasterNodeId + "(" + (currMasterNodeRecord==null ? "<none>" : timestampString(currMasterNodeRecord.getRemoteTimestamp())) + ")"
                 + " to "
                 + (newMasterNodeId == null ? "<none>" :
                     (weAreNewMaster ? "us " : "")
@@ -754,16 +772,9 @@ public class HighAvailabilityManagerImpl implements HighAvailabilityManager {
                 LOG.warn("Problem in promption-listener (continuing)", e);
             }
         }
-        boolean wasHot = (getInternalNodeState()==ManagementNodeState.HOT_STANDBY || getInternalNodeState()==ManagementNodeState.HOT_BACKUP);
         setInternalNodeState(ManagementNodeState.MASTER);
         publishPromotionToMaster();
         try {
-            if (wasHot) {
-                // could just promote the standby items; but for now we stop the old read-only and re-load them, to make sure nothing has been missed
-                // TODO ideally there'd be an incremental rebind as well as an incremental persist
-                managementContext.getRebindManager().stopReadOnly();
-                clearManagedItems(ManagementTransitionMode.REBINDING_DESTROYED);
-            }
             managementContext.getRebindManager().rebind(managementContext.getCatalog().getRootClassLoader(), null, getInternalNodeState());
         } catch (Exception e) {
             LOG.error("Management node enountered problem during rebind when promoting self to master; demoting to FAILED and rethrowing: "+e);

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/24a3c341/core/src/main/java/brooklyn/management/internal/AbstractManagementContext.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/management/internal/AbstractManagementContext.java b/core/src/main/java/brooklyn/management/internal/AbstractManagementContext.java
index 944dac4..e35b708 100644
--- a/core/src/main/java/brooklyn/management/internal/AbstractManagementContext.java
+++ b/core/src/main/java/brooklyn/management/internal/AbstractManagementContext.java
@@ -76,6 +76,7 @@ import brooklyn.util.guava.Maybe;
 import brooklyn.util.task.BasicExecutionContext;
 import brooklyn.util.task.Tasks;
 
+import com.google.api.client.repackaged.com.google.common.base.Objects;
 import com.google.common.base.Function;
 
 public abstract class AbstractManagementContext implements ManagementContextInternal {
@@ -382,14 +383,21 @@ public abstract class AbstractManagementContext implements ManagementContextInte
      * is scanning the default classpath.  Usually it infers the right thing, but some classloaders
      * (e.g. surefire) do funny things which the underlying org.reflections.Reflectiosn library can't see in to. 
      * <p>
-     * Only settable once, before catalog is loaded.
+     * This should normally be invoked early in the server startup.  Setting it after the catalog is loaded will not
+     * take effect without an explicit internal call to do so.  Once set, it can be changed prior to catalog loading
+     * but it cannot be <i>changed</i> once the catalog is loaded.
      * <p>
-     * ClasspathHelper.forJavaClassPath() is often a good argument to pass. */
+     * ClasspathHelper.forJavaClassPath() is often a good argument to pass, and is used internally in some places
+     * when no items are found on the catalog. */
     @Override
     public void setBaseClassPathForScanning(Iterable<URL> urls) {
-        if (baseClassPathForScanning == urls) return;
-        if (baseClassPathForScanning != null) throw new IllegalStateException("Cannot change base class path for scanning (in "+this+")");
-        if (catalog != null) throw new IllegalStateException("Cannot set base class path for scanning after catalog has been loaded (in "+this+")");
+        if (Objects.equal(baseClassPathForScanning, urls)) return;
+        if (baseClassPathForScanning != null) {
+            if (catalog==null)
+                log.warn("Changing scan classpath to "+urls+" from "+baseClassPathForScanning);
+            else
+                throw new IllegalStateException("Cannot change base class path for scanning (in "+this+")");
+        }
         this.baseClassPathForScanning = urls;
     }
     /**