You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@brooklyn.apache.org by al...@apache.org on 2016/07/20 17:20:10 UTC

[03/11] brooklyn-server git commit: Cleaner for the old dangling references

Cleaner for the old dangling references


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

Branch: refs/heads/master
Commit: 59f8e8b44c0399ef270da8b53d4b3e6851069cf4
Parents: c7c8db5
Author: Ivana Yovcheva <iv...@gmail.com>
Authored: Wed Jun 15 19:22:43 2016 +0300
Committer: Ivana Yovcheva <iv...@gmail.com>
Committed: Tue Jul 19 18:30:15 2016 +0300

----------------------------------------------------------------------
 .../DeleteOrphanedLocationsTransformer.java     | 147 ++++++++++-
 .../brooklyn/launcher/common/BasicLauncher.java |   7 +-
 .../CleanOrphanedLocationsLiveTest.java         | 257 +++++++++++++++++++
 .../main/java/org/apache/brooklyn/cli/Main.java |  77 ++++++
 4 files changed, 475 insertions(+), 13 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/59f8e8b4/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
index 985ce37..1c9cd9e 100644
--- 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
@@ -20,13 +20,17 @@ package org.apache.brooklyn.core.mgmt.rebind.transformer.impl;
 
 import java.util.Collection;
 import java.util.List;
+import java.util.Map;
 import java.util.Set;
 
+import com.sun.org.apache.xml.internal.dtm.ref.DTMNodeList;
 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.dto.BrooklynMementoImpl;
 import org.apache.brooklyn.core.mgmt.rebind.transformer.BrooklynMementoTransformer;
+import org.apache.brooklyn.core.mgmt.rebind.transformer.CompoundTransformer;
 import org.apache.brooklyn.util.collections.MutableList;
 import org.apache.brooklyn.util.collections.MutableMap;
 import org.apache.brooklyn.util.collections.MutableSet;
@@ -34,17 +38,49 @@ import org.apache.brooklyn.util.collections.MutableSet;
 import com.google.common.annotations.Beta;
 import com.google.common.collect.Lists;
 import com.google.common.collect.Sets;
+import org.apache.brooklyn.util.core.xstream.XmlUtil;
+import org.w3c.dom.Node;
+
+import javax.xml.xpath.XPathConstants;
 
 @Beta
-public class DeleteOrphanedLocationsTransformer implements BrooklynMementoTransformer {
+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 {
+
+        public DeleteOrphanedLocationsTransformer build() {
+            return new DeleteOrphanedLocationsTransformer(this);
+        }
+    }
+
+    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();
+    }
 
     // TODO Work in progress; untested code!
-    
+
     public BrooklynMemento transform(BrooklynMemento input) throws Exception {
         Set<String> referencedLocationIds = findReferencedLocationIds(input);
         Set<String> unreferencedLocationIds = Sets.newLinkedHashSet();
         List<String> toCheck = Lists.newLinkedList(input.getLocationIds());
-        
+
         while (!toCheck.isEmpty()) {
             String locationId = toCheck.remove(0);
             List<String> locationsInHierarchy = MutableList.<String>builder()
@@ -52,7 +88,7 @@ public class DeleteOrphanedLocationsTransformer implements BrooklynMementoTransf
                     .addAll(findLocationAncestors(input, locationId))
                     .addAll(findLocationDescendents(input, locationId))
                     .build();
-            
+
             if (containsAny(referencedLocationIds, locationsInHierarchy)) {
                 // keep them all
             } else {
@@ -60,7 +96,7 @@ public class DeleteOrphanedLocationsTransformer implements BrooklynMementoTransf
             }
             toCheck.removeAll(locationsInHierarchy);
         }
-        
+
         // TODO What about brooklyn version?
         return BrooklynMementoImpl.builder()
                 .applicationIds(input.getApplicationIds())
@@ -78,26 +114,113 @@ public class DeleteOrphanedLocationsTransformer implements BrooklynMementoTransf
                 .catalogItems(input.getCatalogItemMementos())
                 .build();
     }
