You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@brooklyn.apache.org by he...@apache.org on 2017/10/27 10:57:03 UTC

[1/7] brooklyn-server git commit: Support force-bundle-removal on init

Repository: brooklyn-server
Updated Branches:
  refs/heads/master 05d6ee096 -> 9045005dd


Support force-bundle-removal on init


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

Branch: refs/heads/master
Commit: dba226fc2aa7fa6fd8eb6569fadb3aaf6bb6b052
Parents: 98731c2
Author: Aled Sage <al...@gmail.com>
Authored: Fri Oct 20 15:41:50 2017 +0100
Committer: Aled Sage <al...@gmail.com>
Committed: Mon Oct 23 17:41:46 2017 +0100

----------------------------------------------------------------------
 .../catalog/internal/BundleUpgradeParser.java   | 150 ++++++++++++-------
 .../catalog/internal/CatalogInitialization.java |  57 ++++---
 .../core/mgmt/rebind/RebindIteration.java       |  13 ++
 .../internal/BundleUpgradeParserTest.java       |  79 +++++++++-
 .../AbstractBrooklynLauncherRebindTest.java     | 119 +++++++++++----
 .../BrooklynLauncherUpgradeCatalogOsgiTest.java |  78 ++++++++--
 6 files changed, 378 insertions(+), 118 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/dba226fc/core/src/main/java/org/apache/brooklyn/core/catalog/internal/BundleUpgradeParser.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/brooklyn/core/catalog/internal/BundleUpgradeParser.java b/core/src/main/java/org/apache/brooklyn/core/catalog/internal/BundleUpgradeParser.java
index 31df462..0773014 100644
--- a/core/src/main/java/org/apache/brooklyn/core/catalog/internal/BundleUpgradeParser.java
+++ b/core/src/main/java/org/apache/brooklyn/core/catalog/internal/BundleUpgradeParser.java
@@ -21,43 +21,62 @@ package org.apache.brooklyn.core.catalog.internal;
 import static com.google.common.base.Preconditions.checkNotNull;
 
 import java.util.ArrayList;
+import java.util.Collection;
 import java.util.Dictionary;
 import java.util.LinkedHashSet;
 import java.util.List;
 import java.util.Set;
 
-import javax.annotation.Nullable;
-
-import org.apache.brooklyn.api.catalog.BrooklynCatalog;
 import org.apache.brooklyn.api.catalog.CatalogItem;
+import org.apache.brooklyn.util.osgi.VersionedName;
 import org.apache.brooklyn.util.text.BrooklynVersionSyntax;
 import org.apache.brooklyn.util.text.QuotedStringTokenizer;
 import org.apache.brooklyn.util.text.Strings;
 import org.osgi.framework.Bundle;
-import org.osgi.framework.Version;
 import org.osgi.framework.VersionRange;
 
 import com.google.common.annotations.Beta;
 import com.google.common.annotations.VisibleForTesting;
 import com.google.common.base.Objects;
