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.s...@gmail.com> Authored: Wed Jul 20 09:16:14 2016 +0100 Committer: Ivana Yovcheva <ivana.yovch...@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