-    
+
     public boolean containsAny(Collection<?> container, Iterable<?> contenders) {
         for (Object contender : contenders) {
             if (container.contains(contender)) return true;
         }
         return false;
     }
-    
+
     public Set<String> findReferencedLocationIds(BrooklynMemento input) {
         Set<String> result = Sets.newLinkedHashSet();
-        
+
         for (EntityMemento entity : input.getEntityMementos().values()) {
             result.addAll(entity.getLocations());
         }
         return result;
     }
-    
+
+    public Map<String, String> findLocationsToKeep(BrooklynMementoRawData input) {
+        Map<String, String> locationsToKeep = MutableMap.of();
+        Set<String> allReferencedLocations = findAllReferencedLocations(input);
+
+        for (Map.Entry location: input.getLocations().entrySet()) {
+            if (allReferencedLocations.contains(location.getKey())) {
+                locationsToKeep.put((String) location.getKey(), (String) location.getValue());
+            }
+        }
+        return locationsToKeep;
+    }
+
+    public Set<String> findAllReferencedLocations(BrooklynMementoRawData input) {
+        Set<String> allReferencedLocations = MutableSet.of();
+
+        allReferencedLocations.addAll(searchLocationsToKeepInEntities(input.getEntities()));
+        allReferencedLocations.addAll(searchLocationsToKeepInPolicies(input.getPolicies()));
+        allReferencedLocations.addAll(searchLocationsToKeepInEnrichers(input.getEnrichers()));
+        allReferencedLocations.addAll(searchLocationsToKeepInLocations(input.getLocations(), allReferencedLocations));
+
+        return allReferencedLocations;
+    }
+
+    private Set<String> searchLocationsToKeepInEntities(Map<String, String> entities) {
+        Set<String> result = MutableSet.of();
+
+        for(Map.Entry entity: entities.entrySet()) {
+
+            String prefix = "/entity";
+            String location = "/locations/string";
+            String locationProxy = "/attributes/softwareservice.provisioningLocation/locationProxy";
+
+            result.addAll(getAllNodesFromXpath((DTMNodeList) XmlUtil.xpath((String) entity.getValue(), prefix+location, XPathConstants.NODESET)));
+            result.addAll(getAllNodesFromXpath((DTMNodeList) XmlUtil.xpath((String) entity.getValue(), prefix+locationProxy, XPathConstants.NODESET)));
+        }
+
+        return result;
+    }
+
+    private 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 locationParent = "/locationConfig/jcloudsParent/locationProxy";
+        String locationProxy = "/locationConfig/vmInstanceIds/map/entry/locationProxy";
+
+        for (Map.Entry location: locations.entrySet()) {
+            if (alreadyReferencedLocations.contains(location)) {
+                result.addAll(getAllNodesFromXpath((DTMNodeList) XmlUtil.xpath((String) location.getValue(), prefix+locationId, XPathConstants.NODESET)));
+                result.addAll(getAllNodesFromXpath((DTMNodeList) XmlUtil.xpath((String) location.getValue(), prefix+locationChildren, XPathConstants.NODESET)));
+                result.addAll(getAllNodesFromXpath((DTMNodeList) XmlUtil.xpath((String) location.getValue(), prefix+locationParent, XPathConstants.NODESET)));
+                result.addAll(getAllNodesFromXpath((DTMNodeList) XmlUtil.xpath((String) location.getValue(), prefix+locationParentDirectTag, XPathConstants.NODESET)));
+                result.addAll(getAllNodesFromXpath((DTMNodeList) XmlUtil.xpath((String) location.getValue(), prefix+locationProxy, XPathConstants.NODESET)));
+            }
+        }
+
+        return result;
+    }
+
+    private Set<String> searchLocationsToKeepInPolicies(Map<String, String> policies) {
+        Set<String> result = MutableSet.of();
+
+        return result;
+    }
+
+    private Set<String> searchLocationsToKeepInEnrichers(Map<String, String> enrichers) {
+        Set<String> result = MutableSet.of();
+
+        return result;
+    }
+
+    private Set<String> getAllNodesFromXpath(DTMNodeList 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;
+    }
+
     public Set<String> findLocationAncestors(BrooklynMemento input, String locationId) {
         Set<String> result = Sets.newLinkedHashSet();
-        
+
         String parentId = null;
         do {
             LocationMemento memento = input.getLocationMemento(locationId);
@@ -107,11 +230,11 @@ public class DeleteOrphanedLocationsTransformer implements BrooklynMementoTransf
 
         return result;
     }
-    
+
     public 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));

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/59f8e8b4/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 b550a5b..3c74549 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
@@ -45,6 +45,7 @@ import org.apache.brooklyn.config.ConfigKey;
 import org.apache.brooklyn.core.catalog.internal.CatalogInitialization;
 import org.apache.brooklyn.core.entity.Entities;
 import org.apache.brooklyn.core.entity.StartableApplication;