+import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableSet;
 
 public class BundleUpgradeParser {
 
     @Beta
-    public static final String MANIFEST_HEADER_REMOVE_LEGACY_ITEMS = "brooklyn-catalog-force-remove-legacy-items";
+    public static final String MANIFEST_HEADER_FORCE_REMOVE_LEGACY_ITEMS = "brooklyn-catalog-force-remove-legacy-items";
+
+    @Beta
+    public static final String MANIFEST_HEADER_FORCE_REMOVE_BUNDLES = "brooklyn-catalog-force-remove-bundles";
+    
+    @Beta
+    public static final String MANIFEST_HEADER_UPGRADE_BUNDLES = "brooklyn-catalog-upgrade-for-bundles";
 
-    public static class TypeUpgrades {
-        static final TypeUpgrades EMPTY = new TypeUpgrades(ImmutableSet.of());
+    /**
+     * The result from parsing bundle(s) to find their upgrade info.
+     */
+    public static class CatalogUpgrades {
+        static final CatalogUpgrades EMPTY = new CatalogUpgrades(builder());
         
         static class Builder {
             private Set<VersionRangedName> removedLegacyItems = new LinkedHashSet<>();
-            
-            public void addAll(TypeUpgrades other) {
+            private Set<VersionRangedName> removedBundles = new LinkedHashSet<>();
+
+            public Builder removedLegacyItems(Collection<VersionRangedName> vals) {
+                removedLegacyItems.addAll(vals);
+                return this;
+            }
+            public Builder removedBundles(Collection<VersionRangedName> vals) {
+                removedBundles.addAll(vals);
+                return this;
+            }
+            public Builder addAll(CatalogUpgrades other) {
                 removedLegacyItems.addAll(other.removedLegacyItems);
+                removedBundles.addAll(other.removedBundles);
+                return this;
             }
-            public TypeUpgrades build() {
-                return new TypeUpgrades(removedLegacyItems);
+            public CatalogUpgrades build() {
+                return new CatalogUpgrades(this);
             }
         }
         
@@ -65,23 +84,38 @@ public class BundleUpgradeParser {
             return new Builder();
         }
         
-        private Set<VersionRangedName> removedLegacyItems; // TODO Want version ranges as well
+        private Set<VersionRangedName> removedLegacyItems;
+        private Set<VersionRangedName> removedBundles;
         
-        public TypeUpgrades(Iterable<? extends VersionRangedName> removedLegacyItems) {
-            this.removedLegacyItems = ImmutableSet.copyOf(removedLegacyItems);
+        public CatalogUpgrades(Builder builder) {
+            this.removedLegacyItems = ImmutableSet.copyOf(builder.removedLegacyItems);
+            this.removedBundles = ImmutableSet.copyOf(builder.removedBundles);
         }
 
         public boolean isEmpty() {
-            return removedLegacyItems.isEmpty();
+            return removedLegacyItems.isEmpty() && removedBundles.isEmpty();
         }
 
-        public boolean isRemoved(CatalogItem<?, ?> legacyCatalogItem) {
-            String name = legacyCatalogItem.getSymbolicName();
-            String versionStr = legacyCatalogItem.getVersion();
-            Version version = Version.valueOf(BrooklynVersionSyntax.toValidOsgiVersion(versionStr == null ? BrooklynCatalog.DEFAULT_VERSION : versionStr));
-            
-            for (VersionRangedName removedLegacyItem : removedLegacyItems) {
-                if (removedLegacyItem.getSymbolicName().equals(name) && removedLegacyItem.getOsgiVersionRange().includes(version)) {
+        public Set<VersionRangedName> getRemovedLegacyItems() {
+            return removedLegacyItems;
+        }
+        
+        public Set<VersionRangedName> getRemovedBundles() {
+            return removedBundles;
+        }
+
+        public boolean isLegacyItemRemoved(CatalogItem<?, ?> legacyCatalogItem) {
+            VersionedName name = new VersionedName(legacyCatalogItem.getSymbolicName(), legacyCatalogItem.getVersion());
+            return contains(removedLegacyItems, name);
+        }
+
+        public boolean isBundleRemoved(VersionedName bundle) {
+            return contains(removedBundles, bundle);
+        }
+        
+        public boolean contains(Iterable<VersionRangedName> names, VersionedName name) {
+            for (VersionRangedName contender : names) {
+                if (contender.getSymbolicName().equals(name.getSymbolicName()) && contender.getOsgiVersionRange().includes(name.getOsgiVersion())) {
                     return true;
                 }
             }
@@ -89,12 +123,18 @@ public class BundleUpgradeParser {
         }
     }
     
-    /** Records a name (string) and version range (string),
-     * with conveniences for pretty-printing and converting to OSGi format. */
+    /**
+     * Records a name (string) and version range (string),
+     * with conveniences for pretty-printing and converting to OSGi format.
+     * 
+     * Implementation-wise, this is similar to {@link VersionedName}, but is intended
+     * as internal-only so is cut down to only what is needed.
+     */
     public static class VersionRangedName {
         private final String name;
         private final String v;
-        
+        private transient volatile VersionRange cachedOsgiVersionRange;
+
         public static VersionRangedName fromString(String val, boolean singleVersionIsOsgiRange) {
             if (Strings.isBlank(val)) {
                 throw new IllegalArgumentException("Must not be blank");
@@ -112,13 +152,14 @@ public class BundleUpgradeParser {
             }
         }
 
-        public VersionRangedName(String name, String v) {
-            this.name = checkNotNull(name, "name");
-            this.v = v;
-        }
-        public VersionRangedName(String name, @Nullable VersionRange v) {
+        public VersionRangedName(String name, VersionRange v) {
             this.name = checkNotNull(name, "name").toString();
-            this.v = v==null ? null : v.toString();
+            this.v = checkNotNull(v, "versionRange").toString();
+        }
+        
+        private VersionRangedName(String name, String v) {
+            this.name = checkNotNull(name, "name");
+            this.v = checkNotNull(v, "versionRange");
         }
         
         @Override
@@ -130,35 +171,17 @@ public class BundleUpgradeParser {
             return name + ":" + getOsgiVersionRange();
         }
 
-        public boolean equals(String sn, String v) {
-            return name.equals(sn) && Objects.equal(this.v, v);
-        }
-
-        public boolean equals(String sn, VersionRange v) {
-            return name.equals(sn) && Objects.equal(getOsgiVersionRange(), v);
-        }
-
         public String getSymbolicName() {
             return name;
         }
 
-        private transient VersionRange cachedOsgiVersionRange;
-        @Nullable
         public VersionRange getOsgiVersionRange() {
-            if (cachedOsgiVersionRange==null && v!=null) {
-                cachedOsgiVersionRange = v==null ? null : VersionRange.valueOf(BrooklynVersionSyntax.toValidOsgiVersionRange(v));
+            if (cachedOsgiVersionRange == null) {
+                cachedOsgiVersionRange = VersionRange.valueOf(BrooklynVersionSyntax.toValidOsgiVersionRange(v));
             }
             return cachedOsgiVersionRange;
         }
 
-        @Nullable
-        public String getOsgiVersionRangeString() {
-            VersionRange ov = getOsgiVersionRange();
-            if (ov==null) return null;
-            return ov.toString();
-        }
-
-        @Nullable
         public String getVersionString() {
             return v;
         }
@@ -178,14 +201,25 @@ public class BundleUpgradeParser {
         }
     }
 
-    public static TypeUpgrades parseBundleManifestForTypeUpgrades(Bundle bundle) {
+    public static CatalogUpgrades parseBundleManifestForCatalogUpgrades(Bundle bundle) {
         Dictionary<String, String> headers = bundle.getHeaders();
-        String removedLegacyItems = headers.get(MANIFEST_HEADER_REMOVE_LEGACY_ITEMS);
-        if (removedLegacyItems != null) {
-            List<VersionRangedName> versionedItems = parseVersionRangedNameList(removedLegacyItems, false);
-            return new TypeUpgrades(versionedItems);
-        }
-        return TypeUpgrades.EMPTY;
+        String removedLegacyItemsHeader = headers.get(MANIFEST_HEADER_FORCE_REMOVE_LEGACY_ITEMS);
+        String removedBundlesHeader = headers.get(MANIFEST_HEADER_FORCE_REMOVE_BUNDLES);
+        List<VersionRangedName> removedLegacyItems = ImmutableList.of();
+        List<VersionRangedName> removedBundles = ImmutableList.of();
+        if (removedLegacyItemsHeader == null && removedBundlesHeader == null) {
+            return CatalogUpgrades.EMPTY;
+        }
+        if (removedLegacyItemsHeader != null) {
+            removedLegacyItems = parseVersionRangedNameList(removedLegacyItemsHeader, false);
+        }
+        if (removedBundlesHeader != null) {
+            removedBundles = parseVersionRangedNameList(removedBundlesHeader, false);
+        }
+        return CatalogUpgrades.builder()
+                .removedLegacyItems(removedLegacyItems)
+                .removedBundles(removedBundles)
+                .build();
     }
     
     @VisibleForTesting

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/dba226fc/core/src/main/java/org/apache/brooklyn/core/catalog/internal/CatalogInitialization.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/brooklyn/core/catalog/internal/CatalogInitialization.java b/core/src/main/java/org/apache/brooklyn/core/catalog/internal/CatalogInitialization.java
index 87e52e0..9f9da67 100644
--- a/core/src/main/java/org/apache/brooklyn/core/catalog/internal/CatalogInitialization.java
+++ b/core/src/main/java/org/apache/brooklyn/core/catalog/internal/CatalogInitialization.java
@@ -25,6 +25,7 @@ import java.io.IOException;
 import java.io.InputStream;
 import java.util.ArrayList;
 import java.util.Collection;
+import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
@@ -41,7 +42,7 @@ import org.apache.brooklyn.api.objs.BrooklynObjectType;
 import org.apache.brooklyn.api.typereg.BrooklynTypeRegistry.RegisteredTypeKind;
 import org.apache.brooklyn.api.typereg.ManagedBundle;
 import org.apache.brooklyn.api.typereg.RegisteredType;
-import org.apache.brooklyn.core.catalog.internal.BundleUpgradeParser.TypeUpgrades;
+import org.apache.brooklyn.core.catalog.internal.BundleUpgradeParser.CatalogUpgrades;
 import org.apache.brooklyn.core.mgmt.ManagementContextInjectable;
 import org.apache.brooklyn.core.mgmt.ha.OsgiBundleInstallationResult;
 import org.apache.brooklyn.core.mgmt.ha.OsgiManager;
@@ -528,37 +529,59 @@ public class CatalogInitialization implements ManagementContextInjectable {
     }
 
     private PersistedCatalogState filterPersistedState(PersistedCatalogState persistedState, RebindLogger rebindLogger) {
-        Maybe<OsgiManager> osgiManager = ((ManagementContextInternal)managementContext).getOsgiManager();
-        if (osgiManager.isAbsent()) {
-            // Could be running tests; do no filtering
+        CatalogUpgrades catalogUpgrades = findCatalogUpgrades(rebindLogger);
+        
+        if (catalogUpgrades.isEmpty()) {
             return persistedState;
+        } else {
+            rebindLogger.info("Filtering out persisted catalog: removedBundles="+catalogUpgrades.getRemovedBundles()+"; removedLegacyItems="+catalogUpgrades.getRemovedLegacyItems());
         }
         
-        TypeUpgrades.Builder typeUpgradesBuilder = TypeUpgrades.builder();
-        Collection<ManagedBundle> managedBundles = osgiManager.get().getManagedBundles().values();
-        for (ManagedBundle managedBundle : managedBundles) {
-            Maybe<Bundle> bundle = osgiManager.get().findBundle(managedBundle);
-            if (bundle.isPresent()) {
-                typeUpgradesBuilder.addAll(BundleUpgradeParser.parseBundleManifestForTypeUpgrades(bundle.get()));
+        Map<VersionedName, InstallableManagedBundle> bundles = new LinkedHashMap<>();
+        for (Map.Entry<VersionedName, InstallableManagedBundle> entry : persistedState.getBundles().entrySet()) {
+            if (catalogUpgrades.isBundleRemoved(entry.getKey())) {
+                rebindLogger.debug("Filtering out persisted bundle "+entry.getKey());
+            } else {
+                bundles.put(entry.getKey(), entry.getValue());
             }
         }
-        TypeUpgrades typeUpgrades = typeUpgradesBuilder.build();
-        
-        if (typeUpgrades.isEmpty()) {
-            return persistedState;
-        }
-        Map<VersionedName, InstallableManagedBundle> bundles = persistedState.getBundles();
+
         List<CatalogItem<?, ?>> legacyCatalogItems = new ArrayList<>();
         for (CatalogItem<?, ?> legacyCatalogItem : persistedState.getLegacyCatalogItems()) {
-            if (!typeUpgrades.isRemoved(legacyCatalogItem)) {
+            if (catalogUpgrades.isLegacyItemRemoved(legacyCatalogItem)) {
+                rebindLogger.debug("Filtering out persisted legacy catalog item "+legacyCatalogItem.getId());
+            } else {
                 legacyCatalogItems.add(legacyCatalogItem);
             }
         }
+        
         return new PersistedCatalogState(bundles, legacyCatalogItems);
     }
 
+    private CatalogUpgrades findCatalogUpgrades(RebindLogger rebindLogger) {
+        Maybe<OsgiManager> osgiManager = ((ManagementContextInternal)managementContext).getOsgiManager();
+        if (osgiManager.isAbsent()) {
+            // Can't find any bundles to tell if there are upgrades. Could be running tests; do no filtering.
+            return CatalogUpgrades.EMPTY;
+        }
+        
+        CatalogUpgrades.Builder catalogUpgradesBuilder = CatalogUpgrades.builder();
+        Collection<ManagedBundle> managedBundles = osgiManager.get().getManagedBundles().values();
+        for (ManagedBundle managedBundle : managedBundles) {
+            Maybe<Bundle> bundle = osgiManager.get().findBundle(managedBundle);
+            if (bundle.isPresent()) {
+                catalogUpgradesBuilder.addAll(BundleUpgradeParser.parseBundleManifestForCatalogUpgrades(bundle.get()));
+            } else {
+                rebindLogger.info("Managed bundle "+managedBundle.getId()+" not found by OSGi Manager; "
+                        + "ignoring when calculating persisted state catalog upgrades");
+            }
+        }
+        return catalogUpgradesBuilder.build();
+    }
+    
     public interface RebindLogger {
         void debug(String message, Object... args);
+        void info(String message, Object... args);
     }
     
     public interface InstallableManagedBundle {

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/dba226fc/core/src/main/java/org/apache/brooklyn/core/mgmt/rebind/RebindIteration.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/brooklyn/core/mgmt/rebind/RebindIteration.java b/core/src/main/java/org/apache/brooklyn/core/mgmt/rebind/RebindIteration.java
index 20c20e3..3038429 100644
--- a/core/src/main/java/org/apache/brooklyn/core/mgmt/rebind/RebindIteration.java
+++ b/core/src/main/java/org/apache/brooklyn/core/mgmt/rebind/RebindIteration.java
@@ -331,6 +331,10 @@ public abstract class RebindIteration {
             public void debug(String message, Object... args) {
                 logRebindingDebug(message, args);
             }
+            @Override
+            public void info(String message, Object... args) {
+                logRebindingInfo(message, args);
+            }
         };
 
         class InstallableManagedBundleImpl implements CatalogInitialization.InstallableManagedBundle {
@@ -1276,6 +1280,15 @@ public abstract class RebindIteration {
         }
     }
     
+    /** logs at info, except during subsequent read-only rebinds, in which it logs trace */
+    protected void logRebindingInfo(String message, Object... args) {
+        if (shouldLogRebinding()) {
+            LOG.info(message, args);
+        } else {
+            LOG.trace(message, args);
+        }
+    }
+    
     protected boolean shouldLogRebinding() {
         return (readOnlyRebindCount.get() < 5) || (readOnlyRebindCount.get()%1000==0);
     }

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/dba226fc/core/src/test/java/org/apache/brooklyn/core/catalog/internal/BundleUpgradeParserTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/org/apache/brooklyn/core/catalog/internal/BundleUpgradeParserTest.java b/core/src/test/java/org/apache/brooklyn/core/catalog/internal/BundleUpgradeParserTest.java
index 08cc4c9..a409eaf 100644
--- a/core/src/test/java/org/apache/brooklyn/core/catalog/internal/BundleUpgradeParserTest.java
+++ b/core/src/test/java/org/apache/brooklyn/core/catalog/internal/BundleUpgradeParserTest.java
@@ -19,15 +19,27 @@
 package org.apache.brooklyn.core.catalog.internal;
 
 import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertFalse;
+import static org.testng.Assert.assertTrue;
 
+import java.util.Dictionary;
+import java.util.Hashtable;
 import java.util.List;
+import java.util.Map;
 
+import org.apache.brooklyn.api.catalog.CatalogItem;
+import org.apache.brooklyn.core.catalog.internal.BundleUpgradeParser.CatalogUpgrades;
 import org.apache.brooklyn.core.catalog.internal.BundleUpgradeParser.VersionRangedName;
+import org.apache.brooklyn.test.Asserts;
+import org.apache.brooklyn.util.osgi.VersionedName;
+import org.mockito.Mockito;
+import org.osgi.framework.Bundle;
 import org.osgi.framework.Version;
 import org.osgi.framework.VersionRange;
 import org.testng.annotations.Test;
 
 import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
 
 public class BundleUpgradeParserTest {
 
@@ -37,6 +49,18 @@ public class BundleUpgradeParserTest {
     private VersionRangedName barFrom0lessThan1 = new VersionRangedName("bar", from0lessThan1);
 
     @Test
+    public void testVersionRangedName() throws Exception {
+        assertEquals(VersionRangedName.fromString("foo:0.1.0", true).toOsgiString(), "foo:0.1.0");
+        assertEquals(VersionRangedName.fromString("foo:0.1.0", false).toOsgiString(), "foo:[0.1.0,0.1.0]");
+        assertEquals(VersionRangedName.fromString("foo:[0,1)", false).toOsgiString(), "foo:[0.0.0,1.0.0)");
+        
+        assertVersionRangedNameFails("foo", "'foo' must be of 'name:versionRange' syntax");
+        assertVersionRangedNameFails("foo:bar:0.1.0", "has too many parts");
+        assertVersionRangedNameFails("", "Must not be blank");
+        assertVersionRangedNameFails(null, "Must not be blank");
+    }
+    
+    @Test
     public void testParseSingleQuotedVal() throws Exception {
         String input = "\"foo:[0,1.0.0)\"";
         assertParsed(input, ImmutableList.of(fooFrom0lessThan1));
@@ -60,7 +84,51 @@ public class BundleUpgradeParserTest {
         assertParsed(input, ImmutableList.of(new VersionRangedName("foo", exactly0dot1)));
     }
 
-    protected void assertParsed(String input, List<VersionRangedName> expected) throws Exception {
+    @Test
+    public void testParseBundleEmptyManifest() throws Exception {
+        Bundle bundle = newMockBundle(ImmutableMap.of());
+        
+        CatalogUpgrades upgrades = BundleUpgradeParser.parseBundleManifestForCatalogUpgrades(bundle);
+        assertTrue(upgrades.isEmpty());
+        assertFalse(upgrades.isBundleRemoved(new VersionedName("org.example.brooklyn.mybundle", "0.1.0")));
+        assertFalse(upgrades.isLegacyItemRemoved(newMockCatalogItem("foo", "0.1.0")));
+    }
+
+    @Test
+    public void testParseBundleManifest() throws Exception {
+        Bundle bundle = newMockBundle(ImmutableMap.of(
+                BundleUpgradeParser.MANIFEST_HEADER_FORCE_REMOVE_LEGACY_ITEMS, "\"foo:[0,1.0.0)\",\"bar:[0,1.0.0)\"",
+                BundleUpgradeParser.MANIFEST_HEADER_FORCE_REMOVE_BUNDLES, "\"org.example.brooklyn.mybundle:[0,1.0.0)\""));
+        
+        CatalogUpgrades upgrades = BundleUpgradeParser.parseBundleManifestForCatalogUpgrades(bundle);
+        assertFalse(upgrades.isEmpty());
+        assertTrue(upgrades.isBundleRemoved(new VersionedName("org.example.brooklyn.mybundle", "0.1.0")));
+        assertFalse(upgrades.isBundleRemoved(new VersionedName("org.example.brooklyn.mybundle", "1.0.0")));
+        
+        assertTrue(upgrades.isLegacyItemRemoved(newMockCatalogItem("foo", "0.1.0")));
+        assertFalse(upgrades.isLegacyItemRemoved(newMockCatalogItem("foo", "1.0.0")));
+        assertTrue(upgrades.isLegacyItemRemoved(newMockCatalogItem("bar", "0.1.0")));
+        assertFalse(upgrades.isLegacyItemRemoved(newMockCatalogItem("bar", "1.0.0")));
+        assertFalse(upgrades.isLegacyItemRemoved(newMockCatalogItem("different", "0.1.0")));
+    }
+
+    private Bundle newMockBundle(Map<String, String> rawHeaders) {
+        Dictionary<String, String> headers = new Hashtable<>(rawHeaders);
+        Bundle result = Mockito.mock(Bundle.class);
+        Mockito.when(result.getHeaders()).thenReturn(headers);
+        return result;
+    }
+
+    private CatalogItem<?,?> newMockCatalogItem(String symbolicName, String version) {
+        CatalogItem<?,?> result = Mockito.mock(CatalogItem.class);
+        Mockito.when(result.getSymbolicName()).thenReturn(symbolicName);
+        Mockito.when(result.getVersion()).thenReturn(version);
+        Mockito.when(result.getId()).thenReturn(symbolicName+":"+version);
+        Mockito.when(result.getCatalogItemId()).thenReturn(symbolicName+":"+version);
+        return result;
+    }
+    
+    private void assertParsed(String input, List<VersionRangedName> expected) throws Exception {
         List<VersionRangedName> actual = BundleUpgradeParser.parseVersionRangedNameList(input, false);
         assertEquals(actual.size(), expected.size(), "actual="+actual); 
         for (int i = 0; i < actual.size(); i++) {
@@ -68,4 +136,13 @@ public class BundleUpgradeParserTest {
             assertEquals(actual.get(i).getOsgiVersionRange(), expected.get(i).getOsgiVersionRange());
         }
     }
+    
+    private void assertVersionRangedNameFails(String input, String expectedFailure, String... optionalOtherExpectedFailures) {
+        try {
+            VersionRangedName.fromString(input, false);
+            Asserts.shouldHaveFailedPreviously();
+        } catch (IllegalArgumentException e) {
+            Asserts.expectedFailureContains(e, expectedFailure, optionalOtherExpectedFailures);
+        }
+    }
 }

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/dba226fc/launcher/src/test/java/org/apache/brooklyn/launcher/AbstractBrooklynLauncherRebindTest.java
----------------------------------------------------------------------
diff --git a/launcher/src/test/java/org/apache/brooklyn/launcher/AbstractBrooklynLauncherRebindTest.java b/launcher/src/test/java/org/apache/brooklyn/launcher/AbstractBrooklynLauncherRebindTest.java
index b4b5361..0369862 100644
--- a/launcher/src/test/java/org/apache/brooklyn/launcher/AbstractBrooklynLauncherRebindTest.java
+++ b/launcher/src/test/java/org/apache/brooklyn/launcher/AbstractBrooklynLauncherRebindTest.java
@@ -20,9 +20,11 @@ package org.apache.brooklyn.launcher;
 
 import static org.testng.Assert.assertEquals;
 import static org.testng.Assert.assertNotNull;
+import static org.testng.Assert.assertNull;
 
 import java.io.ByteArrayInputStream;
 import java.io.File;
+import java.io.FileInputStream;
 import java.io.InputStream;
 import java.net.URI;
 import java.nio.charset.StandardCharsets;
@@ -53,6 +55,7 @@ import org.apache.brooklyn.util.core.ResourceUtils;
 import org.apache.brooklyn.util.core.osgi.BundleMaker;
 import org.apache.brooklyn.util.os.Os;
 import org.apache.brooklyn.util.osgi.VersionedName;
+import org.apache.brooklyn.util.stream.Streams;
 import org.apache.brooklyn.util.text.Identifiers;
 import org.apache.brooklyn.util.time.Duration;
 import org.osgi.framework.Constants;
@@ -209,6 +212,12 @@ public abstract class AbstractBrooklynLauncherRebindTest {
         assertEquals(actualCatalogItems, expectedCatalogItems, "actual="+actualCatalogItems+"; expected="+expectedCatalogItems);
     }
 
+    protected void assertNotManagedBundle(BrooklynLauncher launcher, VersionedName bundleId) {
+        ManagementContextInternal mgmt = (ManagementContextInternal)launcher.getManagementContext();
+        ManagedBundle bundle = mgmt.getOsgiManager().get().getManagedBundle(bundleId);
+        assertNull(bundle, bundleId+" should not exist");
+    }
+
     private static <T> boolean compareIterablesWithoutOrderMatters(Iterable<T> a, Iterable<T> b) {
         List<T> aList = Lists.newArrayList(a);
         List<T> bList = Lists.newArrayList(b);
@@ -216,36 +225,17 @@ public abstract class AbstractBrooklynLauncherRebindTest {
         return aList.containsAll(bList) && bList.containsAll(aList);
     }
     
-    protected void initPersistedState(Map<String, String> legacyCatalogContents) throws Exception {
-        CatalogInitialization catalogInitialization = new CatalogInitialization(CATALOG_EMPTY_INITIAL);
-        BrooklynLauncher launcher = newLauncherForTests()
-                .catalogInitialization(catalogInitialization);
-        launcher.start();
-        assertCatalogConsistsOfIds(launcher, ImmutableList.of());
-        launcher.terminate();
-        
-        for (Map.Entry<String, String> entry : legacyCatalogContents.entrySet()) {
-            addMemento(BrooklynObjectType.CATALOG_ITEM, entry.getKey(), entry.getValue());
-        }
-    }
-
-    protected void addMemento(BrooklynObjectType type, String id, String contents) throws Exception {
-        File persistedFile = getPersistanceFile(type, id);
-        Files.write(contents.getBytes(StandardCharsets.UTF_8), persistedFile);
-    }
-
-    protected File getPersistanceFile(BrooklynObjectType type, String id) {
-        String dir;
-        switch (type) {
-            case ENTITY: dir = "entities"; break;
-            case LOCATION: dir = "locations"; break;
-            case POLICY: dir = "policies"; break;
-            case ENRICHER: dir = "enrichers"; break;
-            case FEED: dir = "feeds"; break;
-            case CATALOG_ITEM: dir = "catalog"; break;
-            default: throw new UnsupportedOperationException("type="+type);
-        }
-        return new File(persistenceDir, Os.mergePaths(dir, id));
+    protected String createPersistenceManagedBundle(String randomId, VersionedName bundleName) {
+        return Joiner.on("\n").join(
+                "<managedBundle>",
+                "<brooklynVersion>1.0.0-SNAPSHOT</brooklynVersion>",
+                "<type>org.apache.brooklyn.core:org.apache.brooklyn.core.typereg.BasicManagedBundle</type>",
+                "<id>"+randomId+"</id>",
+                "<searchPath class=\"ImmutableList\"/>",
+                "<symbolicName>"+bundleName.getSymbolicName()+"</symbolicName>",
+                "<version>"+bundleName.getVersionString()+"</version>",
+                "<url>http://example.org/brooklyn/"+bundleName.getSymbolicName()+"/"+bundleName.getVersionString()+"</url>",
+                "</managedBundle>");
     }
     
     protected String createLegacyPersistenceCatalogItem(VersionedName itemName) {
@@ -293,4 +283,73 @@ public abstract class AbstractBrooklynLauncherRebindTest {
         }
         return result.toString();
     }
+    
+    public PersistedStateInitializer newPersistedStateInitializer() {
+        return new PersistedStateInitializer();
+    }
+    
+    public class PersistedStateInitializer {
+        private final Map<String, String> legacyCatalogContents = new LinkedHashMap<>();
+        private final Map<VersionedName, File> bundles = new LinkedHashMap<>();
+
+        public PersistedStateInitializer legacyCatalogItems(Map<String, String> vals) throws Exception {
+            legacyCatalogContents.putAll(vals);
+            return this;
+        }
+        
+        public PersistedStateInitializer bundle(VersionedName bundleName, File file) throws Exception {
+            bundles.put(bundleName, file);
+            return this;
+        }
+        
+        public PersistedStateInitializer bundles(Map<VersionedName, File> vals) throws Exception {
+            bundles.putAll(vals);
+            return this;
+        }
+        
+        public void initState() throws Exception {
+            initEmptyState();
+            
+            for (Map.Entry<String, String> entry : legacyCatalogContents.entrySet()) {
+                addMemento(BrooklynObjectType.CATALOG_ITEM, entry.getKey(), entry.getValue().getBytes(StandardCharsets.UTF_8));
+            }
+            for (Map.Entry<VersionedName, File> entry : bundles.entrySet()) {
+                VersionedName bundleName = entry.getKey();
+                String randomId = Identifiers.makeRandomId(8);
+                String managedBundleXml = createPersistenceManagedBundle(randomId, bundleName);
+                addMemento(BrooklynObjectType.MANAGED_BUNDLE, randomId, managedBundleXml.getBytes(StandardCharsets.UTF_8));
+                addMemento(BrooklynObjectType.MANAGED_BUNDLE, randomId+".jar", Streams.readFullyAndClose(new FileInputStream(entry.getValue())));
+            }
+        }
+
+        private void addMemento(BrooklynObjectType type, String id, byte[] contents) throws Exception {
+            File persistedFile = getPersistanceFile(type, id);
+            Files.createParentDirs(persistedFile);
+            Files.write(contents, persistedFile);
+        }
+
+        private File getPersistanceFile(BrooklynObjectType type, String id) {
+            String dir;
+            switch (type) {
+                case ENTITY: dir = "entities"; break;
+                case LOCATION: dir = "locations"; break;
+                case POLICY: dir = "policies"; break;
+                case ENRICHER: dir = "enrichers"; break;
+                case FEED: dir = "feeds"; break;
+                case CATALOG_ITEM: dir = "catalog"; break;
+                case MANAGED_BUNDLE: dir = "bundles"; break;
+                default: throw new UnsupportedOperationException("type="+type);
+            }
+            return new File(persistenceDir, Os.mergePaths(dir, id));
+        }
+        
+        private void initEmptyState() {
+            CatalogInitialization catalogInitialization = new CatalogInitialization(CATALOG_EMPTY_INITIAL);
+            BrooklynLauncher launcher = newLauncherForTests()
+                    .catalogInitialization(catalogInitialization);
+            launcher.start();
+            assertCatalogConsistsOfIds(launcher, ImmutableList.of());
+            launcher.terminate();
+        }
+    }
 }

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/dba226fc/launcher/src/test/java/org/apache/brooklyn/launcher/BrooklynLauncherUpgradeCatalogOsgiTest.java
----------------------------------------------------------------------
diff --git a/launcher/src/test/java/org/apache/brooklyn/launcher/BrooklynLauncherUpgradeCatalogOsgiTest.java b/launcher/src/test/java/org/apache/brooklyn/launcher/BrooklynLauncherUpgradeCatalogOsgiTest.java
index 1922d72..8ef3466 100644
--- a/launcher/src/test/java/org/apache/brooklyn/launcher/BrooklynLauncherUpgradeCatalogOsgiTest.java
+++ b/launcher/src/test/java/org/apache/brooklyn/launcher/BrooklynLauncherUpgradeCatalogOsgiTest.java
@@ -18,13 +18,16 @@
  */
 package org.apache.brooklyn.launcher;
 
+import static org.apache.brooklyn.core.catalog.internal.BasicBrooklynCatalog.CATALOG_BOM;
+import static org.apache.brooklyn.core.catalog.internal.BundleUpgradeParser.MANIFEST_HEADER_FORCE_REMOVE_BUNDLES;
+import static org.apache.brooklyn.core.catalog.internal.BundleUpgradeParser.MANIFEST_HEADER_FORCE_REMOVE_LEGACY_ITEMS;
+
 import java.io.File;
 import java.net.URI;
 import java.nio.charset.StandardCharsets;
 import java.util.Map;
 
 import org.apache.brooklyn.core.catalog.internal.BasicBrooklynCatalog;
-import org.apache.brooklyn.core.catalog.internal.BundleUpgradeParser;
 import org.apache.brooklyn.core.catalog.internal.CatalogInitialization;
 import org.apache.brooklyn.util.osgi.VersionedName;
 import org.testng.annotations.BeforeMethod;
@@ -67,20 +70,21 @@ public class BrooklynLauncherUpgradeCatalogOsgiTest extends AbstractBrooklynLaun
         VersionedName three_0_2_0 = VersionedName.fromString("three:0.2.0");
         VersionedName four_0_1_0 = VersionedName.fromString("four:0.1.0");
         
-        initPersistedState(ImmutableMap.<String, String>builder()
-                .put("one_0.1.0", createLegacyPersistenceCatalogItem(one_0_1_0))
-                .put("two_0.1.0", createLegacyPersistenceCatalogItem(two_0_1_0))
-                .put("two_1.0.0", createLegacyPersistenceCatalogItem(two_1_0_0))
-                .put("three_0.1.0", createLegacyPersistenceCatalogItem(three_0_1_0))
-                .put("three_0.2.0", createLegacyPersistenceCatalogItem(three_0_2_0))
-                .put("four_0.1.0", createLegacyPersistenceCatalogItem(four_0_1_0))
-                .build());
+        newPersistedStateInitializer()
+                .legacyCatalogItems(ImmutableMap.<String, String>builder()
+                    .put("one_0.1.0", createLegacyPersistenceCatalogItem(one_0_1_0))
+                    .put("two_0.1.0", createLegacyPersistenceCatalogItem(two_0_1_0))
+                    .put("two_1.0.0", createLegacyPersistenceCatalogItem(two_1_0_0))
+                    .put("three_0.1.0", createLegacyPersistenceCatalogItem(three_0_1_0))
+                    .put("three_0.2.0", createLegacyPersistenceCatalogItem(three_0_2_0))
+                    .put("four_0.1.0", createLegacyPersistenceCatalogItem(four_0_1_0))
+                    .build())
+                .initState();
         
         String bundleBom = createCatalogYaml(ImmutableList.<URI>of(), ImmutableSet.<VersionedName>of());
         VersionedName bundleName = new VersionedName("org.example.testRemoveLegacyItems", "1.0.0");
-        String removedLegacyItems = "\"one:[0,1.0.0)\",\"two:[0,1.0.0)\",\"three:0.1.0\"";
-        Map<String, String> bundleManifest = ImmutableMap.of(BundleUpgradeParser.MANIFEST_HEADER_REMOVE_LEGACY_ITEMS, removedLegacyItems);
-        File bundleFile = newTmpBundle(ImmutableMap.of(BasicBrooklynCatalog.CATALOG_BOM, bundleBom.getBytes(StandardCharsets.UTF_8)), bundleName, bundleManifest);
+        Map<String, String> bundleManifest = ImmutableMap.of(MANIFEST_HEADER_FORCE_REMOVE_LEGACY_ITEMS, "\"one:[0,1.0.0)\",\"two:[0,1.0.0)\",\"three:0.1.0\"");
+        File bundleFile = newTmpBundle(ImmutableMap.of(CATALOG_BOM, bundleBom.getBytes(StandardCharsets.UTF_8)), bundleName, bundleManifest);
         File initialBomFile = newTmpFile(createCatalogYaml(ImmutableList.of(bundleFile.toURI()), ImmutableList.of()));
         
         BrooklynLauncher launcher = newLauncherForTests(initialBomFile.getAbsolutePath());
@@ -90,4 +94,54 @@ public class BrooklynLauncherUpgradeCatalogOsgiTest extends AbstractBrooklynLaun
 
         launcher.terminate();
     }
+    
+    @Test
+    public void testForceUpgradeBundle() throws Exception {
+        VersionedName one_1_0_0 = VersionedName.fromString("one:1.0.0");
+        VersionedName one_2_0_0 = VersionedName.fromString("one:2.0.0");
+        
+        String bundleSymbolicName = "org.example.testForceUpgradeBundle";
+        VersionedName bundleVersionedName1 = new VersionedName(bundleSymbolicName, "1.0.0");
+        String bundleBom1 = createCatalogYaml(ImmutableList.<URI>of(), ImmutableSet.<VersionedName>of(one_1_0_0));
+        File bundleFile1 = newTmpBundle(ImmutableMap.of(BasicBrooklynCatalog.CATALOG_BOM, bundleBom1.getBytes(StandardCharsets.UTF_8)), bundleVersionedName1);
+
+        newPersistedStateInitializer()
+                .bundle(bundleVersionedName1, bundleFile1)
+                .initState();
+        
+        VersionedName bundleVersionedName2 = new VersionedName(bundleSymbolicName, "2.0.0");
+        String bundleBom2 = createCatalogYaml(ImmutableList.<URI>of(), ImmutableSet.<VersionedName>of(one_2_0_0));
+        Map<String, String> bundleManifest2 = ImmutableMap.of(MANIFEST_HEADER_FORCE_REMOVE_BUNDLES, "\""+bundleSymbolicName+":[0.0.0,2.0.0)\"");
+        File bundleFile2 = newTmpBundle(ImmutableMap.of(BasicBrooklynCatalog.CATALOG_BOM, bundleBom2.getBytes(StandardCharsets.UTF_8)), bundleVersionedName2, bundleManifest2);
+
+        File initialBomFile = newTmpFile(createCatalogYaml(ImmutableList.of(bundleFile2.toURI()), ImmutableList.of()));
+
+        BrooklynLauncher launcher = newLauncherForTests(initialBomFile.getAbsolutePath());
+        launcher.start();
+        assertCatalogConsistsOfIds(launcher, ImmutableList.of(one_2_0_0));
+        assertManagedBundle(launcher, bundleVersionedName2, ImmutableSet.<VersionedName>of(one_2_0_0));
+        assertNotManagedBundle(launcher, bundleVersionedName1);
+        launcher.terminate();
+    }
+    
+    // Simple test (no upgrade), important for validating that other tests really do as expected!
+    @Test
+    public void testLoadsBundleFromPersistedState() throws Exception {
+        VersionedName one_1_0_0 = VersionedName.fromString("one:1.0.0");
+        
+        String bundleSymbolicName = "org.example.testForceUpgradeBundle";
+        VersionedName bundleVersionedName = new VersionedName(bundleSymbolicName, "1.0.0");
+        String bundleBom = createCatalogYaml(ImmutableList.<URI>of(), ImmutableSet.<VersionedName>of(one_1_0_0));
+        File bundleFile = newTmpBundle(ImmutableMap.of(BasicBrooklynCatalog.CATALOG_BOM, bundleBom.getBytes(StandardCharsets.UTF_8)), bundleVersionedName);
+
+        newPersistedStateInitializer()
+                .bundle(bundleVersionedName, bundleFile)
+                .initState();
+        
+        BrooklynLauncher launcher = newLauncherForTests(CATALOG_EMPTY_INITIAL);
+        launcher.start();
+        assertCatalogConsistsOfIds(launcher, ImmutableList.of(one_1_0_0));
+        assertManagedBundle(launcher, bundleVersionedName, ImmutableSet.<VersionedName>of(one_1_0_0));
+        launcher.terminate();
+    }
 }


[2/7] brooklyn-server git commit: Support catalog item removal on init

Posted by he...@apache.org.
Support catalog item removal on init


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

Branch: refs/heads/master
Commit: 98731c212602e96428c32fbbdf2a0cc1fe91cbda
Parents: 05d6ee0
Author: Aled Sage <al...@gmail.com>
Authored: Thu Oct 19 09:47:57 2017 +0100
Committer: Aled Sage <al...@gmail.com>
Committed: Mon Oct 23 17:41:46 2017 +0100

----------------------------------------------------------------------
 .../catalog/internal/BundleUpgradeParser.java   | 205 +++++++++++++++++++
 .../catalog/internal/CatalogInitialization.java |  39 +++-
 .../internal/BundleUpgradeParserTest.java       |  71 +++++++
 .../AbstractBrooklynLauncherRebindTest.java     | 137 ++++++++++++-
 .../BrooklynLauncherRebindCatalogOsgiTest.java  |  47 -----
 .../BrooklynLauncherUpgradeCatalogOsgiTest.java |  93 +++++++++
 .../util/text/BrooklynVersionSyntax.java        |  26 +++
 .../util/text/BrooklynVersionSyntaxTest.java    |  15 ++
 8 files changed, 572 insertions(+), 61 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/98731c21/core/src/main/java/org/apache/brooklyn/core/catalog/internal/BundleUpgradeParser.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/brooklyn/core/catalog/internal/BundleUpgradeParser.java b/core/src/main/java/org/apache/brooklyn/core/catalog/internal/BundleUpgradeParser.java
new file mode 100644
index 0000000..31df462
--- /dev/null
+++ b/core/src/main/java/org/apache/brooklyn/core/catalog/internal/BundleUpgradeParser.java
@@ -0,0 +1,205 @@
+/*
+ * 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.catalog.internal;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import java.util.ArrayList;
+import java.util.Dictionary;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Set;
+
+import javax.annotation.Nullable;
+
+import org.apache.brooklyn.api.catalog.BrooklynCatalog;
+import org.apache.brooklyn.api.catalog.CatalogItem;
+import org.apache.brooklyn.util.text.BrooklynVersionSyntax;
+import org.apache.brooklyn.util.text.QuotedStringTokenizer;
+import org.apache.brooklyn.util.text.Strings;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.Version;
+import org.osgi.framework.VersionRange;
+
+import com.google.common.annotations.Beta;
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Objects;
+import com.google.common.collect.ImmutableSet;
+
+public class BundleUpgradeParser {
+
+    @Beta
+    public static final String MANIFEST_HEADER_REMOVE_LEGACY_ITEMS = "brooklyn-catalog-force-remove-legacy-items";
+
+    public static class TypeUpgrades {
+        static final TypeUpgrades EMPTY = new TypeUpgrades(ImmutableSet.of());
+        
+        static class Builder {
+            private Set<VersionRangedName> removedLegacyItems = new LinkedHashSet<>();
+            
+            public void addAll(TypeUpgrades other) {
+                removedLegacyItems.addAll(other.removedLegacyItems);
+            }
+            public TypeUpgrades build() {
+                return new TypeUpgrades(removedLegacyItems);
+            }
+        }
+        
+        public static Builder builder() {
+            return new Builder();
+        }
+        
+        private Set<VersionRangedName> removedLegacyItems; // TODO Want version ranges as well
+        
+        public TypeUpgrades(Iterable<? extends VersionRangedName> removedLegacyItems) {
+            this.removedLegacyItems = ImmutableSet.copyOf(removedLegacyItems);
+        }
+
+        public boolean isEmpty() {
+            return removedLegacyItems.isEmpty();
+        }
+
+        public boolean isRemoved(CatalogItem<?, ?> legacyCatalogItem) {
+            String name = legacyCatalogItem.getSymbolicName();
+            String versionStr = legacyCatalogItem.getVersion();
+            Version version = Version.valueOf(BrooklynVersionSyntax.toValidOsgiVersion(versionStr == null ? BrooklynCatalog.DEFAULT_VERSION : versionStr));
+            
+            for (VersionRangedName removedLegacyItem : removedLegacyItems) {
+                if (removedLegacyItem.getSymbolicName().equals(name) && removedLegacyItem.getOsgiVersionRange().includes(version)) {
+                    return true;
+                }
+            }
+            return false;
+        }
+    }
+    
+    /** Records a name (string) and version range (string),
+     * with conveniences for pretty-printing and converting to OSGi format. */
+    public static class VersionRangedName {
+        private final String name;
+        private final String v;
+        
+        public static VersionRangedName fromString(String val, boolean singleVersionIsOsgiRange) {
+            if (Strings.isBlank(val)) {
+                throw new IllegalArgumentException("Must not be blank");
+            }
+            String[] parts = val.split(":");
+            if (parts.length > 2) {
+                throw new IllegalArgumentException("Identifier '"+val+"' has too many parts; max one ':' symbol");
+            }
+            if (parts.length == 1 || Strings.isBlank(parts[1])) {
+                throw new IllegalArgumentException("Identifier '"+val+"' must be of 'name:versionRange' syntax");
+            } else if (singleVersionIsOsgiRange || (parts[1].startsWith("(") || parts[1].startsWith("["))) {
+                return new VersionRangedName(parts[0], parts[1]);
+            } else {
+                return new VersionRangedName(parts[0], "["+parts[1]+","+parts[1]+"]");
+            }
+        }
+
+        public VersionRangedName(String name, String v) {
+            this.name = checkNotNull(name, "name");
+            this.v = v;
+        }
+        public VersionRangedName(String name, @Nullable VersionRange v) {
+            this.name = checkNotNull(name, "name").toString();
+            this.v = v==null ? null : v.toString();
+        }
+        
+        @Override
+        public String toString() {
+            return name + ":" + v;
+        }
+        
+        public String toOsgiString() {
+            return name + ":" + getOsgiVersionRange();
+        }
+
+        public boolean equals(String sn, String v) {
+            return name.equals(sn) && Objects.equal(this.v, v);
+        }
+
+        public boolean equals(String sn, VersionRange v) {
+            return name.equals(sn) && Objects.equal(getOsgiVersionRange(), v);
+        }
+
+        public String getSymbolicName() {
+            return name;
+        }
+
+        private transient VersionRange cachedOsgiVersionRange;
+        @Nullable
+        public VersionRange getOsgiVersionRange() {
+            if (cachedOsgiVersionRange==null && v!=null) {
+                cachedOsgiVersionRange = v==null ? null : VersionRange.valueOf(BrooklynVersionSyntax.toValidOsgiVersionRange(v));
+            }
+            return cachedOsgiVersionRange;
+        }
+
+        @Nullable
+        public String getOsgiVersionRangeString() {
+            VersionRange ov = getOsgiVersionRange();
+            if (ov==null) return null;
+            return ov.toString();
+        }
+
+        @Nullable
+        public String getVersionString() {
+            return v;
+        }
+        
+        @Override
+        public int hashCode() {
+            return Objects.hashCode(name, v);
+        }
+
+        @Override
+        public boolean equals(Object other) {
+            if (!(other instanceof VersionRangedName)) {
+                return false;
+            }
+            VersionRangedName o = (VersionRangedName) other;
+            return Objects.equal(name, o.name) && Objects.equal(v, o.v);
+        }
+    }
+
+    public static TypeUpgrades parseBundleManifestForTypeUpgrades(Bundle bundle) {
+        Dictionary<String, String> headers = bundle.getHeaders();
+        String removedLegacyItems = headers.get(MANIFEST_HEADER_REMOVE_LEGACY_ITEMS);
+        if (removedLegacyItems != null) {
+            List<VersionRangedName> versionedItems = parseVersionRangedNameList(removedLegacyItems, false);
+            return new TypeUpgrades(versionedItems);
+        }
+        return TypeUpgrades.EMPTY;
+    }
+    
+    @VisibleForTesting
+    static List<VersionRangedName> parseVersionRangedNameList(String input, boolean singleVersionIsOsgiRange) {
+        List<String> vals = QuotedStringTokenizer.builder()
+                .delimiterChars(",")
+                .includeQuotes(false)
+                .includeDelimiters(false)
+                .buildList(input);
+        
+        List<VersionRangedName> versionedItems = new ArrayList<>();
+        for (String val : vals) {
+            versionedItems.add(VersionRangedName.fromString(val, singleVersionIsOsgiRange));
+        }
+        return versionedItems;
+    }
+}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/98731c21/core/src/main/java/org/apache/brooklyn/core/catalog/internal/CatalogInitialization.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/brooklyn/core/catalog/internal/CatalogInitialization.java b/core/src/main/java/org/apache/brooklyn/core/catalog/internal/CatalogInitialization.java
index 58999a2..87e52e0 100644
--- a/core/src/main/java/org/apache/brooklyn/core/catalog/internal/CatalogInitialization.java
+++ b/core/src/main/java/org/apache/brooklyn/core/catalog/internal/CatalogInitialization.java
@@ -25,8 +25,6 @@ import java.io.IOException;
 import java.io.InputStream;
 import java.util.ArrayList;
 import java.util.Collection;
-import java.util.Dictionary;
-import java.util.LinkedHashSet;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
@@ -43,6 +41,7 @@ import org.apache.brooklyn.api.objs.BrooklynObjectType;
 import org.apache.brooklyn.api.typereg.BrooklynTypeRegistry.RegisteredTypeKind;
 import org.apache.brooklyn.api.typereg.ManagedBundle;
 import org.apache.brooklyn.api.typereg.RegisteredType;
+import org.apache.brooklyn.core.catalog.internal.BundleUpgradeParser.TypeUpgrades;
 import org.apache.brooklyn.core.mgmt.ManagementContextInjectable;
 import org.apache.brooklyn.core.mgmt.ha.OsgiBundleInstallationResult;
 import org.apache.brooklyn.core.mgmt.ha.OsgiManager;
@@ -73,10 +72,8 @@ import org.slf4j.LoggerFactory;
 
 import com.google.common.annotations.Beta;
 import com.google.common.annotations.VisibleForTesting;
-import com.google.common.base.Splitter;
 import com.google.common.base.Stopwatch;
 import com.google.common.collect.ImmutableList;
-import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Iterables;
 
 @Beta
@@ -237,7 +234,9 @@ public class CatalogInitialization implements ManagementContextInjectable {
             }
             
             populateInitialCatalogImpl(true);
-            addPersistedCatalogImpl(persistedState, exceptionHandler, rebindLogger);
+            
+            PersistedCatalogState filteredPersistedState = filterPersistedState(persistedState, rebindLogger);
+            addPersistedCatalogImpl(filteredPersistedState, exceptionHandler, rebindLogger);
             
             if (mode == ManagementNodeState.MASTER) {
                 // TODO ideally this would remain false until it has *persisted* the changed catalog;
@@ -528,6 +527,36 @@ public class CatalogInitialization implements ManagementContextInjectable {
         }
     }
 
+    private PersistedCatalogState filterPersistedState(PersistedCatalogState persistedState, RebindLogger rebindLogger) {
+        Maybe<OsgiManager> osgiManager = ((ManagementContextInternal)managementContext).getOsgiManager();
+        if (osgiManager.isAbsent()) {
+            // Could be running tests; do no filtering
+            return persistedState;
+        }
+        
+        TypeUpgrades.Builder typeUpgradesBuilder = TypeUpgrades.builder();
+        Collection<ManagedBundle> managedBundles = osgiManager.get().getManagedBundles().values();
+        for (ManagedBundle managedBundle : managedBundles) {
+            Maybe<Bundle> bundle = osgiManager.get().findBundle(managedBundle);
+            if (bundle.isPresent()) {
+                typeUpgradesBuilder.addAll(BundleUpgradeParser.parseBundleManifestForTypeUpgrades(bundle.get()));
+            }
+        }
+        TypeUpgrades typeUpgrades = typeUpgradesBuilder.build();
+        
+        if (typeUpgrades.isEmpty()) {
+            return persistedState;
+        }
+        Map<VersionedName, InstallableManagedBundle> bundles = persistedState.getBundles();
+        List<CatalogItem<?, ?>> legacyCatalogItems = new ArrayList<>();
+        for (CatalogItem<?, ?> legacyCatalogItem : persistedState.getLegacyCatalogItems()) {
+            if (!typeUpgrades.isRemoved(legacyCatalogItem)) {
+                legacyCatalogItems.add(legacyCatalogItem);
+            }
+        }
+        return new PersistedCatalogState(bundles, legacyCatalogItems);
+    }
+
     public interface RebindLogger {
         void debug(String message, Object... args);
     }

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/98731c21/core/src/test/java/org/apache/brooklyn/core/catalog/internal/BundleUpgradeParserTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/org/apache/brooklyn/core/catalog/internal/BundleUpgradeParserTest.java b/core/src/test/java/org/apache/brooklyn/core/catalog/internal/BundleUpgradeParserTest.java
new file mode 100644
index 0000000..08cc4c9
--- /dev/null
+++ b/core/src/test/java/org/apache/brooklyn/core/catalog/internal/BundleUpgradeParserTest.java
@@ -0,0 +1,71 @@
+/*
+ * 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.catalog.internal;
+
+import static org.testng.Assert.assertEquals;
+
+import java.util.List;
+
+import org.apache.brooklyn.core.catalog.internal.BundleUpgradeParser.VersionRangedName;
+import org.osgi.framework.Version;
+import org.osgi.framework.VersionRange;
+import org.testng.annotations.Test;
+
+import com.google.common.collect.ImmutableList;
+
+public class BundleUpgradeParserTest {
+
+    private VersionRange from0lessThan1 = new VersionRange('[', Version.valueOf("0"), Version.valueOf("1.0.0"), ')');
+    private VersionRange exactly0dot1 = new VersionRange('[', Version.valueOf("0.1.0"), Version.valueOf("0.1.0"), ']');
+    private VersionRangedName fooFrom0lessThan1 = new VersionRangedName("foo", from0lessThan1);
+    private VersionRangedName barFrom0lessThan1 = new VersionRangedName("bar", from0lessThan1);
+
+    @Test
+    public void testParseSingleQuotedVal() throws Exception {
+        String input = "\"foo:[0,1.0.0)\"";
+        assertParsed(input, ImmutableList.of(fooFrom0lessThan1));
+    }
+    
+    @Test
+    public void testParseSingleQuotedValWithNestedQuotes() throws Exception {
+        String input = "\"foo:[0,\"1.0.0\")\"";
+        assertParsed(input, ImmutableList.of(fooFrom0lessThan1));
+    }
+    
+    @Test
+    public void testParseMultipleVals() throws Exception {
+        String input = "\"foo:[0,1.0.0)\",\"bar:[0,1.0.0)\"";
+        assertParsed(input, ImmutableList.of(fooFrom0lessThan1, barFrom0lessThan1));
+    }
+
+    @Test
+    public void testParseValWithExactVersion() throws Exception {
+        String input = "\"foo:0.1.0\"";
+        assertParsed(input, ImmutableList.of(new VersionRangedName("foo", exactly0dot1)));
+    }
+
+    protected void assertParsed(String input, List<VersionRangedName> expected) throws Exception {
+        List<VersionRangedName> actual = BundleUpgradeParser.parseVersionRangedNameList(input, false);
+        assertEquals(actual.size(), expected.size(), "actual="+actual); 
+        for (int i = 0; i < actual.size(); i++) {
+            assertEquals(actual.get(i).getSymbolicName(), expected.get(i).getSymbolicName());
+            assertEquals(actual.get(i).getOsgiVersionRange(), expected.get(i).getOsgiVersionRange());
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/98731c21/launcher/src/test/java/org/apache/brooklyn/launcher/AbstractBrooklynLauncherRebindTest.java
----------------------------------------------------------------------
diff --git a/launcher/src/test/java/org/apache/brooklyn/launcher/AbstractBrooklynLauncherRebindTest.java b/launcher/src/test/java/org/apache/brooklyn/launcher/AbstractBrooklynLauncherRebindTest.java
index 6b13313..b4b5361 100644
--- a/launcher/src/test/java/org/apache/brooklyn/launcher/AbstractBrooklynLauncherRebindTest.java
+++ b/launcher/src/test/java/org/apache/brooklyn/launcher/AbstractBrooklynLauncherRebindTest.java
@@ -18,13 +18,20 @@
  */
 package org.apache.brooklyn.launcher;
 
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertNotNull;
+
 import java.io.ByteArrayInputStream;
 import java.io.File;
 import java.io.InputStream;
+import java.net.URI;
+import java.nio.charset.StandardCharsets;
 import java.util.ArrayList;
 import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
 import java.util.List;
 import java.util.Map;
+import java.util.Set;
 import java.util.zip.ZipEntry;
 
 import javax.annotation.Nullable;
@@ -32,10 +39,15 @@ import javax.annotation.Nullable;
 import org.apache.brooklyn.api.catalog.BrooklynCatalog;
 import org.apache.brooklyn.api.catalog.CatalogItem;
 import org.apache.brooklyn.api.mgmt.ha.HighAvailabilityMode;
+import org.apache.brooklyn.api.objs.BrooklynObjectType;
 import org.apache.brooklyn.api.typereg.BrooklynTypeRegistry;
+import org.apache.brooklyn.api.typereg.ManagedBundle;
 import org.apache.brooklyn.api.typereg.RegisteredType;
+import org.apache.brooklyn.core.catalog.internal.CatalogInitialization;
+import org.apache.brooklyn.core.mgmt.internal.ManagementContextInternal;
 import org.apache.brooklyn.core.mgmt.persist.PersistMode;
 import org.apache.brooklyn.core.test.entity.LocalManagementContextForTests;
+import org.apache.brooklyn.entity.stock.BasicEntity;
 import org.apache.brooklyn.util.collections.MutableMap;
 import org.apache.brooklyn.util.core.ResourceUtils;
 import org.apache.brooklyn.util.core.osgi.BundleMaker;
@@ -43,7 +55,6 @@ import org.apache.brooklyn.util.os.Os;
 import org.apache.brooklyn.util.osgi.VersionedName;
 import org.apache.brooklyn.util.text.Identifiers;
 import org.apache.brooklyn.util.time.Duration;
-import org.apache.commons.collections.IteratorUtils;
 import org.osgi.framework.Constants;
 import org.testng.Assert;
 import org.testng.annotations.AfterMethod;
@@ -51,12 +62,18 @@ import org.testng.annotations.BeforeMethod;
 
 import com.google.common.base.Charsets;
 import com.google.common.base.Function;
+import com.google.common.base.Joiner;
+import com.google.common.base.Objects;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.Iterables;
 import com.google.common.collect.Lists;
 import com.google.common.io.Files;
 
 public abstract class AbstractBrooklynLauncherRebindTest {
 
+    protected static final String CATALOG_EMPTY_INITIAL = "classpath://rebind-test-empty-catalog.bom";
+
     protected List<BrooklynLauncher> launchers = Lists.newCopyOnWriteArrayList();
     protected List<File> tmpFiles = new ArrayList<>();
 
@@ -118,8 +135,12 @@ public abstract class AbstractBrooklynLauncherRebindTest {
         Files.write(contents, file, Charsets.UTF_8);
         return file;
     }
-    
+
     protected File newTmpBundle(Map<String, byte[]> files, VersionedName bundleName) {
+        return newTmpBundle(files, bundleName, ImmutableMap.of());
+    }
+    
+    protected File newTmpBundle(Map<String, byte[]> files, VersionedName bundleName, Map<String, String> manifestLines) {
         Map<ZipEntry, InputStream> zipEntries = new LinkedHashMap<>();
         for (Map.Entry<String, byte[]> entry : files.entrySet()) {
             zipEntries.put(new ZipEntry(entry.getKey()), new ByteArrayInputStream(entry.getValue()));
@@ -129,11 +150,16 @@ public abstract class AbstractBrooklynLauncherRebindTest {
         File bf = bundleMaker.createTempZip("test", zipEntries);
         tmpFiles.add(bf);
         
-        if (bundleName!=null) {
-            bf = bundleMaker.copyAddingManifest(bf, MutableMap.of(
-                "Manifest-Version", "2.0",
-                Constants.BUNDLE_SYMBOLICNAME, bundleName.getSymbolicName(),
-                Constants.BUNDLE_VERSION, bundleName.getOsgiVersion().toString()));
+        if (bundleName != null || manifestLines.size() > 0) {
+            Map<String, String> manifestAllLines = MutableMap.<String, String>builder()
+                    .putAll(manifestLines)
+                    .putIfAbsent("Manifest-Version", "2.0")
+                    .build();
+            if (bundleName != null) {
+                manifestAllLines.putIfAbsent(Constants.BUNDLE_SYMBOLICNAME, bundleName.getSymbolicName());
+                manifestAllLines.putIfAbsent(Constants.BUNDLE_VERSION, bundleName.getOsgiVersion().toString());
+            }
+            bf = bundleMaker.copyAddingManifest(bf, manifestAllLines);
             tmpFiles.add(bf);
         }
         return bf;
@@ -168,10 +194,103 @@ public abstract class AbstractBrooklynLauncherRebindTest {
         Assert.assertTrue(compareIterablesWithoutOrderMatters(ids, idsFromItems), String.format("Expected %s, found %s", ids, idsFromItems));
     }
 
+    protected void assertManagedBundle(BrooklynLauncher launcher, VersionedName bundleId, Set<VersionedName> expectedCatalogItems) {
+        ManagementContextInternal mgmt = (ManagementContextInternal)launcher.getManagementContext();
+        ManagedBundle bundle = mgmt.getOsgiManager().get().getManagedBundle(bundleId);
+        assertNotNull(bundle, bundleId+" not found");
+        
+        Set<VersionedName> actualCatalogItems = new LinkedHashSet<>();
+        Iterable<RegisteredType> types = launcher.getManagementContext().getTypeRegistry().getAll();
+        for (RegisteredType type : types) {
+            if (Objects.equal(bundleId.toOsgiString(), type.getContainingBundle())) {
+                actualCatalogItems.add(type.getVersionedName());
+            }
+        }
+        assertEquals(actualCatalogItems, expectedCatalogItems, "actual="+actualCatalogItems+"; expected="+expectedCatalogItems);
+    }
+
     private static <T> boolean compareIterablesWithoutOrderMatters(Iterable<T> a, Iterable<T> b) {
-        List<T> aList = IteratorUtils.toList(a.iterator());
-        List<T> bList = IteratorUtils.toList(b.iterator());
+        List<T> aList = Lists.newArrayList(a);
+        List<T> bList = Lists.newArrayList(b);
 
         return aList.containsAll(bList) && bList.containsAll(aList);
     }
+    
+    protected void initPersistedState(Map<String, String> legacyCatalogContents) throws Exception {
+        CatalogInitialization catalogInitialization = new CatalogInitialization(CATALOG_EMPTY_INITIAL);
+        BrooklynLauncher launcher = newLauncherForTests()
+                .catalogInitialization(catalogInitialization);
+        launcher.start();
+        assertCatalogConsistsOfIds(launcher, ImmutableList.of());
+        launcher.terminate();
+        
+        for (Map.Entry<String, String> entry : legacyCatalogContents.entrySet()) {
+            addMemento(BrooklynObjectType.CATALOG_ITEM, entry.getKey(), entry.getValue());
+        }
+    }
+
+    protected void addMemento(BrooklynObjectType type, String id, String contents) throws Exception {
+        File persistedFile = getPersistanceFile(type, id);
+        Files.write(contents.getBytes(StandardCharsets.UTF_8), persistedFile);
+    }
+
+    protected File getPersistanceFile(BrooklynObjectType type, String id) {
+        String dir;
+        switch (type) {
+            case ENTITY: dir = "entities"; break;
+            case LOCATION: dir = "locations"; break;
+            case POLICY: dir = "policies"; break;
+            case ENRICHER: dir = "enrichers"; break;
+            case FEED: dir = "feeds"; break;
+            case CATALOG_ITEM: dir = "catalog"; break;
+            default: throw new UnsupportedOperationException("type="+type);
+        }
+        return new File(persistenceDir, Os.mergePaths(dir, id));
+    }
+    
+    protected String createLegacyPersistenceCatalogItem(VersionedName itemName) {
+        return Joiner.on("\n").join(
+                "<catalogItem>",
+                "  <brooklynVersion>0.12.0-20170901.1331</brooklynVersion>",
+                "  <type>org.apache.brooklyn.core:org.apache.brooklyn.core.catalog.internal.CatalogEntityItemDto</type>",
+                "  <id>"+itemName.getSymbolicName()+":"+itemName.getVersionString()+"</id>",
+                "  <catalogItemId>"+itemName.getSymbolicName()+":"+itemName.getVersionString()+"</catalogItemId>",
+                "  <searchPath class=\"ImmutableList\"/>",
+                "  <registeredTypeName>"+itemName.getSymbolicName()+"</registeredTypeName>",
+                "  <version>"+itemName.getVersionString()+"</version>",
+                "  <planYaml>services: [{type: org.apache.brooklyn.entity.stock.BasicApplication}]</planYaml>",
+                "  <libraries class=\"ImmutableList\" reference=\"../searchPath\"/>",
+                "  <catalogItemType>ENTITY</catalogItemType>",
+                "  <catalogItemJavaType>org.apache.brooklyn.api:org.apache.brooklyn.api.entity.Entity</catalogItemJavaType>",
+                "  <specType>org.apache.brooklyn.api:org.apache.brooklyn.api.entity.EntitySpec</specType>",
+                "  <deprecated>false</deprecated>",
+                "  <disabled>false</disabled>",
+                "</catalogItem>");
+    }
+    
+    protected String createCatalogYaml(Iterable<URI> libraries, Iterable<VersionedName> entities) {
+        if (Iterables.isEmpty(libraries) && Iterables.isEmpty(entities)) {
+            return "brooklyn.catalog: {}\n";
+        }
+        
+        StringBuilder result = new StringBuilder();
+        result.append("brooklyn.catalog:\n");
+        if (!Iterables.isEmpty(libraries)) {
+            result.append("  brooklyn.libraries:\n");
+        }
+        for (URI library : libraries) {
+            result.append("    - " + library+"\n");
+        }
+        if (!Iterables.isEmpty(entities)) {
+            result.append("  items:\n");
+        }
+        for (VersionedName entity : entities) {
+            result.append("    - id: " + entity.getSymbolicName()+"\n");
+            result.append("      version: " + entity.getVersionString()+"\n");
+            result.append("      itemType: entity"+"\n");
+            result.append("      item:"+"\n");
+            result.append("        type: " + BasicEntity.class.getName()+"\n");
+        }
+        return result.toString();
+    }
 }

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/98731c21/launcher/src/test/java/org/apache/brooklyn/launcher/BrooklynLauncherRebindCatalogOsgiTest.java
----------------------------------------------------------------------
diff --git a/launcher/src/test/java/org/apache/brooklyn/launcher/BrooklynLauncherRebindCatalogOsgiTest.java b/launcher/src/test/java/org/apache/brooklyn/launcher/BrooklynLauncherRebindCatalogOsgiTest.java
index c52fec4..2e9c78e 100644
--- a/launcher/src/test/java/org/apache/brooklyn/launcher/BrooklynLauncherRebindCatalogOsgiTest.java
+++ b/launcher/src/test/java/org/apache/brooklyn/launcher/BrooklynLauncherRebindCatalogOsgiTest.java
@@ -18,21 +18,12 @@
  */
 package org.apache.brooklyn.launcher;
 
-import static org.testng.Assert.assertEquals;
-import static org.testng.Assert.assertNotNull;
-
 import java.io.File;
-import java.net.URI;
 import java.nio.charset.StandardCharsets;
-import java.util.LinkedHashSet;
 import java.util.Set;
 
-import org.apache.brooklyn.api.typereg.ManagedBundle;
-import org.apache.brooklyn.api.typereg.RegisteredType;
 import org.apache.brooklyn.core.catalog.internal.BasicBrooklynCatalog;
 import org.apache.brooklyn.core.catalog.internal.CatalogInitialization;
-import org.apache.brooklyn.core.mgmt.internal.ManagementContextInternal;
-import org.apache.brooklyn.entity.stock.BasicEntity;
 import org.apache.brooklyn.test.support.TestResourceUnavailableException;
 import org.apache.brooklyn.util.osgi.OsgiTestResources;
 import org.apache.brooklyn.util.osgi.VersionedName;
@@ -40,7 +31,6 @@ import org.testng.annotations.BeforeMethod;
 import org.testng.annotations.Test;
 
 import com.google.common.base.Joiner;
-import com.google.common.base.Objects;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.ImmutableSet;
@@ -179,41 +169,4 @@ public class BrooklynLauncherRebindCatalogOsgiTest extends AbstractBrooklynLaunc
         assertCatalogConsistsOfIds(newLauncher, COM_EXAMPLE_BUNDLE_CATALOG_IDS);
         assertManagedBundle(newLauncher, COM_EXAMPLE_BUNDLE_ID, COM_EXAMPLE_BUNDLE_CATALOG_IDS);
     }
-    
-    private void assertManagedBundle(BrooklynLauncher launcher, VersionedName bundleId, Set<VersionedName> expectedCatalogItems) {
-        ManagementContextInternal mgmt = (ManagementContextInternal)launcher.getManagementContext();
-        ManagedBundle bundle = mgmt.getOsgiManager().get().getManagedBundle(bundleId);
-        assertNotNull(bundle, bundleId+" not found");
-        
-        Set<VersionedName> actualCatalogItems = new LinkedHashSet<>();
-        Iterable<RegisteredType> types = launcher.getManagementContext().getTypeRegistry().getAll();
-        for (RegisteredType type : types) {
-            if (Objects.equal(bundleId.toOsgiString(), type.getContainingBundle())) {
-                actualCatalogItems.add(type.getVersionedName());
-            }
-        }
-        assertEquals(actualCatalogItems, expectedCatalogItems, "actual="+actualCatalogItems+"; expected="+expectedCatalogItems);
-    }
-    
-    private String createCatalogYaml(Iterable<URI> libraries, Iterable<VersionedName> entities) {
-        StringBuilder result = new StringBuilder();
-        result.append("brooklyn.catalog:\n");
-        if (!Iterables.isEmpty(libraries)) {
-            result.append("  brooklyn.libraries:\n");
-        }
-        for (URI library : libraries) {
-            result.append("    - " + library+"\n");
-        }
-        if (!Iterables.isEmpty(entities)) {
-            result.append("  items:\n");
-        }
-        for (VersionedName entity : entities) {
-            result.append("    - id: " + entity.getSymbolicName()+"\n");
-            result.append("      version: " + entity.getVersionString()+"\n");
-            result.append("      itemType: entity"+"\n");
-            result.append("      item:"+"\n");
-            result.append("        type: " + BasicEntity.class.getName()+"\n");
-        }
-        return result.toString();
-    }
 }

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/98731c21/launcher/src/test/java/org/apache/brooklyn/launcher/BrooklynLauncherUpgradeCatalogOsgiTest.java
----------------------------------------------------------------------
diff --git a/launcher/src/test/java/org/apache/brooklyn/launcher/BrooklynLauncherUpgradeCatalogOsgiTest.java b/launcher/src/test/java/org/apache/brooklyn/launcher/BrooklynLauncherUpgradeCatalogOsgiTest.java
new file mode 100644
index 0000000..1922d72
--- /dev/null
+++ b/launcher/src/test/java/org/apache/brooklyn/launcher/BrooklynLauncherUpgradeCatalogOsgiTest.java
@@ -0,0 +1,93 @@
+/*
+ * 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 java.io.File;
+import java.net.URI;
+import java.nio.charset.StandardCharsets;
+import java.util.Map;
+
+import org.apache.brooklyn.core.catalog.internal.BasicBrooklynCatalog;
+import org.apache.brooklyn.core.catalog.internal.BundleUpgradeParser;
+import org.apache.brooklyn.core.catalog.internal.CatalogInitialization;
+import org.apache.brooklyn.util.osgi.VersionedName;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+
+public class BrooklynLauncherUpgradeCatalogOsgiTest extends AbstractBrooklynLauncherRebindTest {
+
+    @BeforeMethod(alwaysRun=true)
+    @Override
+    public void setUp() throws Exception {
+        super.setUp();
+    }
+    
+    @Override
+    protected boolean useOsgi() {
+        return true;
+    }
+    
+    @Override
+    protected boolean reuseOsgi() {
+        return false;
+    }
+
+    private BrooklynLauncher newLauncherForTests(String catalogInitial) {
+        CatalogInitialization catalogInitialization = new CatalogInitialization(catalogInitial);
+        return super.newLauncherForTests()
+                .catalogInitialization(catalogInitialization);
+    }
+
+    @Test
+    public void testRemoveLegacyItems() throws Exception {
+        VersionedName one_0_1_0 = VersionedName.fromString("one:0.1.0");
+        VersionedName two_0_1_0 = VersionedName.fromString("two:0.1.0");
+        VersionedName two_1_0_0 = VersionedName.fromString("two:1.0.0");
+        VersionedName three_0_1_0 = VersionedName.fromString("three:0.1.0");
+        VersionedName three_0_2_0 = VersionedName.fromString("three:0.2.0");
+        VersionedName four_0_1_0 = VersionedName.fromString("four:0.1.0");
+        
+        initPersistedState(ImmutableMap.<String, String>builder()
+                .put("one_0.1.0", createLegacyPersistenceCatalogItem(one_0_1_0))
+                .put("two_0.1.0", createLegacyPersistenceCatalogItem(two_0_1_0))
+                .put("two_1.0.0", createLegacyPersistenceCatalogItem(two_1_0_0))
+                .put("three_0.1.0", createLegacyPersistenceCatalogItem(three_0_1_0))
+                .put("three_0.2.0", createLegacyPersistenceCatalogItem(three_0_2_0))
+                .put("four_0.1.0", createLegacyPersistenceCatalogItem(four_0_1_0))
+                .build());
+        
+        String bundleBom = createCatalogYaml(ImmutableList.<URI>of(), ImmutableSet.<VersionedName>of());
+        VersionedName bundleName = new VersionedName("org.example.testRemoveLegacyItems", "1.0.0");
+        String removedLegacyItems = "\"one:[0,1.0.0)\",\"two:[0,1.0.0)\",\"three:0.1.0\"";
+        Map<String, String> bundleManifest = ImmutableMap.of(BundleUpgradeParser.MANIFEST_HEADER_REMOVE_LEGACY_ITEMS, removedLegacyItems);
+        File bundleFile = newTmpBundle(ImmutableMap.of(BasicBrooklynCatalog.CATALOG_BOM, bundleBom.getBytes(StandardCharsets.UTF_8)), bundleName, bundleManifest);
+        File initialBomFile = newTmpFile(createCatalogYaml(ImmutableList.of(bundleFile.toURI()), ImmutableList.of()));
+        
+        BrooklynLauncher launcher = newLauncherForTests(initialBomFile.getAbsolutePath());
+        launcher.start();
+        assertCatalogConsistsOfIds(launcher, ImmutableList.of(two_1_0_0, three_0_2_0, four_0_1_0));
+        assertManagedBundle(launcher, bundleName, ImmutableSet.<VersionedName>of());
+
+        launcher.terminate();
+    }
+}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/98731c21/utils/common/src/main/java/org/apache/brooklyn/util/text/BrooklynVersionSyntax.java
----------------------------------------------------------------------
diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/text/BrooklynVersionSyntax.java b/utils/common/src/main/java/org/apache/brooklyn/util/text/BrooklynVersionSyntax.java
index 1e0e698..89262d5 100644
--- a/utils/common/src/main/java/org/apache/brooklyn/util/text/BrooklynVersionSyntax.java
+++ b/utils/common/src/main/java/org/apache/brooklyn/util/text/BrooklynVersionSyntax.java
@@ -108,6 +108,32 @@ public class BrooklynVersionSyntax {
          */
     }
 
+    /**
+     * See {@link #toValidOsgiVersion(String)}, but takes a version range (in the standard OSGi format).
+     */
+    public static String toValidOsgiVersionRange(String input) {
+        String beginning = "";
+        String ending = "";
+        if (input.startsWith("[") || input.startsWith("(")) {
+            beginning = input.substring(0, 1);
+        }
+        if (input.endsWith("]") || input.endsWith(")")) {
+            ending = input.substring(input.length() - 1, input.length());
+        }
+        String middle = input.substring(beginning.length(), input.length() - ending.length());
+        String[] middleParts = middle.split(",");
+        
+        StringBuilder result = new StringBuilder();
+        result.append(beginning);
+        for (int i = 0; i < middleParts.length; i++) {
+            String middlePart = middleParts[i];
+            if (i != 0) result.append(",");
+            result.append(toValidOsgiVersion(middlePart.trim()));
+        }
+        result.append(ending);
+        return result.toString();
+    }
+
     /** Creates a string satisfying {@link #isGoodBrooklynVersion(String)} based on the input.
      * For input satisfying {@link #isGoodBrooklynVersion(String)} the input will be returned unchanged.
      * For input satisfying {@link #isValidOsgiVersion(String)} the qualifier separator will be changed to "-",

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/98731c21/utils/common/src/test/java/org/apache/brooklyn/util/text/BrooklynVersionSyntaxTest.java
----------------------------------------------------------------------
diff --git a/utils/common/src/test/java/org/apache/brooklyn/util/text/BrooklynVersionSyntaxTest.java b/utils/common/src/test/java/org/apache/brooklyn/util/text/BrooklynVersionSyntaxTest.java
index b938935..1bd8e3a 100644
--- a/utils/common/src/test/java/org/apache/brooklyn/util/text/BrooklynVersionSyntaxTest.java
+++ b/utils/common/src/test/java/org/apache/brooklyn/util/text/BrooklynVersionSyntaxTest.java
@@ -95,4 +95,19 @@ public class BrooklynVersionSyntaxTest {
         // potentially surprising, and different to maven (0.0.0.4aug..)
         assertOsgiVersion("4aug2000r7-dev", "4.0.0.aug2000r7-dev");
     }
+    
+    public void testOsgiVersionRanges() {
+        assertOsgiVersionRange("1.0.0", "1.0.0");
+        assertOsgiVersionRange("[1.0.0,2.0.0)", "[1.0.0,2.0.0)");
+        assertOsgiVersionRange("[1.0.0, 2.0.0)", "[1.0.0,2.0.0)");
+        assertOsgiVersionRange("1.0.0-SNAPSHOT", "1.0.0.SNAPSHOT");
+        assertOsgiVersionRange("[1.0.0-SNAPSHOT]", "[1.0.0.SNAPSHOT]");
+        assertOsgiVersionRange("[1.0.0-SNAPSHOT,2.0.0-SNAPSHOT]", "[1.0.0.SNAPSHOT,2.0.0.SNAPSHOT]");
+        assertOsgiVersionRange("[1.0.0-SNAPSHOT,2.0.0-SNAPSHOT)", "[1.0.0.SNAPSHOT,2.0.0.SNAPSHOT)");
+        assertOsgiVersionRange("(1.0.0-SNAPSHOT,2.0.0-SNAPSHOT]", "(1.0.0.SNAPSHOT,2.0.0.SNAPSHOT]");
+    }
+    
+    private void assertOsgiVersionRange(String input, String osgi) {
+        Assert.assertEquals(BrooklynVersionSyntax.toValidOsgiVersionRange(input), osgi, "conversion to valid osgi range");
+    }
 }


[3/7] brooklyn-server git commit: Add support for `force-remove-bundles: *`

Posted by he...@apache.org.
Add support for `force-remove-bundles: *`

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

Branch: refs/heads/master
Commit: 54ecbf9605154e6f134a73578b6300bcba614a65
Parents: 22fda0d
Author: Aled Sage <al...@gmail.com>
Authored: Wed Oct 25 08:32:50 2017 +0100
Committer: Aled Sage <al...@gmail.com>
Committed: Wed Oct 25 08:37:58 2017 +0100

----------------------------------------------------------------------
 .../core/typereg/BundleUpgradeParser.java       | 30 ++++++++++--
 .../core/typereg/BundleUpgradeParserTest.java   | 51 ++++++++++++++++----
 .../util/text/BrooklynVersionSyntax.java        | 14 ++++++
 .../util/text/BrooklynVersionSyntaxTest.java    | 23 +++++++++
 4 files changed, 104 insertions(+), 14 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/54ecbf96/core/src/main/java/org/apache/brooklyn/core/typereg/BundleUpgradeParser.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/brooklyn/core/typereg/BundleUpgradeParser.java b/core/src/main/java/org/apache/brooklyn/core/typereg/BundleUpgradeParser.java
index 96cd2b6..9766514 100644
--- a/core/src/main/java/org/apache/brooklyn/core/typereg/BundleUpgradeParser.java
+++ b/core/src/main/java/org/apache/brooklyn/core/typereg/BundleUpgradeParser.java
@@ -213,18 +213,33 @@ public class BundleUpgradeParser {
     }
 
     public static CatalogUpgrades parseBundleManifestForCatalogUpgrades(Bundle bundle) {
-        // TODO Add support for "*" for "force-remove-bundles", to indicate all lower-versioned 
-        // bundles with the same symbolic name as this bundle. Also add support for the other 
-        // options described in the proposal:
+        // TODO Add support for the other options described in the proposal:
         //   https://docs.google.com/document/d/1Lm47Kx-cXPLe8BO34-qrL3ZMPosuUHJILYVQUswEH6Y/edit#
         //   section "Bundle Upgrade Metadata"
         
         Dictionary<String, String> headers = bundle.getHeaders();
         return CatalogUpgrades.builder()
                 .removedLegacyItems(parseVersionRangedNameList(headers.get(MANIFEST_HEADER_FORCE_REMOVE_LEGACY_ITEMS), false))
-                .removedBundles(parseVersionRangedNameList(headers.get(MANIFEST_HEADER_FORCE_REMOVE_BUNDLES), false))
+                .removedBundles(parseForceRemoveBundlesHeader(bundle, headers.get(MANIFEST_HEADER_FORCE_REMOVE_BUNDLES)))
                 .build();
     }
+
+    @VisibleForTesting
+    static List<VersionRangedName> parseForceRemoveBundlesHeader(Bundle context, String input) {
+        if (input == null) return ImmutableList.of();
+        if (stripQuotes(input).equals("*")) {
+            String bundleVersion = context.getVersion().toString();
+            String maxVersion;
+            if (BrooklynVersionSyntax.isSnapshot(bundleVersion)) {
+                maxVersion = BrooklynVersionSyntax.stripSnapshot(bundleVersion);
+            } else {
+                maxVersion = bundleVersion;
+            }
+            return ImmutableList.of(new VersionRangedName(context.getSymbolicName(), "[0,"+maxVersion+")"));
+        } else {
+            return parseVersionRangedNameList(input, false);
+        }
+    }
     
     @VisibleForTesting
     static List<VersionRangedName> parseVersionRangedNameList(String input, boolean singleVersionIsOsgiRange) {
@@ -242,4 +257,11 @@ public class BundleUpgradeParser {
         }
         return versionedItems;
     }
+    
+    private static String stripQuotes(String input) {
+        String quoteChars = QuotedStringTokenizer.DEFAULT_QUOTE_CHARS;
+        boolean quoted = (input.length() >= 2) && quoteChars.contains(input.substring(0, 1))
+                && quoteChars.contains(input.substring(input.length() - 1));
+        return (quoted ? input.substring(1, input.length() - 1) : input);
+    }
 }

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/54ecbf96/core/src/test/java/org/apache/brooklyn/core/typereg/BundleUpgradeParserTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/org/apache/brooklyn/core/typereg/BundleUpgradeParserTest.java b/core/src/test/java/org/apache/brooklyn/core/typereg/BundleUpgradeParserTest.java
index 143649a..7573bee 100644
--- a/core/src/test/java/org/apache/brooklyn/core/typereg/BundleUpgradeParserTest.java
+++ b/core/src/test/java/org/apache/brooklyn/core/typereg/BundleUpgradeParserTest.java
@@ -28,7 +28,6 @@ import java.util.List;
 import java.util.Map;
 
 import org.apache.brooklyn.api.catalog.CatalogItem;
-import org.apache.brooklyn.core.typereg.BundleUpgradeParser;
 import org.apache.brooklyn.core.typereg.BundleUpgradeParser.CatalogUpgrades;
 import org.apache.brooklyn.core.typereg.BundleUpgradeParser.VersionRangedName;
 import org.apache.brooklyn.test.Asserts;
@@ -46,6 +45,7 @@ import com.google.common.collect.ImmutableMap;
 public class BundleUpgradeParserTest {
 
     private VersionRange from0lessThan1 = new VersionRange('[', Version.valueOf("0"), Version.valueOf("1.0.0"), ')');
+    private VersionRange from0lessThan1_2_3 = new VersionRange('[', Version.valueOf("0"), Version.valueOf("1.2.3"), ')');
     private VersionRange exactly0dot1 = new VersionRange('[', Version.valueOf("0.1.0"), Version.valueOf("0.1.0"), ']');
     private VersionRangedName fooFrom0lessThan1 = new VersionRangedName("foo", from0lessThan1);
     private VersionRangedName barFrom0lessThan1 = new VersionRangedName("bar", from0lessThan1);
@@ -82,28 +82,49 @@ public class BundleUpgradeParserTest {
     @Test
     public void testParseSingleQuotedVal() throws Exception {
         String input = "\"foo:[0,1.0.0)\"";
-        assertParsed(input, ImmutableList.of(fooFrom0lessThan1));
+        assertParseList(input, ImmutableList.of(fooFrom0lessThan1));
     }
     
     @Test
     public void testParseSingleQuotedValWithNestedQuotes() throws Exception {
         String input = "\"foo:[0,\"1.0.0\")\"";
-        assertParsed(input, ImmutableList.of(fooFrom0lessThan1));
+        assertParseList(input, ImmutableList.of(fooFrom0lessThan1));
     }
     
     @Test
     public void testParseMultipleVals() throws Exception {
         String input = "\"foo:[0,1.0.0)\",\"bar:[0,1.0.0)\"";
-        assertParsed(input, ImmutableList.of(fooFrom0lessThan1, barFrom0lessThan1));
+        assertParseList(input, ImmutableList.of(fooFrom0lessThan1, barFrom0lessThan1));
     }
 
     @Test
     public void testParseValWithExactVersion() throws Exception {
         String input = "\"foo:0.1.0\"";
-        assertParsed(input, ImmutableList.of(new VersionRangedName("foo", exactly0dot1)));
+        assertParseList(input, ImmutableList.of(new VersionRangedName("foo", exactly0dot1)));
     }
 
     @Test
+    public void testParseForceRemoveBundlesHeader() throws Exception {
+        Bundle bundle = Mockito.mock(Bundle.class);
+        Mockito.when(bundle.getSymbolicName()).thenReturn("foo.bar");
+        Mockito.when(bundle.getVersion()).thenReturn(Version.valueOf("1.2.3"));
+        
+        assertParseForceRemoveBundlesHeader(bundle, "\"foo:0.1.0\"", ImmutableList.of(new VersionRangedName("foo", exactly0dot1)));
+        assertParseForceRemoveBundlesHeader(bundle, "\"*\"", ImmutableList.of(new VersionRangedName("foo.bar", from0lessThan1_2_3)));
+        assertParseForceRemoveBundlesHeader(bundle, "*", ImmutableList.of(new VersionRangedName("foo.bar", from0lessThan1_2_3)));
+    }
+    
+    @Test
+    public void testParseForceRemoveBundlesHeaderWithSnapshot() throws Exception {
+        Bundle bundle = Mockito.mock(Bundle.class);
+        Mockito.when(bundle.getSymbolicName()).thenReturn("foo.bar");
+        Mockito.when(bundle.getVersion()).thenReturn(Version.valueOf("1.2.3.SNAPSHOT"));
+        
+        assertParseForceRemoveBundlesHeader(bundle, "\"*\"", ImmutableList.of(new VersionRangedName("foo.bar", from0lessThan1_2_3)));
+        assertParseForceRemoveBundlesHeader(bundle, "*", ImmutableList.of(new VersionRangedName("foo.bar", from0lessThan1_2_3)));
+    }
+    
+    @Test
     public void testParseBundleEmptyManifest() throws Exception {
         Bundle bundle = newMockBundle(ImmutableMap.of());
         
@@ -147,15 +168,25 @@ public class BundleUpgradeParserTest {
         return result;
     }
     
-    private void assertParsed(String input, List<VersionRangedName> expected) throws Exception {
+    private void assertParseList(String input, List<VersionRangedName> expected) throws Exception {
         List<VersionRangedName> actual = BundleUpgradeParser.parseVersionRangedNameList(input, false);
-        assertEquals(actual.size(), expected.size(), "actual="+actual); 
+        assertListsEqual(actual, expected);
+    }
+    
+    private void assertParseForceRemoveBundlesHeader(Bundle bundle, String input, List<VersionRangedName> expected) throws Exception {
+        List<VersionRangedName> actual = BundleUpgradeParser.parseForceRemoveBundlesHeader(bundle, input);
+        assertListsEqual(actual, expected);
+    }
+
+    private void assertListsEqual(List<VersionRangedName> actual, List<VersionRangedName> expected) throws Exception {
+        String errMsg = "actual="+actual;
+        assertEquals(actual.size(), expected.size(), errMsg); 
         for (int i = 0; i < actual.size(); i++) {
-            assertEquals(actual.get(i).getSymbolicName(), expected.get(i).getSymbolicName());
-            assertEquals(actual.get(i).getOsgiVersionRange(), expected.get(i).getOsgiVersionRange());
+            assertEquals(actual.get(i).getSymbolicName(), expected.get(i).getSymbolicName(), errMsg);
+            assertEquals(actual.get(i).getOsgiVersionRange(), expected.get(i).getOsgiVersionRange(), errMsg);
         }
     }
-    
+
     private void assertVersionRangedNameFails(String input, String expectedFailure, String... optionalOtherExpectedFailures) {
         try {
             VersionRangedName.fromString(input, false);

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/54ecbf96/utils/common/src/main/java/org/apache/brooklyn/util/text/BrooklynVersionSyntax.java
----------------------------------------------------------------------
diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/text/BrooklynVersionSyntax.java b/utils/common/src/main/java/org/apache/brooklyn/util/text/BrooklynVersionSyntax.java
index 89262d5..6f23489 100644
--- a/utils/common/src/main/java/org/apache/brooklyn/util/text/BrooklynVersionSyntax.java
+++ b/utils/common/src/main/java/org/apache/brooklyn/util/text/BrooklynVersionSyntax.java
@@ -225,4 +225,18 @@ public class BrooklynVersionSyntax {
         return version.toUpperCase().contains(SNAPSHOT);
     }
 
+    /**
+     * Returns the version without "SNAPSHOT" (normally this will return the next expected release version).
+     * For example, "1.0.0.SNAPSHOT" or "1.0.0-SNAPSHOT" becomes "1.0.0".
+     */
+    public static String stripSnapshot(String input) {
+        if (input==null) return input;
+        int stripIndex = input.toUpperCase().indexOf(SNAPSHOT);
+        if (stripIndex <= 0) return input;
+        char charBeforeSnapshot = input.charAt(stripIndex - 1);
+        if (charBeforeSnapshot == '.' || charBeforeSnapshot == '_' || charBeforeSnapshot == '-') {
+            stripIndex--;
+        }
+        return (stripIndex <= 0) ? input : input.substring(0, stripIndex);
+    }
 }

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/54ecbf96/utils/common/src/test/java/org/apache/brooklyn/util/text/BrooklynVersionSyntaxTest.java
----------------------------------------------------------------------
diff --git a/utils/common/src/test/java/org/apache/brooklyn/util/text/BrooklynVersionSyntaxTest.java b/utils/common/src/test/java/org/apache/brooklyn/util/text/BrooklynVersionSyntaxTest.java
index 1bd8e3a..4fa02d6 100644
--- a/utils/common/src/test/java/org/apache/brooklyn/util/text/BrooklynVersionSyntaxTest.java
+++ b/utils/common/src/test/java/org/apache/brooklyn/util/text/BrooklynVersionSyntaxTest.java
@@ -107,6 +107,29 @@ public class BrooklynVersionSyntaxTest {
         assertOsgiVersionRange("(1.0.0-SNAPSHOT,2.0.0-SNAPSHOT]", "(1.0.0.SNAPSHOT,2.0.0.SNAPSHOT]");
     }
     
+    public void testIsSnapshot() {
+        Assert.assertTrue(BrooklynVersionSyntax.isSnapshot("1.0.0.SNAPSHOT"));
+        Assert.assertTrue(BrooklynVersionSyntax.isSnapshot("1.0.0.20171025_SNAPSHOT"));
+        Assert.assertTrue(BrooklynVersionSyntax.isSnapshot("1.0.0-SNAPSHOT"));
+        Assert.assertFalse(BrooklynVersionSyntax.isSnapshot("1.0.0"));
+    }
+
+    public void testStripSnapshot() {
+        assertStripSnapshot("1.0.0.SNAPSHOT", "1.0.0");
+        assertStripSnapshot("1.0.0-SNAPSHOT", "1.0.0");
+        assertStripSnapshot("1-SNAPSHOT", "1");
+        assertStripSnapshot("1.0.0.20171025_SNAPSHOT", "1.0.0.20171025");
+        assertStripSnapshot("1.0.0", "1.0.0");
+        
+        // for weird things, don't make them even weirder!
+        assertStripSnapshot("SNAPSHOT", "SNAPSHOT");
+        assertStripSnapshot("_SNAPSHOT", "_SNAPSHOT");
+    }
+
+    private void assertStripSnapshot(String input, String expected) {
+        Assert.assertEquals(BrooklynVersionSyntax.stripSnapshot(input), expected, "conversion to strip snapshot");
+    }
+    
     private void assertOsgiVersionRange(String input, String osgi) {
         Assert.assertEquals(BrooklynVersionSyntax.toValidOsgiVersionRange(input), osgi, "conversion to valid osgi range");
     }


[5/7] brooklyn-server git commit: Add support for `force-remove-legacy-items: *`

Posted by he...@apache.org.
Add support for `force-remove-legacy-items: *`

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

Branch: refs/heads/master
Commit: 2cd4e0879201586bd406e3f21e4ce95dd3de64d8
Parents: 54ecbf9
Author: Aled Sage <al...@gmail.com>
Authored: Wed Oct 25 16:31:07 2017 +0100
Committer: Aled Sage <al...@gmail.com>
Committed: Wed Oct 25 16:31:07 2017 +0100

----------------------------------------------------------------------
 .../catalog/internal/CatalogInitialization.java | 25 ++++++-
 .../core/typereg/BundleUpgradeParser.java       | 61 +++++++++++++---
 .../core/typereg/BundleUpgradeParserTest.java   | 74 ++++++++++++++++----
 3 files changed, 134 insertions(+), 26 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/2cd4e087/core/src/main/java/org/apache/brooklyn/core/catalog/internal/CatalogInitialization.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/brooklyn/core/catalog/internal/CatalogInitialization.java b/core/src/main/java/org/apache/brooklyn/core/catalog/internal/CatalogInitialization.java
index 107a6b2..225e3fd 100644
--- a/core/src/main/java/org/apache/brooklyn/core/catalog/internal/CatalogInitialization.java
+++ b/core/src/main/java/org/apache/brooklyn/core/catalog/internal/CatalogInitialization.java
@@ -39,6 +39,7 @@ import org.apache.brooklyn.api.mgmt.ManagementContext;
 import org.apache.brooklyn.api.mgmt.ha.ManagementNodeState;
 import org.apache.brooklyn.api.mgmt.rebind.RebindExceptionHandler;
 import org.apache.brooklyn.api.objs.BrooklynObjectType;
+import org.apache.brooklyn.api.typereg.BrooklynTypeRegistry;
 import org.apache.brooklyn.api.typereg.BrooklynTypeRegistry.RegisteredTypeKind;
 import org.apache.brooklyn.api.typereg.ManagedBundle;
 import org.apache.brooklyn.api.typereg.RegisteredType;
@@ -49,9 +50,9 @@ import org.apache.brooklyn.core.mgmt.internal.ManagementContextInternal;
 import org.apache.brooklyn.core.objs.BrooklynTypes;
 import org.apache.brooklyn.core.server.BrooklynServerConfig;
 import org.apache.brooklyn.core.typereg.BundleUpgradeParser;
+import org.apache.brooklyn.core.typereg.BundleUpgradeParser.CatalogUpgrades;
 import org.apache.brooklyn.core.typereg.RegisteredTypePredicates;
 import org.apache.brooklyn.core.typereg.RegisteredTypes;
-import org.apache.brooklyn.core.typereg.BundleUpgradeParser.CatalogUpgrades;
 import org.apache.brooklyn.util.collections.MutableList;
 import org.apache.brooklyn.util.collections.MutableMap;
 import org.apache.brooklyn.util.collections.MutableSet;
@@ -75,6 +76,7 @@ import org.slf4j.LoggerFactory;
 import com.google.common.annotations.Beta;
 import com.google.common.annotations.VisibleForTesting;
 import com.google.common.base.Stopwatch;
+import com.google.common.base.Supplier;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.Iterables;
 
@@ -576,7 +578,10 @@ public class CatalogInitialization implements ManagementContextInjectable {
         for (ManagedBundle managedBundle : managedBundles) {
             Maybe<Bundle> bundle = osgiManager.get().findBundle(managedBundle);
             if (bundle.isPresent()) {
-                catalogUpgradesBuilder.addAll(BundleUpgradeParser.parseBundleManifestForCatalogUpgrades(bundle.get()));
+                CatalogUpgrades catalogUpgrades = BundleUpgradeParser.parseBundleManifestForCatalogUpgrades(
+                        bundle.get(),
+                        new RegisteredTypesSupplier(managementContext, managedBundle));
+                catalogUpgradesBuilder.addAll(catalogUpgrades);
             } else {
                 rebindLogger.info("Managed bundle "+managedBundle.getId()+" not found by OSGi Manager; "
                         + "ignoring when calculating persisted state catalog upgrades");
@@ -584,7 +589,21 @@ public class CatalogInitialization implements ManagementContextInjectable {
         }
         return catalogUpgradesBuilder.build();
     }
-    
+
+    private static class RegisteredTypesSupplier implements Supplier<Iterable<RegisteredType>> {
+        private final BrooklynTypeRegistry typeRegistry;
+        private final ManagedBundle bundle;
+        
+        RegisteredTypesSupplier(ManagementContext mgmt, ManagedBundle bundle) {
+            this.typeRegistry = mgmt.getTypeRegistry();
+            this.bundle = bundle;
+        }
+        @Override
+        public Iterable<RegisteredType> get() {
+            return typeRegistry.getMatching(RegisteredTypePredicates.containingBundle(bundle));
+        }
+    }
+
     public interface RebindLogger {
         void debug(String message, Object... args);
         void info(String message, Object... args);

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/2cd4e087/core/src/main/java/org/apache/brooklyn/core/typereg/BundleUpgradeParser.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/brooklyn/core/typereg/BundleUpgradeParser.java b/core/src/main/java/org/apache/brooklyn/core/typereg/BundleUpgradeParser.java
index 9766514..69bd583 100644
--- a/core/src/main/java/org/apache/brooklyn/core/typereg/BundleUpgradeParser.java
+++ b/core/src/main/java/org/apache/brooklyn/core/typereg/BundleUpgradeParser.java
@@ -28,6 +28,7 @@ import java.util.List;
 import java.util.Set;
 
 import org.apache.brooklyn.api.catalog.CatalogItem;
+import org.apache.brooklyn.api.typereg.RegisteredType;
 import org.apache.brooklyn.util.osgi.VersionedName;
 import org.apache.brooklyn.util.text.BrooklynVersionSyntax;
 import org.apache.brooklyn.util.text.QuotedStringTokenizer;
@@ -38,6 +39,7 @@ import org.osgi.framework.VersionRange;
 import com.google.common.annotations.Beta;
 import com.google.common.annotations.VisibleForTesting;
 import com.google.common.base.Objects;
+import com.google.common.base.Supplier;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableSet;
 
@@ -50,6 +52,19 @@ public class BundleUpgradeParser {
      * A header in a bundle's manifest, indicating that this bundle will force the removal of the 
      * given legacy catalog items. Here "legacy" means those in the `/catalog` persisted state, 
      * rather than items added in bundles.
+     * 
+     * The format for the value is one of:
+     * <ul>
+     *   <li>Quoted {@code name:versionRange}, where version range follows the OSGi conventions 
+     *       (except that a single version number means exactly that version rather than greater 
+     *       than or equal to that verison). For example, {@code "my-tomcat:[0,1)"}
+     *   <li>Comma-separated list of quoted {@code name:versionRange}. For example,
+     *       {@code "my-tomcat:[0,1)","my-nginx:[0,1)"}
+     *   <li>{@code "*"} means all legacy items for things defined in this bundle, with version
+     *       numbers older than the version of the bundle. For example, if the bundle is 
+     *       version 1.0.0 and its catalog.bom contains items "foo" and "bar", then it is equivalent
+     *       to writing {@code "foo:[0,1.0.0)","bar:[0,1.0.0)"}
+     * </ul>
      */
     @Beta
     public static final String MANIFEST_HEADER_FORCE_REMOVE_LEGACY_ITEMS = "brooklyn-catalog-force-remove-legacy-items";
@@ -57,6 +72,19 @@ public class BundleUpgradeParser {
     /**
      * A header in a bundle's manifest, indicating that this bundle will force the removal of matching 
      * bundle(s) that are in the `/bundles` persisted state.
+     * 
+     * The format for the value is one of:
+     * <ul>
+     *   <li>Quoted {@code name:versionRange}, where version range follows the OSGi conventions 
+     *       (except that a single version number means exactly that version rather than greater 
+     *       than or equal to that verison). For example, {@code "org.example.mybundle:[0,1)"}
+     *   <li>Comma-separated list of quoted {@code name:versionRange}. For example,
+     *       {@code "org.example.mybundle:[0,1)","org.example.myotherbundle:[0,1)"} (useful for
+     *       when this bundle merges the contents of two previous bundles).
+     *   <li>{@code "*"} means all older versions of this bundle. For example, if the bundle is 
+     *       {@code org.example.mybundle:1.0.0}, then it is equivalent to writing 
+     *       {@code "org.example.mybundle:[0,1.0.0)"}
+     * </ul>
      */
     @Beta
     public static final String MANIFEST_HEADER_FORCE_REMOVE_BUNDLES = "brooklyn-catalog-force-remove-bundles";
@@ -212,30 +240,46 @@ public class BundleUpgradeParser {
         }
     }
 
-    public static CatalogUpgrades parseBundleManifestForCatalogUpgrades(Bundle bundle) {
+    public static CatalogUpgrades parseBundleManifestForCatalogUpgrades(Bundle bundle, Supplier<? extends Iterable<? extends RegisteredType>> typeSupplier) {
         // TODO Add support for the other options described in the proposal:
         //   https://docs.google.com/document/d/1Lm47Kx-cXPLe8BO34-qrL3ZMPosuUHJILYVQUswEH6Y/edit#
         //   section "Bundle Upgrade Metadata"
         
         Dictionary<String, String> headers = bundle.getHeaders();
         return CatalogUpgrades.builder()
-                .removedLegacyItems(parseVersionRangedNameList(headers.get(MANIFEST_HEADER_FORCE_REMOVE_LEGACY_ITEMS), false))
-                .removedBundles(parseForceRemoveBundlesHeader(bundle, headers.get(MANIFEST_HEADER_FORCE_REMOVE_BUNDLES)))
+                .removedLegacyItems(parseForceRemoveLegacyItemsHeader(headers.get(MANIFEST_HEADER_FORCE_REMOVE_LEGACY_ITEMS), bundle, typeSupplier))
+                .removedBundles(parseForceRemoveBundlesHeader(headers.get(MANIFEST_HEADER_FORCE_REMOVE_BUNDLES), bundle))
                 .build();
     }
 
     @VisibleForTesting
-    static List<VersionRangedName> parseForceRemoveBundlesHeader(Bundle context, String input) {
+    static List<VersionRangedName> parseForceRemoveLegacyItemsHeader(String input, Bundle bundle, Supplier<? extends Iterable<? extends RegisteredType>> typeSupplier) {
+        if (input == null) return ImmutableList.of();
+        if (stripQuotes(input.trim()).equals("*")) {
+            VersionRange versionRange = VersionRange.valueOf("[0,"+bundle.getVersion()+")");
+            List<VersionRangedName> result = new ArrayList<>();
+            for (RegisteredType item : typeSupplier.get()) {
+                result.add(new VersionRangedName(item.getSymbolicName(), versionRange));
+            }
+            return result;
+        } else {
+            return parseVersionRangedNameList(input, false);
+        }
+    }
+    
+
+    @VisibleForTesting
+    static List<VersionRangedName> parseForceRemoveBundlesHeader(String input, Bundle bundle) {
         if (input == null) return ImmutableList.of();
-        if (stripQuotes(input).equals("*")) {
-            String bundleVersion = context.getVersion().toString();
+        if (stripQuotes(input.trim()).equals("*")) {
+            String bundleVersion = bundle.getVersion().toString();
             String maxVersion;
             if (BrooklynVersionSyntax.isSnapshot(bundleVersion)) {
                 maxVersion = BrooklynVersionSyntax.stripSnapshot(bundleVersion);
             } else {
                 maxVersion = bundleVersion;
             }
-            return ImmutableList.of(new VersionRangedName(context.getSymbolicName(), "[0,"+maxVersion+")"));
+            return ImmutableList.of(new VersionRangedName(bundle.getSymbolicName(), "[0,"+maxVersion+")"));
         } else {
             return parseVersionRangedNameList(input, false);
         }
@@ -258,7 +302,8 @@ public class BundleUpgradeParser {
         return versionedItems;
     }
     
-    private static String stripQuotes(String input) {
+    @VisibleForTesting
+    static String stripQuotes(String input) {
         String quoteChars = QuotedStringTokenizer.DEFAULT_QUOTE_CHARS;
         boolean quoted = (input.length() >= 2) && quoteChars.contains(input.substring(0, 1))
                 && quoteChars.contains(input.substring(input.length() - 1));

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/2cd4e087/core/src/test/java/org/apache/brooklyn/core/typereg/BundleUpgradeParserTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/org/apache/brooklyn/core/typereg/BundleUpgradeParserTest.java b/core/src/test/java/org/apache/brooklyn/core/typereg/BundleUpgradeParserTest.java
index 7573bee..341cccb 100644
--- a/core/src/test/java/org/apache/brooklyn/core/typereg/BundleUpgradeParserTest.java
+++ b/core/src/test/java/org/apache/brooklyn/core/typereg/BundleUpgradeParserTest.java
@@ -28,6 +28,7 @@ import java.util.List;
 import java.util.Map;
 
 import org.apache.brooklyn.api.catalog.CatalogItem;
+import org.apache.brooklyn.api.typereg.RegisteredType;
 import org.apache.brooklyn.core.typereg.BundleUpgradeParser.CatalogUpgrades;
 import org.apache.brooklyn.core.typereg.BundleUpgradeParser.VersionRangedName;
 import org.apache.brooklyn.test.Asserts;
@@ -39,6 +40,8 @@ import org.osgi.framework.Version;
 import org.osgi.framework.VersionRange;
 import org.testng.annotations.Test;
 
+import com.google.common.base.Supplier;
+import com.google.common.base.Suppliers;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
 
@@ -105,30 +108,39 @@ public class BundleUpgradeParserTest {
 
     @Test
     public void testParseForceRemoveBundlesHeader() throws Exception {
-        Bundle bundle = Mockito.mock(Bundle.class);
-        Mockito.when(bundle.getSymbolicName()).thenReturn("foo.bar");
-        Mockito.when(bundle.getVersion()).thenReturn(Version.valueOf("1.2.3"));
+        Bundle bundle = newMockBundle(new VersionedName("foo.bar", "1.2.3"));
         
-        assertParseForceRemoveBundlesHeader(bundle, "\"foo:0.1.0\"", ImmutableList.of(new VersionRangedName("foo", exactly0dot1)));
-        assertParseForceRemoveBundlesHeader(bundle, "\"*\"", ImmutableList.of(new VersionRangedName("foo.bar", from0lessThan1_2_3)));
-        assertParseForceRemoveBundlesHeader(bundle, "*", ImmutableList.of(new VersionRangedName("foo.bar", from0lessThan1_2_3)));
+        assertParseForceRemoveBundlesHeader("\"foo:0.1.0\"", bundle, ImmutableList.of(new VersionRangedName("foo", exactly0dot1)));
+        assertParseForceRemoveBundlesHeader("\"*\"", bundle, ImmutableList.of(new VersionRangedName("foo.bar", from0lessThan1_2_3)));
+        assertParseForceRemoveBundlesHeader("*", bundle, ImmutableList.of(new VersionRangedName("foo.bar", from0lessThan1_2_3)));
     }
     
     @Test
     public void testParseForceRemoveBundlesHeaderWithSnapshot() throws Exception {
-        Bundle bundle = Mockito.mock(Bundle.class);
-        Mockito.when(bundle.getSymbolicName()).thenReturn("foo.bar");
-        Mockito.when(bundle.getVersion()).thenReturn(Version.valueOf("1.2.3.SNAPSHOT"));
+        Bundle bundle = newMockBundle(new VersionedName("foo.bar", "1.2.3.SNAPSHOT"));
         
-        assertParseForceRemoveBundlesHeader(bundle, "\"*\"", ImmutableList.of(new VersionRangedName("foo.bar", from0lessThan1_2_3)));
-        assertParseForceRemoveBundlesHeader(bundle, "*", ImmutableList.of(new VersionRangedName("foo.bar", from0lessThan1_2_3)));
+        assertParseForceRemoveBundlesHeader("\"*\"", bundle, ImmutableList.of(new VersionRangedName("foo.bar", from0lessThan1_2_3)));
+        assertParseForceRemoveBundlesHeader("*", bundle, ImmutableList.of(new VersionRangedName("foo.bar", from0lessThan1_2_3)));
+    }
+    
+    @Test
+    public void testParseForceRemoveLegacyItemsHeader() throws Exception {
+        Bundle bundle = newMockBundle(new VersionedName("mybundle", "1.0.0"));
+        Supplier<Iterable<RegisteredType>> typeSupplier = Suppliers.ofInstance(ImmutableList.of(
+                newMockRegisteredType("foo", "1.0.0"),
+                newMockRegisteredType("bar", "1.0.0")));
+        
+        assertParseForceRemoveLegacyItemsHeader("\"foo:0.1.0\"", bundle, typeSupplier, ImmutableList.of(new VersionRangedName("foo", exactly0dot1)));
+        assertParseForceRemoveLegacyItemsHeader("\"*\"", bundle, typeSupplier, ImmutableList.of(new VersionRangedName("foo", from0lessThan1), new VersionRangedName("bar", from0lessThan1)));
+        assertParseForceRemoveLegacyItemsHeader("*", bundle, typeSupplier, ImmutableList.of(new VersionRangedName("foo", from0lessThan1), new VersionRangedName("bar", from0lessThan1)));
     }
     
     @Test
     public void testParseBundleEmptyManifest() throws Exception {
         Bundle bundle = newMockBundle(ImmutableMap.of());
+        Supplier<Iterable<RegisteredType>> typeSupplier = Suppliers.ofInstance(ImmutableList.of());
         
-        CatalogUpgrades upgrades = BundleUpgradeParser.parseBundleManifestForCatalogUpgrades(bundle);
+        CatalogUpgrades upgrades = BundleUpgradeParser.parseBundleManifestForCatalogUpgrades(bundle, typeSupplier);
         assertTrue(upgrades.isEmpty());
         assertFalse(upgrades.isBundleRemoved(new VersionedName("org.example.brooklyn.mybundle", "0.1.0")));
         assertFalse(upgrades.isLegacyItemRemoved(newMockCatalogItem("foo", "0.1.0")));
@@ -139,8 +151,9 @@ public class BundleUpgradeParserTest {
         Bundle bundle = newMockBundle(ImmutableMap.of(
                 BundleUpgradeParser.MANIFEST_HEADER_FORCE_REMOVE_LEGACY_ITEMS, "\"foo:[0,1.0.0)\",\"bar:[0,1.0.0)\"",
                 BundleUpgradeParser.MANIFEST_HEADER_FORCE_REMOVE_BUNDLES, "\"org.example.brooklyn.mybundle:[0,1.0.0)\""));
+        Supplier<Iterable<RegisteredType>> typeSupplier = Suppliers.ofInstance(ImmutableList.of());
         
-        CatalogUpgrades upgrades = BundleUpgradeParser.parseBundleManifestForCatalogUpgrades(bundle);
+        CatalogUpgrades upgrades = BundleUpgradeParser.parseBundleManifestForCatalogUpgrades(bundle, typeSupplier);
         assertFalse(upgrades.isEmpty());
         assertTrue(upgrades.isBundleRemoved(new VersionedName("org.example.brooklyn.mybundle", "0.1.0")));
         assertFalse(upgrades.isBundleRemoved(new VersionedName("org.example.brooklyn.mybundle", "1.0.0")));
@@ -152,10 +165,36 @@ public class BundleUpgradeParserTest {
         assertFalse(upgrades.isLegacyItemRemoved(newMockCatalogItem("different", "0.1.0")));
     }
 
+    @Test
+    public void testStripQuotes() throws Exception {
+        assertEquals(BundleUpgradeParser.stripQuotes("a"), "a");
+        assertEquals(BundleUpgradeParser.stripQuotes("'a'"), "a");
+        assertEquals(BundleUpgradeParser.stripQuotes("\"\""), "");
+        assertEquals(BundleUpgradeParser.stripQuotes("''"), "");
+    }
+    
     private Bundle newMockBundle(Map<String, String> rawHeaders) {
+        return newMockBundle(VersionedName.fromString("do.no.care:1.2.3"), rawHeaders);
+    }
+
+    private Bundle newMockBundle(VersionedName name) {
+        return newMockBundle(name, ImmutableMap.of());
+    }
+    
+    private Bundle newMockBundle(VersionedName name, Map<String, String> rawHeaders) {
         Dictionary<String, String> headers = new Hashtable<>(rawHeaders);
         Bundle result = Mockito.mock(Bundle.class);
         Mockito.when(result.getHeaders()).thenReturn(headers);
+        Mockito.when(result.getSymbolicName()).thenReturn(name.getSymbolicName());
+        Mockito.when(result.getVersion()).thenReturn(Version.valueOf(name.getOsgiVersionString()));
+        return result;
+    }
+
+    private RegisteredType newMockRegisteredType(String symbolicName, String version) {
+        RegisteredType result = Mockito.mock(RegisteredType.class);
+        Mockito.when(result.getSymbolicName()).thenReturn(symbolicName);
+        Mockito.when(result.getVersion()).thenReturn(version);
+        Mockito.when(result.getVersionedName()).thenReturn(new VersionedName(symbolicName, version));
         return result;
     }
 
@@ -173,8 +212,13 @@ public class BundleUpgradeParserTest {
         assertListsEqual(actual, expected);
     }
     
-    private void assertParseForceRemoveBundlesHeader(Bundle bundle, String input, List<VersionRangedName> expected) throws Exception {
-        List<VersionRangedName> actual = BundleUpgradeParser.parseForceRemoveBundlesHeader(bundle, input);
+    private void assertParseForceRemoveBundlesHeader(String input, Bundle bundle, List<VersionRangedName> expected) throws Exception {
+        List<VersionRangedName> actual = BundleUpgradeParser.parseForceRemoveBundlesHeader(input, bundle);
+        assertListsEqual(actual, expected);
+    }
+
+    private void assertParseForceRemoveLegacyItemsHeader(String input, Bundle bundle, Supplier<? extends Iterable<? extends RegisteredType>> typeSupplier, List<VersionRangedName> expected) throws Exception {
+        List<VersionRangedName> actual = BundleUpgradeParser.parseForceRemoveLegacyItemsHeader(input, bundle, typeSupplier);
         assertListsEqual(actual, expected);
     }
 


[7/7] brooklyn-server git commit: This closes #866

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


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

Branch: refs/heads/master
Commit: 9045005ddeb89cedebf626d6d6a2b2fef50c27dd
Parents: 05d6ee0 2ecdf6a
Author: Alex Heneveld <al...@cloudsoftcorp.com>
Authored: Fri Oct 27 11:56:50 2017 +0100
Committer: Alex Heneveld <al...@cloudsoftcorp.com>
Committed: Fri Oct 27 11:56:50 2017 +0100

----------------------------------------------------------------------
 .../catalog/internal/CatalogInitialization.java |  87 +++++-
 .../core/mgmt/rebind/RebindIteration.java       |  18 +-
 .../core/typereg/BundleUpgradeParser.java       | 312 +++++++++++++++++++
 .../core/typereg/BundleUpgradeParserTest.java   | 242 ++++++++++++++
 .../AbstractBrooklynLauncherRebindTest.java     | 196 +++++++++++-
 .../BrooklynLauncherRebindCatalogOsgiTest.java  |  47 ---
 .../BrooklynLauncherUpgradeCatalogOsgiTest.java | 147 +++++++++
 .../util/text/BrooklynVersionSyntax.java        |  40 +++
 .../util/text/BrooklynVersionSyntaxTest.java    |  38 +++
 9 files changed, 1064 insertions(+), 63 deletions(-)
----------------------------------------------------------------------



[6/7] brooklyn-server git commit: rebind complete: log how many catalog bundles

Posted by he...@apache.org.
rebind complete: log how many catalog bundles

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

Branch: refs/heads/master
Commit: 2ecdf6afa5f79f6edd64d4f02b90dc9dbeeb04dd
Parents: 2cd4e08
Author: Aled Sage <al...@gmail.com>
Authored: Fri Oct 27 09:41:41 2017 +0100
Committer: Aled Sage <al...@gmail.com>
Committed: Fri Oct 27 09:41:41 2017 +0100

----------------------------------------------------------------------
 .../org/apache/brooklyn/core/mgmt/rebind/RebindIteration.java   | 5 +++--
 1 file changed, 3 insertions(+), 2 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/2ecdf6af/core/src/main/java/org/apache/brooklyn/core/mgmt/rebind/RebindIteration.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/brooklyn/core/mgmt/rebind/RebindIteration.java b/core/src/main/java/org/apache/brooklyn/core/mgmt/rebind/RebindIteration.java
index 3038429..755107a 100644
--- a/core/src/main/java/org/apache/brooklyn/core/mgmt/rebind/RebindIteration.java
+++ b/core/src/main/java/org/apache/brooklyn/core/mgmt/rebind/RebindIteration.java
@@ -761,14 +761,15 @@ public abstract class RebindIteration {
         if (!isEmpty) {
             BrooklynLogging.log(LOG, shouldLogRebinding() ? LoggingLevel.INFO : LoggingLevel.DEBUG, 
                 "Rebind complete " + "("+mode+(readOnlyRebindCount.get()>=0 ? ", iteration "+readOnlyRebindCount : "")+")" +
-                    " in {}: {} app{}, {} entit{}, {} location{}, {} polic{}, {} enricher{}, {} feed{}, {} catalog item{}",
+                    " in {}: {} app{}, {} entit{}, {} location{}, {} polic{}, {} enricher{}, {} feed{}, {} catalog item{}, {} catalog bundle{}",
                 Time.makeTimeStringRounded(timer), applications.size(), Strings.s(applications),
                 rebindContext.getEntities().size(), Strings.ies(rebindContext.getEntities()),
                 rebindContext.getLocations().size(), Strings.s(rebindContext.getLocations()),
                 rebindContext.getPolicies().size(), Strings.ies(rebindContext.getPolicies()),
                 rebindContext.getEnrichers().size(), Strings.s(rebindContext.getEnrichers()),
                 rebindContext.getFeeds().size(), Strings.s(rebindContext.getFeeds()),
-                rebindContext.getCatalogItems().size(), Strings.s(rebindContext.getCatalogItems())
+                rebindContext.getCatalogItems().size(), Strings.s(rebindContext.getCatalogItems()),
+                rebindContext.getBundles().size(), Strings.s(rebindContext.getBundles())
             );
         }
 


[4/7] brooklyn-server git commit: PR #866: incorporate comments for bundle-upgrade

Posted by he...@apache.org.
PR #866: incorporate comments for bundle-upgrade


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

Branch: refs/heads/master
Commit: 22fda0dd7980ea3456b3b3e7f5687d9271fdcf32
Parents: dba226f
Author: Aled Sage <al...@gmail.com>
Authored: Wed Oct 25 07:41:54 2017 +0100
Committer: Aled Sage <al...@gmail.com>
Committed: Wed Oct 25 08:37:58 2017 +0100

----------------------------------------------------------------------
 .../catalog/internal/BundleUpgradeParser.java   | 239 ------------------
 .../catalog/internal/CatalogInitialization.java |  16 +-
 .../core/typereg/BundleUpgradeParser.java       | 245 +++++++++++++++++++
 .../internal/BundleUpgradeParserTest.java       | 148 -----------
 .../core/typereg/BundleUpgradeParserTest.java   | 167 +++++++++++++
 .../BrooklynLauncherUpgradeCatalogOsgiTest.java |   4 +-
 6 files changed, 425 insertions(+), 394 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/22fda0dd/core/src/main/java/org/apache/brooklyn/core/catalog/internal/BundleUpgradeParser.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/brooklyn/core/catalog/internal/BundleUpgradeParser.java b/core/src/main/java/org/apache/brooklyn/core/catalog/internal/BundleUpgradeParser.java
deleted file mode 100644
index 0773014..0000000
--- a/core/src/main/java/org/apache/brooklyn/core/catalog/internal/BundleUpgradeParser.java
+++ /dev/null
@@ -1,239 +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.catalog.internal;
-
-import static com.google.common.base.Preconditions.checkNotNull;
-
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Dictionary;
-import java.util.LinkedHashSet;
-import java.util.List;
-import java.util.Set;
-
-import org.apache.brooklyn.api.catalog.CatalogItem;
-import org.apache.brooklyn.util.osgi.VersionedName;
-import org.apache.brooklyn.util.text.BrooklynVersionSyntax;
-import org.apache.brooklyn.util.text.QuotedStringTokenizer;
-import org.apache.brooklyn.util.text.Strings;
-import org.osgi.framework.Bundle;
-import org.osgi.framework.VersionRange;
-
-import com.google.common.annotations.Beta;
-import com.google.common.annotations.VisibleForTesting;
-import com.google.common.base.Objects;
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.ImmutableSet;
-
-public class BundleUpgradeParser {
-
-    @Beta
-    public static final String MANIFEST_HEADER_FORCE_REMOVE_LEGACY_ITEMS = "brooklyn-catalog-force-remove-legacy-items";
-
-    @Beta
-    public static final String MANIFEST_HEADER_FORCE_REMOVE_BUNDLES = "brooklyn-catalog-force-remove-bundles";
-    
-    @Beta
-    public static final String MANIFEST_HEADER_UPGRADE_BUNDLES = "brooklyn-catalog-upgrade-for-bundles";
-
-    /**
-     * The result from parsing bundle(s) to find their upgrade info.
-     */
-    public static class CatalogUpgrades {
-        static final CatalogUpgrades EMPTY = new CatalogUpgrades(builder());
-        
-        static class Builder {
-            private Set<VersionRangedName> removedLegacyItems = new LinkedHashSet<>();
-            private Set<VersionRangedName> removedBundles = new LinkedHashSet<>();
-
-            public Builder removedLegacyItems(Collection<VersionRangedName> vals) {
-                removedLegacyItems.addAll(vals);
-                return this;
-            }
-            public Builder removedBundles(Collection<VersionRangedName> vals) {
-                removedBundles.addAll(vals);
-                return this;
-            }
-            public Builder addAll(CatalogUpgrades other) {
-                removedLegacyItems.addAll(other.removedLegacyItems);
-                removedBundles.addAll(other.removedBundles);
-                return this;
-            }
-            public CatalogUpgrades build() {
-                return new CatalogUpgrades(this);
-            }
-        }
-        
-        public static Builder builder() {
-            return new Builder();
-        }
-        
-        private Set<VersionRangedName> removedLegacyItems;
-        private Set<VersionRangedName> removedBundles;
-        
-        public CatalogUpgrades(Builder builder) {
-            this.removedLegacyItems = ImmutableSet.copyOf(builder.removedLegacyItems);
-            this.removedBundles = ImmutableSet.copyOf(builder.removedBundles);
-        }
-
-        public boolean isEmpty() {
-            return removedLegacyItems.isEmpty() && removedBundles.isEmpty();
-        }
-
-        public Set<VersionRangedName> getRemovedLegacyItems() {
-            return removedLegacyItems;
-        }
-        
-        public Set<VersionRangedName> getRemovedBundles() {
-            return removedBundles;
-        }
-
-        public boolean isLegacyItemRemoved(CatalogItem<?, ?> legacyCatalogItem) {
-            VersionedName name = new VersionedName(legacyCatalogItem.getSymbolicName(), legacyCatalogItem.getVersion());
-            return contains(removedLegacyItems, name);
-        }
-
-        public boolean isBundleRemoved(VersionedName bundle) {
-            return contains(removedBundles, bundle);
-        }
-        
-        public boolean contains(Iterable<VersionRangedName> names, VersionedName name) {
-            for (VersionRangedName contender : names) {
-                if (contender.getSymbolicName().equals(name.getSymbolicName()) && contender.getOsgiVersionRange().includes(name.getOsgiVersion())) {
-                    return true;
-                }
-            }
-            return false;
-        }
-    }
-    
-    /**
-     * Records a name (string) and version range (string),
-     * with conveniences for pretty-printing and converting to OSGi format.
-     * 
-     * Implementation-wise, this is similar to {@link VersionedName}, but is intended
-     * as internal-only so is cut down to only what is needed.
-     */
-    public static class VersionRangedName {
-        private final String name;
-        private final String v;
-        private transient volatile VersionRange cachedOsgiVersionRange;
-
-        public static VersionRangedName fromString(String val, boolean singleVersionIsOsgiRange) {
-            if (Strings.isBlank(val)) {
-                throw new IllegalArgumentException("Must not be blank");
-            }
-            String[] parts = val.split(":");
-            if (parts.length > 2) {
-                throw new IllegalArgumentException("Identifier '"+val+"' has too many parts; max one ':' symbol");
-            }
-            if (parts.length == 1 || Strings.isBlank(parts[1])) {
-                throw new IllegalArgumentException("Identifier '"+val+"' must be of 'name:versionRange' syntax");
-            } else if (singleVersionIsOsgiRange || (parts[1].startsWith("(") || parts[1].startsWith("["))) {
-                return new VersionRangedName(parts[0], parts[1]);
-            } else {
-                return new VersionRangedName(parts[0], "["+parts[1]+","+parts[1]+"]");
-            }
-        }
-
-        public VersionRangedName(String name, VersionRange v) {
-            this.name = checkNotNull(name, "name").toString();
-            this.v = checkNotNull(v, "versionRange").toString();
-        }
-        
-        private VersionRangedName(String name, String v) {
-            this.name = checkNotNull(name, "name");
-            this.v = checkNotNull(v, "versionRange");
-        }
-        
-        @Override
-        public String toString() {
-            return name + ":" + v;
-        }
-        
-        public String toOsgiString() {
-            return name + ":" + getOsgiVersionRange();
-        }
-
-        public String getSymbolicName() {
-            return name;
-        }
-
-        public VersionRange getOsgiVersionRange() {
-            if (cachedOsgiVersionRange == null) {
-                cachedOsgiVersionRange = VersionRange.valueOf(BrooklynVersionSyntax.toValidOsgiVersionRange(v));
-            }
-            return cachedOsgiVersionRange;
-        }
-
-        public String getVersionString() {
-            return v;
-        }
-        
-        @Override
-        public int hashCode() {
-            return Objects.hashCode(name, v);
-        }
-
-        @Override
-        public boolean equals(Object other) {
-            if (!(other instanceof VersionRangedName)) {
-                return false;
-            }
-            VersionRangedName o = (VersionRangedName) other;
-            return Objects.equal(name, o.name) && Objects.equal(v, o.v);
-        }
-    }
-
-    public static CatalogUpgrades parseBundleManifestForCatalogUpgrades(Bundle bundle) {
-        Dictionary<String, String> headers = bundle.getHeaders();
-        String removedLegacyItemsHeader = headers.get(MANIFEST_HEADER_FORCE_REMOVE_LEGACY_ITEMS);
-        String removedBundlesHeader = headers.get(MANIFEST_HEADER_FORCE_REMOVE_BUNDLES);
-        List<VersionRangedName> removedLegacyItems = ImmutableList.of();
-        List<VersionRangedName> removedBundles = ImmutableList.of();
-        if (removedLegacyItemsHeader == null && removedBundlesHeader == null) {
-            return CatalogUpgrades.EMPTY;
-        }
-        if (removedLegacyItemsHeader != null) {
-            removedLegacyItems = parseVersionRangedNameList(removedLegacyItemsHeader, false);
-        }
-        if (removedBundlesHeader != null) {
-            removedBundles = parseVersionRangedNameList(removedBundlesHeader, false);
-        }
-        return CatalogUpgrades.builder()
-                .removedLegacyItems(removedLegacyItems)
-                .removedBundles(removedBundles)
-                .build();
-    }
-    
-    @VisibleForTesting
-    static List<VersionRangedName> parseVersionRangedNameList(String input, boolean singleVersionIsOsgiRange) {
-        List<String> vals = QuotedStringTokenizer.builder()
-                .delimiterChars(",")
-                .includeQuotes(false)
-                .includeDelimiters(false)
-                .buildList(input);
-        
-        List<VersionRangedName> versionedItems = new ArrayList<>();
-        for (String val : vals) {
-            versionedItems.add(VersionRangedName.fromString(val, singleVersionIsOsgiRange));
-        }
-        return versionedItems;
-    }
-}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/22fda0dd/core/src/main/java/org/apache/brooklyn/core/catalog/internal/CatalogInitialization.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/brooklyn/core/catalog/internal/CatalogInitialization.java b/core/src/main/java/org/apache/brooklyn/core/catalog/internal/CatalogInitialization.java
index 9f9da67..107a6b2 100644
--- a/core/src/main/java/org/apache/brooklyn/core/catalog/internal/CatalogInitialization.java
+++ b/core/src/main/java/org/apache/brooklyn/core/catalog/internal/CatalogInitialization.java
@@ -42,15 +42,16 @@ import org.apache.brooklyn.api.objs.BrooklynObjectType;
 import org.apache.brooklyn.api.typereg.BrooklynTypeRegistry.RegisteredTypeKind;
 import org.apache.brooklyn.api.typereg.ManagedBundle;
 import org.apache.brooklyn.api.typereg.RegisteredType;
-import org.apache.brooklyn.core.catalog.internal.BundleUpgradeParser.CatalogUpgrades;
 import org.apache.brooklyn.core.mgmt.ManagementContextInjectable;
 import org.apache.brooklyn.core.mgmt.ha.OsgiBundleInstallationResult;
 import org.apache.brooklyn.core.mgmt.ha.OsgiManager;
 import org.apache.brooklyn.core.mgmt.internal.ManagementContextInternal;
 import org.apache.brooklyn.core.objs.BrooklynTypes;
 import org.apache.brooklyn.core.server.BrooklynServerConfig;
+import org.apache.brooklyn.core.typereg.BundleUpgradeParser;
 import org.apache.brooklyn.core.typereg.RegisteredTypePredicates;
 import org.apache.brooklyn.core.typereg.RegisteredTypes;
+import org.apache.brooklyn.core.typereg.BundleUpgradeParser.CatalogUpgrades;
 import org.apache.brooklyn.util.collections.MutableList;
 import org.apache.brooklyn.util.collections.MutableMap;
 import org.apache.brooklyn.util.collections.MutableSet;
@@ -236,7 +237,7 @@ public class CatalogInitialization implements ManagementContextInjectable {
             
             populateInitialCatalogImpl(true);
             
-            PersistedCatalogState filteredPersistedState = filterPersistedState(persistedState, rebindLogger);
+            PersistedCatalogState filteredPersistedState = filterBundlesAndCatalogInPersistedState(persistedState, rebindLogger);
             addPersistedCatalogImpl(filteredPersistedState, exceptionHandler, rebindLogger);
             
             if (mode == ManagementNodeState.MASTER) {
@@ -528,8 +529,13 @@ public class CatalogInitialization implements ManagementContextInjectable {
         }
     }
 
-    private PersistedCatalogState filterPersistedState(PersistedCatalogState persistedState, RebindLogger rebindLogger) {
-        CatalogUpgrades catalogUpgrades = findCatalogUpgrades(rebindLogger);
+    private PersistedCatalogState filterBundlesAndCatalogInPersistedState(PersistedCatalogState persistedState, RebindLogger rebindLogger) {
+        // TODO Will need to share the results of `findCatalogUpgrades` when we support
+        // other options, such as, `brooklyn-catalog-upgrade-for-bundles`, described in the proposal:
+        //   https://docs.google.com/document/d/1Lm47Kx-cXPLe8BO34-qrL3ZMPosuUHJILYVQUswEH6Y/edit#
+        //   section "Bundle Upgrade Metadata"
+
+        CatalogUpgrades catalogUpgrades = findCatalogUpgradesInstructions(rebindLogger);
         
         if (catalogUpgrades.isEmpty()) {
             return persistedState;
@@ -558,7 +564,7 @@ public class CatalogInitialization implements ManagementContextInjectable {
         return new PersistedCatalogState(bundles, legacyCatalogItems);
     }
 
-    private CatalogUpgrades findCatalogUpgrades(RebindLogger rebindLogger) {
+    private CatalogUpgrades findCatalogUpgradesInstructions(RebindLogger rebindLogger) {
         Maybe<OsgiManager> osgiManager = ((ManagementContextInternal)managementContext).getOsgiManager();
         if (osgiManager.isAbsent()) {
             // Can't find any bundles to tell if there are upgrades. Could be running tests; do no filtering.

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/22fda0dd/core/src/main/java/org/apache/brooklyn/core/typereg/BundleUpgradeParser.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/brooklyn/core/typereg/BundleUpgradeParser.java b/core/src/main/java/org/apache/brooklyn/core/typereg/BundleUpgradeParser.java
new file mode 100644
index 0000000..96cd2b6
--- /dev/null
+++ b/core/src/main/java/org/apache/brooklyn/core/typereg/BundleUpgradeParser.java
@@ -0,0 +1,245 @@
+/*
+ * 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.typereg;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Dictionary;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Set;
+
+import org.apache.brooklyn.api.catalog.CatalogItem;
+import org.apache.brooklyn.util.osgi.VersionedName;
+import org.apache.brooklyn.util.text.BrooklynVersionSyntax;
+import org.apache.brooklyn.util.text.QuotedStringTokenizer;
+import org.apache.brooklyn.util.text.Strings;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.VersionRange;
+
+import com.google.common.annotations.Beta;
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Objects;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+
+/**
+ * Internal class for parsing bundle manifests to extract their upgrade instructions.
+ */
+public class BundleUpgradeParser {
+
+    /**
+     * A header in a bundle's manifest, indicating that this bundle will force the removal of the 
+     * given legacy catalog items. Here "legacy" means those in the `/catalog` persisted state, 
+     * rather than items added in bundles.
+     */
+    @Beta
+    public static final String MANIFEST_HEADER_FORCE_REMOVE_LEGACY_ITEMS = "brooklyn-catalog-force-remove-legacy-items";
+
+    /**
+     * A header in a bundle's manifest, indicating that this bundle will force the removal of matching 
+     * bundle(s) that are in the `/bundles` persisted state.
+     */
+    @Beta
+    public static final String MANIFEST_HEADER_FORCE_REMOVE_BUNDLES = "brooklyn-catalog-force-remove-bundles";
+    
+    /**
+     * The result from parsing bundle(s) to find their upgrade info.
+     */
+    public static class CatalogUpgrades {
+        public static final CatalogUpgrades EMPTY = new CatalogUpgrades(builder());
+        
+        public static class Builder {
+            private Set<VersionRangedName> removedLegacyItems = new LinkedHashSet<>();
+            private Set<VersionRangedName> removedBundles = new LinkedHashSet<>();
+
+            public Builder removedLegacyItems(Collection<VersionRangedName> vals) {
+                removedLegacyItems.addAll(vals);
+                return this;
+            }
+            public Builder removedBundles(Collection<VersionRangedName> vals) {
+                removedBundles.addAll(vals);
+                return this;
+            }
+            public Builder addAll(CatalogUpgrades other) {
+                removedLegacyItems.addAll(other.removedLegacyItems);
+                removedBundles.addAll(other.removedBundles);
+                return this;
+            }
+            public CatalogUpgrades build() {
+                return new CatalogUpgrades(this);
+            }
+        }
+        
+        public static Builder builder() {
+            return new Builder();
+        }
+        
+        private final Set<VersionRangedName> removedLegacyItems;
+        private final Set<VersionRangedName> removedBundles;
+        
+        public CatalogUpgrades(Builder builder) {
+            this.removedLegacyItems = ImmutableSet.copyOf(builder.removedLegacyItems);
+            this.removedBundles = ImmutableSet.copyOf(builder.removedBundles);
+        }
+
+        public boolean isEmpty() {
+            return removedLegacyItems.isEmpty() && removedBundles.isEmpty();
+        }
+
+        public Set<VersionRangedName> getRemovedLegacyItems() {
+            return removedLegacyItems;
+        }
+        
+        public Set<VersionRangedName> getRemovedBundles() {
+            return removedBundles;
+        }
+
+        public boolean isLegacyItemRemoved(CatalogItem<?, ?> legacyCatalogItem) {
+            VersionedName name = new VersionedName(legacyCatalogItem.getSymbolicName(), legacyCatalogItem.getVersion());
+            return contains(removedLegacyItems, name);
+        }
+
+        public boolean isBundleRemoved(VersionedName bundle) {
+            return contains(removedBundles, bundle);
+        }
+        
+        public boolean contains(Iterable<VersionRangedName> names, VersionedName name) {
+            for (VersionRangedName contender : names) {
+                if (contender.getSymbolicName().equals(name.getSymbolicName()) && contender.getOsgiVersionRange().includes(name.getOsgiVersion())) {
+                    return true;
+                }
+            }
+            return false;
+        }
+    }
+    
+    /**
+     * Records a name (string) and version range (string),
+     * with conveniences for pretty-printing and converting to OSGi format.
+     * 
+     * Implementation-wise, this is similar to {@link VersionedName}, but is intended
+     * as internal-only so is cut down to only what is needed.
+     * 
+     * Both the name and the version range are required.
+     */
+    public static class VersionRangedName {
+        private final String name;
+        private final String v;
+        private transient volatile VersionRange cachedOsgiVersionRange;
+
+        public static VersionRangedName fromString(String val, boolean singleVersionIsOsgiRange) {
+            if (Strings.isBlank(val)) {
+                throw new IllegalArgumentException("Must not be blank");
+            }
+            String[] parts = val.split(":");
+            if (parts.length > 2) {
+                throw new IllegalArgumentException("Identifier '"+val+"' has too many parts; max one ':' symbol");
+            }
+            if (parts.length == 1 || Strings.isBlank(parts[1])) {
+                throw new IllegalArgumentException("Identifier '"+val+"' must be of 'name:versionRange' syntax");
+            } else if (singleVersionIsOsgiRange || (parts[1].startsWith("(") || parts[1].startsWith("["))) {
+                return new VersionRangedName(parts[0], parts[1]);
+            } else {
+                return new VersionRangedName(parts[0], "["+parts[1]+","+parts[1]+"]");
+            }
+        }
+
+        public VersionRangedName(String name, VersionRange v) {
+            this.name = checkNotNull(name, "name").toString();
+            this.v = checkNotNull(v, "versionRange").toString();
+        }
+        
+        private VersionRangedName(String name, String v) {
+            this.name = checkNotNull(name, "name");
+            this.v = checkNotNull(v, "versionRange");
+        }
+        
+        @Override
+        public String toString() {
+            return name + ":" + v;
+        }
+        
+        public String toOsgiString() {
+            return name + ":" + getOsgiVersionRange();
+        }
+
+        public String getSymbolicName() {
+            return name;
+        }
+
+        public VersionRange getOsgiVersionRange() {
+            if (cachedOsgiVersionRange == null) {
+                cachedOsgiVersionRange = VersionRange.valueOf(BrooklynVersionSyntax.toValidOsgiVersionRange(v));
+            }
+            return cachedOsgiVersionRange;
+        }
+
+        public String getVersionString() {
+            return v;
+        }
+        
+        @Override
+        public int hashCode() {
+            return Objects.hashCode(name, v);
+        }
+
+        @Override
+        public boolean equals(Object other) {
+            if (!(other instanceof VersionRangedName)) {
+                return false;
+            }
+            VersionRangedName o = (VersionRangedName) other;
+            return Objects.equal(name, o.name) && Objects.equal(v, o.v);
+        }
+    }
+
+    public static CatalogUpgrades parseBundleManifestForCatalogUpgrades(Bundle bundle) {
+        // TODO Add support for "*" for "force-remove-bundles", to indicate all lower-versioned 
+        // bundles with the same symbolic name as this bundle. Also add support for the other 
+        // options described in the proposal:
+        //   https://docs.google.com/document/d/1Lm47Kx-cXPLe8BO34-qrL3ZMPosuUHJILYVQUswEH6Y/edit#
+        //   section "Bundle Upgrade Metadata"
+        
+        Dictionary<String, String> headers = bundle.getHeaders();
+        return CatalogUpgrades.builder()
+                .removedLegacyItems(parseVersionRangedNameList(headers.get(MANIFEST_HEADER_FORCE_REMOVE_LEGACY_ITEMS), false))
+                .removedBundles(parseVersionRangedNameList(headers.get(MANIFEST_HEADER_FORCE_REMOVE_BUNDLES), false))
+                .build();
+    }
+    
+    @VisibleForTesting
+    static List<VersionRangedName> parseVersionRangedNameList(String input, boolean singleVersionIsOsgiRange) {
+        if (input == null) return ImmutableList.of();
+        
+        List<String> vals = QuotedStringTokenizer.builder()
+                .delimiterChars(",")
+                .includeQuotes(false)
+                .includeDelimiters(false)
+                .buildList(input);
+        
+        List<VersionRangedName> versionedItems = new ArrayList<>();
+        for (String val : vals) {
+            versionedItems.add(VersionRangedName.fromString(val, singleVersionIsOsgiRange));
+        }
+        return versionedItems;
+    }
+}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/22fda0dd/core/src/test/java/org/apache/brooklyn/core/catalog/internal/BundleUpgradeParserTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/org/apache/brooklyn/core/catalog/internal/BundleUpgradeParserTest.java b/core/src/test/java/org/apache/brooklyn/core/catalog/internal/BundleUpgradeParserTest.java
deleted file mode 100644
index a409eaf..0000000
--- a/core/src/test/java/org/apache/brooklyn/core/catalog/internal/BundleUpgradeParserTest.java
+++ /dev/null
@@ -1,148 +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.catalog.internal;
-
-import static org.testng.Assert.assertEquals;
-import static org.testng.Assert.assertFalse;
-import static org.testng.Assert.assertTrue;
-
-import java.util.Dictionary;
-import java.util.Hashtable;
-import java.util.List;
-import java.util.Map;
-
-import org.apache.brooklyn.api.catalog.CatalogItem;
-import org.apache.brooklyn.core.catalog.internal.BundleUpgradeParser.CatalogUpgrades;
-import org.apache.brooklyn.core.catalog.internal.BundleUpgradeParser.VersionRangedName;
-import org.apache.brooklyn.test.Asserts;
-import org.apache.brooklyn.util.osgi.VersionedName;
-import org.mockito.Mockito;
-import org.osgi.framework.Bundle;
-import org.osgi.framework.Version;
-import org.osgi.framework.VersionRange;
-import org.testng.annotations.Test;
-
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.ImmutableMap;
-
-public class BundleUpgradeParserTest {
-
-    private VersionRange from0lessThan1 = new VersionRange('[', Version.valueOf("0"), Version.valueOf("1.0.0"), ')');
-    private VersionRange exactly0dot1 = new VersionRange('[', Version.valueOf("0.1.0"), Version.valueOf("0.1.0"), ']');
-    private VersionRangedName fooFrom0lessThan1 = new VersionRangedName("foo", from0lessThan1);
-    private VersionRangedName barFrom0lessThan1 = new VersionRangedName("bar", from0lessThan1);
-
-    @Test
-    public void testVersionRangedName() throws Exception {
-        assertEquals(VersionRangedName.fromString("foo:0.1.0", true).toOsgiString(), "foo:0.1.0");
-        assertEquals(VersionRangedName.fromString("foo:0.1.0", false).toOsgiString(), "foo:[0.1.0,0.1.0]");
-        assertEquals(VersionRangedName.fromString("foo:[0,1)", false).toOsgiString(), "foo:[0.0.0,1.0.0)");
-        
-        assertVersionRangedNameFails("foo", "'foo' must be of 'name:versionRange' syntax");
-        assertVersionRangedNameFails("foo:bar:0.1.0", "has too many parts");
-        assertVersionRangedNameFails("", "Must not be blank");
-        assertVersionRangedNameFails(null, "Must not be blank");
-    }
-    
-    @Test
-    public void testParseSingleQuotedVal() throws Exception {
-        String input = "\"foo:[0,1.0.0)\"";
-        assertParsed(input, ImmutableList.of(fooFrom0lessThan1));
-    }
-    
-    @Test
-    public void testParseSingleQuotedValWithNestedQuotes() throws Exception {
-        String input = "\"foo:[0,\"1.0.0\")\"";
-        assertParsed(input, ImmutableList.of(fooFrom0lessThan1));
-    }
-    
-    @Test
-    public void testParseMultipleVals() throws Exception {
-        String input = "\"foo:[0,1.0.0)\",\"bar:[0,1.0.0)\"";
-        assertParsed(input, ImmutableList.of(fooFrom0lessThan1, barFrom0lessThan1));
-    }
-
-    @Test
-    public void testParseValWithExactVersion() throws Exception {
-        String input = "\"foo:0.1.0\"";
-        assertParsed(input, ImmutableList.of(new VersionRangedName("foo", exactly0dot1)));
-    }
-
-    @Test
-    public void testParseBundleEmptyManifest() throws Exception {
-        Bundle bundle = newMockBundle(ImmutableMap.of());
-        
-        CatalogUpgrades upgrades = BundleUpgradeParser.parseBundleManifestForCatalogUpgrades(bundle);
-        assertTrue(upgrades.isEmpty());
-        assertFalse(upgrades.isBundleRemoved(new VersionedName("org.example.brooklyn.mybundle", "0.1.0")));
-        assertFalse(upgrades.isLegacyItemRemoved(newMockCatalogItem("foo", "0.1.0")));
-    }
-
-    @Test
-    public void testParseBundleManifest() throws Exception {
-        Bundle bundle = newMockBundle(ImmutableMap.of(
-                BundleUpgradeParser.MANIFEST_HEADER_FORCE_REMOVE_LEGACY_ITEMS, "\"foo:[0,1.0.0)\",\"bar:[0,1.0.0)\"",
-                BundleUpgradeParser.MANIFEST_HEADER_FORCE_REMOVE_BUNDLES, "\"org.example.brooklyn.mybundle:[0,1.0.0)\""));
-        
-        CatalogUpgrades upgrades = BundleUpgradeParser.parseBundleManifestForCatalogUpgrades(bundle);
-        assertFalse(upgrades.isEmpty());
-        assertTrue(upgrades.isBundleRemoved(new VersionedName("org.example.brooklyn.mybundle", "0.1.0")));
-        assertFalse(upgrades.isBundleRemoved(new VersionedName("org.example.brooklyn.mybundle", "1.0.0")));
-        
-        assertTrue(upgrades.isLegacyItemRemoved(newMockCatalogItem("foo", "0.1.0")));
-        assertFalse(upgrades.isLegacyItemRemoved(newMockCatalogItem("foo", "1.0.0")));
-        assertTrue(upgrades.isLegacyItemRemoved(newMockCatalogItem("bar", "0.1.0")));
-        assertFalse(upgrades.isLegacyItemRemoved(newMockCatalogItem("bar", "1.0.0")));
-        assertFalse(upgrades.isLegacyItemRemoved(newMockCatalogItem("different", "0.1.0")));
-    }
-
-    private Bundle newMockBundle(Map<String, String> rawHeaders) {
-        Dictionary<String, String> headers = new Hashtable<>(rawHeaders);
-        Bundle result = Mockito.mock(Bundle.class);
-        Mockito.when(result.getHeaders()).thenReturn(headers);
-        return result;
-    }
-
-    private CatalogItem<?,?> newMockCatalogItem(String symbolicName, String version) {
-        CatalogItem<?,?> result = Mockito.mock(CatalogItem.class);
-        Mockito.when(result.getSymbolicName()).thenReturn(symbolicName);
-        Mockito.when(result.getVersion()).thenReturn(version);
-        Mockito.when(result.getId()).thenReturn(symbolicName+":"+version);
-        Mockito.when(result.getCatalogItemId()).thenReturn(symbolicName+":"+version);
-        return result;
-    }
-    
-    private void assertParsed(String input, List<VersionRangedName> expected) throws Exception {
-        List<VersionRangedName> actual = BundleUpgradeParser.parseVersionRangedNameList(input, false);
-        assertEquals(actual.size(), expected.size(), "actual="+actual); 
-        for (int i = 0; i < actual.size(); i++) {
-            assertEquals(actual.get(i).getSymbolicName(), expected.get(i).getSymbolicName());
-            assertEquals(actual.get(i).getOsgiVersionRange(), expected.get(i).getOsgiVersionRange());
-        }
-    }
-    
-    private void assertVersionRangedNameFails(String input, String expectedFailure, String... optionalOtherExpectedFailures) {
-        try {
-            VersionRangedName.fromString(input, false);
-            Asserts.shouldHaveFailedPreviously();
-        } catch (IllegalArgumentException e) {
-            Asserts.expectedFailureContains(e, expectedFailure, optionalOtherExpectedFailures);
-        }
-    }
-}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/22fda0dd/core/src/test/java/org/apache/brooklyn/core/typereg/BundleUpgradeParserTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/org/apache/brooklyn/core/typereg/BundleUpgradeParserTest.java b/core/src/test/java/org/apache/brooklyn/core/typereg/BundleUpgradeParserTest.java
new file mode 100644
index 0000000..143649a
--- /dev/null
+++ b/core/src/test/java/org/apache/brooklyn/core/typereg/BundleUpgradeParserTest.java
@@ -0,0 +1,167 @@
+/*
+ * 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.typereg;
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertFalse;
+import static org.testng.Assert.assertTrue;
+
+import java.util.Dictionary;
+import java.util.Hashtable;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.brooklyn.api.catalog.CatalogItem;
+import org.apache.brooklyn.core.typereg.BundleUpgradeParser;
+import org.apache.brooklyn.core.typereg.BundleUpgradeParser.CatalogUpgrades;
+import org.apache.brooklyn.core.typereg.BundleUpgradeParser.VersionRangedName;
+import org.apache.brooklyn.test.Asserts;
+import org.apache.brooklyn.util.osgi.VersionedName;
+import org.apache.brooklyn.util.text.BrooklynVersionSyntax;
+import org.mockito.Mockito;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.Version;
+import org.osgi.framework.VersionRange;
+import org.testng.annotations.Test;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+
+public class BundleUpgradeParserTest {
+
+    private VersionRange from0lessThan1 = new VersionRange('[', Version.valueOf("0"), Version.valueOf("1.0.0"), ')');
+    private VersionRange exactly0dot1 = new VersionRange('[', Version.valueOf("0.1.0"), Version.valueOf("0.1.0"), ']');
+    private VersionRangedName fooFrom0lessThan1 = new VersionRangedName("foo", from0lessThan1);
+    private VersionRangedName barFrom0lessThan1 = new VersionRangedName("bar", from0lessThan1);
+
+    @Test
+    public void testVersionRangedName() throws Exception {
+        assertEquals(VersionRangedName.fromString("foo:0.1.0", true).toOsgiString(), "foo:0.1.0");
+        assertEquals(VersionRangedName.fromString("foo:0.1.0", false).toOsgiString(), "foo:[0.1.0,0.1.0]");
+        assertEquals(VersionRangedName.fromString("foo:[0,1)", false).toOsgiString(), "foo:[0.0.0,1.0.0)");
+        
+        assertVersionRangedNameFails("foo", "'foo' must be of 'name:versionRange' syntax");
+        assertVersionRangedNameFails("foo:bar:0.1.0", "has too many parts");
+        assertVersionRangedNameFails("", "Must not be blank");
+        assertVersionRangedNameFails(null, "Must not be blank");
+    }
+    
+    @Test
+    public void testVersionRangesWithSnapshots() throws Exception {
+        VersionRange from0lessThan1 = VersionRangedName.fromString("foo:[0,1)", false).getOsgiVersionRange();
+        assertTrue(from0lessThan1.includes(Version.valueOf("0.1.0.SNAPSHOT")));
+        assertTrue(from0lessThan1.includes(Version.valueOf(BrooklynVersionSyntax.toValidOsgiVersion("0.1.0-SNAPSHOT"))));
+        assertTrue(from0lessThan1.includes(Version.valueOf("0.0.0.SNAPSHOT")));
+        assertTrue(from0lessThan1.includes(Version.valueOf(BrooklynVersionSyntax.toValidOsgiVersion("0.0.0-SNAPSHOT"))));
+        assertFalse(from0lessThan1.includes(Version.valueOf("1.0.0.SNAPSHOT")));
+        assertFalse(from0lessThan1.includes(Version.valueOf(BrooklynVersionSyntax.toValidOsgiVersion("1.0.0-SNAPSHOT"))));
+        
+        VersionRange from1 = VersionRangedName.fromString("foo:[1,9999)", false).getOsgiVersionRange();
+        assertTrue(from1.includes(Version.valueOf("1.0.0.SNAPSHOT")));
+        assertTrue(from1.includes(Version.valueOf(BrooklynVersionSyntax.toValidOsgiVersion("1.SNAPSHOT"))));
+        assertFalse(from1.includes(Version.valueOf("0.0.0.SNAPSHOT")));
+        assertFalse(from1.includes(Version.valueOf("0.1.0.SNAPSHOT")));
+    }
+    
+    @Test
+    public void testParseSingleQuotedVal() throws Exception {
+        String input = "\"foo:[0,1.0.0)\"";
+        assertParsed(input, ImmutableList.of(fooFrom0lessThan1));
+    }
+    
+    @Test
+    public void testParseSingleQuotedValWithNestedQuotes() throws Exception {
+        String input = "\"foo:[0,\"1.0.0\")\"";
+        assertParsed(input, ImmutableList.of(fooFrom0lessThan1));
+    }
+    
+    @Test
+    public void testParseMultipleVals() throws Exception {
+        String input = "\"foo:[0,1.0.0)\",\"bar:[0,1.0.0)\"";
+        assertParsed(input, ImmutableList.of(fooFrom0lessThan1, barFrom0lessThan1));
+    }
+
+    @Test
+    public void testParseValWithExactVersion() throws Exception {
+        String input = "\"foo:0.1.0\"";
+        assertParsed(input, ImmutableList.of(new VersionRangedName("foo", exactly0dot1)));
+    }
+
+    @Test
+    public void testParseBundleEmptyManifest() throws Exception {
+        Bundle bundle = newMockBundle(ImmutableMap.of());
+        
+        CatalogUpgrades upgrades = BundleUpgradeParser.parseBundleManifestForCatalogUpgrades(bundle);
+        assertTrue(upgrades.isEmpty());
+        assertFalse(upgrades.isBundleRemoved(new VersionedName("org.example.brooklyn.mybundle", "0.1.0")));
+        assertFalse(upgrades.isLegacyItemRemoved(newMockCatalogItem("foo", "0.1.0")));
+    }
+
+    @Test
+    public void testParseBundleManifest() throws Exception {
+        Bundle bundle = newMockBundle(ImmutableMap.of(
+                BundleUpgradeParser.MANIFEST_HEADER_FORCE_REMOVE_LEGACY_ITEMS, "\"foo:[0,1.0.0)\",\"bar:[0,1.0.0)\"",
+                BundleUpgradeParser.MANIFEST_HEADER_FORCE_REMOVE_BUNDLES, "\"org.example.brooklyn.mybundle:[0,1.0.0)\""));
+        
+        CatalogUpgrades upgrades = BundleUpgradeParser.parseBundleManifestForCatalogUpgrades(bundle);
+        assertFalse(upgrades.isEmpty());
+        assertTrue(upgrades.isBundleRemoved(new VersionedName("org.example.brooklyn.mybundle", "0.1.0")));
+        assertFalse(upgrades.isBundleRemoved(new VersionedName("org.example.brooklyn.mybundle", "1.0.0")));
+        
+        assertTrue(upgrades.isLegacyItemRemoved(newMockCatalogItem("foo", "0.1.0")));
+        assertFalse(upgrades.isLegacyItemRemoved(newMockCatalogItem("foo", "1.0.0")));
+        assertTrue(upgrades.isLegacyItemRemoved(newMockCatalogItem("bar", "0.1.0")));
+        assertFalse(upgrades.isLegacyItemRemoved(newMockCatalogItem("bar", "1.0.0")));
+        assertFalse(upgrades.isLegacyItemRemoved(newMockCatalogItem("different", "0.1.0")));
+    }
+
+    private Bundle newMockBundle(Map<String, String> rawHeaders) {
+        Dictionary<String, String> headers = new Hashtable<>(rawHeaders);
+        Bundle result = Mockito.mock(Bundle.class);
+        Mockito.when(result.getHeaders()).thenReturn(headers);
+        return result;
+    }
+
+    private CatalogItem<?,?> newMockCatalogItem(String symbolicName, String version) {
+        CatalogItem<?,?> result = Mockito.mock(CatalogItem.class);
+        Mockito.when(result.getSymbolicName()).thenReturn(symbolicName);
+        Mockito.when(result.getVersion()).thenReturn(version);
+        Mockito.when(result.getId()).thenReturn(symbolicName+":"+version);
+        Mockito.when(result.getCatalogItemId()).thenReturn(symbolicName+":"+version);
+        return result;
+    }
+    
+    private void assertParsed(String input, List<VersionRangedName> expected) throws Exception {
+        List<VersionRangedName> actual = BundleUpgradeParser.parseVersionRangedNameList(input, false);
+        assertEquals(actual.size(), expected.size(), "actual="+actual); 
+        for (int i = 0; i < actual.size(); i++) {
+            assertEquals(actual.get(i).getSymbolicName(), expected.get(i).getSymbolicName());
+            assertEquals(actual.get(i).getOsgiVersionRange(), expected.get(i).getOsgiVersionRange());
+        }
+    }
+    
+    private void assertVersionRangedNameFails(String input, String expectedFailure, String... optionalOtherExpectedFailures) {
+        try {
+            VersionRangedName.fromString(input, false);
+            Asserts.shouldHaveFailedPreviously();
+        } catch (IllegalArgumentException e) {
+            Asserts.expectedFailureContains(e, expectedFailure, optionalOtherExpectedFailures);
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/22fda0dd/launcher/src/test/java/org/apache/brooklyn/launcher/BrooklynLauncherUpgradeCatalogOsgiTest.java
----------------------------------------------------------------------
diff --git a/launcher/src/test/java/org/apache/brooklyn/launcher/BrooklynLauncherUpgradeCatalogOsgiTest.java b/launcher/src/test/java/org/apache/brooklyn/launcher/BrooklynLauncherUpgradeCatalogOsgiTest.java
index 8ef3466..a6f5129 100644
--- a/launcher/src/test/java/org/apache/brooklyn/launcher/BrooklynLauncherUpgradeCatalogOsgiTest.java
+++ b/launcher/src/test/java/org/apache/brooklyn/launcher/BrooklynLauncherUpgradeCatalogOsgiTest.java
@@ -19,8 +19,8 @@
 package org.apache.brooklyn.launcher;
 
 import static org.apache.brooklyn.core.catalog.internal.BasicBrooklynCatalog.CATALOG_BOM;
-import static org.apache.brooklyn.core.catalog.internal.BundleUpgradeParser.MANIFEST_HEADER_FORCE_REMOVE_BUNDLES;
-import static org.apache.brooklyn.core.catalog.internal.BundleUpgradeParser.MANIFEST_HEADER_FORCE_REMOVE_LEGACY_ITEMS;
+import static org.apache.brooklyn.core.typereg.BundleUpgradeParser.MANIFEST_HEADER_FORCE_REMOVE_BUNDLES;
+import static org.apache.brooklyn.core.typereg.BundleUpgradeParser.MANIFEST_HEADER_FORCE_REMOVE_LEGACY_ITEMS;
 
 import java.io.File;
 import java.net.URI;