PR #867: incorporate review comments And better handle pre-existing bundles.
Project: http://git-wip-us.apache.org/repos/asf/brooklyn-server/repo Commit: http://git-wip-us.apache.org/repos/asf/brooklyn-server/commit/85196899 Tree: http://git-wip-us.apache.org/repos/asf/brooklyn-server/tree/85196899 Diff: http://git-wip-us.apache.org/repos/asf/brooklyn-server/diff/85196899 Branch: refs/heads/master Commit: 85196899f1a7b151767623f3f735c6d155dd5e28 Parents: 2a539c0 Author: Aled Sage <[email protected]> Authored: Tue Oct 24 13:50:01 2017 +0100 Committer: Aled Sage <[email protected]> Committed: Fri Oct 27 13:49:55 2017 +0100 ---------------------------------------------------------------------- .../rebind/mementos/ManagedBundleMemento.java | 5 + .../camp/brooklyn/AbstractYamlTest.java | 2 +- .../catalog/CatalogOsgiYamlEntityTest.java | 2 +- .../catalog/CatalogOsgiYamlVersioningTest.java | 2 +- .../brooklyn/test/lite/CampYamlLiteTest.java | 2 +- .../catalog/internal/BasicBrooklynCatalog.java | 2 +- .../core/mgmt/ha/OsgiArchiveInstaller.java | 241 +++++++++----- .../core/mgmt/rebind/RebindIteration.java | 2 +- .../rebind/dto/BasicManagedBundleMemento.java | 20 +- .../core/typereg/BasicManagedBundle.java | 20 +- .../AbstractBrooklynLauncherRebindTest.java | 30 +- .../BrooklynLauncherRebindCatalogOsgiTest.java | 321 ++++++++++++++++--- 12 files changed, 494 insertions(+), 155 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/85196899/api/src/main/java/org/apache/brooklyn/api/mgmt/rebind/mementos/ManagedBundleMemento.java ---------------------------------------------------------------------- diff --git a/api/src/main/java/org/apache/brooklyn/api/mgmt/rebind/mementos/ManagedBundleMemento.java b/api/src/main/java/org/apache/brooklyn/api/mgmt/rebind/mementos/ManagedBundleMemento.java index 7e487ff..ffcd783 100644 --- a/api/src/main/java/org/apache/brooklyn/api/mgmt/rebind/mementos/ManagedBundleMemento.java +++ b/api/src/main/java/org/apache/brooklyn/api/mgmt/rebind/mementos/ManagedBundleMemento.java @@ -18,6 +18,8 @@ */ package org.apache.brooklyn.api.mgmt.rebind.mementos; +import javax.annotation.Nullable; + import com.google.common.io.ByteSource; public interface ManagedBundleMemento extends Memento { @@ -27,6 +29,9 @@ public interface ManagedBundleMemento extends Memento { String getUrl(); + @Nullable + String getChecksum(); + ByteSource getJarContent(); void setJarContent(ByteSource byteSource); http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/85196899/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/AbstractYamlTest.java ---------------------------------------------------------------------- diff --git a/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/AbstractYamlTest.java b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/AbstractYamlTest.java index 3d4bbd2..091598f 100644 --- a/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/AbstractYamlTest.java +++ b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/AbstractYamlTest.java @@ -263,7 +263,7 @@ public abstract class AbstractYamlTest { File bf = bundleMaker.createTempZip("test", MutableMap.of( new ZipEntry(BasicBrooklynCatalog.CATALOG_BOM), new ByteArrayInputStream(catalogYaml.getBytes()))); ReferenceWithError<OsgiBundleInstallationResult> b = ((ManagementContextInternal)mgmt).getOsgiManager().get().installDeferredStart( - new BasicManagedBundle(bundleName.getSymbolicName(), bundleName.getVersionString(), null), + new BasicManagedBundle(bundleName.getSymbolicName(), bundleName.getVersionString(), null, null), new FileInputStream(bf), false); http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/85196899/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/catalog/CatalogOsgiYamlEntityTest.java ---------------------------------------------------------------------- diff --git a/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/catalog/CatalogOsgiYamlEntityTest.java b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/catalog/CatalogOsgiYamlEntityTest.java index 6d7df19..ae0df91 100644 --- a/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/catalog/CatalogOsgiYamlEntityTest.java +++ b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/catalog/CatalogOsgiYamlEntityTest.java @@ -333,7 +333,7 @@ public class CatalogOsgiYamlEntityTest extends AbstractYamlTest { addCatalogOSGiEntity(id, SIMPLE_ENTITY_TYPE, true); Asserts.shouldHaveFailedPreviously(); } catch (Exception e) { - Asserts.expectedFailureContainsIgnoreCase(e, id, "already installed", "cannot install a different bundle at a same non-snapshot version"); + Asserts.expectedFailureContainsIgnoreCase(e, id, "already installed", "cannot install a different bundle with the same non-snapshot version"); } } http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/85196899/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/catalog/CatalogOsgiYamlVersioningTest.java ---------------------------------------------------------------------- diff --git a/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/catalog/CatalogOsgiYamlVersioningTest.java b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/catalog/CatalogOsgiYamlVersioningTest.java index 672c722..9042663 100644 --- a/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/catalog/CatalogOsgiYamlVersioningTest.java +++ b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/catalog/CatalogOsgiYamlVersioningTest.java @@ -74,7 +74,7 @@ public class CatalogOsgiYamlVersioningTest extends CatalogYamlVersioningTest { @Override protected void checkAddSameVersionFailsWhenIconIsDifferent(Exception e) { Asserts.expectedFailureContainsIgnoreCase(e, - "cannot install a different bundle at a same non-snapshot version"); + "cannot install a different bundle with the same non-snapshot version"); assertExpectedFailureIncludesSampleId(e); } http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/85196899/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/test/lite/CampYamlLiteTest.java ---------------------------------------------------------------------- diff --git a/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/test/lite/CampYamlLiteTest.java b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/test/lite/CampYamlLiteTest.java index 92b053e..d4b67c5 100644 --- a/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/test/lite/CampYamlLiteTest.java +++ b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/test/lite/CampYamlLiteTest.java @@ -213,7 +213,7 @@ public class CampYamlLiteTest { // install bundle for class access but without loading its catalog.bom, // since we only have mock matchers here // (if we don't do this, the default routines install it and try to process the catalog.bom, failing) - ((ManagementContextInternal)mgmt).getOsgiManager().get().installDeferredStart(new BasicManagedBundle(null, null, bundleUrl), null, false).get(); + ((ManagementContextInternal)mgmt).getOsgiManager().get().installDeferredStart(new BasicManagedBundle(null, null, bundleUrl, null), null, false).get(); } private void assertMgmtHasSampleMyCatalogApp(String symbolicName, String bundleUrl) { http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/85196899/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 810ffc1..c1c893c 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 @@ -1503,7 +1503,7 @@ public class BasicBrooklynCatalog implements BrooklynCatalog { OsgiBundleInstallationResult result = null; try { - result = osgiManager.install(new BasicManagedBundle(vn.getSymbolicName(), vn.getVersionString(), null), new FileInputStream(bf), true, true, forceUpdate).get(); + result = osgiManager.install(new BasicManagedBundle(vn.getSymbolicName(), vn.getVersionString(), null, null), new FileInputStream(bf), true, true, forceUpdate).get(); } catch (FileNotFoundException e) { throw Exceptions.propagate(e); } finally { http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/85196899/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 index 968fbf9..e3b91fa 100644 --- 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 @@ -26,6 +26,7 @@ import java.io.IOException; import java.io.InputStream; import java.net.URL; import java.util.Collections; +import java.util.List; import java.util.Map; import java.util.TreeMap; import java.util.jar.Attributes; @@ -362,6 +363,7 @@ class OsgiArchiveInstaller { final boolean updating; result.metadata = osgiManager.getManagedBundle(inferredMetadata.getVersionedName()); if (result.getMetadata()!=null) { + // already have a managed bundle - check if this is using a new/different URL if (suppliedKnownBundleMetadata!=null && suppliedKnownBundleMetadata.getUrl()!=null) { String knownIdForThisUrl = osgiManager.managedBundlesRecord.getManagedBundleIdFromUrl(suppliedKnownBundleMetadata.getUrl()); @@ -376,68 +378,106 @@ class OsgiArchiveInstaller { osgiManager.managedBundlesRecord.setManagedBundleUrl(suppliedKnownBundleMetadata.getUrl(), result.getMetadata().getId()); } } + + result.bundle = osgiManager.framework.getBundleContext().getBundle(result.getMetadata().getOsgiUniqueUrl()); + + // Check if exactly this bundle is already installed + if (result.bundle != null && checksumsMatch(result.getMetadata(), inferredMetadata)) { + // e.g. repeatedly installing the same bundle + log.trace("Bundle "+inferredMetadata+" matches already installed managed bundle "+result.getMetadata() + +"; install is no-op"); + result.setIgnoringAlreadyInstalled(); + return ReferenceWithError.newInstanceWithoutError(result); + } else if (isEquivalentBundleAlreadyOsgiInstalled(osgiManager, inferredMetadata, zipFile)) { + // e.g. happens if pre-installed bundle is brought under management, and then add it again via a mvn-style url. + // We wouldn't know the checksum from the pre-installed bundle. + log.trace("Bundle "+inferredMetadata+" matches metadata of managed bundle "+result.getMetadata() + +" (but not OSGi bundle location "+result.getMetadata().getOsgiUniqueUrl()+"), " + + "and matches already installed OSGi bundle; ; install is no-op"); + result.setIgnoringAlreadyInstalled(); + return ReferenceWithError.newInstanceWithoutError(result); + } + if (canUpdate()) { - - result.bundle = osgiManager.framework.getBundleContext().getBundle(result.getMetadata().getOsgiUniqueUrl()); - if (result.getBundle()==null) { - if (hasBundleOnInstall(osgiManager, inferredMetadata, zipFile)) { - // e.g. happens if system-bundle is brought under management, and then try to add it again: - // if added from "initial catalog" has it, and then added from persisted state as well. - result.setIgnoringAlreadyInstalled(); - return ReferenceWithError.newInstanceWithoutError(result); - } else { - log.warn("Brooklyn thought is was already managing bundle "+result.getMetadata().getVersionedName()+" but it's not installed to framework; reinstalling it"); - updating = false; - } + if (result.getBundle() == null) { + log.warn("Brooklyn thought is was already managing bundle "+result.getMetadata().getVersionedName() + +" but it's not installed to framework at location "+result.getMetadata().getOsgiUniqueUrl()+"; reinstalling it"); + updating = false; } else { + log.trace("Updating existing brooklyn-managed bundle "+result); updating = true; } } else { - if (result.getMetadata().getChecksum()==null || inferredMetadata.getChecksum()==null) { - log.warn("Missing bundle checksum data for "+result+"; assuming bundle replacement is permitted"); - } else if (!Objects.equal(result.getMetadata().getChecksum(), inferredMetadata.getChecksum())) { + List<Bundle> existingBundles = findBundlesByVersion(osgiManager, inferredMetadata); + if (existingBundles.size() > 0 && (result.getMetadata().getChecksum()==null || inferredMetadata.getChecksum()==null)) { + // e.g. Checksum would be missing if we brought under management a pre-installed bundle with an unusable url. + log.info("Missing bundle checksum data for "+result+"; assuming bundle matches existing brooklyn-managed bundle (not re-installing)"); + result.setIgnoringAlreadyInstalled(); + return ReferenceWithError.newInstanceWithoutError(result); + } else if (result.bundle != null || existingBundles.size() > 0) { throw new IllegalArgumentException("Bundle "+result.getMetadata().getVersionedName()+" already installed; " - + "cannot install a different bundle at a same non-snapshot version"); + + "cannot install a different bundle with the same non-snapshot version"); + } else { + throw new IllegalArgumentException("Bundle "+result.getMetadata().getVersionedName()+" already a brooklyn-managed bundle, but not found in OSGi framework; " + + "will not re-install without use of 'force'"); } - result.setIgnoringAlreadyInstalled(); - return ReferenceWithError.newInstanceWithoutError(result); } } else { + // No such Brooklyn-managed bundle. + + // Check if likely-looking bundle already installed to OSGi subsystem, but brooklyn not aware of it. + // This will often happen on a karaf restart where bundle was cached by karaf, so we need to allow it; + // can also happen if brooklyn.libraries references an existing bundle. + // + // If we're not certain that the bundle is identical + + result.metadata = inferredMetadata; - // no such Brooklyn-managed bundle - Maybe<Bundle> b = Osgis.bundleFinder(osgiManager.framework).symbolicName(result.getMetadata().getSymbolicName()).version(result.getMetadata().getSuppliedVersionString()).find(); - if (b.isPresent()) { - // bundle already installed to OSGi subsystem but brooklyn not aware of it; - // this will often happen on a karaf restart where bundle was cached by karaf, - // so we need to allow it. can also happen if brooklyn.libraries references an existing bundle. + + // search for already-installed bundles. + List<Bundle> existingBundles = findBundlesByVersion(osgiManager, inferredMetadata); + Maybe<Bundle> existingEquivalentBundle = tryFindEquivalentBundle(existingBundles, inferredMetadata, zipFile); + + if (existingEquivalentBundle.isPresent()) { + // Identical bundle (by osgi location or binary content) already installed; just bring that under management. + // This will often happen on a karaf restart: bundles from persisted state match those cached by karaf, + isBringingExistingOsgiInstalledBundleUnderBrooklynManagement = true; + result.bundle = existingEquivalentBundle.get(); - // determine if we are simply bringing existing installed under Brooklyn management (because url or binary content identical and not forced) - // or if the bundle should be reinstalled/updated - if (!force) { - if (!isBringingExistingOsgiInstalledBundleUnderBrooklynManagement) { - isBringingExistingOsgiInstalledBundleUnderBrooklynManagement = hasBundleOnInstall(osgiManager, inferredMetadata, zipFile); - } - } else { + } else if (existingBundles.size() > 0) { + Bundle existingBundle = existingBundles.get(0); + + if (force) { if (isBringingExistingOsgiInstalledBundleUnderBrooklynManagement) { log.debug("Request to install "+inferredMetadata+" was forced, so forcing reinstallation " - + "of existing OSGi installed (but not Brooklyn-managed) bundle "+b.get()); + + "of existing OSGi installed (but not Brooklyn-managed) bundle "+existingBundle); isBringingExistingOsgiInstalledBundleUnderBrooklynManagement = false; } } if (isBringingExistingOsgiInstalledBundleUnderBrooklynManagement) { - result.bundle = b.get(); - + // We were explicitly asked to bring an existing OSGi bundle under management; + // no equivalence check required + result.bundle = existingBundle; } else { - // needs reinstallation - // we could update instead of uninstall/reinstall - // note however either way we won't be able to rollback if there is a failure + // Uninstall and re-install the bundle. + // This is a good idea for brooklyn managed bundles that were in the karaf cache (when we can't + // determine that they are definitely identical). + // It's less good for pre-installed bundles, but if the user has said to deploy it or has + // referenced it in `brooklyn.libraries` then we'll go for it anyway! Let's hope they didn't + // reference `org.apache.brooklyn.core` or some such. + // + // We are this extreme because we want rebind to always work! If a user did a `force` install + // of a bundle, then we want to do the same on rebind (rather than risk failing). + // + // Instead of uninstall, we could update the bundle. + // Note however either way we won't be able to rollback if there is a failure log.debug("Brooklyn install of "+result.getMetadata().getVersionedName()+" detected already loaded in OSGi; uninstalling that to reinstall as Brooklyn-managed"); - b.get().uninstall(); + existingBundle.uninstall(); result.bundle = null; } } - // normal install + updating = false; } @@ -449,6 +489,7 @@ class OsgiArchiveInstaller { log.debug("Brooklyn install of "+result.getMetadata().getVersionedName()+" detected already loaded "+result.getBundle()+" in OSGi can be re-used, skipping OSGi install"); } else { assert result.getBundle()==null; + log.debug("Installing bundle "+result.getMetadata().getVersionedName()+", using OSGi location "+result.getMetadata().getOsgiUniqueUrl()); result.bundle = osgiManager.framework.getBundleContext().installBundle(result.getMetadata().getOsgiUniqueUrl(), fin); } } else { @@ -464,14 +505,18 @@ class OsgiArchiveInstaller { osgiManager.managedBundlesRecord.addManagedBundle(result, zipFile); result.code = OsgiBundleInstallationResult.ResultCode.INSTALLED_NEW_BUNDLE; result.message = "Installed Brooklyn catalog bundle "+result.getMetadata().getVersionedName()+" with ID "+result.getMetadata().getId()+" ["+result.bundle.getBundleId()+"]"; - ((BasicManagedBundle)result.getMetadata()).setPersistenceNeeded(true); - mgmt().getRebindManager().getChangeListener().onManaged(result.getMetadata()); + if (!isBlacklistedForPersistence(result.getMetadata())) { + ((BasicManagedBundle)result.getMetadata()).setPersistenceNeeded(true); + mgmt().getRebindManager().getChangeListener().onManaged(result.getMetadata()); + } } else { oldZipFile = osgiManager.managedBundlesRecord.updateManagedBundleFile(result, zipFile); result.code = OsgiBundleInstallationResult.ResultCode.UPDATED_EXISTING_BUNDLE; result.message = "Updated Brooklyn catalog bundle "+result.getMetadata().getVersionedName()+" as existing ID "+result.getMetadata().getId()+" ["+result.bundle.getBundleId()+"]"; - ((BasicManagedBundle)result.getMetadata()).setPersistenceNeeded(true); - mgmt().getRebindManager().getChangeListener().onChanged(result.getMetadata()); + if (!isBlacklistedForPersistence(result.getMetadata())) { + ((BasicManagedBundle)result.getMetadata()).setPersistenceNeeded(true); + mgmt().getRebindManager().getChangeListener().onChanged(result.getMetadata()); + } } log.debug(result.message + " (partial): OSGi bundle installed, with bundle start and Brooklyn management to follow"); // can now delete and close (copy has been made and is available from OsgiManager) @@ -505,8 +550,10 @@ class OsgiArchiveInstaller { + "installation will likely be corrupted and correct version should be manually installed.", e); } - ((BasicManagedBundle)result.getMetadata()).setPersistenceNeeded(true); - mgmt().getRebindManager().getChangeListener().onChanged(result.getMetadata()); + if (!isBlacklistedForPersistence(result.getMetadata())) { + ((BasicManagedBundle)result.getMetadata()).setPersistenceNeeded(true); + mgmt().getRebindManager().getChangeListener().onChanged(result.getMetadata()); + } } else { if (isBringingExistingOsgiInstalledBundleUnderBrooklynManagement) { log.debug("Uninstalling bundle "+result.getVersionedName()+" from Brooklyn management only (rollback needed but it was already installed to OSGi)"); @@ -514,8 +561,10 @@ class OsgiArchiveInstaller { log.debug("Uninstalling bundle "+result.getVersionedName()+" (roll back of failed fresh install, no previous version to revert to)"); } osgiManager.uninstallUploadedBundle(result.getMetadata(), false, isBringingExistingOsgiInstalledBundleUnderBrooklynManagement); - ((BasicManagedBundle)result.getMetadata()).setPersistenceNeeded(true); - mgmt().getRebindManager().getChangeListener().onUnmanaged(result.getMetadata()); + if (!isBlacklistedForPersistence(result.getMetadata())) { + ((BasicManagedBundle)result.getMetadata()).setPersistenceNeeded(true); + mgmt().getRebindManager().getChangeListener().onUnmanaged(result.getMetadata()); + } } } public void run() { @@ -631,47 +680,75 @@ class OsgiArchiveInstaller { close(); } } - - private static boolean hasBundleOnInstall(OsgiManager osgiManager, ManagedBundle desired, File zipFile) { + + private static boolean isBlacklistedForPersistence(ManagedBundle managedBundle) { + // Specifically, we treat as "managed bundles" (to extract their catalog.bom) the contents of: + // - org.apache.brooklyn.core + // - org.apache.brooklyn.policy + // - org.apache.brooklyn.test-framework + // - org.apache.brooklyn.software-* + // - org.apache.brooklyn.library-catalog + // - org.apache.brooklyn.karaf-init (not sure why this one could end up in persisted state!) + return managedBundle.getSymbolicName().startsWith("org.apache.brooklyn."); + } + + private static List<Bundle> findBundlesByVersion(OsgiManager osgiManager, ManagedBundle desired) { + return Osgis.bundleFinder(osgiManager.framework).symbolicName(desired.getSymbolicName()).version(desired.getOsgiVersionString()).findAll(); + } + + private static boolean checksumsMatch(ManagedBundle actual, ManagedBundle desired) { + return actual.getChecksum() != null && Objects.equal(actual.getChecksum(), desired.getChecksum()); + } + + private static boolean isEquivalentBundleAlreadyOsgiInstalled(OsgiManager osgiManager, ManagedBundle desired, File zipFile) { + for (Bundle bundle : findBundlesByVersion(osgiManager, desired)) { + if (isEquivalentBundle(bundle, desired, zipFile)) { + return true; + } + } + + return false; + } + + private static Maybe<Bundle> tryFindEquivalentBundle(Iterable<? extends Bundle> bundles, ManagedBundle desired, File zipFile) { + for (Bundle bundle : bundles) { + if (isEquivalentBundle(bundle, desired, zipFile)) { + return Maybe.of(bundle); + } + } + + return Maybe.absent(); + } + + private static boolean isEquivalentBundle(Bundle bundle, ManagedBundle desired, File zipFile) { // Would be nice to also use `desired.getChecksum()`, but not clear if we can get // MD5 checksum from an installed OSGi bundle. - Maybe<Bundle> b = Osgis.bundleFinder(osgiManager.framework).symbolicName(desired.getSymbolicName()).version(desired.getOsgiVersionString()).find(); - if (b.isPresent()) { - // bundle already installed to OSGi subsystem (but brooklyn not aware of it as "managed bundle"); - // this will often happen on a karaf restart where bundle was cached by karaf, - // so we need to allow it. can also happen if brooklyn.libraries references an existing bundle. - - // determine if we are simply bringing existing installed under Brooklyn management (because url or binary content identical and not forced) - // or if the bundle should be reinstalled/updated - if (Objects.equal(b.get().getLocation(), desired.getUrl())) { - // installation request was for identical location, so assume we are simply bringing under mgmt - log.debug("Request to install "+desired+" from same location "+b.get().getLocation()+ - " as existing OSGi installed (but not Brooklyn-managed) bundle "+b.get()+", so skipping reinstall"); - return true; - } else { - // different locations, but see if we can compare input stream contents - // (prevents needless uninstall/reinstall of already installed bundles) - try { - if (Streams.compare(new FileInputStream(zipFile), new URL(b.get().getLocation()).openStream())) { - log.debug("Request to install "+desired+" has same contents"+ - " as existing OSGi installed (but not Brooklyn-managed) bundle "+b.get()+", so skipping reinstall"); - return true; - } else { - log.debug("Request to install "+desired+" has different contents"+ - " as existing OSGi installed (but not Brooklyn-managed) bundle "+b.get()+", so will do reinstall"); - return false; - } - } catch (Exception e) { - Exceptions.propagateIfFatal(e); - // probably an invalid URL on installed bundle; that's allowed - log.debug("Request to install "+desired+" could not compare contents"+ - " with existing OSGi installed (but not Brooklyn-managed) bundle "+b.get()+", so will do reinstall (error "+e+" loading from "+b.get().getLocation()+")"); + if (Objects.equal(bundle.getLocation(), desired.getUrl())) { + // installation request was for identical location, so assume we are simply bringing under mgmt + log.debug("Request to install "+desired+" from same location "+bundle.getLocation()+ + " as existing OSGi installed (but not Brooklyn-managed) bundle "+bundle+", so skipping reinstall"); + return true; + } else { + // different locations, but see if we can compare input stream contents + // (prevents needless uninstall/reinstall of already installed bundles) + try { + if (Streams.compare(new FileInputStream(zipFile), new URL(bundle.getLocation()).openStream())) { + log.debug("Request to install "+desired+" has same contents"+ + " as existing OSGi installed (but not Brooklyn-managed) bundle "+bundle+", so skipping reinstall"); + return true; + } else { + log.debug("Request to install "+desired+" has different contents"+ + " as existing OSGi installed (but not Brooklyn-managed) bundle "+bundle+", so will do reinstall (if no other equivalents found)"); return false; } + } catch (Exception e) { + Exceptions.propagateIfFatal(e); + // probably an invalid URL on installed bundle; that's allowed + log.debug("Request to install "+desired+" could not compare contents"+ + " with existing OSGi installed (but not Brooklyn-managed) bundle "+bundle+", so will do reinstall if not other equivalents found (error "+e+" loading from "+bundle.getLocation()+")"); + return false; } - } else { - return false; } } http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/85196899/core/src/main/java/org/apache/brooklyn/core/mgmt/rebind/RebindIteration.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/org/apache/brooklyn/core/mgmt/rebind/RebindIteration.java b/core/src/main/java/org/apache/brooklyn/core/mgmt/rebind/RebindIteration.java index 755107a..8a667eb 100644 --- a/core/src/main/java/org/apache/brooklyn/core/mgmt/rebind/RebindIteration.java +++ b/core/src/main/java/org/apache/brooklyn/core/mgmt/rebind/RebindIteration.java @@ -1233,7 +1233,7 @@ public abstract class RebindIteration { } protected ManagedBundle newManagedBundle(ManagedBundleMemento memento) { - ManagedBundle result = new BasicManagedBundle(memento.getSymbolicName(), memento.getVersion(), memento.getUrl()); + ManagedBundle result = new BasicManagedBundle(memento.getSymbolicName(), memento.getVersion(), memento.getUrl(), memento.getChecksum()); FlagUtils.setFieldsFromFlags(ImmutableMap.of("id", memento.getId()), result); return result; } http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/85196899/core/src/main/java/org/apache/brooklyn/core/mgmt/rebind/dto/BasicManagedBundleMemento.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/org/apache/brooklyn/core/mgmt/rebind/dto/BasicManagedBundleMemento.java b/core/src/main/java/org/apache/brooklyn/core/mgmt/rebind/dto/BasicManagedBundleMemento.java index 505e7c8..37afb58 100644 --- a/core/src/main/java/org/apache/brooklyn/core/mgmt/rebind/dto/BasicManagedBundleMemento.java +++ b/core/src/main/java/org/apache/brooklyn/core/mgmt/rebind/dto/BasicManagedBundleMemento.java @@ -42,7 +42,8 @@ public class BasicManagedBundleMemento extends AbstractMemento implements Manage protected String symbolicName; protected String version; protected String url; - + protected String checksum; + public Builder symbolicName(String symbolicName) { this.symbolicName = symbolicName; return self(); @@ -58,11 +59,17 @@ public class BasicManagedBundleMemento extends AbstractMemento implements Manage return self(); } + public Builder checksum(String checksum) { + this.checksum = checksum; + return self(); + } + public Builder from(ManagedBundleMemento other) { super.from(other); symbolicName = other.getSymbolicName(); version = other.getVersion(); url = other.getUrl(); + checksum = other.getChecksum(); return self(); } @@ -74,6 +81,7 @@ public class BasicManagedBundleMemento extends AbstractMemento implements Manage private String symbolicName; private String version; private String url; + private String checksum; transient private ByteSource jarContent; @SuppressWarnings("unused") // For deserialisation @@ -84,6 +92,7 @@ public class BasicManagedBundleMemento extends AbstractMemento implements Manage this.symbolicName = builder.symbolicName; this.version = builder.version; this.url = builder.url; + this.checksum = builder.checksum; } @Override @@ -102,6 +111,11 @@ public class BasicManagedBundleMemento extends AbstractMemento implements Manage } @Override + public String getChecksum() { + return checksum; + } + + @Override public ByteSource getJarContent() { return jarContent; } @@ -129,7 +143,7 @@ public class BasicManagedBundleMemento extends AbstractMemento implements Manage return super.newVerboseStringHelper() .add("symbolicName", getSymbolicName()) .add("version", getVersion()) - .add("url", getUrl()); + .add("url", getUrl()) + .add("checksum", getChecksum()); } - } http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/85196899/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 8372b99..860bf5a 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 @@ -20,6 +20,8 @@ package org.apache.brooklyn.core.typereg; import java.util.Map; +import javax.annotation.Nullable; + import org.apache.brooklyn.api.catalog.CatalogItem.CatalogBundle; import org.apache.brooklyn.api.mgmt.rebind.RebindSupport; import org.apache.brooklyn.api.typereg.ManagedBundle; @@ -46,7 +48,7 @@ public class BasicManagedBundle extends AbstractBrooklynObject implements Manage /** Creates an empty one, with an ID, expecting other fields will be populated. */ public BasicManagedBundle() {} - public BasicManagedBundle(String name, String version, String url) { + public BasicManagedBundle(String name, String version, String url, @Nullable String checksum) { if (name == null && version == null) { Preconditions.checkNotNull(url, "Either a URL or both name and version are required"); } else { @@ -56,6 +58,7 @@ public class BasicManagedBundle extends AbstractBrooklynObject implements Manage this.symbolicName = name; this.version = version; this.url = url; + this.checksum = checksum; } @Override @@ -101,9 +104,16 @@ public class BasicManagedBundle extends AbstractBrooklynObject implements Manage this.url = url; } + /** + * Gets the (internal) value to be used as the location in bundleContext.install(location). + * It thus allows us to tell if a cached OSGi bundle is the same as a bundle we are about to + * install (e.g. one we get from persisted state), or have retrieved from the initial catalog. + * + * Care should be taken to set the checksum <em>before</em> using the OSGi unique url. + */ @Override public String getOsgiUniqueUrl() { - return "brooklyn:"+getId(); + return "brooklyn:" + (checksum != null ? checksum : getId()); } @Override @@ -175,6 +185,7 @@ public class BasicManagedBundle extends AbstractBrooklynObject implements Manage public String getChecksum() { return checksum; } + public void setChecksum(String md5Checksum) { this.checksum = md5Checksum; } @@ -184,8 +195,9 @@ public class BasicManagedBundle extends AbstractBrooklynObject implements Manage throw new UnsupportedOperationException(); } - public static ManagedBundle of(CatalogBundle bundleUrl) { - return new BasicManagedBundle(bundleUrl.getSymbolicName(), bundleUrl.getSuppliedVersionString(), bundleUrl.getUrl()); + public static ManagedBundle of(CatalogBundle bundle) { + String checksum = (bundle instanceof ManagedBundle) ? ((ManagedBundle)bundle).getChecksum() : null; + return new BasicManagedBundle(bundle.getSymbolicName(), bundle.getSuppliedVersionString(), bundle.getUrl(), checksum); } public void setPersistenceNeeded(boolean val) { http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/85196899/launcher/src/test/java/org/apache/brooklyn/launcher/AbstractBrooklynLauncherRebindTest.java ---------------------------------------------------------------------- diff --git a/launcher/src/test/java/org/apache/brooklyn/launcher/AbstractBrooklynLauncherRebindTest.java b/launcher/src/test/java/org/apache/brooklyn/launcher/AbstractBrooklynLauncherRebindTest.java index 89bcae0..400e3a8 100644 --- a/launcher/src/test/java/org/apache/brooklyn/launcher/AbstractBrooklynLauncherRebindTest.java +++ b/launcher/src/test/java/org/apache/brooklyn/launcher/AbstractBrooklynLauncherRebindTest.java @@ -267,19 +267,32 @@ public abstract class AbstractBrooklynLauncherRebindTest { } protected String createCatalogYaml(Iterable<URI> libraries, Iterable<VersionedName> entities) { - if (Iterables.isEmpty(libraries) && Iterables.isEmpty(entities)) { - return "brooklyn.catalog: {}\n"; - } - + return createCatalogYaml(libraries, ImmutableList.of(), entities, null); + } + + protected String createCatalogYaml(Iterable<URI> libraryUris, Iterable<VersionedName> libraryNames, Iterable<VersionedName> entities) { + return createCatalogYaml(libraryUris, libraryNames, entities, null); + } + + protected String createCatalogYaml(Iterable<URI> libraryUris, Iterable<VersionedName> libraryNames, Iterable<VersionedName> entities, String randomNoise) { StringBuilder result = new StringBuilder(); result.append("brooklyn.catalog:\n"); - if (!Iterables.isEmpty(libraries)) { + if (randomNoise != null) { + result.append(" description: "+randomNoise+"\n"); + } + if (!(Iterables.isEmpty(libraryUris) && Iterables.isEmpty(libraryNames))) { result.append(" brooklyn.libraries:\n"); } - for (URI library : libraries) { + for (URI library : libraryUris) { result.append(" - " + library+"\n"); } - if (!Iterables.isEmpty(entities)) { + for (VersionedName library : libraryNames) { + result.append(" - name: "+library.getSymbolicName()+"\n"); + result.append(" version: \""+library.getVersionString()+"\"\n"); + } + if (Iterables.isEmpty(entities)) { + result.append(" items: []\n"); + } else { result.append(" items:\n"); } for (VersionedName entity : entities) { @@ -292,6 +305,9 @@ public abstract class AbstractBrooklynLauncherRebindTest { return result.toString(); } + + + public PersistedStateInitializer newPersistedStateInitializer() { return new PersistedStateInitializer(); } http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/85196899/launcher/src/test/java/org/apache/brooklyn/launcher/BrooklynLauncherRebindCatalogOsgiTest.java ---------------------------------------------------------------------- diff --git a/launcher/src/test/java/org/apache/brooklyn/launcher/BrooklynLauncherRebindCatalogOsgiTest.java b/launcher/src/test/java/org/apache/brooklyn/launcher/BrooklynLauncherRebindCatalogOsgiTest.java index d4e8c75..8985147 100644 --- a/launcher/src/test/java/org/apache/brooklyn/launcher/BrooklynLauncherRebindCatalogOsgiTest.java +++ b/launcher/src/test/java/org/apache/brooklyn/launcher/BrooklynLauncherRebindCatalogOsgiTest.java @@ -19,30 +19,31 @@ package org.apache.brooklyn.launcher; import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertFalse; +import static org.testng.Assert.assertTrue; import java.io.File; -import java.nio.charset.StandardCharsets; import java.io.FileInputStream; -import java.net.URI; -import java.util.LinkedHashSet; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; import java.util.List; import java.util.Set; -import org.apache.brooklyn.api.typereg.ManagedBundle; -import org.apache.brooklyn.api.typereg.RegisteredType; import org.apache.brooklyn.core.catalog.internal.BasicBrooklynCatalog; import org.apache.brooklyn.core.catalog.internal.CatalogInitialization; +import org.apache.brooklyn.core.mgmt.ha.OsgiBundleInstallationResult; import org.apache.brooklyn.core.mgmt.ha.OsgiManager; import org.apache.brooklyn.core.mgmt.internal.ManagementContextInternal; -import org.apache.brooklyn.entity.stock.BasicEntity; import org.apache.brooklyn.test.support.TestResourceUnavailableException; import org.apache.brooklyn.util.core.osgi.Osgis; +import org.apache.brooklyn.util.exceptions.ReferenceWithError; import org.apache.brooklyn.util.os.Os; import org.apache.brooklyn.util.osgi.OsgiTestResources; import org.apache.brooklyn.util.osgi.VersionedName; +import org.apache.brooklyn.util.text.Identifiers; import org.osgi.framework.Bundle; import org.osgi.framework.launch.Framework; -import org.testng.annotations.BeforeMethod; +import org.testng.annotations.AfterMethod; import org.testng.annotations.Test; import com.google.common.base.Joiner; @@ -62,12 +63,29 @@ public class BrooklynLauncherRebindCatalogOsgiTest extends AbstractBrooklynLaunc private static final Set<VersionedName> COM_EXAMPLE_BUNDLE_CATALOG_IDS = ImmutableSet.of( new VersionedName("com.example.simpleTest", "0.0.0-SNAPSHOT")); + /** + * Whether we reuse OSGi Framework depends if we want it to feel like rebinding on a new machine + * (so no cached bundles), or a local restart. We can also use {@code reuseOsgi = true} to emulate + * system bundles (by pre-installing them into the reused framework at the start of the test). + */ private boolean reuseOsgi = false; - @BeforeMethod(alwaysRun=true) + private List<Bundle> reusedBundles = new ArrayList<>(); + + @AfterMethod(alwaysRun=true) @Override - public void setUp() throws Exception { - super.setUp(); + public void tearDown() throws Exception { + try { + reuseOsgi = false; + for (Bundle bundle : reusedBundles) { + if (bundle != null && bundle.getState() != Bundle.UNINSTALLED) { + bundle.uninstall(); + } + } + reusedBundles.clear(); + } finally { + super.tearDown(); + } } @Override @@ -110,7 +128,7 @@ public class BrooklynLauncherRebindCatalogOsgiTest extends AbstractBrooklynLaunc /** * See https://issues.apache.org/jira/browse/BROOKLYN-546. * - * We built up to launcher3, which will have three things: + * We built up to launcher2, which will have three things: * 1. a pre-installed "system bundle" * 2. an initial catalog that references this same system bundle (hence the bundle will be "managed") * 3. persisted state that references this same system bundle. @@ -130,54 +148,201 @@ public class BrooklynLauncherRebindCatalogOsgiTest extends AbstractBrooklynLaunc @Test public void testRebindWithSystemBundleInCatalog() throws Exception { Set<VersionedName> bundleItems = ImmutableSet.of(VersionedName.fromString("one:1.0.0-SNAPSHOT")); - String bundleBom = createCatalogYaml(ImmutableList.of(), bundleItems); - VersionedName bundleName = new VersionedName("org.example.testRebindWithSystemBundleInCatalog", "1.0.0.SNAPSHOT"); - File bundleFile = newTmpBundle(ImmutableMap.of(BasicBrooklynCatalog.CATALOG_BOM, bundleBom.getBytes(StandardCharsets.UTF_8)), bundleName); + VersionedName bundleName = new VersionedName("org.example.brooklynLauncherRebindCatalogOsgiTest."+Identifiers.makeRandomId(4), "1.0.0.SNAPSHOT"); + File bundleFile = newTmpBundle(bundleItems, bundleName); File bundleFileCopy = newTmpCopy(bundleFile); File initialBomFile = newTmpFile(createCatalogYaml(ImmutableList.of(bundleFile.toURI()), ImmutableList.of())); - Framework reusedFamework = null; - Bundle pseudoSystemBundle = null; - try { - reuseOsgi = true; - - // Launch and terminate brooklyn to ensure there is a reusable OSGi Framework - BrooklynLauncher launcher = newLauncherForTests(CATALOG_EMPTY_INITIAL); - launcher.start(); - launcher.terminate(); - Os.deleteRecursively(persistenceDir); - - // Add our bundle, so it feels for all intents and purposes like a "system bundle" - reusedFamework = OsgiManager.tryPeekFrameworkForReuse().get(); - pseudoSystemBundle = installBundle(reusedFamework, bundleFileCopy); - - // Launch brooklyn, where initial catalog includes a duplicate of the system bundle - BrooklynLauncher launcher2 = newLauncherForTests(initialBomFile.getAbsolutePath()); - launcher2.start(); - assertCatalogConsistsOfIds(launcher2, bundleItems); - assertManagedBundle(launcher2, bundleName, bundleItems); - launcher2.terminate(); - - // Launch brooklyn, where persisted state now includes the initial catalog's bundle - BrooklynLauncher launcher3 = newLauncherForTests(initialBomFile.getAbsolutePath()); - launcher3.start(); - assertCatalogConsistsOfIds(launcher3, bundleItems); - assertManagedBundle(launcher3, bundleName, bundleItems); - - // Should not have replaced the original "system bundle" - reusedFamework.getBundleContext().getBundles(); - List<Bundle> matchingBundles = Osgis.bundleFinder(reusedFamework).symbolicName(bundleName.getSymbolicName()).version(bundleName.getOsgiVersionString()).findAll(); - assertEquals(matchingBundles, ImmutableList.of(pseudoSystemBundle)); - - launcher3.terminate(); + reuseOsgi = true; + + // Add our bundle, so it feels for all intents and purposes like a "system bundle" + Framework reusedFramework = initReusableOsgiFramework(); + Bundle pseudoSystemBundle = installBundle(reusedFramework, bundleFileCopy); + reusedBundles.add(pseudoSystemBundle); + + // Launch brooklyn, where initial catalog includes a duplicate of the system bundle + BrooklynLauncher launcher = newLauncherForTests(initialBomFile.getAbsolutePath()); + launcher.start(); + assertCatalogConsistsOfIds(launcher, bundleItems); + assertManagedBundle(launcher, bundleName, bundleItems); + launcher.terminate(); - } finally { - reuseOsgi = false; - if (pseudoSystemBundle != null && pseudoSystemBundle.getState() != Bundle.UNINSTALLED) { - pseudoSystemBundle.uninstall(); - } + // Launch brooklyn, where persisted state now includes the initial catalog's bundle + BrooklynLauncher launcher2 = newLauncherForTests(initialBomFile.getAbsolutePath()); + launcher2.start(); + assertCatalogConsistsOfIds(launcher2, bundleItems); + assertManagedBundle(launcher2, bundleName, bundleItems); + + // Should not have replaced the original "system bundle" + assertOnlyBundle(reusedFramework, bundleName, pseudoSystemBundle); + + launcher2.terminate(); + } + + @Test + public void testInstallPreexistingBundle() throws Exception { + Set<VersionedName> bundleItems = ImmutableSet.of(VersionedName.fromString("one:1.0.0-SNAPSHOT")); + VersionedName bundleName = new VersionedName("org.example.brooklynLauncherRebindCatalogOsgiTest."+Identifiers.makeRandomId(4), "1.0.0.SNAPSHOT"); + File bundleFile = newTmpBundle(bundleItems, bundleName); + File bundleFileCopy = newTmpCopy(bundleFile); + + reuseOsgi = true; + + // Add our bundle, so it feels for all intents and purposes like a "system bundle" + Framework reusedFramework = initReusableOsgiFramework(); + Bundle pseudoSystemBundle = installBundle(reusedFramework, bundleFileCopy); + reusedBundles.add(pseudoSystemBundle); + + // Launch brooklyn, and explicitly install pre-existing bundle. + // Should bring it under brooklyn-management (should not re-install it). + BrooklynLauncher launcher = newLauncherForTests(CATALOG_EMPTY_INITIAL); + launcher.start(); + installBrooklynBundle(launcher, bundleFile, false).getWithError(); + + assertOnlyBundle(launcher, bundleName, pseudoSystemBundle); + assertCatalogConsistsOfIds(launcher, bundleItems); + assertManagedBundle(launcher, bundleName, bundleItems); + launcher.terminate(); + + // Launch brooklyn again (because will have persisted the pre-installed bundle) + BrooklynLauncher launcher2 = newLauncherForTests(CATALOG_EMPTY_INITIAL); + launcher2.start(); + assertOnlyBundle(reusedFramework, bundleName, pseudoSystemBundle); + assertCatalogConsistsOfIds(launcher2, bundleItems); + assertManagedBundle(launcher2, bundleName, bundleItems); + launcher2.terminate(); + } + + @Test + public void testInstallPreexistingBundleViaIndirectBrooklynLibrariesReference() throws Exception { + Set<VersionedName> bundleItems = ImmutableSet.of(VersionedName.fromString("one:1.0.0-SNAPSHOT")); + VersionedName systemBundleName = new VersionedName("org.example.brooklynLauncherRebindCatalogOsgiTest.system"+Identifiers.makeRandomId(4), "1.0.0.SNAPSHOT"); + File systemBundleFile = newTmpBundle(bundleItems, systemBundleName); + + String bundleBom = createCatalogYaml(ImmutableList.of(), ImmutableList.of(systemBundleName), ImmutableList.of()); + VersionedName bundleName = new VersionedName("org.example.brooklynLauncherRebindCatalogOsgiTest.initial"+Identifiers.makeRandomId(4), "1.0.0.SNAPSHOT"); + File bundleFile = newTmpBundle(ImmutableMap.of(BasicBrooklynCatalog.CATALOG_BOM, bundleBom.getBytes(StandardCharsets.UTF_8)), bundleName); + + File initialBomFile = newTmpFile(createCatalogYaml(ImmutableList.of(bundleFile.toURI()), ImmutableList.of())); + + reuseOsgi = true; + + // Add our bundle, so it feels for all intents and purposes like a "system bundle" + Framework reusedFramework = initReusableOsgiFramework(); + Bundle pseudoSystemBundle = installBundle(reusedFramework, systemBundleFile); + reusedBundles.add(pseudoSystemBundle); + + // Launch brooklyn, with initial catalog pointing at bundle that points at system bundle. + // Should bring it under brooklyn-management (without re-installing it). + BrooklynLauncher launcher = newLauncherForTests(initialBomFile.getAbsolutePath()); + launcher.start(); + assertOnlyBundle(launcher, systemBundleName, pseudoSystemBundle); + assertCatalogConsistsOfIds(launcher, bundleItems); + assertManagedBundle(launcher, systemBundleName, bundleItems); + assertManagedBundle(launcher, bundleName, ImmutableSet.of()); + launcher.terminate(); + + // Launch brooklyn again (because will have persisted both those bundles) + BrooklynLauncher launcher2 = newLauncherForTests(CATALOG_EMPTY_INITIAL); + launcher2.start(); + assertOnlyBundle(launcher2, systemBundleName, pseudoSystemBundle); + assertCatalogConsistsOfIds(launcher2, bundleItems); + assertManagedBundle(launcher2, systemBundleName, bundleItems); + assertManagedBundle(launcher2, bundleName, ImmutableSet.of()); + launcher2.terminate(); + } + + @Test + public void testInstallPreexistingBundleViaInitialBomBrooklynLibrariesReference() throws Exception { + runInstallPreexistingBundleViaInitialBomBrooklynLibrariesReference(false); + } + + // Aled thought we supported version ranges in 'brooklyn.libraries', but doesn't work here. + @Test(groups="Broken") + public void testInstallPreexistingBundleViaInitialBomBrooklynLibrariesReferenceWithVersionRange() throws Exception { + runInstallPreexistingBundleViaInitialBomBrooklynLibrariesReference(true); + } + + protected void runInstallPreexistingBundleViaInitialBomBrooklynLibrariesReference(boolean useVersionRange) throws Exception { + Set<VersionedName> bundleItems = ImmutableSet.of(VersionedName.fromString("one:1.0.0")); + VersionedName systemBundleName = new VersionedName("org.example.brooklynLauncherRebindCatalogOsgiTest.system"+Identifiers.makeRandomId(4), "1.0.0"); + File systemBundleFile = newTmpBundle(bundleItems, systemBundleName); + + VersionedName systemBundleNameRef; + if (useVersionRange) { + systemBundleNameRef = new VersionedName(systemBundleName.getSymbolicName(), "[1,2)"); + } else { + systemBundleNameRef = systemBundleName; } + File initialBomFile = newTmpFile(createCatalogYaml(ImmutableList.of(), ImmutableList.of(systemBundleNameRef), ImmutableList.of())); + + reuseOsgi = true; + + // Add our bundle, so it feels for all intents and purposes like a "system bundle" + Framework reusedFramework = initReusableOsgiFramework(); + Bundle pseudoSystemBundle = installBundle(reusedFramework, systemBundleFile); + reusedBundles.add(pseudoSystemBundle); + + // Launch brooklyn, with initial catalog pointing at system bundle. + // Should bring it under brooklyn-management (without re-installing it). + BrooklynLauncher launcher = newLauncherForTests(initialBomFile.getAbsolutePath()); + launcher.start(); + assertOnlyBundle(launcher, systemBundleName, pseudoSystemBundle); + assertCatalogConsistsOfIds(launcher, bundleItems); + assertManagedBundle(launcher, systemBundleName, bundleItems); + launcher.terminate(); + + // Launch brooklyn again (because will have persisted both those bundles) + BrooklynLauncher launcher2 = newLauncherForTests(CATALOG_EMPTY_INITIAL); + launcher2.start(); + assertOnlyBundle(launcher2, systemBundleName, pseudoSystemBundle); + assertCatalogConsistsOfIds(launcher2, bundleItems); + assertManagedBundle(launcher2, systemBundleName, bundleItems); + launcher2.terminate(); + } + + @Test + public void testInstallReplacesPreexistingBundleWithoutForce() throws Exception { + runInstallReplacesPreexistingBundle(false); + } + + @Test + public void testInstallReplacesPreexistingBundleWithForce() throws Exception { + runInstallReplacesPreexistingBundle(true); + } + + protected void runInstallReplacesPreexistingBundle(boolean force) throws Exception { + Set<VersionedName> bundleItems = ImmutableSet.of(VersionedName.fromString("one:1.0.0-SNAPSHOT")); + VersionedName bundleName = new VersionedName("org.example.brooklynLauncherRebindCatalogOsgiTest."+Identifiers.makeRandomId(4), "1.0.0.SNAPSHOT"); + File systemBundleFile = newTmpBundle(bundleItems, bundleName); + File replacementBundleFile = newTmpBundle(bundleItems, bundleName, "randomDifference"+Identifiers.makeRandomId(4)); + + reuseOsgi = true; + + // Add our bundle, so it feels for all intents and purposes like a "system bundle" + Framework reusedFramework = initReusableOsgiFramework(); + Bundle pseudoSystemBundle = installBundle(reusedFramework, systemBundleFile); + reusedBundles.add(pseudoSystemBundle); + + // Launch brooklyn, and explicitly install pre-existing bundle. + // Should bring it under brooklyn-management (should not re-install it). + BrooklynLauncher launcher = newLauncherForTests(CATALOG_EMPTY_INITIAL); + launcher.start(); + installBrooklynBundle(launcher, replacementBundleFile, force).getWithError(); + + assertOnlyBundleReplaced(launcher, bundleName, pseudoSystemBundle); + assertCatalogConsistsOfIds(launcher, bundleItems); + assertManagedBundle(launcher, bundleName, bundleItems); + launcher.terminate(); + + // Launch brooklyn again (because will have persisted the pre-installed bundle) + BrooklynLauncher launcher2 = newLauncherForTests(CATALOG_EMPTY_INITIAL); + launcher2.start(); + assertOnlyBundleReplaced(reusedFramework, bundleName, pseudoSystemBundle); + assertCatalogConsistsOfIds(launcher2, bundleItems); + assertManagedBundle(launcher2, bundleName, bundleItems); + launcher2.terminate(); } @Test @@ -267,4 +432,54 @@ public class BrooklynLauncherRebindCatalogOsgiTest extends AbstractBrooklynLaunc return framework.getBundleContext().installBundle(bundle.toURI().toString(), stream); } } + + private ReferenceWithError<OsgiBundleInstallationResult> installBrooklynBundle(BrooklynLauncher launcher, File bundleFile, boolean force) throws Exception { + OsgiManager osgiManager = ((ManagementContextInternal)launcher.getManagementContext()).getOsgiManager().get(); + try (FileInputStream bundleStream = new FileInputStream(bundleFile)) { + return osgiManager.install(null, bundleStream, true, true, force); + } + } + + private void assertOnlyBundle(BrooklynLauncher launcher, VersionedName bundleName, Bundle expectedBundle) { + Framework framework = ((ManagementContextInternal)launcher.getManagementContext()).getOsgiManager().get().getFramework(); + assertOnlyBundle(framework, bundleName, expectedBundle); + } + + private void assertOnlyBundleReplaced(BrooklynLauncher launcher, VersionedName bundleName, Bundle expectedBundle) { + Framework framework = ((ManagementContextInternal)launcher.getManagementContext()).getOsgiManager().get().getFramework(); + assertOnlyBundleReplaced(framework, bundleName, expectedBundle); + } + + private void assertOnlyBundle(Framework framework, VersionedName bundleName, Bundle expectedBundle) { + List<Bundle> matchingBundles = Osgis.bundleFinder(framework).symbolicName(bundleName.getSymbolicName()).version(bundleName.getOsgiVersionString()).findAll(); + assertTrue(matchingBundles.contains(expectedBundle), "Bundle missing; matching="+matchingBundles); + assertEquals(matchingBundles.size(), 1, "Extra bundles; matching="+matchingBundles); + } + + private void assertOnlyBundleReplaced(Framework framework, VersionedName bundleName, Bundle expectedBundle) { + List<Bundle> matchingBundles = Osgis.bundleFinder(framework).symbolicName(bundleName.getSymbolicName()).version(bundleName.getOsgiVersionString()).findAll(); + assertFalse(matchingBundles.contains(expectedBundle), "Bundle still present; matching="+matchingBundles); + assertEquals(matchingBundles.size(), 1, "Extra bundles; matching="+matchingBundles); + } + + private Framework initReusableOsgiFramework() { + if (!reuseOsgi) throw new IllegalStateException("Must first set reuseOsgi"); + + if (OsgiManager.tryPeekFrameworkForReuse().isAbsent()) { + BrooklynLauncher launcher = newLauncherForTests(CATALOG_EMPTY_INITIAL); + launcher.start(); + launcher.terminate(); + Os.deleteRecursively(persistenceDir); + } + return OsgiManager.tryPeekFrameworkForReuse().get(); + } + + private File newTmpBundle(Set<VersionedName> catalogItems, VersionedName bundleName) { + return newTmpBundle(catalogItems, bundleName, null); + } + + private File newTmpBundle(Set<VersionedName> catalogItems, VersionedName bundleName, String randomNoise) { + String bundleBom = createCatalogYaml(ImmutableList.of(), ImmutableList.of(), catalogItems, randomNoise); + return newTmpBundle(ImmutableMap.of(BasicBrooklynCatalog.CATALOG_BOM, bundleBom.getBytes(StandardCharsets.UTF_8)), bundleName); + } }
