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 2015/05/29 12:36:05 UTC

[05/13] incubator-brooklyn git commit: more code review/cleanup for catalog CLI

more code review/cleanup for catalog CLI

especially around the official/final/unofficial initialization interplay with HA modes,
so that after a node has been promoted to master, on subsequent promotions it doesn't re-apply the initializations


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

Branch: refs/heads/master
Commit: df21c7116adb94986c4cb2c8e12ec6b86d580048
Parents: 9cc7293
Author: Alex Heneveld <al...@cloudsoftcorp.com>
Authored: Mon May 25 11:24:55 2015 +0100
Committer: Alex Heneveld <al...@cloudsoftcorp.com>
Committed: Mon May 25 11:44:09 2015 +0100

----------------------------------------------------------------------
 .../catalog/internal/CatalogInitialization.java | 127 ++++++++++++++-----
 .../internal/CatalogItemDtoAbstract.java        |   4 +-
 .../rebind/PeriodicDeltaChangeListener.java     |  38 +++---
 .../entity/rebind/RebindContextImpl.java        |   6 +-
 .../brooklyn/entity/rebind/RebindIteration.java |  51 +++++---
 .../entity/rebind/RebindManagerImpl.java        |   2 +-
 .../ha/HighAvailabilityManagerImpl.java         |  57 +++++----
 .../internal/AbstractManagementContext.java     |   4 +-
 usage/cli/src/main/java/brooklyn/cli/Main.java  |   4 +
 .../java/brooklyn/cli/lister/ClassFinder.java   |   2 +-
 .../brooklyn/launcher/BrooklynLauncher.java     |  13 +-
 .../rest/filter/HaHotCheckResourceFilter.java   |   2 +-
 .../rest/filter/HaMasterCheckFilter.java        |   2 +-
 .../util/javalang/AggregateClassLoader.java     |   2 +-
 14 files changed, 212 insertions(+), 102 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/df21c711/core/src/main/java/brooklyn/catalog/internal/CatalogInitialization.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/catalog/internal/CatalogInitialization.java b/core/src/main/java/brooklyn/catalog/internal/CatalogInitialization.java
index 6f0b80e..8f45a8c 100644
--- a/core/src/main/java/brooklyn/catalog/internal/CatalogInitialization.java
+++ b/core/src/main/java/brooklyn/catalog/internal/CatalogInitialization.java
@@ -29,6 +29,7 @@ import brooklyn.catalog.CatalogItem;
 import brooklyn.config.BrooklynServerConfig;
 import brooklyn.management.ManagementContext;
 import brooklyn.management.ManagementContextInjectable;
+import brooklyn.management.ha.ManagementNodeState;
 import brooklyn.management.internal.ManagementContextInternal;
 import brooklyn.util.ResourceUtils;
 import brooklyn.util.collections.MutableList;
@@ -37,6 +38,7 @@ import brooklyn.util.exceptions.FatalRuntimeException;
 import brooklyn.util.exceptions.RuntimeInterruptedException;
 import brooklyn.util.flags.TypeCoercions;
 import brooklyn.util.guava.Maybe;
+import brooklyn.util.javalang.JavaClassNames;
 import brooklyn.util.os.Os;
 import brooklyn.util.text.Strings;
 
