brooklyn-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From aleds...@apache.org
Subject [09/11] brooklyn-server git commit: deleting orphaned state: many fixes/tidies
Date Wed, 20 Jul 2016 17:20:16 GMT
deleting orphaned state: many fixes/tidies

Now supports deleting enrichers/policies/feeds that have become
orphaned.


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

Branch: refs/heads/master
Commit: 660956f72a860b38db9c81db4cdffde38b3615b2
Parents: 4d0c4b8
Author: Aled Sage <aled.sage@gmail.com>
Authored: Wed Jul 20 09:16:14 2016 +0100
Committer: Ivana Yovcheva <ivana.yovcheva@gmail.com>
Committed: Wed Jul 20 12:18:49 2016 +0300

----------------------------------------------------------------------
 .../rebind/mementos/BrooklynMementoRawData.java |  13 ++
 .../DeleteOrphanedLocationsTransformer.java     | 192 ------------------
 .../impl/DeleteOrphanedStateTransformer.java    | 203 +++++++++++++++++++
 .../brooklyn/launcher/common/BasicLauncher.java |   4 +-
 ...stractBrooklynLauncherRebindTestFixture.java | 155 ++++++++++++++
 .../AbstractCleanOrphanedStateTest.java         | 179 ++++++++++++++++
 .../BrooklynLauncherCleanStateTest.java         | 112 ++++++++++
 .../BrooklynLauncherRebindTestFixture.java      | 114 +----------
 .../launcher/CleanOrphanedAdjunctsTest.java     |  76 +++++++
 .../launcher/CleanOrphanedLocationsTest.java    | 140 ++++---------
 .../copy-persisted-state-destination/dummy.txt  |   0
 11 files changed, 781 insertions(+), 407 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/660956f7/api/src/main/java/org/apache/brooklyn/api/mgmt/rebind/mementos/BrooklynMementoRawData.java
----------------------------------------------------------------------
diff --git a/api/src/main/java/org/apache/brooklyn/api/mgmt/rebind/mementos/BrooklynMementoRawData.java b/api/src/main/java/org/apache/brooklyn/api/mgmt/rebind/mementos/BrooklynMementoRawData.java
index 804304d..f2ec4a9 100644
--- a/api/src/main/java/org/apache/brooklyn/api/mgmt/rebind/mementos/BrooklynMementoRawData.java
+++ b/api/src/main/java/org/apache/brooklyn/api/mgmt/rebind/mementos/BrooklynMementoRawData.java
@@ -21,6 +21,8 @@ package org.apache.brooklyn.api.mgmt.rebind.mementos;
 import java.util.Collections;
 import java.util.Map;
 
+import javax.annotation.Nullable;
+
 import org.apache.brooklyn.api.objs.BrooklynObjectType;
 
 import com.google.common.annotations.Beta;
@@ -120,6 +122,7 @@ public class BrooklynMementoRawData {
         }
     }
 
+    private final String brooklynVersion;
     private final Map<String, String> entities;
     private final Map<String, String> locations;
     private final Map<String, String> policies;