+import org.apache.brooklyn.core.entity.factory.ApplicationBuilder;
 import org.apache.brooklyn.core.entity.trait.Startable;
 import org.apache.brooklyn.core.internal.BrooklynProperties;
 import org.apache.brooklyn.core.mgmt.EntityManagementUtils;
@@ -60,6 +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.server.BrooklynServerConfig;
 import org.apache.brooklyn.core.server.BrooklynServerPaths;
 import org.apache.brooklyn.entity.brooklynnode.BrooklynNode;
@@ -373,7 +375,6 @@ public class BasicLauncher<T extends BasicLauncher<T>> {
                 managementContext, destinationLocationSpec, destinationDir);
             BrooklynPersistenceUtils.writeMemento(managementContext, memento, destinationObjectStore);
             BrooklynPersistenceUtils.writeManagerMemento(managementContext, planeState, destinationObjectStore);
-
         } catch (Exception e) {
             Exceptions.propagateIfFatal(e);
             LOG.debug("Error copying persisted state (rethrowing): " + e, e);
@@ -382,6 +383,10 @@ public class BasicLauncher<T extends BasicLauncher<T>> {
         }
     }
 
+    public void cleanOrphanedLocations(String destinationDir, @Nullable String destinationLocationSpec) {
+        copyPersistedState(destinationDir, destinationLocationSpec, DeleteOrphanedLocationsTransformer.builder().build());
+    }
+
     /** @deprecated since 0.7.0 use {@link #copyPersistedState} instead */
     // Make private after deprecation
     @Deprecated

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/59f8e8b4/launcher/src/test/java/org/apache/brooklyn/launcher/CleanOrphanedLocationsLiveTest.java
----------------------------------------------------------------------
diff --git a/launcher/src/test/java/org/apache/brooklyn/launcher/CleanOrphanedLocationsLiveTest.java b/launcher/src/test/java/org/apache/brooklyn/launcher/CleanOrphanedLocationsLiveTest.java
new file mode 100644
index 0000000..cda7cc0
--- /dev/null
+++ b/launcher/src/test/java/org/apache/brooklyn/launcher/CleanOrphanedLocationsLiveTest.java
@@ -0,0 +1,257 @@
+/*
+ * 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 org.apache.brooklyn.api.mgmt.ManagementContext;
+import org.apache.brooklyn.api.mgmt.ha.HighAvailabilityMode;
+import org.apache.brooklyn.api.mgmt.rebind.PersistenceExceptionHandler;
+import org.apache.brooklyn.api.mgmt.rebind.RebindManager;
+import org.apache.brooklyn.api.mgmt.rebind.mementos.BrooklynMementoRawData;
+import org.apache.brooklyn.core.mgmt.ha.OsgiManager;
+import org.apache.brooklyn.core.mgmt.internal.LocalManagementContext;
+import org.apache.brooklyn.core.mgmt.internal.ManagementContextInternal;
+import org.apache.brooklyn.core.mgmt.persist.BrooklynMementoPersisterToObjectStore;
+import org.apache.brooklyn.core.mgmt.persist.BrooklynPersistenceUtils;
+import org.apache.brooklyn.core.mgmt.persist.PersistMode;
+import org.apache.brooklyn.core.mgmt.persist.PersistenceObjectStore;
+import org.apache.brooklyn.core.mgmt.rebind.PersistenceExceptionHandlerImpl;
+import org.apache.brooklyn.core.mgmt.rebind.RebindManagerImpl;
+import org.apache.brooklyn.core.mgmt.rebind.transformer.impl.DeleteOrphanedLocationsTransformer;
+import org.apache.brooklyn.core.server.BrooklynServerConfig;
+import org.apache.brooklyn.core.server.BrooklynServerPaths;
+import org.apache.brooklyn.test.Asserts;
+import org.apache.brooklyn.util.collections.MutableMap;
+import org.apache.brooklyn.util.collections.MutableSet;
+import org.apache.brooklyn.util.time.Duration;
+import org.apache.commons.lang.builder.EqualsBuilder;
+import org.testng.annotations.AfterMethod;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import org.apache.brooklyn.core.internal.BrooklynProperties;
+
+import java.util.Map;
+import java.util.Set;
+
+public class CleanOrphanedLocationsLiveTest {
+
+    private String PERSISTED_STATE_PATH_WITH_ORPHANED_LOCATIONS = "/orphaned-locations-test-data/data-with-orphaned-locations";
+    private String PERSISTED_STATE_PATH_WITH_MULTIPLE_LOCATIONS_OCCURRENCE = "/orphaned-locations-test-data/fake-multiple-location-for-multiple-search-tests";
+    private String PERSISTED_STATE_PATH_WITHOUT_ORPHANED_LOCATIONS = "/orphaned-locations-test-data/data-without-orphaned-locations";
+    private String PERSISTED_STATE_DESTINATION_PATH = "/orphaned-locations-test-data/copy-persisted-state-destination";
+
+
+    private String persistenceDirWithOrphanedLocations;
+    private String persistenceDirWithoutOrphanedLocations;
+    private String persistenceDirWithMultipleLocationsOccurrence;
+    private String destinationDir;
+    private String destinationLocation;
+    private String localBrooklynProperties;
+    private String persistenceLocation;
+    private String highAvailabilityMode;
+
+    private DeleteOrphanedLocationsTransformer transformer;
+
+    private ManagementContext managementContext;
+
+    @BeforeMethod(alwaysRun = true)
+    public void initialize() throws Exception {
+        persistenceDirWithOrphanedLocations = getClass().getResource(PERSISTED_STATE_PATH_WITH_ORPHANED_LOCATIONS).getFile();
+        persistenceDirWithoutOrphanedLocations = getClass().getResource(PERSISTED_STATE_PATH_WITHOUT_ORPHANED_LOCATIONS).getFile();
+        persistenceDirWithMultipleLocationsOccurrence = getClass().getResource(PERSISTED_STATE_PATH_WITH_MULTIPLE_LOCATIONS_OCCURRENCE).getFile();
+
+        destinationDir = getClass().getResource(PERSISTED_STATE_DESTINATION_PATH).getFile();
+
+        transformer = DeleteOrphanedLocationsTransformer.builder().build();
+    }
+
+    private void initManagementContextAndPersistence(String persistenceDir) {
+
+        BrooklynProperties brooklynProperties = BrooklynProperties.Factory.builderDefault().build();
+        brooklynProperties.put(BrooklynServerConfig.MGMT_BASE_DIR.getName(), "");
+
+        managementContext = new LocalManagementContext(brooklynProperties);
+
+        if (persistenceLocation == null) {
+            persistenceLocation = brooklynProperties.getConfig(BrooklynServerConfig.PERSISTENCE_LOCATION_SPEC);
+        }
+
+        persistenceDir = BrooklynServerPaths.newMainPersistencePathResolver(brooklynProperties).location(persistenceLocation).dir(persistenceDir).resolve();
+        PersistenceObjectStore objectStore = BrooklynPersistenceUtils.newPersistenceObjectStore(managementContext, persistenceLocation, persistenceDir,
+                PersistMode.AUTO, HighAvailabilityMode.HOT_STANDBY);
+
+        BrooklynMementoPersisterToObjectStore persister = new BrooklynMementoPersisterToObjectStore(
+                objectStore,
+                ((ManagementContextInternal)managementContext).getBrooklynProperties(),
+                managementContext.getCatalogClassLoader());
+
+        RebindManager rebindManager = managementContext.getRebindManager();
+
+        PersistenceExceptionHandler persistenceExceptionHandler = PersistenceExceptionHandlerImpl.builder().build();
+        ((RebindManagerImpl) rebindManager).setPeriodicPersistPeriod(Duration.ONE_SECOND);
+        rebindManager.setPersister(persister, persistenceExceptionHandler);
+    }
+
+    @Test
+    public void testSelectionWithOrphanedLocationsInData() {
+
+        Set<String> locationsToKeep = MutableSet.of(
+                "tjdywoxbji",
+                "pudtixbw89"
+        );
+
+        Set<String> orphanedLocations = MutableSet.of(
+                "banby1jx4j",
+                "msyp655po0",
+                "ppamsemxgo"
+        );
+
+        initManagementContextAndPersistence(persistenceDirWithOrphanedLocations);
+        BrooklynMementoRawData mementoRawData = managementContext.getRebindManager().retrieveMementoRawData();
+
+        Set<String> allReferencedLocationsFoundByTransformer = transformer.findAllReferencedLocations(mementoRawData);
+        Map<String, String> locationsToKeepFoundByTransformer = transformer.findLocationsToKeep(mementoRawData);
+
+        Asserts.assertEquals(allReferencedLocationsFoundByTransformer, locationsToKeep);
+        Asserts.assertEquals(locationsToKeepFoundByTransformer.keySet(), locationsToKeep);
+
+        Map<String, String> locationsNotToKeepDueToTransormer = MutableMap.of();
+        locationsNotToKeepDueToTransormer.putAll(mementoRawData.getLocations());
+        for (Map.Entry location: locationsToKeepFoundByTransformer.entrySet()) {
+            locationsNotToKeepDueToTransormer.remove(location.getKey());
+        }
+        Set<String> notReferencedLocationsDueToTransormer = MutableSet.of();
+        notReferencedLocationsDueToTransormer.addAll(mementoRawData.getLocations().keySet());
+        for (String location: allReferencedLocationsFoundByTransformer) {
+            notReferencedLocationsDueToTransormer.remove(location);
+        }
+
+        Asserts.assertEquals(notReferencedLocationsDueToTransormer, orphanedLocations);
+        Asserts.assertEquals(locationsNotToKeepDueToTransormer.keySet(), orphanedLocations);
+
+
+        BrooklynMementoRawData transformedMemento = transformer.transform(mementoRawData);
+        Asserts.assertFalse(EqualsBuilder.reflectionEquals(mementoRawData, transformedMemento));
+        Asserts.assertTrue(EqualsBuilder.reflectionEquals(mementoRawData.getEntities(), transformedMemento.getEntities()));
+        Asserts.assertTrue(EqualsBuilder.reflectionEquals(mementoRawData.getEnrichers(), transformedMemento.getEnrichers()));
+        Asserts.assertTrue(EqualsBuilder.reflectionEquals(mementoRawData.getPolicies(), transformedMemento.getPolicies()));
+        Asserts.assertFalse(EqualsBuilder.reflectionEquals(mementoRawData.getLocations(), transformedMemento.getLocations()));
+    }
+
+    @Test
+    public void testSelectionWithoutOrphanedLocationsInData() {
+
+        Set<String> locationsToKeep = MutableSet.of(
+                "tjdywoxbji",
+                "pudtixbw89"
+        );
+
+        initManagementContextAndPersistence(persistenceDirWithoutOrphanedLocations);
+        BrooklynMementoRawData mementoRawData = managementContext.getRebindManager().retrieveMementoRawData();
+
+        Set<String> allReferencedLocationsFoundByTransformer = transformer.findAllReferencedLocations(mementoRawData);
+        Map<String, String> locationsToKeepFoundByTransformer = transformer.findLocationsToKeep(mementoRawData);
+
+        Asserts.assertEquals(allReferencedLocationsFoundByTransformer, locationsToKeep);
+        Asserts.assertEquals(locationsToKeepFoundByTransformer.keySet(), locationsToKeep);
+
+        Map<String, String> locationsNotToKeepDueToTransormer = MutableMap.of();
+        locationsNotToKeepDueToTransormer.putAll(mementoRawData.getLocations());
+        for (Map.Entry location: locationsToKeepFoundByTransformer.entrySet()) {
+            locationsNotToKeepDueToTransormer.remove(location.getKey());
+        }
+        Set<String> notReferencedLocationsDueToTransormer = MutableSet.of();
+        notReferencedLocationsDueToTransormer.addAll(mementoRawData.getLocations().keySet());
+        for (String location: allReferencedLocationsFoundByTransformer) {
+            notReferencedLocationsDueToTransormer.remove(location);
+        }
+
+        Asserts.assertSize(locationsNotToKeepDueToTransormer.keySet(), 0);
+        Asserts.assertSize(notReferencedLocationsDueToTransormer, 0);
+
+        BrooklynMementoRawData transformedMemento = transformer.transform(mementoRawData);
+        Asserts.assertTrue(EqualsBuilder.reflectionEquals(mementoRawData, transformedMemento));
+    }
+
+    @Test
+    public void testCleanedCopiedPersistedState() throws Exception {
+
+        BrooklynLauncher launcher = BrooklynLauncher.newInstance()
+                .localBrooklynPropertiesFile(localBrooklynProperties)
+                .brooklynProperties(OsgiManager.USE_OSGI, false)
+                .persistMode(PersistMode.AUTO)
+                .persistenceDir(persistenceDirWithOrphanedLocations)
+                .persistenceLocation(persistenceLocation)
+                .highAvailabilityMode(HighAvailabilityMode.DISABLED);
+
+        try {
+            launcher.cleanOrphanedLocations(destinationDir, destinationLocation);
+
+        } catch (Exception e) {
+            throw new Exception(e);
+        } finally {
+            launcher.terminate();
+        }
+
+        initManagementContextAndPersistence(destinationDir);
+        BrooklynMementoRawData mementoRawDataFromCleanedState = managementContext.getRebindManager().retrieveMementoRawData();
+        Asserts.assertTrue(mementoRawDataFromCleanedState.getEntities().size() != 0);
+        Asserts.assertTrue(mementoRawDataFromCleanedState.getLocations().size() != 0);
+
+
+        initManagementContextAndPersistence(persistenceDirWithoutOrphanedLocations);
+        BrooklynMementoRawData mementoRawData = managementContext.getRebindManager().retrieveMementoRawData();
+
+        Asserts.assertTrue(EqualsBuilder.reflectionEquals(mementoRawData, mementoRawDataFromCleanedState));
+    }
+
+    @Test
+    public void testMultipleLocationOccurrenceInEntity() {
+        Set<String> allReferencedLocations = MutableSet.of(
+                "m72nvkl799",
+                "m72nTYl799",
+                "m72LKVN799",
+                "jf4rwubqyb",
+                "jf4rwuTTTb",
+                "jf4rwuHHHb",
+                "m72nvkl799",
+                "m72nPTUF99",
+                "m72nhtDr99"
+        );
+
+        Set<String> locationsToKeep = MutableSet.of(
+                "m72nvkl799",
+                "jf4rwubqyb"
+        );
+
+        initManagementContextAndPersistence(persistenceDirWithMultipleLocationsOccurrence);
+
+        BrooklynMementoRawData mementoRawData = managementContext.getRebindManager().retrieveMementoRawData();
+        Set<String> allReferencedLocationsFoundByTransformer = transformer.findAllReferencedLocations(mementoRawData);
+        Map<String, String> locationsToKeepFoundByTransformer = transformer.findLocationsToKeep(mementoRawData);
+
+        Asserts.assertEquals(allReferencedLocationsFoundByTransformer, allReferencedLocations);
+        Asserts.assertEquals(locationsToKeepFoundByTransformer.keySet(), locationsToKeep);
+    }
+
+    @AfterMethod
+    public void cleanCopiedPersistedState() {
+
+    }
+}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/59f8e8b4/server-cli/src/main/java/org/apache/brooklyn/cli/Main.java
----------------------------------------------------------------------
diff --git a/server-cli/src/main/java/org/apache/brooklyn/cli/Main.java b/server-cli/src/main/java/org/apache/brooklyn/cli/Main.java
index 2d65ad0..53a7829 100644
--- a/server-cli/src/main/java/org/apache/brooklyn/cli/Main.java
+++ b/server-cli/src/main/java/org/apache/brooklyn/cli/Main.java
@@ -827,6 +827,82 @@ public class Main extends AbstractMain {
         }
     }
 
+    @Command(name = "clean-orphaned-locations", description = "Removes existing orphaned locations")
+    public static class CleanOrphanedLocationsCommand extends BrooklynCommandCollectingArgs {
+
+        @Option(name = { "--localBrooklynProperties" }, title = "local brooklyn.properties file",
+                description = "local brooklyn.properties file, specific to this launch (appending to and overriding global properties)")
+        public String localBrooklynProperties;
+
+        @Option(name = { "--persistenceDir" }, title = "persistence dir",
+                description = "The directory to read persisted state (or container name if using an object store)")
+        public String persistenceDir;
+
+        @Option(name = { "--persistenceLocation" }, title = "persistence location",
+                description = "The location spec for an object store to read persisted state")
+        public String persistenceLocation;
+
+        @Option(name = { "--destinationDir" }, required = true, title = "destination dir",
+                description = "The directory to copy persistence data to without orphaned locations")
+        public String destinationDir;
+
+        @Option(name = { "--destinationLocation" }, title = "persistence location",
+                description = "The location spec for an object store to copy data to")
+        public String destinationLocation;
+
+        @Option(name = { "--transformations" }, title = "transformations",
+                description = "local transformations file, to be applied to the copy of the data before uploading it")
+        public String transformations;
+
+        @Override
+        public Void call() throws Exception {
+            checkNotNull(destinationDir, "orphanedReferencesTmpDir");
+
+            BrooklynLauncher launcher;
+            failIfArguments();
+
+            try {
+                log.info("Retrieving and copying persisted state to " + destinationDir + " without orphaned locations");
+
+
+                PersistMode persistMode = PersistMode.AUTO;
+                HighAvailabilityMode highAvailabilityMode = HighAvailabilityMode.DISABLED;
+
+                launcher = BrooklynLauncher.newInstance()
+                        .localBrooklynPropertiesFile(localBrooklynProperties)
+                        .brooklynProperties(OsgiManager.USE_OSGI, false)
+                        .persistMode(persistMode)
+                        .persistenceDir(persistenceDir)
+                        .persistenceLocation(persistenceLocation)
+                        .highAvailabilityMode(highAvailabilityMode);
+
+            } catch (FatalConfigurationRuntimeException e) {
+                throw e;
+            } catch (Exception e) {
+                throw new FatalConfigurationRuntimeException("Fatal error configuring Brooklyn launch: "+e.getMessage(), e);
+            }
+
+            try {
+                launcher.cleanOrphanedLocations(destinationDir, destinationLocation);
+            } catch (FatalRuntimeException e) {
+                throw e;
+            } catch (Exception e) {
+                Exceptions.propagateIfFatal(e);
+                log.error("Error retrieving persisted state: "+Exceptions.collapseText(e), e);
+                Exceptions.propagate(e);
+            } finally {
+                try {
+                    launcher.terminate();
+                } catch (Exception e2) {
+                    log.warn("Subsequent error during termination: "+e2);
+                    log.debug("Details of subsequent error during termination: "+e2, e2);
+                }
+            }
+
+            return null;
+        }
+    }
+
     @Command(name = "copy-state", description = "Retrieves persisted state")
     public static class CopyStateCommand extends BrooklynCommandCollectingArgs {
 
@@ -931,6 +1007,7 @@ public class Main extends AbstractMain {
                         HelpCommand.class,
                         cliInfoCommand(),
                         GeneratePasswordCommand.class,
+                        CleanOrphanedLocationsCommand.class,
                         CopyStateCommand.class,
                         ListAllCommand.class,
                         cliLaunchCommand()