You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@brooklyn.apache.org by ge...@apache.org on 2017/06/06 12:25:24 UTC
[04/17] brooklyn-server git commit: refactor osgi installation to
facilitate upgrades and richer return semantics
refactor osgi installation to facilitate upgrades and richer return semantics
rebind can break however -- we need to phase the bundle installs and the bundle starts
Project: http://git-wip-us.apache.org/repos/asf/brooklyn-server/repo
Commit: http://git-wip-us.apache.org/repos/asf/brooklyn-server/commit/14524547
Tree: http://git-wip-us.apache.org/repos/asf/brooklyn-server/tree/14524547
Diff: http://git-wip-us.apache.org/repos/asf/brooklyn-server/diff/14524547
Branch: refs/heads/master
Commit: 145245479ea31c6aeffffb86533cb988b39f1820
Parents: f76118c
Author: Alex Heneveld <al...@cloudsoftcorp.com>
Authored: Thu May 4 17:35:32 2017 +0100
Committer: Alex Heneveld <al...@cloudsoftcorp.com>
Committed: Fri May 5 12:49:30 2017 +0100
----------------------------------------------------------------------
.../catalog/CatalogMakeOsgiBundleTest.java | 9 +-
.../CatalogOsgiVersionMoreEntityRebindTest.java | 80 ++++-
.../CatalogOsgiVersionMoreEntityTest.java | 15 +-
.../apache/brooklyn/core/BrooklynVersion.java | 3 +-
.../catalog/internal/BasicBrooklynCatalog.java | 10 +-
.../core/catalog/internal/CatalogUtils.java | 3 +-
.../core/mgmt/ha/OsgiArchiveInstaller.java | 359 +++++++++++++++++++
.../mgmt/ha/OsgiBundleInstallationResult.java | 71 ++++
.../brooklyn/core/mgmt/ha/OsgiManager.java | 129 ++-----
.../core/mgmt/rebind/RebindContextImpl.java | 4 +-
.../core/typereg/BasicManagedBundle.java | 5 +
.../apache/brooklyn/rest/domain/ApiError.java | 21 +-
.../rest/resources/CatalogResource.java | 137 ++-----
.../brooklyn/util/maven/MavenArtifact.java | 3 +-
.../brooklyn/util/text/VersionComparator.java | 9 +-
.../util/text/VersionComparatorTest.java | 7 +
16 files changed, 636 insertions(+), 229 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/14524547/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/catalog/CatalogMakeOsgiBundleTest.java
----------------------------------------------------------------------
diff --git a/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/catalog/CatalogMakeOsgiBundleTest.java b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/catalog/CatalogMakeOsgiBundleTest.java
index 6eca121..044a2ea 100644
--- a/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/catalog/CatalogMakeOsgiBundleTest.java
+++ b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/catalog/CatalogMakeOsgiBundleTest.java
@@ -37,10 +37,11 @@ import org.apache.brooklyn.core.BrooklynFeatureEnablement;
import org.apache.brooklyn.core.catalog.internal.CatalogBomScanner;
import org.apache.brooklyn.core.entity.Entities;
import org.apache.brooklyn.core.entity.EntityAsserts;
+import org.apache.brooklyn.core.mgmt.ha.OsgiBundleInstallationResult;
import org.apache.brooklyn.core.mgmt.internal.LocalManagementContext;
+import org.apache.brooklyn.core.mgmt.internal.ManagementContextInternal;
import org.apache.brooklyn.core.sensor.Sensors;
import org.apache.brooklyn.core.test.entity.LocalManagementContextForTests;
-import org.apache.brooklyn.core.typereg.BasicManagedBundle;
import org.apache.brooklyn.test.Asserts;
import org.apache.brooklyn.util.collections.MutableList;
import org.apache.brooklyn.util.collections.MutableMap;
@@ -172,10 +173,8 @@ public class CatalogMakeOsgiBundleTest extends AbstractYamlTest {
private void installBundle(File jf) {
try (FileInputStream fin = new FileInputStream(jf)) {
- BasicManagedBundle bundleMetadata = new BasicManagedBundle();
- Bundle bundle =
- ((LocalManagementContext)mgmt()).getOsgiManager().get().installUploadedBundle(bundleMetadata, fin, true);
- bundlesToRemove.add(bundle);
+ OsgiBundleInstallationResult br = ((ManagementContextInternal)mgmt()).getOsgiManager().get().install(fin).get();
+ bundlesToRemove.add(br.getBundle());
} catch (Exception e) {
throw Exceptions.propagate(e);
}
http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/14524547/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/catalog/CatalogOsgiVersionMoreEntityRebindTest.java
----------------------------------------------------------------------
diff --git a/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/catalog/CatalogOsgiVersionMoreEntityRebindTest.java b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/catalog/CatalogOsgiVersionMoreEntityRebindTest.java
index e47f719..a258fcd 100644
--- a/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/catalog/CatalogOsgiVersionMoreEntityRebindTest.java
+++ b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/catalog/CatalogOsgiVersionMoreEntityRebindTest.java
@@ -33,10 +33,10 @@ import org.apache.brooklyn.camp.brooklyn.AbstractYamlRebindTest;
import org.apache.brooklyn.core.effector.Effectors;
import org.apache.brooklyn.core.entity.EntityInternal;
import org.apache.brooklyn.core.entity.StartableApplication;
+import org.apache.brooklyn.core.mgmt.ha.OsgiBundleInstallationResult;
import org.apache.brooklyn.core.mgmt.internal.ManagementContextInternal;
import org.apache.brooklyn.core.mgmt.osgi.OsgiVersionMoreEntityTest;
import org.apache.brooklyn.core.test.entity.TestEntity;
-import org.apache.brooklyn.core.typereg.BasicManagedBundle;
import org.apache.brooklyn.entity.stock.BasicApplication;
import org.apache.brooklyn.test.Asserts;
import org.apache.brooklyn.test.support.TestResourceUnavailableException;
@@ -70,8 +70,8 @@ public class CatalogOsgiVersionMoreEntityRebindTest extends AbstractYamlRebindTe
@Test
public void testRebindAppIncludingBundle() throws Exception {
TestResourceUnavailableException.throwIfResourceUnavailable(getClass(), OsgiTestResources.BROOKLYN_TEST_OSGI_ENTITIES_COM_EXAMPLE_PATH);
- ((ManagementContextInternal)mgmt()).getOsgiManager().get().installUploadedBundle(new BasicManagedBundle(),
- new ResourceUtils(getClass()).getResourceFromUrl(BROOKLYN_TEST_MORE_ENTITIES_V1_URL), true);
+ ((ManagementContextInternal)mgmt()).getOsgiManager().get().install(
+ new ResourceUtils(getClass()).getResourceFromUrl(BROOKLYN_TEST_MORE_ENTITIES_V1_URL) );
createAndStartApplication("services: [ { type: "+BROOKLYN_TEST_MORE_ENTITIES_MORE_ENTITY+" } ]");
@@ -208,15 +208,14 @@ public class CatalogOsgiVersionMoreEntityRebindTest extends AbstractYamlRebindTe
TestResourceUnavailableException.throwIfResourceUnavailable(getClass(), BROOKLYN_TEST_OSGI_MORE_ENTITIES_0_1_0_PATH);
// install dependency
- ((ManagementContextInternal)mgmt()).getOsgiManager().get().installUploadedBundle(new BasicManagedBundle(),
- new ResourceUtils(getClass()).getResourceFromUrl(BROOKLYN_TEST_OSGI_ENTITIES_URL), true);
+ ((ManagementContextInternal)mgmt()).getOsgiManager().get().install(
+ new ResourceUtils(getClass()).getResourceFromUrl(BROOKLYN_TEST_OSGI_ENTITIES_URL) );
// now the v2 bundle
- BasicManagedBundle mb = new BasicManagedBundle();
- Bundle b2a = ((ManagementContextInternal)mgmt()).getOsgiManager().get().installUploadedBundle(mb,
- new ResourceUtils(getClass()).getResourceFromUrl(BROOKLYN_TEST_MORE_ENTITIES_V2_URL), true);
+ OsgiBundleInstallationResult b = ((ManagementContextInternal)mgmt()).getOsgiManager().get().install(
+ new ResourceUtils(getClass()).getResourceFromUrl(BROOKLYN_TEST_MORE_ENTITIES_V2_URL) ).get();
- Assert.assertEquals(mb.getVersionedName().toString(), BROOKLYN_TEST_MORE_ENTITIES_SYMBOLIC_NAME_FULL+":"+"0.2.0");
+ Assert.assertEquals(b.getVersionedName().toString(), BROOKLYN_TEST_MORE_ENTITIES_SYMBOLIC_NAME_FULL+":"+"0.2.0");
String yaml = Strings.lines("name: simple-app-yaml",
"services:",
@@ -228,8 +227,8 @@ public class CatalogOsgiVersionMoreEntityRebindTest extends AbstractYamlRebindTe
more.invoke(Effectors.effector(String.class, "sayHI").buildAbstract(), MutableMap.of("name", "Bob")).get(),
"HI BOB FROM V2");
- ((ManagementContextInternal)mgmt()).getOsgiManager().get().uninstallUploadedBundle(mb);
- Assert.assertEquals(b2a.getState(), Bundle.UNINSTALLED);
+ ((ManagementContextInternal)mgmt()).getOsgiManager().get().uninstallUploadedBundle(b.getMetadata());
+ Assert.assertEquals(b.getBundle().getState(), Bundle.UNINSTALLED);
// can still call things
Assert.assertEquals(
@@ -237,7 +236,7 @@ public class CatalogOsgiVersionMoreEntityRebindTest extends AbstractYamlRebindTe
"HI CLAUDIA FROM V2");
// but still uninstalled, and attempt to create makes error
- Assert.assertEquals(b2a.getState(), Bundle.UNINSTALLED);
+ Assert.assertEquals(b.getBundle().getState(), Bundle.UNINSTALLED);
try {
Entity app2 = createAndStartApplication(yaml);
Asserts.shouldHaveFailedPreviously("Expected deployment to fail after uninstall; instead got "+app2);
@@ -254,5 +253,62 @@ public class CatalogOsgiVersionMoreEntityRebindTest extends AbstractYamlRebindTe
}
}
+ @Test
+ public void testClassAccessAfterUpgrade() throws Exception {
+ TestResourceUnavailableException.throwIfResourceUnavailable(getClass(), BROOKLYN_TEST_OSGI_MORE_ENTITIES_0_1_0_PATH);
+
+ // install dependency
+ ((ManagementContextInternal)mgmt()).getOsgiManager().get().install(
+ new ResourceUtils(getClass()).getResourceFromUrl(BROOKLYN_TEST_OSGI_ENTITIES_URL) ).checkNoError();
+
+ // now the v2 bundle
+ OsgiBundleInstallationResult b2a = ((ManagementContextInternal)mgmt()).getOsgiManager().get().install(
+ new ResourceUtils(getClass()).getResourceFromUrl(BROOKLYN_TEST_MORE_ENTITIES_V2_URL) ).get();
+
+ Assert.assertEquals(b2a.getVersionedName().toString(), BROOKLYN_TEST_MORE_ENTITIES_SYMBOLIC_NAME_FULL+":"+"0.2.0");
+ Assert.assertEquals(b2a.getCode(), OsgiBundleInstallationResult.ResultCode.INSTALLED_NEW_BUNDLE);
+
+ String yaml = Strings.lines("name: simple-app-yaml",
+ "services:",
+ "- type: " + BROOKLYN_TEST_MORE_ENTITIES_MORE_ENTITY);
+ Entity app = createAndStartApplication(yaml);
+ Entity more = Iterables.getOnlyElement( app.getChildren() );
+
+ Assert.assertEquals(
+ more.invoke(Effectors.effector(String.class, "sayHI").buildAbstract(), MutableMap.of("name", "Bob")).get(),
+ "HI BOB FROM V2");
+
+ // unforced upgrade should report already installed
+ Assert.assertEquals( ((ManagementContextInternal)mgmt()).getOsgiManager().get().install(
+ new ResourceUtils(getClass()).getResourceFromUrl(BROOKLYN_TEST_MORE_ENTITIES_V2_EVIL_TWIN_URL) ).get().getCode(),
+ OsgiBundleInstallationResult.ResultCode.IGNORING_BUNDLE_AREADY_INSTALLED);
+
+ // force upgrade
+ OsgiBundleInstallationResult b2b = ((ManagementContextInternal)mgmt()).getOsgiManager().get().install(b2a.getMetadata(),
+ new ResourceUtils(getClass()).getResourceFromUrl(BROOKLYN_TEST_MORE_ENTITIES_V2_EVIL_TWIN_URL), true, true).get();
+ Assert.assertEquals(b2a.getBundle(), b2b.getBundle());
+ Assert.assertEquals(b2b.getCode(), OsgiBundleInstallationResult.ResultCode.UPDATED_EXISTING_BUNDLE);
+
+ // calls to things previously instantiated get the old behaviour
+ Assert.assertEquals(
+ more.invoke(Effectors.effector(String.class, "sayHI").buildAbstract(), MutableMap.of("name", "Claudia")).get(),
+ "HI CLAUDIA FROM V2");
+
+ // but new deployment gets the new behaviour
+ StartableApplication app2 = (StartableApplication) createAndStartApplication(yaml);
+ Entity more2 = Iterables.getOnlyElement( app2.getChildren() );
+ Assert.assertEquals(
+ more2.invoke(Effectors.effector(String.class, "sayHI").buildAbstract(), MutableMap.of("name", "Daphne")).get(),
+ "HO DAPHNE FROM V2 EVIL TWIN");
+ app2.stop();
+
+ // and after rebind on the old we get new behaviour
+ StartableApplication app1 = rebind();
+ Entity more1 = Iterables.getOnlyElement( app1.getChildren() );
+ Assert.assertEquals(
+ more1.invoke(Effectors.effector(String.class, "sayHI").buildAbstract(), MutableMap.of("name", "Eric")).get(),
+ "HO ERIC FROM V2 EVIL TWIN");
+ }
+
}
http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/14524547/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/catalog/CatalogOsgiVersionMoreEntityTest.java
----------------------------------------------------------------------
diff --git a/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/catalog/CatalogOsgiVersionMoreEntityTest.java b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/catalog/CatalogOsgiVersionMoreEntityTest.java
index cb38df5..aebfed9 100644
--- a/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/catalog/CatalogOsgiVersionMoreEntityTest.java
+++ b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/catalog/CatalogOsgiVersionMoreEntityTest.java
@@ -33,10 +33,10 @@ import org.apache.brooklyn.api.typereg.ManagedBundle;
import org.apache.brooklyn.api.typereg.RegisteredType;
import org.apache.brooklyn.camp.brooklyn.AbstractYamlTest;
import org.apache.brooklyn.camp.brooklyn.spi.creation.BrooklynEntityMatcher;
+import org.apache.brooklyn.core.mgmt.ha.OsgiBundleInstallationResult;
import org.apache.brooklyn.core.mgmt.internal.ManagementContextInternal;
import org.apache.brooklyn.core.mgmt.osgi.OsgiVersionMoreEntityTest;
import org.apache.brooklyn.core.objs.BrooklynTypes;
-import org.apache.brooklyn.core.typereg.BasicManagedBundle;
import org.apache.brooklyn.core.typereg.RegisteredTypePredicates;
import org.apache.brooklyn.core.typereg.RegisteredTypes;
import org.apache.brooklyn.test.Asserts;
@@ -44,7 +44,6 @@ import org.apache.brooklyn.test.support.TestResourceUnavailableException;
import org.apache.brooklyn.util.core.ResourceUtils;
import org.apache.brooklyn.util.osgi.OsgiTestResources;
import org.apache.brooklyn.util.text.Strings;
-import org.osgi.framework.Bundle;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.testng.Assert;
@@ -69,21 +68,19 @@ public class CatalogOsgiVersionMoreEntityTest extends AbstractYamlTest implement
@Test
public void testBrooklynManagedBundleInstall() throws Exception {
- BasicManagedBundle mb = new BasicManagedBundle();
- Bundle b = ((ManagementContextInternal)mgmt()).getOsgiManager().get().installUploadedBundle(mb,
- new ResourceUtils(getClass()).getResourceFromUrl(BROOKLYN_TEST_MORE_ENTITIES_V1_URL), true);
- Assert.assertEquals(mb.getSymbolicName(), b.getSymbolicName());
- Assert.assertEquals(mb.getVersion(), "0.1.0");
+ OsgiBundleInstallationResult br = ((ManagementContextInternal)mgmt()).getOsgiManager().get().install(
+ new ResourceUtils(getClass()).getResourceFromUrl(BROOKLYN_TEST_MORE_ENTITIES_V1_URL) ).get();
+ Assert.assertEquals(br.getVersionedName().toString(), BROOKLYN_TEST_MORE_ENTITIES_SYMBOLIC_NAME_FULL+":"+"0.1.0");
// bundle installed
Map<String, ManagedBundle> bundles = ((ManagementContextInternal)mgmt()).getOsgiManager().get().getManagedBundles();
Asserts.assertSize(bundles.keySet(), 1);
- Assert.assertEquals(mb.getId(), Iterables.getOnlyElement( bundles.keySet() ));
+ Assert.assertEquals(br.getMetadata().getId(), Iterables.getOnlyElement( bundles.keySet() ));
// types installed
RegisteredType t = mgmt().getTypeRegistry().get(BROOKLYN_TEST_MORE_ENTITIES_MORE_ENTITY);
Assert.assertNotNull(t);
- Assert.assertEquals(t.getContainingBundle(), b.getSymbolicName()+":"+b.getVersion());
+ Assert.assertEquals(t.getContainingBundle(), br.getVersionedName().toString());
// can deploy
createAndStartApplication("services: [ { type: "+BROOKLYN_TEST_MORE_ENTITIES_MORE_ENTITY+" } ]");
http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/14524547/core/src/main/java/org/apache/brooklyn/core/BrooklynVersion.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/brooklyn/core/BrooklynVersion.java b/core/src/main/java/org/apache/brooklyn/core/BrooklynVersion.java
index fc9556d..161eff0 100644
--- a/core/src/main/java/org/apache/brooklyn/core/BrooklynVersion.java
+++ b/core/src/main/java/org/apache/brooklyn/core/BrooklynVersion.java
@@ -46,6 +46,7 @@ import org.apache.brooklyn.util.guava.Maybe;
import org.apache.brooklyn.util.osgi.OsgiUtil;
import org.apache.brooklyn.util.stream.Streams;
import org.apache.brooklyn.util.text.Strings;
+import org.apache.brooklyn.util.text.VersionComparator;
import org.osgi.framework.Bundle;
import org.osgi.framework.Constants;
import org.osgi.framework.FrameworkUtil;
@@ -155,7 +156,7 @@ public class BrooklynVersion implements BrooklynVersionService {
@Override
public boolean isSnapshot() {
- return (getVersion().indexOf("-SNAPSHOT") >= 0);
+ return VersionComparator.isSnapshot(getVersion());
}
private void readPropertiesFromMavenResource(ClassLoader resourceLoader) {
http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/14524547/core/src/main/java/org/apache/brooklyn/core/catalog/internal/BasicBrooklynCatalog.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/brooklyn/core/catalog/internal/BasicBrooklynCatalog.java b/core/src/main/java/org/apache/brooklyn/core/catalog/internal/BasicBrooklynCatalog.java
index 9769f7e..c91b7cd 100644
--- a/core/src/main/java/org/apache/brooklyn/core/catalog/internal/BasicBrooklynCatalog.java
+++ b/core/src/main/java/org/apache/brooklyn/core/catalog/internal/BasicBrooklynCatalog.java
@@ -425,17 +425,25 @@ public class BasicBrooklynCatalog implements BrooklynCatalog {
return getFirstAsMap(itemDef, "brooklyn.catalog").orNull();
}
+ /** @deprecated since 0.12.0 - use {@link #getVersionedName(Map, boolean)} */
+ @Deprecated
public static VersionedName getVersionedName(Map<?,?> catalogMetadata) {
+ return getVersionedName(catalogMetadata, true);
+ }
+
+ public static VersionedName getVersionedName(Map<?,?> catalogMetadata, boolean required) {
String version = getFirstAs(catalogMetadata, String.class, "version").orNull();
String bundle = getFirstAs(catalogMetadata, String.class, "bundle").orNull();
if (Strings.isBlank(bundle) && Strings.isBlank(version)) {
+ if (!required) return null;
throw new IllegalStateException("Catalog BOM must define bundle and version");
}
if (Strings.isBlank(bundle)) {
+ if (!required) return null;
throw new IllegalStateException("Catalog BOM must define bundle");
}
if (Strings.isBlank(version)) {
- throw new IllegalStateException("Catalog BOM must define version");
+ throw new IllegalStateException("Catalog BOM must define version if bundle is defined");
}
return new VersionedName(bundle, Version.valueOf(version));
}
http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/14524547/core/src/main/java/org/apache/brooklyn/core/catalog/internal/CatalogUtils.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/brooklyn/core/catalog/internal/CatalogUtils.java b/core/src/main/java/org/apache/brooklyn/core/catalog/internal/CatalogUtils.java
index 776894f..394814d 100644
--- a/core/src/main/java/org/apache/brooklyn/core/catalog/internal/CatalogUtils.java
+++ b/core/src/main/java/org/apache/brooklyn/core/catalog/internal/CatalogUtils.java
@@ -43,6 +43,7 @@ import org.apache.brooklyn.core.mgmt.ha.OsgiManager;
import org.apache.brooklyn.core.mgmt.internal.ManagementContextInternal;
import org.apache.brooklyn.core.mgmt.rebind.RebindManagerImpl.RebindTracker;
import org.apache.brooklyn.core.objs.BrooklynObjectInternal;
+import org.apache.brooklyn.core.typereg.BasicManagedBundle;
import org.apache.brooklyn.core.typereg.RegisteredTypeLoadingContexts;
import org.apache.brooklyn.core.typereg.RegisteredTypePredicates;
import org.apache.brooklyn.core.typereg.RegisteredTypes;
@@ -170,7 +171,7 @@ public class CatalogUtils {
new Object[] {managementContext, Joiner.on(", ").join(libraries)});
Stopwatch timer = Stopwatch.createStarted();
for (CatalogBundle bundleUrl : libraries) {
- osgi.get().registerBundle(bundleUrl);
+ osgi.get().install(BasicManagedBundle.of(bundleUrl), null, true, false).get();
}
if (log.isDebugEnabled())
logDebugOrTraceIfRebinding(log,
http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/14524547/core/src/main/java/org/apache/brooklyn/core/mgmt/ha/OsgiArchiveInstaller.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/brooklyn/core/mgmt/ha/OsgiArchiveInstaller.java b/core/src/main/java/org/apache/brooklyn/core/mgmt/ha/OsgiArchiveInstaller.java
new file mode 100644
index 0000000..0e069bd
--- /dev/null
+++ b/core/src/main/java/org/apache/brooklyn/core/mgmt/ha/OsgiArchiveInstaller.java
@@ -0,0 +1,359 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.brooklyn.core.mgmt.ha;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.jar.Attributes;
+import java.util.jar.Manifest;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipFile;
+
+import org.apache.brooklyn.api.typereg.ManagedBundle;
+import org.apache.brooklyn.core.catalog.internal.BasicBrooklynCatalog;
+import org.apache.brooklyn.core.mgmt.ha.OsgiBundleInstallationResult.ResultCode;
+import org.apache.brooklyn.core.mgmt.internal.ManagementContextInternal;
+import org.apache.brooklyn.core.typereg.BasicManagedBundle;
+import org.apache.brooklyn.util.core.ResourceUtils;
+import org.apache.brooklyn.util.core.osgi.BundleMaker;
+import org.apache.brooklyn.util.core.osgi.Osgis;
+import org.apache.brooklyn.util.exceptions.Exceptions;
+import org.apache.brooklyn.util.exceptions.ReferenceWithError;
+import org.apache.brooklyn.util.guava.Maybe;
+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.Strings;
+import org.apache.brooklyn.util.text.VersionComparator;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.Constants;
+
+import com.google.common.base.Objects;
+
+// package-private so we can move this one if/when we move OsgiManager
+class OsgiArchiveInstaller {
+
+ final private OsgiManager osgiManager;
+ private ManagedBundle suppliedKnownBundleMetadata;
+ private InputStream zipIn;
+
+ private boolean loadCatalogBom;
+ private boolean force;
+
+ private File zipFile;
+ private Manifest discoveredManifest;
+ private VersionedName discoveredBomVersionedName;
+ OsgiBundleInstallationResult result;
+
+ private ManagedBundle inferredMetadata;
+
+ OsgiArchiveInstaller(OsgiManager osgiManager, ManagedBundle knownBundleMetadata, InputStream zipIn) {
+ this.osgiManager = osgiManager;
+ this.suppliedKnownBundleMetadata = knownBundleMetadata;
+ this.zipIn = zipIn;
+ }
+
+ public void setLoadCatalogBom(boolean loadCatalogBom) {
+ this.loadCatalogBom = loadCatalogBom;
+ }
+
+ public void setForce(boolean force) {
+ this.force = force;
+ }
+
+ private ManagementContextInternal mgmt() {
+ return (ManagementContextInternal) osgiManager.mgmt;
+ }
+
+ private synchronized void init() {
+ if (result!=null) {
+ if (zipFile!=null || zipIn==null) return;
+ throw new IllegalStateException("This installer instance has already been used and the input stream discarded");
+ }
+ result = new OsgiBundleInstallationResult();
+ inferredMetadata = suppliedKnownBundleMetadata==null ? new BasicManagedBundle() : suppliedKnownBundleMetadata;
+ }
+
+ private synchronized void makeLocalZipFileFromInputStreamOrUrl() {
+ if (zipIn==null) {
+ Maybe<Bundle> installedBundle = Maybe.absent();
+ if (suppliedKnownBundleMetadata!=null) {
+ // if no input stream, look for a URL and/or a matching bundle
+ if (installedBundle.isAbsent() && suppliedKnownBundleMetadata.getOsgiUniqueUrl()!=null) {
+ installedBundle = Osgis.bundleFinder(osgiManager.framework).requiringFromUrl(suppliedKnownBundleMetadata.getOsgiUniqueUrl()).find();
+ }
+ if (installedBundle.isAbsent() && suppliedKnownBundleMetadata.getUrl()!=null) {
+ installedBundle = Osgis.bundleFinder(osgiManager.framework).requiringFromUrl(suppliedKnownBundleMetadata.getUrl()).find();
+ }
+ if (installedBundle.isAbsent() && suppliedKnownBundleMetadata.isNameResolved()) {
+ installedBundle = Osgis.bundleFinder(osgiManager.framework).symbolicName(suppliedKnownBundleMetadata.getSymbolicName()).version(suppliedKnownBundleMetadata.getVersion()).find();
+ }
+ if (suppliedKnownBundleMetadata.getUrl()!=null) {
+ if (installedBundle.isAbsent() || force) {
+ // reload
+ zipIn = ResourceUtils.create(mgmt()).getResourceFromUrl(suppliedKnownBundleMetadata.getUrl());
+ }
+ }
+ }
+
+ if (installedBundle.isPresent()) {
+ result.bundle = installedBundle.get();
+
+ if (zipIn==null) {
+ // no way to install (no url), or no need to install (not forced); just ignore it
+ result.metadata = osgiManager.getManagedBundle(new VersionedName(installedBundle.get()));
+ result.setIgnoringAlreadyInstalled();
+ return;
+ }
+ } else {
+ result.metadata = suppliedKnownBundleMetadata;
+ throw new IllegalArgumentException("No input stream available and no URL could be found; nothing to install");
+ }
+ }
+
+ zipFile = Os.newTempFile("brooklyn-bundle-transient-"+suppliedKnownBundleMetadata, "zip");
+ try {
+ FileOutputStream fos = new FileOutputStream(zipFile);
+ Streams.copy(zipIn, fos);
+ zipIn.close();
+ fos.close();
+ } catch (Exception e) {
+ throw Exceptions.propagate(e);
+ } finally {
+ zipIn = null;
+ }
+ }
+
+ private void discoverManifestFromCatalogBom(boolean isCatalogBomRequired) {
+ discoveredManifest = new BundleMaker(mgmt()).getManifest(zipFile);
+ ZipFile zf = null;
+ try {
+ try {
+ zf = new ZipFile(zipFile);
+ } catch (IOException e) {
+ throw new IllegalArgumentException("Invalid ZIP/JAR archive: "+e);
+ }
+ ZipEntry bom = zf.getEntry("catalog.bom");
+ if (bom==null) {
+ bom = zf.getEntry("/catalog.bom");
+ }
+ if (bom==null) {
+ if (isCatalogBomRequired) {
+ throw new IllegalArgumentException("Archive must contain a catalog.bom file in the root");
+ } else {
+ return;
+ }
+ }
+ String bomS;
+ try {
+ bomS = Streams.readFullyString(zf.getInputStream(bom));
+ } catch (IOException e) {
+ throw new IllegalArgumentException("Error reading catalog.bom from ZIP/JAR archive: "+e);
+ }
+ discoveredBomVersionedName = BasicBrooklynCatalog.getVersionedName( BasicBrooklynCatalog.getCatalogMetadata(bomS), false );
+ } finally {
+ Streams.closeQuietly(zf);
+ }
+ }
+
+ private void updateManifestFromAllSourceInformation() {
+ if (discoveredBomVersionedName!=null) {
+ matchSetOrFail("catalog.bom in archive", discoveredBomVersionedName.getSymbolicName(), discoveredBomVersionedName.getVersion().toString());
+ }
+
+ boolean manifestNeedsUpdating = false;
+ if (discoveredManifest==null) {
+ discoveredManifest = new Manifest();
+ manifestNeedsUpdating = true;
+ }
+ if (!matchSetOrFail("MANIFEST.MF in archive", discoveredManifest.getMainAttributes().getValue(Constants.BUNDLE_SYMBOLICNAME),
+ discoveredManifest.getMainAttributes().getValue(Constants.BUNDLE_VERSION) )) {
+ manifestNeedsUpdating = true;
+ discoveredManifest.getMainAttributes().putValue(Constants.BUNDLE_SYMBOLICNAME, inferredMetadata.getSymbolicName());
+ discoveredManifest.getMainAttributes().putValue(Constants.BUNDLE_VERSION, inferredMetadata.getVersion());
+ }
+ if (Strings.isBlank(inferredMetadata.getSymbolicName())) {
+ throw new IllegalArgumentException("Missing bundle symbolic name in BOM or MANIFEST");
+ }
+ if (Strings.isBlank(inferredMetadata.getVersion())) {
+ throw new IllegalArgumentException("Missing bundle version in BOM or MANIFEST");
+ }
+ if (discoveredManifest.getMainAttributes().getValue(Attributes.Name.MANIFEST_VERSION)==null) {
+ discoveredManifest.getMainAttributes().putValue(Attributes.Name.MANIFEST_VERSION.toString(), "1.0");
+ manifestNeedsUpdating = true;
+ }
+ if (manifestNeedsUpdating) {
+ File zf2 = new BundleMaker(mgmt()).copyAddingManifest(zipFile, discoveredManifest);
+ zipFile.delete();
+ zipFile = zf2;
+ }
+ }
+
+ private synchronized void close() {
+ if (zipFile!=null) {
+ zipFile.delete();
+ zipFile = null;
+ }
+ }
+
+ /**
+ * Installs a bundle, taking from ZIP input stream if supplied, falling back to URL in the {@link ManagedBundle} metadata supplied.
+ * It will take metadata from any of: a MANIFEST.MF in the ZIP; a catalog.bom in the ZIP; the {@link ManagedBundle} metadata supplied.
+ * If metadata is supplied in multiple such places, it must match.
+ * Appropriate metadata will be added to the ZIP and installation attempted.
+ * <p>
+ * If a matching bundle is already installed, the installation will stop with a {@link ResultCode#IGNORING_BUNDLE_AREADY_INSTALLED}
+ * unless the bundle is a snapshot or "force" is specified.
+ * In the latter two cases, if there is an installed matching bundle, that bundle will be updated with the input stream here,
+ * with any catalog items from the old bundle removed and those from this bundle installed.
+ * <p>
+ * Default behaviour is {@link #setLoadCatalogBom(boolean)} true and {@link #setForce(boolean)} false.
+ * <p>
+ * The return value is extensive but should be self-evident, and will include a list of any registered types (catalog items) installed.
+ */
+ public ReferenceWithError<OsgiBundleInstallationResult> install() {
+ boolean startedInstallation = false;
+
+ try {
+ init();
+ makeLocalZipFileFromInputStreamOrUrl();
+ if (result.code!=null) return ReferenceWithError.newInstanceWithoutError(result);
+ discoverManifestFromCatalogBom(false);
+ if (result.code!=null) return ReferenceWithError.newInstanceWithoutError(result);
+ updateManifestFromAllSourceInformation();
+ if (result.code!=null) return ReferenceWithError.newInstanceWithoutError(result);
+ assert inferredMetadata.isNameResolved() : "Should have resolved "+inferredMetadata;
+
+ Boolean updating = null;
+ result.metadata = osgiManager.getManagedBundle(inferredMetadata.getVersionedName());
+ if (result.getMetadata()!=null) {
+ // already have a managed bundle
+ if (canUpdate()) {
+ result.bundle = osgiManager.framework.getBundleContext().getBundle(result.getMetadata().getOsgiUniqueUrl());
+ if (result.getBundle()==null) {
+ throw new IllegalStateException("Detected already managing bundle "+result.getMetadata().getVersionedName()+" but framework cannot find it");
+ }
+ updating = true;
+ } else {
+ result.setIgnoringAlreadyInstalled();
+ return ReferenceWithError.newInstanceWithoutError(result);
+ }
+ } else {
+ result.metadata = inferredMetadata;
+ // no such managed bundle
+ Maybe<Bundle> b = Osgis.bundleFinder(osgiManager.framework).symbolicName(result.getMetadata().getSymbolicName()).version(result.getMetadata().getVersion()).find();
+ if (b.isPresent()) {
+ // if it's non-brooklyn installed then fail
+ // (e.g. someone trying to install brooklyn or guice through this mechanism!)
+ result.bundle = b.get();
+ result.code = OsgiBundleInstallationResult.ResultCode.ERROR_INSTALLING_BUNDLE;
+ throw new IllegalStateException("Bundle "+result.getMetadata().getVersionedName()+" already installed in framework but not managed by Brooklyn; cannot install or update through Brooklyn");
+ }
+ // normal install
+ updating = false;
+ }
+
+ startedInstallation = true;
+ try (InputStream fin = new FileInputStream(zipFile)) {
+ if (!updating) {
+ // install new
+ assert result.getBundle()==null;
+ result.bundle = osgiManager.framework.getBundleContext().installBundle(result.getMetadata().getOsgiUniqueUrl(), fin);
+ } else {
+ result.bundle.update(fin);
+ }
+ }
+
+ osgiManager.checkCorrectlyInstalled(result.getMetadata(), result.bundle);
+ ((BasicManagedBundle)result.getMetadata()).setTempLocalFileWhenJustUploaded(zipFile);
+ zipFile = null; // don't close/delete it here, we'll use it for uploading, then it will delete it
+
+ if (!updating) {
+ synchronized (osgiManager.managedBundles) {
+ osgiManager.managedBundles.put(result.getMetadata().getId(), result.getMetadata());
+ osgiManager.managedBundleIds.put(result.getMetadata().getVersionedName(), result.getMetadata().getId());
+ }
+ result.code = OsgiBundleInstallationResult.ResultCode.INSTALLED_NEW_BUNDLE;
+ result.message = "Installed "+result.getMetadata().getVersionedName()+" with ID "+result.getMetadata().getId();
+ mgmt().getRebindManager().getChangeListener().onManaged(result.getMetadata());
+ } else {
+ result.code = OsgiBundleInstallationResult.ResultCode.UPDATED_EXISTING_BUNDLE;
+ result.message = "Updated "+result.getMetadata().getVersionedName()+" as existing ID "+result.getMetadata().getId();
+ mgmt().getRebindManager().getChangeListener().onChanged(result.getMetadata());
+ }
+ // setting the above before the code below means if there is a problem starting or loading catalog items
+ // a user has to remove then add again, or forcibly reinstall;
+ // that seems fine and probably better than allowing bundles to start and catalog items to be installed
+ // when brooklyn isn't aware it is supposed to be managing it
+
+ // starting here flags wiring issues earlier
+ // but may break some things running from the IDE
+ result.bundle.start();
+
+ if (updating!=null) {
+ osgiManager.uninstallCatalogItemsFromBundle( result.getVersionedName() );
+ // (ideally removal and addition would be atomic)
+ }
+ if (loadCatalogBom) {
+ osgiManager.loadCatalogBom(result.bundle);
+ }
+
+ return ReferenceWithError.newInstanceWithoutError(result);
+
+ } catch (Exception e) {
+ Exceptions.propagateIfFatal(e);
+ result.code = startedInstallation ? OsgiBundleInstallationResult.ResultCode.ERROR_INSTALLING_BUNDLE : OsgiBundleInstallationResult.ResultCode.ERROR_PREPARING_BUNDLE;
+ result.message = "Bundle "+inferredMetadata+" failed "+
+ (startedInstallation ? "installation" : "preparation") + ": " + Exceptions.collapseText(e);
+ return ReferenceWithError.newInstanceThrowingError(result, new IllegalStateException(result.message, e));
+ } finally {
+ close();
+ }
+ }
+
+ private boolean canUpdate() {
+ return force || VersionComparator.isSnapshot(inferredMetadata.getVersion());
+ }
+
+ /** true if the supplied name and version are complete; updates if the known data is incomplete;
+ * throws if there is a mismatch; false if the supplied data is incomplete */
+ private boolean matchSetOrFail(String source, String name, String version) {
+ boolean suppliedIsComplete = true;
+ if (Strings.isBlank(name)) {
+ suppliedIsComplete = false;
+ } else if (Strings.isBlank(inferredMetadata.getSymbolicName())) {
+ ((BasicManagedBundle)inferredMetadata).setSymbolicName(name);
+ } else if (!Objects.equal(inferredMetadata.getSymbolicName(), name)){
+ throw new IllegalArgumentException("Symbolic name mismatch '"+name+"' from "+source+" (expected '"+inferredMetadata.getSymbolicName()+"')");
+ }
+
+ if (Strings.isBlank(version)) {
+ suppliedIsComplete = false;
+ } else if (Strings.isBlank(inferredMetadata.getVersion())) {
+ ((BasicManagedBundle)inferredMetadata).setVersion(version);
+ } else if (!Objects.equal(inferredMetadata.getVersion(), version)){
+ throw new IllegalArgumentException("Bundle version mismatch '"+version+"' from "+source+" (expected '"+inferredMetadata.getVersion()+"')");
+ }
+
+ return suppliedIsComplete;
+ }
+}
http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/14524547/core/src/main/java/org/apache/brooklyn/core/mgmt/ha/OsgiBundleInstallationResult.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/brooklyn/core/mgmt/ha/OsgiBundleInstallationResult.java b/core/src/main/java/org/apache/brooklyn/core/mgmt/ha/OsgiBundleInstallationResult.java
new file mode 100644
index 0000000..50ca081
--- /dev/null
+++ b/core/src/main/java/org/apache/brooklyn/core/mgmt/ha/OsgiBundleInstallationResult.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.mgmt.ha;
+
+import java.util.List;
+
+import org.apache.brooklyn.api.typereg.ManagedBundle;
+import org.apache.brooklyn.util.collections.MutableList;
+import org.apache.brooklyn.util.osgi.VersionedName;
+import org.osgi.framework.Bundle;
+
+import com.google.common.annotations.Beta;
+import com.google.common.collect.ImmutableList;
+
+@Beta
+public class OsgiBundleInstallationResult {
+ String message;
+ ManagedBundle metadata;
+ Bundle bundle;
+ ResultCode code;
+
+ public enum ResultCode {
+ INSTALLED_NEW_BUNDLE,
+ UPDATED_EXISTING_BUNDLE,
+ IGNORING_BUNDLE_AREADY_INSTALLED,
+ ERROR_PREPARING_BUNDLE,
+ ERROR_INSTALLING_BUNDLE
+ }
+ final List<String> catalogItemsInstalled = MutableList.of();
+
+ public String getMessage() {
+ return message;
+ }
+ public Bundle getBundle() {
+ return bundle;
+ }
+ public ManagedBundle getMetadata() {
+ return metadata;
+ }
+ public ResultCode getCode() {
+ return code;
+ }
+ public List<String> getCatalogItemsInstalled() {
+ return ImmutableList.copyOf(catalogItemsInstalled);
+ }
+ public VersionedName getVersionedName() {
+ if (getMetadata()==null) return null;
+ return getMetadata().getVersionedName();
+ }
+
+ void setIgnoringAlreadyInstalled() {
+ code = OsgiBundleInstallationResult.ResultCode.IGNORING_BUNDLE_AREADY_INSTALLED;
+ message = "Bundle "+getMetadata().getVersionedName()+" already installed as "+getMetadata().getId();
+ }
+}
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/14524547/core/src/main/java/org/apache/brooklyn/core/mgmt/ha/OsgiManager.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/brooklyn/core/mgmt/ha/OsgiManager.java b/core/src/main/java/org/apache/brooklyn/core/mgmt/ha/OsgiManager.java
index 17d5645..1685541 100644
--- a/core/src/main/java/org/apache/brooklyn/core/mgmt/ha/OsgiManager.java
+++ b/core/src/main/java/org/apache/brooklyn/core/mgmt/ha/OsgiManager.java
@@ -19,8 +19,6 @@
package org.apache.brooklyn.core.mgmt.ha;
import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileOutputStream;
import java.io.InputStream;
import java.net.URL;
import java.util.Arrays;
@@ -33,6 +31,8 @@ import java.util.concurrent.Callable;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
+import javax.annotation.Nullable;
+
import org.apache.brooklyn.api.catalog.CatalogItem;
import org.apache.brooklyn.api.catalog.CatalogItem.CatalogBundle;
import org.apache.brooklyn.api.mgmt.ManagementContext;
@@ -47,7 +47,6 @@ import org.apache.brooklyn.core.config.ConfigKeys;
import org.apache.brooklyn.core.mgmt.persist.OsgiClassPrefixer;
import org.apache.brooklyn.core.server.BrooklynServerConfig;
import org.apache.brooklyn.core.server.BrooklynServerPaths;
-import org.apache.brooklyn.core.typereg.BasicManagedBundle;
import org.apache.brooklyn.util.collections.MutableList;
import org.apache.brooklyn.util.collections.MutableMap;
import org.apache.brooklyn.util.collections.MutableSet;
@@ -55,13 +54,13 @@ import org.apache.brooklyn.util.core.osgi.Osgis;
import org.apache.brooklyn.util.core.osgi.Osgis.BundleFinder;
import org.apache.brooklyn.util.core.osgi.SystemFrameworkLoader;
import org.apache.brooklyn.util.exceptions.Exceptions;
+import org.apache.brooklyn.util.exceptions.ReferenceWithError;
import org.apache.brooklyn.util.exceptions.UserFacingException;
import org.apache.brooklyn.util.guava.Maybe;
import org.apache.brooklyn.util.os.Os;
import org.apache.brooklyn.util.os.Os.DeletionResult;
import org.apache.brooklyn.util.osgi.VersionedName;
import org.apache.brooklyn.util.repeat.Repeater;
-import org.apache.brooklyn.util.stream.Streams;
import org.apache.brooklyn.util.text.Strings;
import org.apache.brooklyn.util.time.Duration;
import org.osgi.framework.Bundle;
@@ -96,18 +95,18 @@ public class OsgiManager {
/* see `Osgis` class for info on starting framework etc */
- protected final ManagementContext mgmt;
- protected final OsgiClassPrefixer osgiClassPrefixer;
- protected Framework framework;
- protected boolean reuseFramework;
+ final ManagementContext mgmt;
+ final OsgiClassPrefixer osgiClassPrefixer;
+ Framework framework;
+
+ private boolean reuseFramework;
private Set<Bundle> bundlesAtStartup;
- protected File osgiCacheDir;
- protected Map<String, ManagedBundle> managedBundles = MutableMap.of();
- protected Map<VersionedName, String> managedBundleIds = MutableMap.of();
- protected AtomicInteger numberOfReusableFrameworksCreated = new AtomicInteger();
-
+ private File osgiCacheDir;
+ Map<String, ManagedBundle> managedBundles = MutableMap.of();
+ Map<VersionedName, String> managedBundleIds = MutableMap.of();
- protected static final List<Framework> OSGI_FRAMEWORK_CONTAINERS_FOR_REUSE = MutableList.of();
+ private static AtomicInteger numberOfReusableFrameworksCreated = new AtomicInteger();
+ private static final List<Framework> OSGI_FRAMEWORK_CONTAINERS_FOR_REUSE = MutableList.of();
public OsgiManager(ManagementContext mgmt) {
this.mgmt = mgmt;
@@ -230,76 +229,21 @@ public class OsgiManager {
}
}
- public Bundle installUploadedBundle(ManagedBundle bundleMetadata, InputStream zipIn, boolean loadCatalogBom) {
- try {
- Bundle alreadyBundle = checkBundleInstalledThrowIfInconsistent(bundleMetadata, false);
- if (alreadyBundle!=null) {
- return alreadyBundle;
- }
-
- File zipF = Os.newTempFile("brooklyn-bundle-transient-"+bundleMetadata, "zip");
- FileOutputStream fos = new FileOutputStream(zipF);
- Streams.copy(zipIn, fos);
- zipIn.close();
- fos.close();
-
- ManagedBundle existingBundleToUpdate = null;
- synchronized (managedBundles) {
- String id = managedBundleIds.get(bundleMetadata.getVersionedName());
- if (id!=null) {
- existingBundleToUpdate = managedBundles.get(id);
- }
- }
-
- Bundle bundleInstalled;
- if (existingBundleToUpdate==null) {
- // install new
- bundleInstalled = framework.getBundleContext().installBundle(bundleMetadata.getOsgiUniqueUrl(),
- new FileInputStream(zipF));
- } else {
- // update
- bundleInstalled = framework.getBundleContext().getBundle(existingBundleToUpdate.getOsgiUniqueUrl());
- if (bundleInstalled==null) {
- throw new IllegalStateException("Detected bundle "+existingBundleToUpdate+" should be installed but framework cannot find it");
- }
- try (InputStream fin = new FileInputStream(zipF)) {
- bundleInstalled.update(fin);
- }
- bundleMetadata = existingBundleToUpdate;
- }
- checkCorrectlyInstalled(bundleMetadata, bundleInstalled);
- if (!bundleMetadata.isNameResolved()) {
- ((BasicManagedBundle)bundleMetadata).setSymbolicName(bundleInstalled.getSymbolicName());
- ((BasicManagedBundle)bundleMetadata).setVersion(bundleInstalled.getVersion().toString());
- }
- ((BasicManagedBundle)bundleMetadata).setTempLocalFileWhenJustUploaded(zipF);
-
- synchronized (managedBundles) {
- managedBundles.put(bundleMetadata.getId(), bundleMetadata);
- managedBundleIds.put(bundleMetadata.getVersionedName(), bundleMetadata.getId());
- }
- mgmt.getRebindManager().getChangeListener().onChanged(bundleMetadata);
-
- // starting here flags wiring issues earlier
- // but may break some things running from the IDE
- bundleInstalled.start();
-
- if (existingBundleToUpdate!=null) {
- // TODO remove old catalog items (see below)
- // (ideally the removal and addition would be atomic)
- }
- if (loadCatalogBom) {
- loadCatalogBom(bundleInstalled);
- }
-
- return bundleInstalled;
- } catch (Exception e) {
- Exceptions.propagateIfFatal(e);
- throw new IllegalStateException("Bundle "+bundleMetadata+" failed to install: " + Exceptions.collapseText(e), e);
- }
+ /** See {@link OsgiArchiveInstaller#install()}, using default values */
+ public ReferenceWithError<OsgiBundleInstallationResult> install(InputStream zipIn) {
+ return install(null, zipIn, true, false);
}
-
+ /** See {@link OsgiArchiveInstaller#install()} */
+ public ReferenceWithError<OsgiBundleInstallationResult> install(@Nullable ManagedBundle knownBundleMetadata, @Nullable InputStream zipIn,
+ boolean loadCatalogBom, boolean forceUpdateOfNonSnapshots) {
+
+ OsgiArchiveInstaller installer = new OsgiArchiveInstaller(this, knownBundleMetadata, zipIn);
+ installer.setLoadCatalogBom(loadCatalogBom);
+ installer.setForce(forceUpdateOfNonSnapshots);
+
+ return installer.install();
+ }
/**
* Removes this bundle from Brooklyn management,
@@ -321,12 +265,8 @@ public class OsgiManager {
managedBundleIds.remove(bundleMetadata.getVersionedName());
}
mgmt.getRebindManager().getChangeListener().onUnmanaged(bundleMetadata);
-
- // uninstall things that come from this bundle
- List<RegisteredType> thingsFromHere = ImmutableList.copyOf(getTypesFromBundle( bundleMetadata.getVersionedName() ));
- for (RegisteredType t: thingsFromHere) {
- mgmt.getCatalog().deleteCatalogItem(t.getSymbolicName(), t.getVersion());
- }
+
+ uninstallCatalogItemsFromBundle( bundleMetadata.getVersionedName() );
Bundle bundle = framework.getBundleContext().getBundle(bundleMetadata.getOsgiUniqueUrl());
if (bundle==null) {
@@ -340,6 +280,13 @@ public class OsgiManager {
}
}
+ void uninstallCatalogItemsFromBundle(VersionedName bundle) {
+ List<RegisteredType> thingsFromHere = ImmutableList.copyOf(getTypesFromBundle( bundle ));
+ for (RegisteredType t: thingsFromHere) {
+ mgmt.getCatalog().deleteCatalogItem(t.getSymbolicName(), t.getVersion());
+ }
+ }
+
protected Iterable<RegisteredType> getTypesFromBundle(final VersionedName vn) {
final String bundleId = vn.toString();
return mgmt.getTypeRegistry().getMatching(new Predicate<RegisteredType>() {
@@ -350,8 +297,8 @@ public class OsgiManager {
});
}
- // TODO DO on snapshot install, uninstall old equivalent snapshots (items in use might stay in use though?)
-
+ /** @deprecated since 0.12.0 use {@link #install(ManagedBundle, InputStream, boolean, boolean)} */
+ @Deprecated
public synchronized Bundle registerBundle(CatalogBundle bundleMetadata) {
try {
Bundle alreadyBundle = checkBundleInstalledThrowIfInconsistent(bundleMetadata, true);
@@ -402,7 +349,7 @@ public class OsgiManager {
return catalogItems;
}
- private void checkCorrectlyInstalled(OsgiBundleWithUrl bundle, Bundle b) {
+ void checkCorrectlyInstalled(OsgiBundleWithUrl bundle, Bundle b) {
String nv = b.getSymbolicName()+":"+b.getVersion().toString();
if (!isBundleNameEqualOrAbsent(bundle, b)) {
http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/14524547/core/src/main/java/org/apache/brooklyn/core/mgmt/rebind/RebindContextImpl.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/brooklyn/core/mgmt/rebind/RebindContextImpl.java b/core/src/main/java/org/apache/brooklyn/core/mgmt/rebind/RebindContextImpl.java
index 1e9cbba..13c85f6 100644
--- a/core/src/main/java/org/apache/brooklyn/core/mgmt/rebind/RebindContextImpl.java
+++ b/core/src/main/java/org/apache/brooklyn/core/mgmt/rebind/RebindContextImpl.java
@@ -36,7 +36,7 @@ import org.apache.brooklyn.api.policy.Policy;
import org.apache.brooklyn.api.sensor.Enricher;
import org.apache.brooklyn.api.sensor.Feed;
import org.apache.brooklyn.api.typereg.ManagedBundle;
-import org.apache.brooklyn.core.mgmt.internal.LocalManagementContext;
+import org.apache.brooklyn.core.mgmt.internal.ManagementContextInternal;
import org.apache.brooklyn.util.collections.MutableMap;
import com.google.common.collect.Maps;
@@ -91,7 +91,7 @@ public class RebindContextImpl implements RebindContext {
// we don't track register/unregister of bundles; it isn't needed as it happens so early
public void installBundle(ManagedBundle bundle, InputStream zipInput) {
- ((LocalManagementContext)mgmt).getOsgiManager().get().installUploadedBundle(bundle, zipInput, true);
+ ((ManagementContextInternal)mgmt).getOsgiManager().get().install(bundle, zipInput, true, false).checkNoError();
}
public void unregisterPolicy(Policy policy) {
http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/14524547/core/src/main/java/org/apache/brooklyn/core/typereg/BasicManagedBundle.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/brooklyn/core/typereg/BasicManagedBundle.java b/core/src/main/java/org/apache/brooklyn/core/typereg/BasicManagedBundle.java
index 37e706f..4799c06 100644
--- a/core/src/main/java/org/apache/brooklyn/core/typereg/BasicManagedBundle.java
+++ b/core/src/main/java/org/apache/brooklyn/core/typereg/BasicManagedBundle.java
@@ -21,6 +21,7 @@ package org.apache.brooklyn.core.typereg;
import java.io.File;
import java.util.Map;
+import org.apache.brooklyn.api.catalog.CatalogItem.CatalogBundle;
import org.apache.brooklyn.api.mgmt.rebind.RebindSupport;
import org.apache.brooklyn.api.typereg.ManagedBundle;
import org.apache.brooklyn.api.typereg.OsgiBundleWithUrl;
@@ -181,4 +182,8 @@ public class BasicManagedBundle extends AbstractBrooklynObject implements Manage
throw new UnsupportedOperationException();
}
+ public static ManagedBundle of(CatalogBundle bundleUrl) {
+ return new BasicManagedBundle(bundleUrl.getSymbolicName(), bundleUrl.getVersion(), bundleUrl.getUrl());
+ }
+
}
http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/14524547/rest/rest-api/src/main/java/org/apache/brooklyn/rest/domain/ApiError.java
----------------------------------------------------------------------
diff --git a/rest/rest-api/src/main/java/org/apache/brooklyn/rest/domain/ApiError.java b/rest/rest-api/src/main/java/org/apache/brooklyn/rest/domain/ApiError.java
index bec524b..a2a1834 100644
--- a/rest/rest-api/src/main/java/org/apache/brooklyn/rest/domain/ApiError.java
+++ b/rest/rest-api/src/main/java/org/apache/brooklyn/rest/domain/ApiError.java
@@ -72,6 +72,7 @@ public class ApiError implements Serializable {
public static class Builder {
private String message;
private String details;
+ private Object data;
private Integer errorCode;
public Builder message(String message) {
@@ -80,7 +81,12 @@ public class ApiError implements Serializable {
}
public Builder details(String details) {
- this.details = checkNotNull(details, "details");
+ this.details = details;
+ return this;
+ }
+
+ public Builder data(Object data) {
+ this.data = data;
return this;
}
@@ -111,7 +117,7 @@ public class ApiError implements Serializable {
}
public ApiError build() {
- return new ApiError(message, details, errorCode);
+ return new ApiError(message, details, data, errorCode);
}
/** @deprecated since 0.7.0; use {@link #copy(ApiError)} */
@@ -136,18 +142,23 @@ public class ApiError implements Serializable {
@JsonInclude(JsonInclude.Include.NON_EMPTY)
private final String details;
+
+ @JsonInclude(JsonInclude.Include.NON_NULL)
+ private final Object data;
@JsonInclude(JsonInclude.Include.NON_NULL)
private final Integer error;
public ApiError(String message) { this(message, null); }
- public ApiError(String message, String details) { this(message, details, null); }
+ public ApiError(String message, String details) { this(message, details, null, null); }
public ApiError(
@JsonProperty("message") String message,
@JsonProperty("details") String details,
+ @JsonProperty("data") Object data,
@JsonProperty("error") Integer error) {
this.message = checkNotNull(message, "message");
this.details = details != null ? details : "";
+ this.data = data;
this.error = error;
}
@@ -159,6 +170,10 @@ public class ApiError implements Serializable {
return details;
}
+ public Object getData() {
+ return data;
+ }
+
public Integer getError() {
return error;
}
http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/14524547/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/resources/CatalogResource.java
----------------------------------------------------------------------
diff --git a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/resources/CatalogResource.java b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/resources/CatalogResource.java
index d0f7270..7359e4d 100644
--- a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/resources/CatalogResource.java
+++ b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/resources/CatalogResource.java
@@ -19,9 +19,6 @@
package org.apache.brooklyn.rest.resources;
import java.io.ByteArrayInputStream;
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.IOException;
import java.io.InputStreamReader;
import java.net.URI;
import java.util.ArrayList;
@@ -29,8 +26,6 @@ import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
-import java.util.jar.Attributes;
-import java.util.jar.Manifest;
import javax.annotation.Nullable;
import javax.ws.rs.core.MediaType;
@@ -44,8 +39,10 @@ import org.apache.brooklyn.api.entity.Entity;
import org.apache.brooklyn.api.entity.EntitySpec;
import org.apache.brooklyn.api.location.Location;
import org.apache.brooklyn.api.location.LocationSpec;
+import org.apache.brooklyn.api.mgmt.ManagementContext;
import org.apache.brooklyn.api.policy.Policy;
import org.apache.brooklyn.api.policy.PolicySpec;
+import org.apache.brooklyn.api.typereg.ManagedBundle;
import org.apache.brooklyn.api.typereg.RegisteredType;
import org.apache.brooklyn.core.catalog.CatalogPredicates;
import org.apache.brooklyn.core.catalog.internal.BasicBrooklynCatalog;
@@ -54,8 +51,8 @@ import org.apache.brooklyn.core.catalog.internal.CatalogItemComparator;
import org.apache.brooklyn.core.catalog.internal.CatalogUtils;
import org.apache.brooklyn.core.mgmt.entitlement.Entitlements;
import org.apache.brooklyn.core.mgmt.entitlement.Entitlements.StringAndArgument;
+import org.apache.brooklyn.core.mgmt.ha.OsgiBundleInstallationResult;
import org.apache.brooklyn.core.mgmt.internal.ManagementContextInternal;
-import org.apache.brooklyn.core.typereg.BasicManagedBundle;
import org.apache.brooklyn.core.typereg.RegisteredTypePredicates;
import org.apache.brooklyn.rest.api.CatalogApi;
import org.apache.brooklyn.rest.domain.ApiError;
@@ -65,24 +62,16 @@ import org.apache.brooklyn.rest.domain.CatalogLocationSummary;
import org.apache.brooklyn.rest.domain.CatalogPolicySummary;
import org.apache.brooklyn.rest.filter.HaHotStateRequired;
import org.apache.brooklyn.rest.transform.CatalogTransformer;
-import org.apache.brooklyn.rest.util.DefaultExceptionMapper;
import org.apache.brooklyn.rest.util.WebResourceUtils;
import org.apache.brooklyn.util.collections.MutableList;
import org.apache.brooklyn.util.collections.MutableMap;
import org.apache.brooklyn.util.collections.MutableSet;
import org.apache.brooklyn.util.core.ResourceUtils;
-import org.apache.brooklyn.util.core.osgi.BundleMaker;
import org.apache.brooklyn.util.exceptions.Exceptions;
-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.exceptions.ReferenceWithError;
import org.apache.brooklyn.util.text.StringPredicates;
import org.apache.brooklyn.util.text.Strings;
import org.apache.brooklyn.util.yaml.Yamls;
-import org.apache.commons.compress.archivers.zip.ZipArchiveEntry;
-import org.apache.commons.compress.archivers.zip.ZipFile;
-import org.osgi.framework.Bundle;
-import org.osgi.framework.Constants;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -161,6 +150,29 @@ public class CatalogResource extends AbstractBrooklynRestResource implements Cat
}
}
+ public static class BundleInstallationRestResult {
+ String message;
+ ManagedBundle metadata;
+
+ enum ResultCode {
+ INSTALLED_NEW_BUNDLE,
+ UPDATED_EXISTING_BUNDLE,
+ IGNORING_BUNDLE_AREADY_INSTALLED,
+ ERROR_PREPARING_BUNDLE,
+ ERROR_INSTALLING_BUNDLE
+ }
+ Map<String,Object> catalogItemsInstalled;
+
+ public String getMessage() {
+ return message;
+ }
+
+ public static BundleInstallationRestResult of(OsgiBundleInstallationResult result, ManagementContext mgmt) {
+ // TODO
+ return null;
+ }
+ }
+
@Override
@Beta
public Response createFromArchive(byte[] zipInput) {
@@ -169,92 +181,15 @@ public class CatalogResource extends AbstractBrooklynRestResource implements Cat
Entitlements.getEntitlementContext().user());
}
- BundleMaker bm = new BundleMaker(mgmtInternal());
- File f=null, f2=null;
- try {
- f = Os.newTempFile("brooklyn-posted-archive", "zip");
- try {
- Files.write(zipInput, f);
- } catch (IOException e) {
- Exceptions.propagate(e);
- }
-
- ZipFile zf;
- try {
- zf = new ZipFile(f);
- } catch (IOException e) {
- throw new IllegalArgumentException("Invalid ZIP/JAR archive: "+e);
- }
- ZipArchiveEntry bom = zf.getEntry("catalog.bom");
- if (bom==null) {
- bom = zf.getEntry("/catalog.bom");
- }
- if (bom==null) {
- throw new IllegalArgumentException("Archive must contain a catalog.bom file in the root");
- }
- String bomS;
- try {
- bomS = Streams.readFullyString(zf.getInputStream(bom));
- } catch (IOException e) {
- throw new IllegalArgumentException("Error reading catalog.bom from ZIP/JAR archive: "+e);
- }
-
- try {
- zf.close();
- } catch (IOException e) {
- log.debug("Swallowed exception closing zipfile. Full error logged at trace: {}", e.getMessage());
- log.trace("Exception closing zipfile", e);
- }
-
- VersionedName vn = BasicBrooklynCatalog.getVersionedName( BasicBrooklynCatalog.getCatalogMetadata(bomS) );
-
- Manifest mf = bm.getManifest(f);
- if (mf==null) {
- mf = new Manifest();
- }
- String bundleNameInMF = mf.getMainAttributes().getValue(Constants.BUNDLE_SYMBOLICNAME);
- if (Strings.isNonBlank(bundleNameInMF)) {
- if (!bundleNameInMF.equals(vn.getSymbolicName())) {
- throw new IllegalArgumentException("JAR MANIFEST symbolic-name '"+bundleNameInMF+"' does not match '"+vn.getSymbolicName()+"' defined in BOM");
- }
- } else {
- bundleNameInMF = vn.getSymbolicName();
- mf.getMainAttributes().putValue(Constants.BUNDLE_SYMBOLICNAME, bundleNameInMF);
- }
-
- String bundleVersionInMF = mf.getMainAttributes().getValue(Constants.BUNDLE_VERSION);
- if (Strings.isNonBlank(bundleVersionInMF)) {
- if (!bundleVersionInMF.equals(vn.getVersion().toString())) {
- throw new IllegalArgumentException("JAR MANIFEST version '"+bundleVersionInMF+"' does not match '"+vn.getVersion()+"' defined in BOM");
- }
- } else {
- bundleVersionInMF = vn.getVersion().toString();
- mf.getMainAttributes().putValue(Constants.BUNDLE_VERSION, bundleVersionInMF);
- }
- if (mf.getMainAttributes().getValue(Attributes.Name.MANIFEST_VERSION)==null) {
- mf.getMainAttributes().putValue(Attributes.Name.MANIFEST_VERSION.toString(), "1.0");
- }
-
- f2 = bm.copyAddingManifest(f, mf);
-
- BasicManagedBundle bundleMetadata = new BasicManagedBundle(bundleNameInMF, bundleVersionInMF, null);
- Bundle bundle;
- try (FileInputStream f2in = new FileInputStream(f2)) {
- bundle = ((ManagementContextInternal)mgmt()).getOsgiManager().get().installUploadedBundle(bundleMetadata, f2in, false);
- } catch (Exception e) {
- throw Exceptions.propagate(e);
- }
-
- Iterable<? extends CatalogItem<?, ?>> catalogItems = MutableList.copyOf(
- ((ManagementContextInternal)mgmt()).getOsgiManager().get().loadCatalogBom(bundle) );
-
- return buildCreateResponse(catalogItems);
- } catch (RuntimeException ex) {
- throw WebResourceUtils.badRequest(ex);
- } finally {
- if (f!=null) f.delete();
- if (f2!=null) f2.delete();
+ ReferenceWithError<OsgiBundleInstallationResult> result = ((ManagementContextInternal)mgmt()).getOsgiManager().get()
+ .install(new ByteArrayInputStream(zipInput));
+
+ if (result.hasError()) {
+ return ApiError.builder().errorCode(Status.BAD_REQUEST).message(result.getWithoutError().getMessage())
+ .data(result).build().asJsonResponse();
}
+
+ return Response.status(Status.CREATED).entity( BundleInstallationRestResult.of(result.get(), mgmt()) ).build();
}
private Response buildCreateResponse(Iterable<? extends CatalogItem<?, ?>> catalogItems) {
@@ -575,4 +510,4 @@ public class CatalogResource extends AbstractBrooklynRestResource implements Cat
return result;
}
}
-
\ No newline at end of file
+
http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/14524547/utils/common/src/main/java/org/apache/brooklyn/util/maven/MavenArtifact.java
----------------------------------------------------------------------
diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/maven/MavenArtifact.java b/utils/common/src/main/java/org/apache/brooklyn/util/maven/MavenArtifact.java
index b9de2f1..248b72e 100644
--- a/utils/common/src/main/java/org/apache/brooklyn/util/maven/MavenArtifact.java
+++ b/utils/common/src/main/java/org/apache/brooklyn/util/maven/MavenArtifact.java
@@ -26,6 +26,7 @@ import javax.annotation.Nullable;
import org.apache.brooklyn.util.collections.MutableList;
import org.apache.brooklyn.util.javalang.JavaClassNames;
import org.apache.brooklyn.util.text.Strings;
+import org.apache.brooklyn.util.text.VersionComparator;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -100,7 +101,7 @@ public class MavenArtifact {
}
public boolean isSnapshot() {
- return getVersion().toUpperCase().contains("SNAPSHOT");
+ return VersionComparator.isSnapshot(getVersion());
}
/** @see #customFileNameAfterArtifactMarker */
http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/14524547/utils/common/src/main/java/org/apache/brooklyn/util/text/VersionComparator.java
----------------------------------------------------------------------
diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/text/VersionComparator.java b/utils/common/src/main/java/org/apache/brooklyn/util/text/VersionComparator.java
index 94553b0..071f591 100644
--- a/utils/common/src/main/java/org/apache/brooklyn/util/text/VersionComparator.java
+++ b/utils/common/src/main/java/org/apache/brooklyn/util/text/VersionComparator.java
@@ -52,14 +52,19 @@ public class VersionComparator implements Comparator<String> {
return INSTANCE;
}
+ public static boolean isSnapshot(String version) {
+ if (version==null) return false;
+ return version.toUpperCase().contains(SNAPSHOT);
+ }
+
@Override
public int compare(String v1, String v2) {
if (v1==null && v2==null) return 0;
if (v1==null) return -1;
if (v2==null) return 1;
- boolean isV1Snapshot = v1.toUpperCase().contains(SNAPSHOT);
- boolean isV2Snapshot = v2.toUpperCase().contains(SNAPSHOT);
+ boolean isV1Snapshot = isSnapshot(v1);
+ boolean isV2Snapshot = isSnapshot(v2);
if (isV1Snapshot == isV2Snapshot) {
// if snapshot status is the same, look at dot-split parts first
return compareDotSplitParts(splitOnDot(v1), splitOnDot(v2));
http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/14524547/utils/common/src/test/java/org/apache/brooklyn/util/text/VersionComparatorTest.java
----------------------------------------------------------------------
diff --git a/utils/common/src/test/java/org/apache/brooklyn/util/text/VersionComparatorTest.java b/utils/common/src/test/java/org/apache/brooklyn/util/text/VersionComparatorTest.java
index 3a4a71c..6c65a8a 100644
--- a/utils/common/src/test/java/org/apache/brooklyn/util/text/VersionComparatorTest.java
+++ b/utils/common/src/test/java/org/apache/brooklyn/util/text/VersionComparatorTest.java
@@ -92,6 +92,13 @@ public class VersionComparatorTest {
assertVersionOrder("0.10.0-SNAPSHOT", "0.10.0.SNAPSHOT", "0.10.0-GA", "0.10.0.GA", "0.10.0");
}
+ @Test
+ public void testIsSnapshot() {
+ Assert.assertTrue(VersionComparator.isSnapshot("0.10.0-SNAPSHOT"));
+ Assert.assertTrue(VersionComparator.isSnapshot("0.10.0.snapshot"));
+ Assert.assertFalse(VersionComparator.isSnapshot("0.10.0"));
+ }
+
private static void assertVersionOrder(String v1, String v2, String ...otherVersions) {
List<String> versions = MutableList.<String>of().append(v1, v2, otherVersions);