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:06 UTC

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

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;