@@ -78,7 +80,15 @@ public class CatalogInitialization implements ManagementContextInjectable {
 
     private boolean disallowLocal = false;
     private List<Function<CatalogInitialization, Void>> callbacks = MutableList.of();
-    private boolean hasRunBestEffort = false, hasRunOfficial = false, isPopulating = false;
+    private boolean 
+        /** has run an unofficial initialization (i.e. an early load, triggered by an early read of the catalog) */
+        hasRunUnofficialInitialization = false, 
+        /** has run an official initialization, but it is not a permanent one (e.g. during a hot standby mode, or a run failed) */
+        hasRunTransientOfficialInitialization = false, 
+        /** has run an official initialization which is permanent (node is master, and the new catalog is now set) */
+        hasRunFinalInitialization = false;
+    /** is running a populate method; used to prevent recursive loops */
+    private boolean isPopulating = false;
     
     private ManagementContext managementContext;
     private boolean isStartingUp = false;
@@ -97,6 +107,7 @@ public class CatalogInitialization implements ManagementContextInjectable {
         this(null, false, null, false);
     }
 
+    @Override
     public void injectManagementContext(ManagementContext managementContext) {
         Preconditions.checkNotNull(managementContext, "management context");
         if (this.managementContext!=null && managementContext!=this.managementContext)
@@ -127,56 +138,108 @@ public class CatalogInitialization implements ManagementContextInjectable {
         return reset;
     }
 
-    public boolean hasRunOfficial() { return hasRunOfficial; }
-    public boolean hasRunIncludingBestEffort() { return hasRunOfficial || hasRunBestEffort; }
-
-    /** makes or updates the mgmt catalog, based on the settings in this class */
-    public void populateCatalog(boolean needsInitial, Collection<CatalogItem<?, ?>> optionalItemsForResettingCatalog) {
+    /** Returns true if the canonical initialization has completed, 
+     * that is, an initialization which is done when a node is rebinded as master
+     * (or an initialization done by the startup routines when not running persistence);
+     * see also {@link #hasRunAnyInitialization()}. */
+    public boolean hasRunFinalInitialization() { return hasRunFinalInitialization; }
+    /** Returns true if an official initialization has run,
+     * even if it was a transient run, e.g. so that the launch sequence can tell whether rebind has triggered initialization */
+    public boolean hasRunOfficialInitialization() { return hasRunFinalInitialization || hasRunTransientOfficialInitialization; }
+    /** Returns true if the initializer has run at all,
+     * including transient initializations which might be needed before a canonical becoming-master rebind,
+     * for instance because the catalog is being accessed before loading rebind information
+     * (done by {@link #populateUnofficial(BasicBrooklynCatalog)}) */
+    public boolean hasRunAnyInitialization() { return hasRunFinalInitialization || hasRunTransientOfficialInitialization || hasRunUnofficialInitialization; }
+
+    /** makes or updates the mgmt catalog, based on the settings in this class 
+     * @param nodeState the management node for which this is being read; if master, then we expect this run to be the last one,
+     *   and so subsequent applications should ignore any initialization data (e.g. on a subsequent promotion to master, 
+     *   after a master -> standby -> master cycle)
+     * @param needsInitialItemsLoaded whether the catalog needs the initial items loaded
+     * @param needsInitialItemsLoaded whether the catalog needs the additiona items loaded
+     * @param optionalExcplicitItemsForResettingCatalog
+     *   if supplied, the catalog is reset to contain only these items, before calling any other initialization
+     *   for use primarily when rebinding
+     */
+    public void populateCatalog(ManagementNodeState nodeState, boolean needsInitialItemsLoaded, boolean needsAdditionsLoaded, Collection<CatalogItem<?, ?>> optionalExcplicitItemsForResettingCatalog) {
+        if (log.isDebugEnabled()) {
+            String message = "Populating catalog for "+nodeState+", needsInitial="+needsInitialItemsLoaded+", needsAdditional="+needsAdditionsLoaded+", explicitItems="+(optionalExcplicitItemsForResettingCatalog==null ? "null" : optionalExcplicitItemsForResettingCatalog.size())+"; from "+JavaClassNames.callerNiceClassAndMethod(1);
+            if (!ManagementNodeState.isHotProxy(nodeState)) {
+                log.debug(message);
+            } else {
+                // in hot modes, make this message trace so we don't get too much output then
+                log.trace(message);
+            }
+        }
         synchronized (populatingCatalogMutex) {
             try {
+                if (hasRunFinalInitialization() && (needsInitialItemsLoaded || needsAdditionsLoaded)) {
+                    // if we have already run "final" then we should only ever be used to reset the catalog, 
+                    // not to initialize or add; e.g. we are being given a fixed list on a subsequent master rebind after the initial master rebind 
+                    log.warn("Catalog initialization called to populate initial, even though it has already run the final official initialization");
+                }
                 isPopulating = true;
                 BasicBrooklynCatalog catalog = (BasicBrooklynCatalog) managementContext.getCatalog();
                 if (!catalog.getCatalog().isLoaded()) {
                     catalog.load();
                 } else {
-                    if (needsInitial && (hasRunOfficial || hasRunBestEffort)) {
+                    if (needsInitialItemsLoaded && hasRunAnyInitialization()) {
                         // an indication that something caused it to load early; not severe, but unusual
-                        log.warn("Catalog initialization has not properly run but management context has a catalog; re-populating, possibly overwriting items installed during earlier access (it may have been an early web request)");
+                        if (hasRunTransientOfficialInitialization) {
+                            log.debug("Catalog initialization now populating, but has noted a previous official run which was not final (probalby loaded while in a standby mode, or a previous run failed); overwriting any items installed earlier");
+                        } else {
+                            log.warn("Catalog initialization now populating, but has noted a previous unofficial run (it may have been an early web request); overwriting any items installed earlier");
+                        }
                         catalog.reset(ImmutableList.<CatalogItem<?,?>>of());
                     }
                 }
-                hasRunOfficial = true;
 
-                populateCatalogImpl(catalog, needsInitial, optionalItemsForResettingCatalog);
+                populateCatalogImpl(catalog, needsInitialItemsLoaded, needsAdditionsLoaded, optionalExcplicitItemsForResettingCatalog);
+                if (nodeState == ManagementNodeState.MASTER) {
+                    // TODO ideally this would remain false until it has *persisted* the changed catalog;
+                    // if there is a subsequent startup failure the forced additions will not be persisted,
+                    // but nor will they be loaded on a subsequent run.
+                    // callers will have to restart a brooklyn, or reach into this class to change this field,
+                    // or (recommended) manually adjust the catalog.
+                    // TODO also, if a node comes up in standby, the addition might not take effector for a while
+                    //
+                    // however since these options are mainly for use on the very first brooklyn run, it's not such a big deal; 
+                    // once up and running the typical way to add items is via the REST API
+                    hasRunFinalInitialization = true;
+                }
             } finally {
-                hasRunOfficial = true;
+                if (!hasRunFinalInitialization) {
+                    hasRunTransientOfficialInitialization = true;
+                }
                 isPopulating = false;
             }
         }
     }
 
-    private void populateCatalogImpl(BasicBrooklynCatalog catalog, boolean needsInitial, Collection<CatalogItem<?, ?>> optionalItemsForResettingCatalog) {
+    private void populateCatalogImpl(BasicBrooklynCatalog catalog, boolean needsInitialItemsLoaded, boolean needsAdditionsLoaded, Collection<CatalogItem<?, ?>> optionalItemsForResettingCatalog) {
         applyCatalogLoadMode();
         
         if (optionalItemsForResettingCatalog!=null) {
             catalog.reset(optionalItemsForResettingCatalog);
         }
         
-        if (needsInitial) {
+        if (needsInitialItemsLoaded) {
             populateInitial(catalog);
         }
-        
-        populateAdditions(catalog);
 
-        populateViaCallbacks(catalog);
+        if (needsAdditionsLoaded) {
+            populateAdditions(catalog);
+            populateViaCallbacks(catalog);
+        }
     }
 
     private enum PopulateMode { YAML, XML, AUTODETECT }
     
     protected void populateInitial(BasicBrooklynCatalog catalog) {
         if (disallowLocal) {
-            if (!hasRunOfficial()) {
-                log.debug("CLI initial catalog not being read with disallow-local mode set.");
+            if (!hasRunFinalInitialization()) {
+                log.debug("CLI initial catalog not being read when local catalog load mode is disallowed.");
             }
             return;
         }
@@ -201,13 +264,13 @@ public class CatalogInitialization implements ManagementContextInjectable {
         
         catalogUrl = Os.mergePaths(BrooklynServerConfig.getMgmtBaseDir( managementContext.getConfig() ), "catalog.bom");
         if (new File(catalogUrl).exists()) {
-            populateInitialFromUri(catalog, "file:"+catalogUrl, PopulateMode.YAML);
+            populateInitialFromUri(catalog, new File(catalogUrl).toURI().toString(), PopulateMode.YAML);
             return;
         }
         
         catalogUrl = Os.mergePaths(BrooklynServerConfig.getMgmtBaseDir( managementContext.getConfig() ), "catalog.xml");
         if (new File(catalogUrl).exists()) {
-            populateInitialFromUri(catalog, "file:"+catalogUrl, PopulateMode.XML);
+            populateInitialFromUri(catalog, new File(catalogUrl).toURI().toString(), PopulateMode.XML);
             return;
         }
 
@@ -287,7 +350,7 @@ public class CatalogInitialization implements ManagementContextInjectable {
         if (Strings.isNonBlank(additionsUri)) {
             if (disallowLocal) {
                 if (!hasRunAdditions) {
-                    log.warn("CLI additions supplied but not supported in disallow-local mode; ignoring.");
+                    log.warn("CLI additions supplied but not supported when catalog load mode disallows local loads; ignoring.");
                 }
                 return;
             }   
@@ -340,21 +403,27 @@ public class CatalogInitialization implements ManagementContextInjectable {
         }
     }
 
-    /** makes the catalog, warning if persistence is on and hasn't run yet 
-     * (as the catalog will be subsequently replaced) */
-    public void populateBestEffort(BasicBrooklynCatalog catalog) {
+    /** Creates the catalog based on parameters set here, if not yet loaded,
+     * but ignoring persisted state and warning if persistence is on and we are starting up
+     * (because the official persistence is preferred and the catalog will be subsequently replaced);
+     * for use when the catalog is accessed before persistence is completed. 
+     * <p>
+     * This method is primarily used during testing, which in many cases does not enforce the full startup order
+     * and which wants a local catalog in any case. It may also be invoked if a client requests the catalog
+     * while the server is starting up. */
+    public void populateUnofficial(BasicBrooklynCatalog catalog) {
         synchronized (populatingCatalogMutex) {
-            if (hasRunOfficial || hasRunBestEffort || isPopulating) return;
-            // if a thread calls back in to this, ie calling to it from a getCatalog() call while populating,
-            // it will own the mutex and observe isRunningBestEffort, returning quickly 
+            // check isPopulating in case this method gets called from inside another populate call
+            if (hasRunAnyInitialization() || isPopulating) return;
+            log.debug("Populating catalog unofficially ("+catalog+")");
             isPopulating = true;
             try {
                 if (isStartingUp) {
                     log.warn("Catalog access requested when not yet initialized; populating best effort rather than through recommended pathway. Catalog data may be replaced subsequently.");
                 }
-                populateCatalogImpl(catalog, true, null);
+                populateCatalogImpl(catalog, true, true, null);
             } finally {
-                hasRunBestEffort = true;
+                hasRunUnofficialInitialization = true;
                 isPopulating = false;
             }
         }

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/df21c711/core/src/main/java/brooklyn/catalog/internal/CatalogItemDtoAbstract.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/catalog/internal/CatalogItemDtoAbstract.java b/core/src/main/java/brooklyn/catalog/internal/CatalogItemDtoAbstract.java
index c6ff97e..131c26e 100644
--- a/core/src/main/java/brooklyn/catalog/internal/CatalogItemDtoAbstract.java
+++ b/core/src/main/java/brooklyn/catalog/internal/CatalogItemDtoAbstract.java
@@ -184,7 +184,9 @@ public abstract class CatalogItemDtoAbstract<T, SpecT> extends AbstractBrooklynO
         if (!Objects.equal(displayName, other.displayName)) return false;
         if (!Objects.equal(iconUrl, other.iconUrl)) return false;
         if (!Objects.equal(tags, other.tags)) return false;
-        // 'type' not checked, because deprecated, we might want to allow removal in future
+        // 'type' not checked, because deprecated, 
+        // and in future we might want to allow it to be removed/blanked in some impls without affecting equality
+        // (in most cases it is the same as symbolicName so doesn't matter)
         return true;
     }
 

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/df21c711/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 f3861bc..4203b4b 100644
--- a/core/src/main/java/brooklyn/entity/rebind/PeriodicDeltaChangeListener.java
+++ b/core/src/main/java/brooklyn/entity/rebind/PeriodicDeltaChangeListener.java
@@ -166,9 +166,8 @@ public class PeriodicDeltaChangeListener implements ChangeListener {
         
     private DeltaCollector deltaCollector = new DeltaCollector();
 
-    private volatile boolean running = false;
-
-    private volatile boolean stopping = false, stopCompleted = false;
+    private enum ListenerState { INIT, RUNNING, STOPPING, STOPPED } 
+    private volatile ListenerState state = ListenerState.INIT;
 
     private volatile ScheduledTask scheduledTask;
 
@@ -197,18 +196,17 @@ public class PeriodicDeltaChangeListener implements ChangeListener {
     @SuppressWarnings("unchecked")
     public void start() {
         synchronized (startStopMutex) {
-            if (running || (scheduledTask!=null && !scheduledTask.isDone())) {
+            if (state==ListenerState.RUNNING || (scheduledTask!=null && !scheduledTask.isDone())) {
                 LOG.warn("Request to start "+this+" when already running - "+scheduledTask+"; ignoring");
                 return;
             }
-            stopCompleted = false;
-            running = true;
+            state = ListenerState.RUNNING;
 
             Callable<Task<?>> taskFactory = new Callable<Task<?>>() {
                 @Override public Task<Void> call() {
                     return Tasks.<Void>builder().dynamic(false).name("periodic-persister").body(new Callable<Void>() {
                         public Void call() {
-                            persistNowSafely(false);
+                            persistNowSafely();
                             return null;
                         }}).build();
                 }
@@ -224,9 +222,8 @@ public class PeriodicDeltaChangeListener implements ChangeListener {
     }
     void stop(Duration timeout, Duration graceTimeoutForSubsequentOperations) {
         synchronized (startStopMutex) {
-            running = false;
+            state = ListenerState.STOPPING;
             try {
-                stopping = true;
 
                 if (scheduledTask != null) {
                     CountdownTimer expiry = timeout.countdownTimer();
@@ -250,8 +247,7 @@ public class PeriodicDeltaChangeListener implements ChangeListener {
                     deltaCollector = new DeltaCollector();
                 }
             } finally {
-                stopCompleted = true;
-                stopping = false;
+                state = ListenerState.STOPPED;
             }
         }
     }
@@ -259,7 +255,7 @@ public class PeriodicDeltaChangeListener implements ChangeListener {
     /** Waits for any in-progress writes to be completed then for or any unwritten data to be written. */
     @VisibleForTesting
     public void waitForPendingComplete(Duration timeout, boolean canTrigger) throws InterruptedException, TimeoutException {
-        if (!isActive() && !stopping) return;
+        if (!isActive() && state != ListenerState.STOPPING) return;
         
         CountdownTimer timer = timeout.isPositive() ? CountdownTimer.newInstanceStarted(timeout) : CountdownTimer.newInstancePaused(Duration.PRACTICALLY_FOREVER);
         Integer targetWriteCount = null;
@@ -299,14 +295,16 @@ public class PeriodicDeltaChangeListener implements ChangeListener {
      * Even when not active, changes will still be tracked unless {@link #isStopped()}.
      */
     private boolean isActive() {
-        return running && persister != null && !isStopped();
+        return state == ListenerState.RUNNING && persister != null && !isStopped();
     }
 
     /**
-     * Whether we have been stopped, in which case will not persist or store anything.
+     * Whether we have been stopped, ie are stopping are or fully stopped,
+     * in which case will not persist or store anything
+     * (except for a final internal persistence called while STOPPING.) 
      */
     private boolean isStopped() {
-        return stopping || stopCompleted || executionContext.isShutdown();
+        return state == ListenerState.STOPPING || state == ListenerState.STOPPED || executionContext.isShutdown();
     }
     
     private void addReferencedObjects(DeltaCollector deltaCollector) {
@@ -336,7 +334,11 @@ public class PeriodicDeltaChangeListener implements ChangeListener {
     }
     
     @VisibleForTesting
-    public boolean persistNowSafely(boolean alreadyHasMutex) {
+    public boolean persistNowSafely() {
+        return persistNowSafely(false);
+    }
+    
+    private boolean persistNowSafely(boolean alreadyHasMutex) {
         Stopwatch timer = Stopwatch.createStarted();
         try {
             persistNowInternal(alreadyHasMutex);
@@ -364,12 +366,12 @@ public class PeriodicDeltaChangeListener implements ChangeListener {
     }
     
     protected void persistNowInternal(boolean alreadyHasMutex) {
-        if (!isActive() && !stopping) {
+        if (!isActive() && state != ListenerState.STOPPING) {
             return;
         }
         try {
             if (!alreadyHasMutex) persistingMutex.acquire();
-            if (!isActive() && !stopping) return;
+            if (!isActive() && state != ListenerState.STOPPING) return;
             
             // Atomically switch the delta, so subsequent modifications will be done in the
             // next scheduled persist

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/df21c711/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 b7eba1a..b39fea4 100644
--- a/core/src/main/java/brooklyn/entity/rebind/RebindContextImpl.java
+++ b/core/src/main/java/brooklyn/entity/rebind/RebindContextImpl.java
@@ -100,6 +100,10 @@ public class RebindContextImpl implements RebindContext {
         catalogItems.remove(item.getId());
     }
 
+    public void clearCatalogItems() {
+        catalogItems.clear();
+    }
+    
     public Entity getEntity(String id) {
         return entities.get(id);
     }
@@ -180,5 +184,5 @@ public class RebindContextImpl implements RebindContext {
     public LookupContext lookup() {
         return lookupContext;
     }
-    
+
 }

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/df21c711/core/src/main/java/brooklyn/entity/rebind/RebindIteration.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/entity/rebind/RebindIteration.java b/core/src/main/java/brooklyn/entity/rebind/RebindIteration.java
index 2d1e47f..ad4915a 100644
--- a/core/src/main/java/brooklyn/entity/rebind/RebindIteration.java
+++ b/core/src/main/java/brooklyn/entity/rebind/RebindIteration.java
@@ -308,7 +308,7 @@ public abstract class RebindIteration {
         if (rebindManager.persistCatalogItemsEnabled) {
             logRebindingDebug("RebindManager instantiating catalog items: {}", mementoManifest.getCatalogItemIds());
             for (CatalogItemMemento catalogItemMemento : mementoManifest.getCatalogItemMementos().values()) {
-                if (LOG.isDebugEnabled()) LOG.debug("RebindManager instantiating catalog item {}", catalogItemMemento);
+                logRebindingDebug("RebindManager instantiating catalog item {}", catalogItemMemento);
                 try {
                     CatalogItem<?, ?> catalogItem = instantiator.newCatalogItem(catalogItemMemento);
                     rebindContext.registerCatalogItem(catalogItemMemento.getId(), catalogItem);
@@ -347,9 +347,9 @@ public abstract class RebindIteration {
         CatalogInitialization catInit = ((ManagementContextInternal)managementContext).getCatalogInitialization();
         catInit.applyCatalogLoadMode();
         Collection<CatalogItem<?,?>> itemsForResettingCatalog = null;
-        boolean needsInitialCatalog;
+        boolean needsInitialItemsLoaded, needsAdditionalItemsLoaded;
         if (rebindManager.persistCatalogItemsEnabled) {
-            if (!catInit.hasRunOfficial() && catInit.isInitialResetRequested()) {
+            if (!catInit.hasRunFinalInitialization() && catInit.isInitialResetRequested()) {
                 String message = "RebindManager resetting catalog on first run (catalog persistence enabled, but reset explicitly specified). ";
                 if (catalogItems.isEmpty()) {
                     message += "Catalog was empty anyway.";
@@ -361,6 +361,15 @@ public abstract class RebindIteration {
                 }
                 logRebindingDebug(message);
 
+                // we will have unnecessarily tried to load the catalog item manifests earlier in this iteration,
+                // and problems there could fail a rebind even when we are resetting;
+                // it might be cleaner to check earlier whether a reset is happening and not load those items at all,
+                // but that would be a significant new code path (to remove a directory in the persistent store, essentially),
+                // and as it stands we don't do much with those manifests (e.g. we won't register them or fail on missing types)
+                // so we think it's only really corrupted XML or CatalogItem schema changes which would cause such problems.
+                // in extremis someone might need to wipe their store but for most purposes i don't think there will be any issue
+                // with loading the catalog item manifests before wiping all those files.
+                
                 itemsForResettingCatalog = MutableList.<CatalogItem<?,?>>of();
                 
                 PersisterDeltaImpl delta = new PersisterDeltaImpl();
@@ -368,36 +377,42 @@ public abstract class RebindIteration {
                 getPersister().queueDelta(delta);
                 
                 mementoRawData.clearCatalogItems();
-                needsInitialCatalog = true;
+                rebindContext.clearCatalogItems();
+                needsInitialItemsLoaded = true;
+                needsAdditionalItemsLoaded = true;
             } else {
                 if (!isEmpty) {
                     logRebindingDebug("RebindManager clearing local catalog and loading from persisted state");
                     itemsForResettingCatalog = rebindContext.getCatalogItems();
-                    needsInitialCatalog = false;
+                    needsInitialItemsLoaded = false;
+                    // only apply "add" if we haven't yet done so while in MASTER mode
+                    needsAdditionalItemsLoaded = !catInit.hasRunFinalInitialization();
                 } else {
-                    if (catInit.hasRunOfficial()) {
-                        logRebindingDebug("RebindManager will re-add any new items (persisted state empty)");
-                        needsInitialCatalog = false;
+                    if (catInit.hasRunFinalInitialization()) {
+                        logRebindingDebug("RebindManager has already done the final official run, not doing anything (even though persisted state empty)");
+                        needsInitialItemsLoaded = false;
+                        needsAdditionalItemsLoaded = false;
                     } else {
-                        logRebindingDebug("RebindManager loading initial catalog locally because persisted state empty");
-                        needsInitialCatalog = true;
+                        logRebindingDebug("RebindManager loading initial catalog locally because persisted state empty and the final official run has not yet been performed");
+                        needsInitialItemsLoaded = true;
+                        needsAdditionalItemsLoaded = true;
                     }
                 }
             }
         } else {
-            if (catInit.hasRunOfficial()) {
+            if (catInit.hasRunFinalInitialization()) {
                 logRebindingDebug("RebindManager skipping catalog init because it has already run (catalog persistence disabled)");
-                needsInitialCatalog = false;
+                needsInitialItemsLoaded = false;
+                needsAdditionalItemsLoaded = false;
             } else {
-                logRebindingDebug("RebindManager will initialize catalog locally because catalog persistence is disabled");
-                needsInitialCatalog = true;
+                logRebindingDebug("RebindManager will initialize catalog locally because catalog persistence is disabled and the final official run has not yet been performed");
+                needsInitialItemsLoaded = true;
+                needsAdditionalItemsLoaded = true;
             }
         }
 
-        // TODO in read-only mode, perhaps do this less frequently than entities etc ?
-        // both in RW and in RO mode, the first run reads the initialization data;
-        // maybe not desired for RO as it defers problems, although if it's standalone it is desired
-        catInit.populateCatalog(needsInitialCatalog, itemsForResettingCatalog);
+        // TODO in read-only mode, perhaps do this less frequently than entities etc, maybe only if things change?
+        catInit.populateCatalog(mode, needsInitialItemsLoaded, needsAdditionalItemsLoaded, itemsForResettingCatalog);
     }
 
     protected void instantiateLocationsAndEntities() {

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/df21c711/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 ded0049..c94e8fa 100644
--- a/core/src/main/java/brooklyn/entity/rebind/RebindManagerImpl.java
+++ b/core/src/main/java/brooklyn/entity/rebind/RebindManagerImpl.java
@@ -441,7 +441,7 @@ public class RebindManagerImpl implements RebindManager {
             }
             persistenceStoreAccess.checkpoint(memento, exceptionHandler);
         } else {
-            if (!persistenceRealChangeListener.persistNowSafely(false)) {
+            if (!persistenceRealChangeListener.persistNowSafely()) {
                 throw new IllegalStateException("Forced persistence failed; see logs fore more detail");
             }
         }

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/df21c711/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 ab033fd..96e6bea 100644
--- a/core/src/main/java/brooklyn/management/ha/HighAvailabilityManagerImpl.java
+++ b/core/src/main/java/brooklyn/management/ha/HighAvailabilityManagerImpl.java
@@ -408,33 +408,40 @@ public class HighAvailabilityManagerImpl implements HighAvailabilityManager {
             throw new IllegalStateException("Unexpected high availability mode "+startMode+" requested for "+this);
         }
         
-        if ((startMode==HighAvailabilityMode.HOT_STANDBY || 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 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 {
-                activateHotProxy(ManagementNodeState.of(startMode).get()).get();
-                // error above now throws
-                nodeStateTransitionComplete = true;
-                publishHealth();
-                
-                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 {
-                    // shouldn't come here, we should have gotten an error above
-                    LOG.warn("Management node "+ownNodeId+" unable to promote to "+startMode+" (currently "+getNodeState()+"); "
-                        + "(see log for further details)");
+        if ((startMode==HighAvailabilityMode.HOT_STANDBY || startMode==HighAvailabilityMode.HOT_BACKUP)) {
+            if (!ManagementNodeState.isHotProxy(oldState)) {
+                // now transition to hot proxy
+                nodeStateTransitionComplete = false;
+                if (startMode==HighAvailabilityMode.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();
                 }
-            } catch (Exception e) {
-                LOG.warn("Management node "+ownNodeId+" unable to promote to "+startMode+" (currently "+getNodeState()+"); rethrowing: "+Exceptions.collapseText(e));
+                try {
+                    activateHotProxy(ManagementNodeState.of(startMode).get()).get();
+                    // error above now throws
+                    nodeStateTransitionComplete = true;
+                    publishHealth();
+
+                    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 {
+                        // shouldn't come here, we should have gotten an error above
+                        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 "+startMode+" (currently "+getNodeState()+"); rethrowing: "+Exceptions.collapseText(e));
+                    nodeStateTransitionComplete = true;
+                    throw Exceptions.propagate(e);
+                }
+            } else {
+                // transitioning among hot proxy states - tell the rebind manager
+                managementContext.getRebindManager().stopReadOnly();
+                managementContext.getRebindManager().startReadOnly(ManagementNodeState.of(startMode).get());
                 nodeStateTransitionComplete = true;
-                throw Exceptions.propagate(e);
             }
         } else {
             nodeStateTransitionComplete = true;

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/df21c711/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 b1fdc0c..81dd26b 100644
--- a/core/src/main/java/brooklyn/management/internal/AbstractManagementContext.java
+++ b/core/src/main/java/brooklyn/management/internal/AbstractManagementContext.java
@@ -363,11 +363,11 @@ public abstract class AbstractManagementContext implements ManagementContextInte
 
     @Override
     public BrooklynCatalog getCatalog() {
-        if (!getCatalogInitialization().hasRunIncludingBestEffort()) {
+        if (!getCatalogInitialization().hasRunAnyInitialization()) {
             // catalog init is needed; normally this will be done from start sequence,
             // but if accessed early -- and in tests -- we will load it here
             getCatalogInitialization().injectManagementContext(this);
-            getCatalogInitialization().populateBestEffort(catalog);
+            getCatalogInitialization().populateUnofficial(catalog);
         }
         return catalog;
     }

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/df21c711/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 d98e0d8..6e48b3f 100644
--- a/usage/cli/src/main/java/brooklyn/cli/Main.java
+++ b/usage/cli/src/main/java/brooklyn/cli/Main.java
@@ -785,6 +785,10 @@ public class Main extends AbstractMain {
                     .add("startupFailsOnCatalogErrors", startupFailOnCatalogErrors)
                     .add("startupContinueOnWebErrors", startupContinueOnWebErrors)
                     .add("startupFailOnManagedAppsErrors", startupFailOnManagedAppsErrors)
+                    .add("catalogInitial", catalogInitial)
+                    .add("catalogAdd", catalogAdd)
+                    .add("catalogReset", catalogReset)
+                    .add("catalogForce", catalogForce)
                     .add("stopWhichAppsOnShutdown", stopWhichAppsOnShutdown)
                     .add("noShutdownOnExit", noShutdownOnExit)
                     .add("stopOnKeyPress", stopOnKeyPress)

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/df21c711/usage/cli/src/main/java/brooklyn/cli/lister/ClassFinder.java
----------------------------------------------------------------------
diff --git a/usage/cli/src/main/java/brooklyn/cli/lister/ClassFinder.java b/usage/cli/src/main/java/brooklyn/cli/lister/ClassFinder.java
index a66fa7d..2fb913b 100644
--- a/usage/cli/src/main/java/brooklyn/cli/lister/ClassFinder.java
+++ b/usage/cli/src/main/java/brooklyn/cli/lister/ClassFinder.java
@@ -130,7 +130,7 @@ public class ClassFinder {
                     }
                 }
             } else {
-                result.add(new URL("file://"+tidiedFile));
+                result.add(tidiedFile.toURI().toURL());
             }
         }
         

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/df21c711/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 6a201a2..e7d3948 100644
--- a/usage/launcher/src/main/java/brooklyn/launcher/BrooklynLauncher.java
+++ b/usage/launcher/src/main/java/brooklyn/launcher/BrooklynLauncher.java
@@ -598,9 +598,16 @@ public class BrooklynLauncher {
         try {
             // run cat init now if it hasn't yet been run; 
             // will also run if there was an ignored error in catalog above, allowing it to fail startup here if requested
-            if (catInit!=null && !catInit.hasRunOfficial()) {
-                LOG.debug("Loading catalog as part of launcher (persistence did not run it)");
-                catInit.populateCatalog(true, null);
+            if (catInit!=null && !catInit.hasRunOfficialInitialization()) {
+                if (persistMode==PersistMode.DISABLED) {
+                    LOG.debug("Loading catalog as part of launch sequence (it was not loaded as part of any rebind sequence)");
+                    catInit.populateCatalog(ManagementNodeState.MASTER, true, true, null);
+                } else {
+                    // should have loaded during rebind
+                    ManagementNodeState state = managementContext.getHighAvailabilityManager().getNodeState();
+                    LOG.warn("Loading catalog for "+state+" as part of launch sequence (it was not loaded as part of the rebind sequence)");
+                    catInit.populateCatalog(state, true, true, null);
+                }
             }
         } catch (Exception e) {
             handleSubsystemStartupError(ignoreCatalogErrors, "initial catalog", e);

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/df21c711/usage/rest-server/src/main/java/brooklyn/rest/filter/HaHotCheckResourceFilter.java
----------------------------------------------------------------------
diff --git a/usage/rest-server/src/main/java/brooklyn/rest/filter/HaHotCheckResourceFilter.java b/usage/rest-server/src/main/java/brooklyn/rest/filter/HaHotCheckResourceFilter.java
index dc2e954..bae561a 100644
--- a/usage/rest-server/src/main/java/brooklyn/rest/filter/HaHotCheckResourceFilter.java
+++ b/usage/rest-server/src/main/java/brooklyn/rest/filter/HaHotCheckResourceFilter.java
@@ -112,7 +112,7 @@ public class HaHotCheckResourceFilter implements ResourceFilterFactory {
         public ContainerRequest filter(ContainerRequest request) {
             String problem = lookForProblem(request);
             if (Strings.isNonBlank(problem)) {
-                log.warn("Disallowing request as "+problem+": "+request+"/"+am+" (caller should set '"+HaMasterCheckFilter.SKIP_CHECK_HEADER+"' to force)");
+                log.warn("Disallowing web request as "+problem+": "+request+"/"+am+" (caller should set '"+HaMasterCheckFilter.SKIP_CHECK_HEADER+"' to force)");
                 throw new WebApplicationException(ApiError.builder()
                     .message("This request is only permitted against an active hot Brooklyn server")
                     .errorCode(Response.Status.FORBIDDEN).build().asJsonResponse());

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/df21c711/usage/rest-server/src/main/java/brooklyn/rest/filter/HaMasterCheckFilter.java
----------------------------------------------------------------------
diff --git a/usage/rest-server/src/main/java/brooklyn/rest/filter/HaMasterCheckFilter.java b/usage/rest-server/src/main/java/brooklyn/rest/filter/HaMasterCheckFilter.java
index 9cca507..74e7cf1 100644
--- a/usage/rest-server/src/main/java/brooklyn/rest/filter/HaMasterCheckFilter.java
+++ b/usage/rest-server/src/main/java/brooklyn/rest/filter/HaMasterCheckFilter.java
@@ -94,7 +94,7 @@ public class HaMasterCheckFilter implements Filter {
     public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
         String problem = lookForProblem(request);
         if (problem!=null) {
-            log.warn("Disallowing request as "+problem+": "+request.getParameterMap()+" (caller should set '"+SKIP_CHECK_HEADER+"' to force)");
+            log.warn("Disallowing web request as "+problem+": "+request.getParameterMap()+" (caller should set '"+SKIP_CHECK_HEADER+"' to force)");
             WebResourceUtils.applyJsonResponse(servletContext, ApiError.builder()
                 .message("This request is only permitted against an active master Brooklyn server")
                 .errorCode(Response.Status.FORBIDDEN).build().asJsonResponse(), (HttpServletResponse)response);

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/df21c711/utils/common/src/main/java/brooklyn/util/javalang/AggregateClassLoader.java
----------------------------------------------------------------------
diff --git a/utils/common/src/main/java/brooklyn/util/javalang/AggregateClassLoader.java b/utils/common/src/main/java/brooklyn/util/javalang/AggregateClassLoader.java
index a3ca374..db0254b 100644
--- a/utils/common/src/main/java/brooklyn/util/javalang/AggregateClassLoader.java
+++ b/utils/common/src/main/java/brooklyn/util/javalang/AggregateClassLoader.java
@@ -115,7 +115,7 @@ public class AggregateClassLoader extends ClassLoader {
 
     public Iterator<ClassLoader> iterator() {
         synchronized (classLoaders) {
-            // provides iterator of snapshot
+            // CopyOnWriteList iterator is immutable view of snapshot
             return classLoaders.iterator();
         }
     }