@@ -128,6 +131,7 @@ public class BrooklynMementoRawData {
     private final Map<String, String> catalogItems;
     
     private BrooklynMementoRawData(Builder builder) {
+        brooklynVersion = builder.brooklynVersion;
         entities = builder.entities;
         locations = builder.locations;
         policies = builder.policies;
@@ -136,6 +140,15 @@ public class BrooklynMementoRawData {
         catalogItems = builder.catalogItems;
     }
 
+    /**
+     * Setting the brooklyn version explicitly is optional. 
+     */
+    @Beta
+    @Nullable
+    public String getBrooklynVersion() {
+        return brooklynVersion;
+    }
+    
     public Map<String, String> getEntities() {
         return Collections.unmodifiableMap(entities);
     }

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/660956f7/core/src/main/java/org/apache/brooklyn/core/mgmt/rebind/transformer/impl/DeleteOrphanedLocationsTransformer.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/brooklyn/core/mgmt/rebind/transformer/impl/DeleteOrphanedLocationsTransformer.java b/core/src/main/java/org/apache/brooklyn/core/mgmt/rebind/transformer/impl/DeleteOrphanedLocationsTransformer.java
deleted file mode 100644
index a26d807..0000000
--- a/core/src/main/java/org/apache/brooklyn/core/mgmt/rebind/transformer/impl/DeleteOrphanedLocationsTransformer.java
+++ /dev/null
@@ -1,192 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.brooklyn.core.mgmt.rebind.transformer.impl;
-
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-
-import javax.xml.xpath.XPathConstants;
-
-import org.apache.brooklyn.api.mgmt.rebind.mementos.BrooklynMemento;
-import org.apache.brooklyn.api.mgmt.rebind.mementos.BrooklynMementoRawData;
-import org.apache.brooklyn.api.mgmt.rebind.mementos.EntityMemento;
-import org.apache.brooklyn.api.mgmt.rebind.mementos.LocationMemento;
-import org.apache.brooklyn.core.mgmt.rebind.transformer.BrooklynMementoTransformer;
-import org.apache.brooklyn.core.mgmt.rebind.transformer.CompoundTransformer;
-import org.apache.brooklyn.util.collections.MutableMap;
-import org.apache.brooklyn.util.collections.MutableSet;
-import org.apache.brooklyn.util.core.xstream.XmlUtil;
-import org.w3c.dom.Node;
-
-import com.google.common.annotations.Beta;
-import com.google.common.annotations.VisibleForTesting;
-import com.google.common.collect.Lists;
-import com.google.common.collect.Sets;
-
-@Beta
-public class DeleteOrphanedLocationsTransformer extends CompoundTransformer implements BrooklynMementoTransformer {
-
-    protected DeleteOrphanedLocationsTransformer(DeleteOrphanedLocationsTransformer.Builder builder) {
-        super(builder);
-    }
-
-    public static Builder builder() {
-        return new DeleteOrphanedLocationsTransformer.Builder();
-    }
-
-    public static class Builder extends CompoundTransformer.Builder {
-
-        @Override
-        public DeleteOrphanedLocationsTransformer build() {
-            return new DeleteOrphanedLocationsTransformer(this);
-        }
-    }
-
-    @Override
-    public BrooklynMementoRawData transform(BrooklynMementoRawData input) {
-        Map<String, String> locationsToKeep = findLocationsToKeep(input);
-
-        return BrooklynMementoRawData.builder()
-                .catalogItems(input.getCatalogItems())
-                .enrichers(input.getEnrichers())
-                .entities(input.getEntities())
-                .feeds(input.getFeeds())
-                .locations(locationsToKeep)
-                .policies(input.getPolicies())
-                .build();
-    }
-
-    @Override
-    public BrooklynMemento transform(BrooklynMemento input) throws Exception {
-        throw new UnsupportedOperationException();
-    }
-
-    protected Set<String> findReferencedLocationIds(BrooklynMemento input) {
-        Set<String> result = Sets.newLinkedHashSet();
-
-        for (EntityMemento entity : input.getEntityMementos().values()) {
-            result.addAll(entity.getLocations());
-        }
-        return result;
-    }
-
-    @VisibleForTesting
-    public Map<String, String> findLocationsToKeep(BrooklynMementoRawData input) {
-        Map<String, String> locationsToKeep = MutableMap.of();
-        Set<String> allReferencedLocations = findAllReferencedLocations(input);
-
-        for (Map.Entry<String, String> location: input.getLocations().entrySet()) {
-            if (allReferencedLocations.contains(location.getKey())) {
-                locationsToKeep.put(location.getKey(), location.getValue());
-            }
-        }
-        return locationsToKeep;
-    }
-
-    @VisibleForTesting
-    public Set<String> findAllReferencedLocations(BrooklynMementoRawData input) {
-        Set<String> allReferencedLocations = MutableSet.of();
-
-        allReferencedLocations.addAll(searchLocationsToKeep(input.getEntities(), "/entity"));
-        allReferencedLocations.addAll(searchLocationsToKeep(input.getPolicies(), "/policy"));
-        allReferencedLocations.addAll(searchLocationsToKeep(input.getEnrichers(), "/enricher"));
-        allReferencedLocations.addAll(searchLocationsToKeep(input.getFeeds(), "/feed"));
-        allReferencedLocations.addAll(searchLocationsToKeepInLocations(input.getLocations(), allReferencedLocations));
-
-        return allReferencedLocations;
-    }
-
-    protected Set<String> searchLocationsToKeep(Map<String, String> entities, String searchInTypePrefix) {
-        Set<String> result = MutableSet.of();
-
-        for(Map.Entry<String, String> entity: entities.entrySet()) {
-
-            String location = "/locations/string";
-            String locationProxy = "//locationProxy";
-
-            result.addAll(getAllNodesFromXpath((org.w3c.dom.NodeList) XmlUtil.xpath(entity.getValue(), searchInTypePrefix+location, XPathConstants.NODESET)));
-            result.addAll(getAllNodesFromXpath((org.w3c.dom.NodeList) XmlUtil.xpath(entity.getValue(), searchInTypePrefix+locationProxy, XPathConstants.NODESET)));
-        }
-
-        return result;
-    }
-
-    protected Set<String> searchLocationsToKeepInLocations(Map<String, String> locations, Set<String> alreadyReferencedLocations) {
-        Set<String> result = MutableSet.of();
-
-        String prefix = "/location";
-        String locationId = "/id";
-        String locationChildren = "/children/string";
-        String locationParentDirectTag = "/parent";
-        String locationProxy = "//locationProxy";
-
-        for (Map.Entry<String, String> location: locations.entrySet()) {
-            if (alreadyReferencedLocations.contains(location.getKey())) {
-                result.addAll(getAllNodesFromXpath((org.w3c.dom.NodeList) XmlUtil.xpath(location.getValue(), prefix+locationId, XPathConstants.NODESET)));
-                result.addAll(getAllNodesFromXpath((org.w3c.dom.NodeList) XmlUtil.xpath(location.getValue(), prefix+locationChildren, XPathConstants.NODESET)));
-                result.addAll(getAllNodesFromXpath((org.w3c.dom.NodeList) XmlUtil.xpath(location.getValue(), prefix+locationParentDirectTag, XPathConstants.NODESET)));
-                result.addAll(getAllNodesFromXpath((org.w3c.dom.NodeList) XmlUtil.xpath(location.getValue(), prefix+locationProxy, XPathConstants.NODESET)));
-            }
-        }
-
-        return result;
-    }
-
-    protected Set<String> getAllNodesFromXpath(org.w3c.dom.NodeList nodeList) {
-        Set<String> result = MutableSet.of();
-
-        int i = 0;
-        Node nextNode = nodeList.item(i);
-        while (nextNode != null) {
-            result.add(nextNode.getTextContent());
-            nextNode = nodeList.item(++i);
-        }
-
-        return result;
-    }
-
-    protected Set<String> findLocationAncestors(BrooklynMemento input, String locationId) {
-        Set<String> result = Sets.newLinkedHashSet();
-
-        String parentId = null;
-        do {
-            LocationMemento memento = input.getLocationMemento(locationId);
-            parentId = memento.getParent();
-            if (parentId != null) result.add(parentId);
-        } while (parentId != null);
-
-        return result;
-    }
-
-    protected Set<String> findLocationDescendents(BrooklynMemento input, String locationId) {
-        Set<String> result = Sets.newLinkedHashSet();
-        List<String> tovisit = Lists.newLinkedList();
-
-        tovisit.add(locationId);
-        while (!tovisit.isEmpty()) {
-            LocationMemento memento = input.getLocationMemento(tovisit.remove(0));
-            List<String> children = memento.getChildren();
-            result.addAll(children);
-            tovisit.addAll(children);
-        };
-
-        return result;
-    }
-}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/660956f7/core/src/main/java/org/apache/brooklyn/core/mgmt/rebind/transformer/impl/DeleteOrphanedStateTransformer.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/brooklyn/core/mgmt/rebind/transformer/impl/DeleteOrphanedStateTransformer.java b/core/src/main/java/org/apache/brooklyn/core/mgmt/rebind/transformer/impl/DeleteOrphanedStateTransformer.java
new file mode 100644
index 0000000..9b76e28
--- /dev/null
+++ b/core/src/main/java/org/apache/brooklyn/core/mgmt/rebind/transformer/impl/DeleteOrphanedStateTransformer.java
@@ -0,0 +1,203 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.brooklyn.core.mgmt.rebind.transformer.impl;
+
+import java.util.Map;
+import java.util.Set;
+
+import javax.xml.xpath.XPathConstants;
+
+import org.apache.brooklyn.api.mgmt.rebind.mementos.BrooklynMementoRawData;
+import org.apache.brooklyn.core.mgmt.rebind.transformer.CompoundTransformer;
+import org.apache.brooklyn.util.collections.MutableMap;
+import org.apache.brooklyn.util.collections.MutableSet;
+import org.apache.brooklyn.util.core.xstream.XmlUtil;
+import org.apache.brooklyn.util.text.Strings;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.w3c.dom.Node;
+
+import com.google.common.annotations.Beta;
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.collect.Sets;
+
+@Beta
+public class DeleteOrphanedStateTransformer extends CompoundTransformer {
+
+    // TODO this isn't really a "CompoundTransformer" - it doesn't register any transformers 
+    // in its builder, but instead overrides {@link #transform(BrooklynMementoRawData)}.
+    // That is a convenient, concise way to get the behaviour we want. But it is a code smell!
+    // We should probably either extract some common super-type, or another interface like
+    // BrooklynMementoRawDataTransformer (but then what would call the latter? Would it be
+    // a CompoundTransformer?!).
+    
+    private static final Logger LOG = LoggerFactory.getLogger(DeleteOrphanedStateTransformer.class);
+
+    public static Builder builder() {
+        return new DeleteOrphanedStateTransformer.Builder();
+    }
+
+    public static class Builder extends CompoundTransformer.Builder {
+        @Override
+        public DeleteOrphanedStateTransformer build() {
+            return new DeleteOrphanedStateTransformer(this);
+        }
+    }
+
+    protected DeleteOrphanedStateTransformer(DeleteOrphanedStateTransformer.Builder builder) {
+        super(builder);
+    }
+
+    @Override
+    public BrooklynMementoRawData transform(BrooklynMementoRawData input) {
+        Map<String, String> locationsToKeep = findLocationsToKeep(input);
+        Map<String, String> enrichersToKeep = copyRetainingKeys(input.getEnrichers(), findAllReferencedEnrichers(input));
+        Map<String, String> policiesToKeep = copyRetainingKeys(input.getPolicies(), findAllReferencedPolicies(input));
+        Map<String, String> feedsToKeep = copyRetainingKeys(input.getFeeds(), findAllReferencedFeeds(input));
+
+        Set<String> locsToDelete = Sets.difference(input.getLocations().keySet(), locationsToKeep.keySet());
+        Set<String> enrichersToDelete = Sets.difference(input.getEnrichers().keySet(), enrichersToKeep.keySet());
+        Set<String> policiesToDelete = Sets.difference(input.getPolicies().keySet(), policiesToKeep.keySet());
+        Set<String> feedsToDelete = Sets.difference(input.getFeeds().keySet(), feedsToKeep.keySet());
+        LOG.info("Deleting {} orphaned location{}: {}", new Object[] {locsToDelete.size(), Strings.s(locsToDelete.size()), locsToDelete});
+        LOG.info("Deleting {} orphaned enricher{}: {}", new Object[] {enrichersToDelete.size(), Strings.s(enrichersToDelete.size()), enrichersToDelete});
+        LOG.info("Deleting {} orphaned polic{}: {}", new Object[] {policiesToDelete.size(), (policiesToDelete.size() == 1 ? "y" : "ies"), policiesToDelete});
+        LOG.info("Deleting {} orphaned feed{}: {}", new Object[] {feedsToDelete.size(), Strings.s(feedsToDelete.size()), feedsToDelete});
+        
+        return BrooklynMementoRawData.builder()
+                .brooklynVersion(input.getBrooklynVersion())
+                .catalogItems(input.getCatalogItems())
+                .entities(input.getEntities())
+                .locations(locationsToKeep)
+                .enrichers(enrichersToKeep)
+                .policies(policiesToKeep)
+                .feeds(feedsToKeep)
+                .build();
+    }
+
+    protected Set<String> findAllReferencedEnrichers(BrooklynMementoRawData input) {
+        return findAllReferencedAdjuncts(input.getEntities(), "entity" + "/enrichers/string");
+    }
+
+    protected Set<String> findAllReferencedPolicies(BrooklynMementoRawData input) {
+        return findAllReferencedAdjuncts(input.getEntities(), "entity" + "/policies/string");
+    }
+
+    protected Set<String> findAllReferencedFeeds(BrooklynMementoRawData input) {
+        return findAllReferencedAdjuncts(input.getEntities(), "entity" + "/feeds/string");
+    }
+
+    protected Set<String> findAllReferencedAdjuncts(Map<String, String> items, String xpath) {
+        Set<String> result = Sets.newLinkedHashSet();
+
+        for(Map.Entry<String, String> entry : items.entrySet()) {
+            result.addAll(getAllNodesFromXpath((org.w3c.dom.NodeList) XmlUtil.xpath(entry.getValue(), xpath, XPathConstants.NODESET)));
+        }
+
+        return result;
+    }
+
+    @VisibleForTesting
+    public Map<String, String> findLocationsToKeep(BrooklynMementoRawData input) {
+        Set<String> allReferencedLocations = findAllReferencedLocations(input);
+        return copyRetainingKeys(input.getLocations(), allReferencedLocations);
+    }
+
+    @VisibleForTesting
+    public Set<String> findAllReferencedLocations(BrooklynMementoRawData input) {
+        Set<String> result = Sets.newLinkedHashSet();
+
+        result.addAll(searchLocationsToKeep(input.getEntities(), "/entity"));
+        result.addAll(searchLocationsToKeep(input.getPolicies(), "/policy"));
+        result.addAll(searchLocationsToKeep(input.getEnrichers(), "/enricher"));
+        result.addAll(searchLocationsToKeep(input.getFeeds(), "/feed"));
+        result.addAll(searchLocationsToKeepInLocations(input.getLocations(), result));
+
+        return result;
+    }
+
+    protected Set<String> searchLocationsToKeep(Map<String, String> items, String searchInTypePrefix) {
+        String locationsXpath = searchInTypePrefix+"/locations/string";
+        String locationProxyXpath = searchInTypePrefix+"//locationProxy";
+
+        Set<String> result = Sets.newLinkedHashSet();
+
+        for(Map.Entry<String, String> entry : items.entrySet()) {
+            result.addAll(getAllNodesFromXpath((org.w3c.dom.NodeList) XmlUtil.xpath(entry.getValue(), locationsXpath, XPathConstants.NODESET)));
+            result.addAll(getAllNodesFromXpath((org.w3c.dom.NodeList) XmlUtil.xpath(entry.getValue(), locationProxyXpath, XPathConstants.NODESET)));
+        }
+
+        return result;
+    }
+
+    protected Set<String> searchLocationsToKeepInLocations(Map<String, String> locations, Set<String> alreadyReferencedLocations) {
+        Set<String> result = Sets.newLinkedHashSet();
+
+        String prefix = "/location";
+        String locationChildrenXpath = prefix+"/children/string";
+        String locationParentDirectTagXpath = prefix+"/parent";
+        String locationProxyXpath = prefix+"//locationProxy";
+
+        Set<String> locsToInspect = alreadyReferencedLocations;
+        
+        while (locsToInspect.size() > 0) {
+            Set<String> referencedLocs = Sets.newLinkedHashSet();
+            for (String id : locsToInspect) {
+                String xmlData = locations.get(id);
+                if (xmlData != null) {
+                    referencedLocs.addAll(getAllNodesFromXpath((org.w3c.dom.NodeList) XmlUtil.xpath(xmlData, locationChildrenXpath, XPathConstants.NODESET)));
+                    referencedLocs.addAll(getAllNodesFromXpath((org.w3c.dom.NodeList) XmlUtil.xpath(xmlData, locationParentDirectTagXpath, XPathConstants.NODESET)));
+                    referencedLocs.addAll(getAllNodesFromXpath((org.w3c.dom.NodeList) XmlUtil.xpath(xmlData, locationProxyXpath, XPathConstants.NODESET)));
+                }
+            }
+            Set<String> newlyDiscoveredLocs = MutableSet.<String>builder()
+                    .addAll(referencedLocs)
+                    .removeAll(alreadyReferencedLocations)
+                    .removeAll(result)
+                    .build();
+            result.addAll(newlyDiscoveredLocs);
+            locsToInspect = newlyDiscoveredLocs;
+        }
+
+        return result;
+    }
+
+    protected <K, V> Map<K, V> copyRetainingKeys(Map<K, V> orig, Set<? extends K> keysToKeep) {
+        Map<K, V> result = MutableMap.of();
+        for (Map.Entry<K, V> entry : orig.entrySet()) {
+            if (keysToKeep.contains(entry.getKey())) {
+                result.put(entry.getKey(), entry.getValue());
+            }
+        }
+        return result;
+    }
+
+    protected Set<String> getAllNodesFromXpath(org.w3c.dom.NodeList nodeList) {
+        Set<String> result = Sets.newLinkedHashSet();
+
+        for (int i = 0; i < nodeList.getLength(); i++) {
+            Node nextNode = nodeList.item(i);
+            if (nextNode != null) {
+                result.add(nextNode.getTextContent());
+            }
+        }
+        
+        return result;
+    }
+}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/660956f7/launcher-common/src/main/java/org/apache/brooklyn/launcher/common/BasicLauncher.java
----------------------------------------------------------------------
diff --git a/launcher-common/src/main/java/org/apache/brooklyn/launcher/common/BasicLauncher.java b/launcher-common/src/main/java/org/apache/brooklyn/launcher/common/BasicLauncher.java
index 3c74549..e328d61 100644
--- a/launcher-common/src/main/java/org/apache/brooklyn/launcher/common/BasicLauncher.java
+++ b/launcher-common/src/main/java/org/apache/brooklyn/launcher/common/BasicLauncher.java
@@ -61,7 +61,7 @@ import org.apache.brooklyn.core.mgmt.persist.PersistenceObjectStore;
 import org.apache.brooklyn.core.mgmt.rebind.PersistenceExceptionHandlerImpl;
 import org.apache.brooklyn.core.mgmt.rebind.RebindManagerImpl;
 import org.apache.brooklyn.core.mgmt.rebind.transformer.CompoundTransformer;
-import org.apache.brooklyn.core.mgmt.rebind.transformer.impl.DeleteOrphanedLocationsTransformer;
+import org.apache.brooklyn.core.mgmt.rebind.transformer.impl.DeleteOrphanedStateTransformer;
 import org.apache.brooklyn.core.server.BrooklynServerConfig;
 import org.apache.brooklyn.core.server.BrooklynServerPaths;
 import org.apache.brooklyn.entity.brooklynnode.BrooklynNode;
@@ -384,7 +384,7 @@ public class BasicLauncher<T extends BasicLauncher<T>> {
     }
 
     public void cleanOrphanedLocations(String destinationDir, @Nullable String destinationLocationSpec) {
-        copyPersistedState(destinationDir, destinationLocationSpec, DeleteOrphanedLocationsTransformer.builder().build());
+        copyPersistedState(destinationDir, destinationLocationSpec, DeleteOrphanedStateTransformer.builder().build());
     }
 
     /** @deprecated since 0.7.0 use {@link #copyPersistedState} instead */

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/660956f7/launcher/src/test/java/org/apache/brooklyn/launcher/AbstractBrooklynLauncherRebindTestFixture.java
----------------------------------------------------------------------
diff --git a/launcher/src/test/java/org/apache/brooklyn/launcher/AbstractBrooklynLauncherRebindTestFixture.java b/launcher/src/test/java/org/apache/brooklyn/launcher/AbstractBrooklynLauncherRebindTestFixture.java
new file mode 100644
index 0000000..f768d81
--- /dev/null
+++ b/launcher/src/test/java/org/apache/brooklyn/launcher/AbstractBrooklynLauncherRebindTestFixture.java
@@ -0,0 +1,155 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.brooklyn.launcher;
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertNotNull;
+
+import java.util.List;
+
+import org.apache.brooklyn.api.entity.Application;
+import org.apache.brooklyn.api.entity.EntitySpec;
+import org.apache.brooklyn.api.mgmt.ManagementContext;
+import org.apache.brooklyn.api.mgmt.ha.HighAvailabilityMode;
+import org.apache.brooklyn.core.entity.StartableApplication;
+import org.apache.brooklyn.core.internal.BrooklynProperties;
+import org.apache.brooklyn.core.mgmt.persist.BrooklynMementoPersisterToObjectStore;
+import org.apache.brooklyn.core.mgmt.persist.PersistMode;
+import org.apache.brooklyn.core.mgmt.persist.PersistenceObjectStore;
+import org.apache.brooklyn.core.test.entity.LocalManagementContextForTests;
+import org.apache.brooklyn.core.test.entity.TestApplication;
+import org.apache.brooklyn.test.Asserts;
+import org.apache.brooklyn.util.collections.MutableList;
+import org.apache.brooklyn.util.exceptions.FatalConfigurationRuntimeException;
+import org.apache.brooklyn.util.time.Duration;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.testng.annotations.AfterMethod;
+import org.testng.annotations.BeforeMethod;
+
+import com.google.common.base.Function;
+import com.google.common.base.Predicates;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Iterables;
+
+public abstract class AbstractBrooklynLauncherRebindTestFixture {
+
+    @SuppressWarnings("unused")
+    private static final Logger log = LoggerFactory.getLogger(AbstractBrooklynLauncherRebindTestFixture.class);
+    
+    protected String persistenceDir;
+    protected String persistenceLocationSpec;
+    protected List<BrooklynLauncher> launchers = MutableList.of();
+    
+    protected abstract String newTempPersistenceContainerName();
+
+    @BeforeMethod(alwaysRun=true)
+    public void setUp() throws Exception {
+        persistenceDir = newTempPersistenceContainerName();
+    }
+    
+    @AfterMethod(alwaysRun=true)
+    public void tearDown() throws Exception {
+        for (BrooklynLauncher l: launchers) {
+            if (l.isStarted()) {
+                l.terminate();
+                PersistenceObjectStore store = getPersistenceStore(l.getServerDetails().getManagementContext());
+                if (store!=null) store.deleteCompletely();
+            }
+        }
+    }
+
+    protected BrooklynLauncher newLauncherBase() {
+        BrooklynLauncher l = BrooklynLauncher.newInstance()
+            .webconsole(false);
+        launchers.add(l);
+        return l;
+    }
+    
+    protected BrooklynLauncher newLauncherDefault(PersistMode mode) {
+        return newLauncherBase()
+                .managementContext(newManagementContextForTests(null))
+                .persistMode(mode)
+                .persistenceDir(persistenceDir)
+                .persistPeriod(Duration.millis(10));
+    }
+    
+    protected LocalManagementContextForTests newManagementContextForTests(BrooklynProperties props) {
+        if (props==null)
+            return new LocalManagementContextForTests();
+        else
+            return new LocalManagementContextForTests(props);
+    }
+
+    protected ManagementContext lastMgmt() {
+        return Iterables.getLast(launchers).getServerDetails().getManagementContext();
+    }
+    
+    protected void runRebindFails(PersistMode persistMode, String dir, String errmsg) throws Exception {
+        try {
+            newLauncherDefault(persistMode)
+                    .persistenceDir(dir)
+                    .start();
+        } catch (FatalConfigurationRuntimeException e) {
+            if (!e.toString().contains(errmsg)) {
+                throw e;
+            }
+        }
+    }
+
+    protected void populatePersistenceDir(String dir, EntitySpec<? extends StartableApplication> appSpec) throws Exception {
+        BrooklynLauncher launcher = newLauncherDefault(PersistMode.CLEAN)
+                .highAvailabilityMode(HighAvailabilityMode.MASTER)
+                .persistenceDir(dir)
+                .application(appSpec)
+                .start();
+        launcher.terminate();
+        assertMementoContainerNonEmptyForTypeEventually("entities");
+    }
+    
+    protected void populatePersistenceDir(String dir, Function<ManagementContext, Void> populator) throws Exception {
+        BrooklynLauncher launcher = newLauncherDefault(PersistMode.CLEAN)
+                .highAvailabilityMode(HighAvailabilityMode.MASTER)
+                .persistenceDir(dir)
+                .start();
+        populator.apply(launcher.getManagementContext());
+        launcher.terminate();
+        assertMementoContainerNonEmptyForTypeEventually("entities");
+    }
+    
+    protected void assertOnlyApp(ManagementContext managementContext, Class<? extends Application> expectedType) {
+        assertEquals(managementContext.getApplications().size(), 1, "apps="+managementContext.getApplications());
+        assertNotNull(Iterables.find(managementContext.getApplications(), Predicates.instanceOf(TestApplication.class), null), "apps="+managementContext.getApplications());
+    }
+    
+    protected void assertMementoContainerNonEmptyForTypeEventually(final String type) {
+        Asserts.succeedsEventually(ImmutableMap.of("timeout", Duration.TEN_SECONDS), new Runnable() {
+            @Override public void run() {
+                getPersistenceStore(lastMgmt()).listContentsWithSubPath(type);
+            }});
+    }
+
+    static PersistenceObjectStore getPersistenceStore(ManagementContext managementContext) {
+        if (managementContext==null) return null;
+        BrooklynMementoPersisterToObjectStore persister = (BrooklynMementoPersisterToObjectStore)managementContext.getRebindManager().getPersister();
+        if (persister==null) return null;
+        return persister.getObjectStore();
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/660956f7/launcher/src/test/java/org/apache/brooklyn/launcher/AbstractCleanOrphanedStateTest.java
----------------------------------------------------------------------
diff --git a/launcher/src/test/java/org/apache/brooklyn/launcher/AbstractCleanOrphanedStateTest.java b/launcher/src/test/java/org/apache/brooklyn/launcher/AbstractCleanOrphanedStateTest.java
new file mode 100644
index 0000000..f8d7455
--- /dev/null
+++ b/launcher/src/test/java/org/apache/brooklyn/launcher/AbstractCleanOrphanedStateTest.java
@@ -0,0 +1,179 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.brooklyn.launcher;
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertTrue;
+
+import java.util.Arrays;
+import java.util.Set;
+
+import org.apache.brooklyn.api.mgmt.rebind.mementos.BrooklynMementoRawData;
+import org.apache.brooklyn.core.enricher.AbstractEnricher;
+import org.apache.brooklyn.core.feed.AbstractFeed;
+import org.apache.brooklyn.core.mgmt.rebind.RebindTestFixtureWithApp;
+import org.apache.brooklyn.core.mgmt.rebind.RebindTestUtils;
+import org.apache.brooklyn.core.mgmt.rebind.transformer.impl.DeleteOrphanedStateTransformer;
+import org.apache.brooklyn.core.policy.AbstractPolicy;
+import org.apache.brooklyn.core.test.entity.TestEntityImpl;
+import org.apache.brooklyn.util.collections.MutableMap;
+import org.apache.brooklyn.util.core.flags.SetFromFlag;
+import org.apache.commons.lang.builder.EqualsBuilder;
+
+import com.beust.jcommander.internal.Sets;
+import com.google.common.base.Function;
+import com.google.common.base.Functions;
+
+public abstract class AbstractCleanOrphanedStateTest extends RebindTestFixtureWithApp {
+    
+    public static class MementoTweaker implements Function<BrooklynMementoRawData, BrooklynMementoRawData> {
+        private Deletions deletions;
+        
+        public MementoTweaker(Deletions deletions) {
+            this.deletions = deletions;
+        }
+        @Override
+        public BrooklynMementoRawData apply(BrooklynMementoRawData input) {
+            return BrooklynMementoRawData.builder()
+                    .brooklynVersion(input.getBrooklynVersion())
+                    .catalogItems(input.getCatalogItems())
+                    .entities(MutableMap.<String, String>builder().putAll(input.getEntities()).removeAll(deletions.entities).build())
+                    .locations(MutableMap.<String, String>builder().putAll(input.getLocations()).removeAll(deletions.locations).build())
+                    .feeds(MutableMap.<String, String>builder().putAll(input.getFeeds()).removeAll(deletions.feeds).build())
+                    .enrichers(MutableMap.<String, String>builder().putAll(input.getEnrichers()).removeAll(deletions.enrichers).build())
+                    .policies(MutableMap.<String, String>builder().putAll(input.getPolicies()).removeAll(deletions.policies).build())
+                    .build();
+
+        }
+    }
+
+    protected void assertTransformIsNoop() throws Exception {
+        assertTransformIsNoop(getRawData());
+    }
+
+    protected void assertTransformIsNoop(BrooklynMementoRawData origData) throws Exception {
+        assertTransformIsNoop(origData, Functions.<BrooklynMementoRawData>identity());
+    }
+
+    protected void assertTransformIsNoop(Function<? super BrooklynMementoRawData, ? extends BrooklynMementoRawData> origDataTweaker) throws Exception {
+        assertTransformIsNoop(getRawData(), origDataTweaker);
+    }
+    
+    protected void assertTransformIsNoop(BrooklynMementoRawData origData, Function<? super BrooklynMementoRawData, ? extends BrooklynMementoRawData> origDataTweaker) throws Exception {
+        BrooklynMementoRawData origDataTweaked = origDataTweaker.apply(origData);
+        BrooklynMementoRawData transformedData = transformRawData(origDataTweaked);
+        assertRawData(transformedData, origDataTweaked);
+    }
+    
+    protected void assertTransformDeletes(Deletions deletions) throws Exception {
+        assertTransformDeletes(deletions, Functions.<BrooklynMementoRawData>identity());
+    }
+
+    protected void assertTransformDeletes(Deletions deletions, Function<? super BrooklynMementoRawData, ? extends BrooklynMementoRawData> origDataTweaker) throws Exception {
+        BrooklynMementoRawData origData = getRawData();
+        BrooklynMementoRawData origDataTweaked = origDataTweaker.apply(origData);
+        BrooklynMementoRawData transformedData = transformRawData(origDataTweaked);
+        assertRawData(transformedData, origDataTweaked, deletions);
+    }
+
+    protected BrooklynMementoRawData getRawData() throws Exception {
+        RebindTestUtils.waitForPersisted(origApp);
+        return mgmt().getRebindManager().retrieveMementoRawData();
+    }
+
+    protected BrooklynMementoRawData transformRawData(BrooklynMementoRawData rawData) throws Exception {
+        DeleteOrphanedStateTransformer transformer = DeleteOrphanedStateTransformer.builder().build();
+        return transformer.transform(rawData);
+    }
+
+    protected void assertRawData(BrooklynMementoRawData actual, BrooklynMementoRawData expected) {
+        // asserting lots of times, to ensure we get a more specific error message!
+        assertEquals(actual.getCatalogItems().keySet(), expected.getCatalogItems().keySet(), "catalog ids differ");
+        assertEquals(actual.getCatalogItems(), expected.getCatalogItems());
+        assertEquals(actual.getEntities().keySet(), expected.getEntities().keySet(), "entity ids differ");
+        assertEquals(actual.getEntities(), expected.getEntities());
+        assertEquals(actual.getLocations().keySet(), expected.getLocations().keySet(), "location ids differ");
+        assertEquals(actual.getLocations(), expected.getLocations());
+        assertEquals(actual.getEnrichers().keySet(), expected.getEnrichers().keySet(), "enricher ids differ");
+        assertEquals(actual.getEnrichers(), expected.getEnrichers());
+        assertEquals(actual.getPolicies().keySet(), expected.getPolicies().keySet(), "policy ids differ");
+        assertEquals(actual.getPolicies(), expected.getPolicies());
+        assertEquals(actual.getFeeds().keySet(), expected.getFeeds().keySet(), "feed ids differ");
+        assertEquals(actual.getFeeds(), expected.getFeeds());
+        assertTrue(EqualsBuilder.reflectionEquals(actual, expected));
+    }
+    
+    protected void assertRawData(BrooklynMementoRawData actual, BrooklynMementoRawData orig, Deletions deletions) {
+        BrooklynMementoRawData expected = new MementoTweaker(deletions).apply(orig);
+        assertRawData(actual, expected);
+        
+        // double-check that the orig data contains what we think we are deleting!
+        // otherwise we could get a lot of false positive tests.
+        assertTrue(orig.getEntities().keySet().containsAll(deletions.entities));
+        assertTrue(orig.getLocations().keySet().containsAll(deletions.locations));
+        assertTrue(orig.getEnrichers().keySet().containsAll(deletions.enrichers));
+        assertTrue(orig.getPolicies().keySet().containsAll(deletions.policies));
+        assertTrue(orig.getFeeds().keySet().containsAll(deletions.feeds));
+    }
+    
+    protected static class Deletions {
+        final Set<String> entities = Sets.newLinkedHashSet();
+        final Set<String> locations = Sets.newLinkedHashSet();
+        final Set<String> feeds = Sets.newLinkedHashSet();
+        final Set<String> enrichers = Sets.newLinkedHashSet();
+        final Set<String> policies = Sets.newLinkedHashSet();
+        
+        protected Deletions entities(String... vals) {
+            if (vals != null) entities.addAll(Arrays.asList(vals));
+            return this;
+        }
+        protected Deletions locations(String... vals) {
+            if (vals != null) locations.addAll(Arrays.asList(vals));
+            return this;
+        }
+        protected Deletions feeds(String... vals) {
+            if (vals != null) feeds.addAll(Arrays.asList(vals));
+            return this;
+        }
+        protected Deletions enrichers(String... vals) {
+            if (vals != null) enrichers.addAll(Arrays.asList(vals));
+            return this;
+        }
+        protected Deletions policies(String... vals) {
+            if (vals != null) policies.addAll(Arrays.asList(vals));
+            return this;
+        }
+    }
+    
+    public static class MyEnricher extends AbstractEnricher {
+        @SetFromFlag Object obj;
+    }
+    
+    public static class MyPolicy extends AbstractPolicy {
+    }
+    
+    public static class MyFeed extends AbstractFeed {
+    }
+    
+    public static class MyEntity extends TestEntityImpl {
+        @Override protected void initEnrichers() {
+            // no stock enrichers
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/660956f7/launcher/src/test/java/org/apache/brooklyn/launcher/BrooklynLauncherCleanStateTest.java
----------------------------------------------------------------------
diff --git a/launcher/src/test/java/org/apache/brooklyn/launcher/BrooklynLauncherCleanStateTest.java b/launcher/src/test/java/org/apache/brooklyn/launcher/BrooklynLauncherCleanStateTest.java
new file mode 100644
index 0000000..25d23d5
--- /dev/null
+++ b/launcher/src/test/java/org/apache/brooklyn/launcher/BrooklynLauncherCleanStateTest.java
@@ -0,0 +1,112 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.brooklyn.launcher;
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertNotEquals;
+import static org.testng.Assert.assertTrue;
+
+import java.io.File;
+import java.util.concurrent.atomic.AtomicReference;
+
+import org.apache.brooklyn.api.entity.Entity;
+import org.apache.brooklyn.api.entity.EntitySpec;
+import org.apache.brooklyn.api.location.Location;
+import org.apache.brooklyn.api.location.LocationSpec;
+import org.apache.brooklyn.api.mgmt.ManagementContext;
+import org.apache.brooklyn.api.mgmt.ha.HighAvailabilityMode;
+import org.apache.brooklyn.core.mgmt.persist.PersistMode;
+import org.apache.brooklyn.core.test.entity.TestApplication;
+import org.apache.brooklyn.location.ssh.SshMachineLocation;
+import org.apache.brooklyn.util.os.Os;
+import org.testng.annotations.Test;
+
+import com.google.common.base.Function;
+import com.google.common.base.Joiner;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Iterables;
+import com.google.common.io.Files;
+
+/**
+ * See {@link CleanOrphanedLocationsTest} for more thorough testing. This test is to ensure that
+ * it works through the launcher.
+ */
+public class BrooklynLauncherCleanStateTest extends AbstractBrooklynLauncherRebindTestFixture {
+
+    @Override
+    protected String newTempPersistenceContainerName() {
+        File persistenceDirF = Files.createTempDir();
+        Os.deleteOnExitRecursively(persistenceDirF);
+        return persistenceDirF.getAbsolutePath();
+    }
+    
+    @Test(groups="Integration")
+    public void testCleanStateState() throws Exception {
+        final AtomicReference<Entity> appToKeep = new AtomicReference<>();
+        final AtomicReference<Location> locToKeep = new AtomicReference<>();
+        final AtomicReference<Location> locToDelete = new AtomicReference<>();
+        populatePersistenceDir(persistenceDir, new Function<ManagementContext, Void>() {
+            @Override
+            public Void apply(ManagementContext mgmt) {
+                TestApplication app = mgmt.getEntityManager().createEntity(EntitySpec.create(TestApplication.class));
+                SshMachineLocation loc = mgmt.getLocationManager().createLocation(LocationSpec.create(SshMachineLocation.class));
+                SshMachineLocation loc2 = mgmt.getLocationManager().createLocation(LocationSpec.create(SshMachineLocation.class));
+                app.addLocations(ImmutableList.of(loc));
+                appToKeep.set(app);
+                locToKeep.set(loc);
+                locToDelete.set(loc2);
+                return null;
+            }});
+
+        File destinationDir = Files.createTempDir();
+        String destination = destinationDir.getAbsolutePath();
+        String destinationLocation = null; // i.e. file system, rather than object store
+        try {
+            // Clean the state
+            BrooklynLauncher launcher = newLauncherDefault(PersistMode.AUTO)
+                    .highAvailabilityMode(HighAvailabilityMode.MASTER)
+                    .webconsole(false);
+            launcher.cleanOrphanedLocations(destination, destinationLocation);
+            launcher.terminate();
+
+            // Sanity checks (copied from BrooklynLauncherRebindTestToFiles#testCopyPersistedState)
+            File entities = new File(Os.mergePaths(destination), "entities");
+            assertTrue(entities.isDirectory(), "entities directory should exist");
+            assertEquals(entities.listFiles().length, 1, "entities directory should contain one file (contained: "+
+                    Joiner.on(", ").join(entities.listFiles()) +")");
+
+            File nodes = new File(Os.mergePaths(destination, "nodes"));
+            assertTrue(nodes.isDirectory(), "nodes directory should exist");
+            assertNotEquals(nodes.listFiles().length, 0, "nodes directory should not be empty");
+
+            // Should now have a usable copy in the destinationDir
+            newLauncherDefault(PersistMode.AUTO)
+                    .webconsole(false)
+                    .persistenceDir(destinationDir)
+                    .start();
+            
+            Entity restoredApp = Iterables.getOnlyElement(lastMgmt().getEntityManager().getEntities());
+            Location restoredLoc = Iterables.getOnlyElement(lastMgmt().getLocationManager().getLocations());
+            assertEquals(restoredApp.getId(), appToKeep.get().getId());
+            assertEquals(restoredLoc.getId(), locToKeep.get().getId());
+        } finally {
+            Os.deleteRecursively(destinationDir);
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/660956f7/launcher/src/test/java/org/apache/brooklyn/launcher/BrooklynLauncherRebindTestFixture.java
----------------------------------------------------------------------
diff --git a/launcher/src/test/java/org/apache/brooklyn/launcher/BrooklynLauncherRebindTestFixture.java b/launcher/src/test/java/org/apache/brooklyn/launcher/BrooklynLauncherRebindTestFixture.java
index 59372e3..a98bc23 100644
--- a/launcher/src/test/java/org/apache/brooklyn/launcher/BrooklynLauncherRebindTestFixture.java
+++ b/launcher/src/test/java/org/apache/brooklyn/launcher/BrooklynLauncherRebindTestFixture.java
@@ -18,94 +18,32 @@
  */
 package org.apache.brooklyn.launcher;
 
-import org.apache.brooklyn.api.entity.Application;
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertNotNull;
+import static org.testng.Assert.assertTrue;
+
 import org.apache.brooklyn.api.entity.EntitySpec;
 import org.apache.brooklyn.api.location.Location;
-import org.apache.brooklyn.api.mgmt.ManagementContext;
-import org.apache.brooklyn.api.mgmt.ha.HighAvailabilityMode;
 import org.apache.brooklyn.core.entity.EntityPredicates;
 import org.apache.brooklyn.core.entity.StartableApplication;
 import org.apache.brooklyn.core.internal.BrooklynProperties;
-import org.apache.brooklyn.core.mgmt.persist.BrooklynMementoPersisterToObjectStore;
 import org.apache.brooklyn.core.mgmt.persist.PersistMode;
-import org.apache.brooklyn.core.mgmt.persist.PersistenceObjectStore;
 import org.apache.brooklyn.core.server.BrooklynServerConfig;
 import org.apache.brooklyn.core.test.entity.LocalManagementContextForTests;
 import org.apache.brooklyn.core.test.entity.TestApplication;
-import org.apache.brooklyn.launcher.BrooklynLauncher;
-import org.apache.brooklyn.test.Asserts;
-import org.apache.brooklyn.util.collections.MutableList;
-import org.apache.brooklyn.util.exceptions.FatalConfigurationRuntimeException;
 import org.apache.brooklyn.util.time.Duration;
-
-import static org.testng.Assert.assertEquals;
-import static org.testng.Assert.assertNotNull;
-import static org.testng.Assert.assertTrue;
-
-import java.util.List;
-
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
-import org.testng.annotations.AfterMethod;
-import org.testng.annotations.BeforeMethod;
 import org.testng.annotations.Test;
 
-import com.google.common.base.Predicates;
 import com.google.common.collect.ImmutableList;
-import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.Iterables;
 
-public abstract class BrooklynLauncherRebindTestFixture {
+public abstract class BrooklynLauncherRebindTestFixture extends AbstractBrooklynLauncherRebindTestFixture {
 
     @SuppressWarnings("unused")
     private static final Logger log = LoggerFactory.getLogger(BrooklynLauncherRebindTestFixture.class);
     
-    protected String persistenceDir;
-    protected String persistenceLocationSpec;
-    protected List<BrooklynLauncher> launchers = MutableList.of();
-    
-    @BeforeMethod(alwaysRun=true)
-    public void setUp() throws Exception {
-        persistenceDir = newTempPersistenceContainerName();
-    }
-    
-    protected abstract String newTempPersistenceContainerName();
-
-    @AfterMethod(alwaysRun=true)
-    public void tearDown() throws Exception {
-        for (BrooklynLauncher l: launchers) {
-            if (l.isStarted()) {
-                l.terminate();
-                PersistenceObjectStore store = getPersistenceStore(l.getServerDetails().getManagementContext());
-                if (store!=null) store.deleteCompletely();
-            }
-        }
-    }
-
-    protected BrooklynLauncher newLauncherBase() {
-        BrooklynLauncher l = BrooklynLauncher.newInstance()
-            .webconsole(false);
-        launchers.add(l);
-        return l;
-    }
-    protected BrooklynLauncher newLauncherDefault(PersistMode mode) {
-        return newLauncherBase()
-                .managementContext(newManagementContextForTests(null))
-                .persistMode(mode)
-                .persistenceDir(persistenceDir)
-                .persistPeriod(Duration.millis(10));
-    }
-    protected LocalManagementContextForTests newManagementContextForTests(BrooklynProperties props) {
-        if (props==null)
-            return new LocalManagementContextForTests();
-        else
-            return new LocalManagementContextForTests(props);
-    }
-
-    protected ManagementContext lastMgmt() {
-        return Iterables.getLast(launchers).getServerDetails().getManagementContext();
-    }
-    
     @Test
     public void testRebindsToExistingApp() throws Exception {
         populatePersistenceDir(persistenceDir, EntitySpec.create(TestApplication.class).displayName("myorig"));
@@ -212,46 +150,4 @@ public abstract class BrooklynLauncherRebindTestFixture {
     public void testExplicitRebindFailsIfEmpty() throws Exception {
         runRebindFails(PersistMode.REBIND, persistenceDir, "directory is empty");
     }
-
-    protected void runRebindFails(PersistMode persistMode, String dir, String errmsg) throws Exception {
-        try {
-            newLauncherDefault(persistMode)
-                    .persistenceDir(dir)
-                    .start();
-        } catch (FatalConfigurationRuntimeException e) {
-            if (!e.toString().contains(errmsg)) {
-                throw e;
-            }
-        }
-    }
-
-    protected void populatePersistenceDir(String dir, EntitySpec<? extends StartableApplication> appSpec) throws Exception {
-        BrooklynLauncher launcher = newLauncherDefault(PersistMode.CLEAN)
-                .highAvailabilityMode(HighAvailabilityMode.MASTER)
-                .persistenceDir(dir)
-                .application(appSpec)
-                .start();
-        launcher.terminate();
-        assertMementoContainerNonEmptyForTypeEventually("entities");
-    }
-    
-    protected void assertOnlyApp(ManagementContext managementContext, Class<? extends Application> expectedType) {
-        assertEquals(managementContext.getApplications().size(), 1, "apps="+managementContext.getApplications());
-        assertNotNull(Iterables.find(managementContext.getApplications(), Predicates.instanceOf(TestApplication.class), null), "apps="+managementContext.getApplications());
-    }
-    
-    protected void assertMementoContainerNonEmptyForTypeEventually(final String type) {
-        Asserts.succeedsEventually(ImmutableMap.of("timeout", Duration.TEN_SECONDS), new Runnable() {
-            @Override public void run() {
-                getPersistenceStore(lastMgmt()).listContentsWithSubPath(type);
-            }});
-    }
-
-    static PersistenceObjectStore getPersistenceStore(ManagementContext managementContext) {
-        if (managementContext==null) return null;
-        BrooklynMementoPersisterToObjectStore persister = (BrooklynMementoPersisterToObjectStore)managementContext.getRebindManager().getPersister();
-        if (persister==null) return null;
-        return persister.getObjectStore();
-    }
-
 }

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/660956f7/launcher/src/test/java/org/apache/brooklyn/launcher/CleanOrphanedAdjunctsTest.java
----------------------------------------------------------------------
diff --git a/launcher/src/test/java/org/apache/brooklyn/launcher/CleanOrphanedAdjunctsTest.java b/launcher/src/test/java/org/apache/brooklyn/launcher/CleanOrphanedAdjunctsTest.java
new file mode 100644
index 0000000..f9e772a
--- /dev/null
+++ b/launcher/src/test/java/org/apache/brooklyn/launcher/CleanOrphanedAdjunctsTest.java
@@ -0,0 +1,76 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.brooklyn.launcher;
+
+import static org.testng.Assert.assertTrue;
+
+import org.apache.brooklyn.api.entity.Entity;
+import org.apache.brooklyn.api.entity.EntitySpec;
+import org.apache.brooklyn.api.mgmt.rebind.mementos.BrooklynMementoRawData;
+import org.apache.brooklyn.api.policy.PolicySpec;
+import org.apache.brooklyn.api.sensor.EnricherSpec;
+import org.apache.brooklyn.api.sensor.Feed;
+import org.apache.brooklyn.core.entity.EntityInternal;
+import org.apache.brooklyn.core.test.entity.TestEntity;
+import org.testng.annotations.Test;
+
+/**
+ * When entity that the adjunct is associated with is deleted, the adjunct can safely be deleted.
+ */
+public class CleanOrphanedAdjunctsTest extends AbstractCleanOrphanedStateTest {
+
+    @Test
+    public void testDeletesOrphanedEnricher() throws Exception {
+        Entity entity = origApp.addChild(EntitySpec.create(TestEntity.class).impl(MyEntity.class));
+        MyEnricher enricher = entity.enrichers().add(EnricherSpec.create(MyEnricher.class));
+        MementoTweaker tweaker = new MementoTweaker(new Deletions().entities(entity.getId()));
+        assertTransformDeletes(new Deletions().enrichers(enricher.getId()), tweaker);
+    }
+    
+    @Test
+    public void testDeletesOrphanedPolicies() throws Exception {
+        Entity entity = origApp.addChild(EntitySpec.create(TestEntity.class).impl(MyEntity.class));
+        MyPolicy policy = entity.policies().add(PolicySpec.create(MyPolicy.class));
+        MementoTweaker tweaker = new MementoTweaker(new Deletions().entities(entity.getId()));
+        assertTransformDeletes(new Deletions().policies(policy.getId()), tweaker);
+    }
+    
+    @Test
+    public void testDeletesOrphanedFeeds() throws Exception {
+        EntityInternal entity = origApp.addChild(EntitySpec.create(TestEntity.class).impl(MyEntity.class));
+        Feed feed = entity.feeds().add(new MyFeed());
+        MementoTweaker tweaker = new MementoTweaker(new Deletions().entities(entity.getId()));
+        assertTransformDeletes(new Deletions().feeds(feed.getId()), tweaker);
+    }
+    
+    @Test
+    public void testKeepsReachableAdjuncts() throws Exception {
+        MyPolicy policy = origApp.policies().add(PolicySpec.create(MyPolicy.class));
+        MyEnricher enricher = origApp.enrichers().add(EnricherSpec.create(MyEnricher.class));
+        Feed feed = origApp.feeds().add(new MyFeed());
+        
+        // Double-check we have the state we expected for the assertions that it is unmodified!
+        BrooklynMementoRawData origData = getRawData();
+        assertTrue(origData.getPolicies().containsKey(policy.getId()));
+        assertTrue(origData.getEnrichers().containsKey(enricher.getId()));
+        assertTrue(origData.getFeeds().containsKey(feed.getId()));
+        
+        assertTransformIsNoop(origData);
+    }
+}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/660956f7/launcher/src/test/java/org/apache/brooklyn/launcher/CleanOrphanedLocationsTest.java
----------------------------------------------------------------------
diff --git a/launcher/src/test/java/org/apache/brooklyn/launcher/CleanOrphanedLocationsTest.java b/launcher/src/test/java/org/apache/brooklyn/launcher/CleanOrphanedLocationsTest.java
index 7be6bd5..258060b 100644
--- a/launcher/src/test/java/org/apache/brooklyn/launcher/CleanOrphanedLocationsTest.java
+++ b/launcher/src/test/java/org/apache/brooklyn/launcher/CleanOrphanedLocationsTest.java
@@ -18,36 +18,23 @@
  */
 package org.apache.brooklyn.launcher;
 
-import static org.testng.Assert.assertEquals;
-import static org.testng.Assert.assertTrue;
-
-import java.util.Arrays;
-import java.util.Set;
+import static org.testng.Assert.assertFalse;
 
 import org.apache.brooklyn.api.location.Location;
 import org.apache.brooklyn.api.location.LocationSpec;
-import org.apache.brooklyn.api.mgmt.rebind.mementos.BrooklynMementoRawData;
+import org.apache.brooklyn.api.location.MachineLocation;
 import org.apache.brooklyn.api.policy.PolicySpec;
 import org.apache.brooklyn.api.sensor.EnricherSpec;
 import org.apache.brooklyn.core.config.ConfigKeys;
-import org.apache.brooklyn.core.enricher.AbstractEnricher;
-import org.apache.brooklyn.core.feed.AbstractFeed;
-import org.apache.brooklyn.core.mgmt.rebind.RebindTestFixtureWithApp;
-import org.apache.brooklyn.core.mgmt.rebind.RebindTestUtils;
-import org.apache.brooklyn.core.mgmt.rebind.transformer.impl.DeleteOrphanedLocationsTransformer;
-import org.apache.brooklyn.core.policy.AbstractPolicy;
+import org.apache.brooklyn.core.location.Locations;
 import org.apache.brooklyn.core.sensor.Sensors;
 import org.apache.brooklyn.location.ssh.SshMachineLocation;
-import org.apache.brooklyn.util.collections.MutableMap;
-import org.apache.brooklyn.util.core.flags.SetFromFlag;
-import org.apache.commons.lang.builder.EqualsBuilder;
 import org.testng.annotations.Test;
 
-import com.beust.jcommander.internal.Sets;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
 
-public class CleanOrphanedLocationsTest extends RebindTestFixtureWithApp {
+public class CleanOrphanedLocationsTest extends AbstractCleanOrphanedStateTest {
 
     @Test
     public void testDeletesOrphanedLocations() throws Exception {
@@ -73,6 +60,37 @@ public class CleanOrphanedLocationsTest extends RebindTestFixtureWithApp {
         assertTransformDeletes(new Deletions().locations(loc1.getId(), loc2.getId()));
     }
 
+    // Tests that we don't accidentally abort or throw exceptions. 
+    @Test
+    public void testHandlesDanglingReference() throws Exception {
+        SshMachineLocation todelete = mgmt().getLocationManager().createLocation(LocationSpec.create(SshMachineLocation.class));
+        SshMachineLocation loc = mgmt().getLocationManager().createLocation(LocationSpec.create(SshMachineLocation.class));
+        origApp.addLocations(ImmutableList.of(todelete));
+        origApp.sensors().set(Sensors.newSensor(Object.class, "mysensor"), loc);
+        origApp.config().set(ConfigKeys.newConfigKey(Object.class, "myconfig"), loc);
+        origApp.tags().addTag(loc);
+        loc.config().set(ConfigKeys.newConfigKey(Object.class, "myconfig"), todelete);
+        
+        Locations.unmanage(todelete);
+        assertFalse(getRawData().getLocations().containsKey(todelete.getId()));
+
+        assertTransformIsNoop();
+    }
+
+    // Previously when iterating over the list, we'd abort on the first null rather than looking
+    // at all the nodes in the list. This test doesn't quite manage to reproduce that. The non-null
+    // dangling references are persisted.
+    @Test
+    public void testHandlesDanglingReferencesInLocationListSurroundingValidReference() throws Exception {
+        SshMachineLocation todelete = mgmt().getLocationManager().createLocation(LocationSpec.create(SshMachineLocation.class));
+        SshMachineLocation loc = mgmt().getLocationManager().createLocation(LocationSpec.create(SshMachineLocation.class));
+        SshMachineLocation todelete2 = mgmt().getLocationManager().createLocation(LocationSpec.create(SshMachineLocation.class));
+        origApp.addLocations(ImmutableList.of(todelete, loc, todelete2));
+        Locations.unmanage(todelete);
+
+        assertTransformIsNoop();
+    }
+
     @Test
     public void testKeepsLocationsReferencedInEntityLocs() throws Exception {
         SshMachineLocation loc = mgmt().getLocationManager().createLocation(LocationSpec.create(SshMachineLocation.class));
@@ -120,7 +138,7 @@ public class CleanOrphanedLocationsTest extends RebindTestFixtureWithApp {
     @Test(groups="WIP", enabled=false)
     public void testKeepsLocationsReferencedInConfigKeyDefault() throws Exception {
         SshMachineLocation loc = mgmt().getLocationManager().createLocation(LocationSpec.create(SshMachineLocation.class));
-        origApp.config().set(ConfigKeys.newConfigKey(Object.class, "myconfig", "my description", loc), null);
+        origApp.config().set(ConfigKeys.newConfigKey(MachineLocation.class, "myconfig", "my description", loc), (MachineLocation)null);
         assertTransformIsNoop();
     }
     
@@ -198,90 +216,4 @@ public class CleanOrphanedLocationsTest extends RebindTestFixtureWithApp {
         origApp.feeds().add(feed);
         assertTransformIsNoop();
     }
-    
-    private void assertTransformIsNoop() throws Exception {
-        BrooklynMementoRawData origData = getRawData();
-        BrooklynMementoRawData transformedData = transformRawData(origData);
-        assertRawData(transformedData, origData);
-    }
-
-    private void assertTransformDeletes(Deletions deletions) throws Exception {
-        BrooklynMementoRawData origData = getRawData();
-        BrooklynMementoRawData transformedData = transformRawData(origData);
-        assertRawData(transformedData, origData, deletions);
-    }
-
-    protected BrooklynMementoRawData getRawData() throws Exception {
-        RebindTestUtils.waitForPersisted(origApp);
-        return mgmt().getRebindManager().retrieveMementoRawData();
-    }
-
-    protected BrooklynMementoRawData transformRawData(BrooklynMementoRawData rawData) throws Exception {
-        DeleteOrphanedLocationsTransformer transformer = DeleteOrphanedLocationsTransformer.builder().build();
-        return transformer.transform(rawData);
-    }
-
-    protected void assertRawData(BrooklynMementoRawData actual, BrooklynMementoRawData expected) {
-        // asserting lots of times, to ensure we get a more specific error message!
-        assertEquals(actual.getCatalogItems().keySet(), expected.getCatalogItems().keySet());
-        assertEquals(actual.getCatalogItems(), expected.getCatalogItems());
-        assertEquals(actual.getEntities().keySet(), expected.getEntities().keySet());
-        assertEquals(actual.getEntities(), expected.getEntities());
-        assertEquals(actual.getLocations().keySet(), expected.getLocations().keySet());
-        assertEquals(actual.getLocations(), expected.getLocations());
-        assertEquals(actual.getEnrichers().keySet(), expected.getEnrichers().keySet());
-        assertEquals(actual.getEnrichers(), expected.getEnrichers());
-        assertEquals(actual.getPolicies().keySet(), expected.getPolicies().keySet());
-        assertEquals(actual.getPolicies(), expected.getPolicies());
-        assertEquals(actual.getFeeds().keySet(), expected.getFeeds().keySet());
-        assertEquals(actual.getFeeds(), expected.getFeeds());
-        assertTrue(EqualsBuilder.reflectionEquals(actual, expected));
-    }
-    
-    protected void assertRawData(BrooklynMementoRawData actual, BrooklynMementoRawData orig, Deletions deletions) {
-        BrooklynMementoRawData expected = BrooklynMementoRawData.builder()
-                .catalogItems(orig.getCatalogItems())
-                .entities(orig.getEntities())
-                .locations(MutableMap.<String, String>builder().putAll(orig.getLocations()).removeAll(deletions.locations).build())
-                .feeds(MutableMap.<String, String>builder().putAll(orig.getFeeds()).removeAll(deletions.feeds).build())
-                .enrichers(MutableMap.<String, String>builder().putAll(orig.getEnrichers()).removeAll(deletions.enrichers).build())
-                .policies(MutableMap.<String, String>builder().putAll(orig.getPolicies()).removeAll(deletions.policies).build())
-                .build();
-        assertRawData(actual, expected);
-    }
-    
-    @SuppressWarnings("unused")
-    private static class Deletions {
-        final Set<String> locations = Sets.newLinkedHashSet();
-        final Set<String> feeds = Sets.newLinkedHashSet();
-        final Set<String> enrichers = Sets.newLinkedHashSet();
-        final Set<String> policies = Sets.newLinkedHashSet();
-        
-        Deletions locations(String... vals) {
-            if (vals != null) locations.addAll(Arrays.asList(vals));
-            return this;
-        }
-        Deletions feeds(String... vals) {
-            if (vals != null) feeds.addAll(Arrays.asList(vals));
-            return this;
-        }
-        Deletions enrichers(String... vals) {
-            if (vals != null) enrichers.addAll(Arrays.asList(vals));
-            return this;
-        }
-        Deletions policies(String... vals) {
-            if (vals != null) policies.addAll(Arrays.asList(vals));
-            return this;
-        }
-    }
-    
-    public static class MyEnricher extends AbstractEnricher {
-        @SetFromFlag Object obj;
-    }
-    
-    public static class MyPolicy extends AbstractPolicy {
-    }
-    
-    public static class MyFeed extends AbstractFeed {
-    }
 }

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/660956f7/launcher/src/test/resources/orphaned-locations-test-data/copy-persisted-state-destination/dummy.txt
----------------------------------------------------------------------
diff --git a/launcher/src/test/resources/orphaned-locations-test-data/copy-persisted-state-destination/dummy.txt b/launcher/src/test/resources/orphaned-locations-test-data/copy-persisted-state-destination/dummy.txt
deleted file mode 100644
index e69de29..0000000


Mime
View raw message