in core/src/main/java/org/apache/brooklyn/core/mgmt/ha/BrooklynBomOsgiArchiveInstaller.java [482:852]
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;
assert inferredMetadata instanceof BasicManagedBundle : "Only BasicManagedBundles supported";
((BasicManagedBundle)inferredMetadata).setChecksum(getChecksum(new ZipFile(zipFile.getFile())));
((BasicManagedBundle)inferredMetadata).setFormat(format);
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());
if (knownIdForThisUrl==null) {
// it's a new URL, but a bundle we already know about
log.warn("Request to install from "+suppliedKnownBundleMetadata.getUrl()+" which is not recognized but "+
"appears to match "+result.getMetadata()+"; now associating with the latter");
osgiManager.managedBundlesRecord.setManagedBundleUrl(suppliedKnownBundleMetadata.getUrl(), result.getMetadata().getId());
} else if (!knownIdForThisUrl.equals(result.getMetadata().getId())) {
log.warn("Request to install from "+suppliedKnownBundleMetadata.getUrl()+" which is associated to "+knownIdForThisUrl+" but "+
"appears to match "+result.getMetadata()+"; now associating with the latter");
osgiManager.managedBundlesRecord.setManagedBundleUrl(suppliedKnownBundleMetadata.getUrl(), result.getMetadata().getId());
}
}
log.debug("Request to install "+inferredMetadata.getVersionedName()+" (checksum "+inferredMetadata.getChecksum()+", OSGi URL "+inferredMetadata.getOsgiUniqueUrl()+") in the presence of "+result.getMetadata().getVersionedName()+" (checksum "+result.getMetadata().getChecksum()+", OSGi URL "+result.getMetadata().getOsgiUniqueUrl()+")");
result.bundle = osgiManager.getFramework().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);
}
List<Bundle> matchingVsnBundles = findBundlesBySymbolicNameAndVersion(osgiManager, inferredMetadata);
List<Bundle> sameContentBundles = matchingVsnBundles.stream().filter(b -> isBundleSameOsgiUrlOrSameContents(b, inferredMetadata, zipFile.getFile())).collect(Collectors.toList());
if (!sameContentBundles.isEmpty()) {
// 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, the osgi locations might be different,
// but the contents are the same
log.trace("Bundle "+inferredMetadata+" matches metadata of managed bundle "+result.getMetadata()
+" (but not OSGi bundle location "+result.getMetadata().getOsgiUniqueUrl()+"), "
+ "and identified as equivalent to installed OSGi bundle; ; install is no-op");
result.setIgnoringAlreadyInstalled();
result.bundle = sameContentBundles.iterator().next();
return ReferenceWithError.newInstanceWithoutError(result);
}
if (canUpdate()) {
if (result.bundle == null && !matchingVsnBundles.isEmpty()) {
// if we are updating a snapshot bundle or forcing, and somehow we did not manage to preserve the original OSGi location
log.info("Updating existing brooklyn-managed bundle "+result+" with "+inferredMetadata+" with different OSGi location and different contents");
result.bundle = matchingVsnBundles.iterator().next();
}
if (result.getBundle() == null) {
log.warn("Brooklyn thought it 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 (matchingVsnBundles.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 || matchingVsnBundles.size() > 0) {
throw new IllegalArgumentException("Bundle "+result.getMetadata().getVersionedName()+" already installed; "
+ "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'");
}
}
// if proceeding with install, use the new metadata but the old id and osgi url
// (the osgi url must match because we use "getBundle(...)" to update it)
result.metadata = BasicManagedBundle.copyFirstWithCoordsOfSecond(inferredMetadata, result.metadata);
} 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
// First check if this bundle is forcibly removed (and optionally upgraded).
// If so, don't install it - return the replacement, if any.
Maybe<VersionedName> forcedReplacementBundle = CatalogUpgrades.tryGetBundleForcedReplaced(mgmt(), inferredMetadata.getVersionedName());
if (forcedReplacementBundle.isPresent()) {
setResultForciblyRemovedResult(osgiManager, inferredMetadata.getVersionedName(), inferredMetadata, forcedReplacementBundle, result);
return ReferenceWithError.newInstanceWithoutError(result);
}
result.metadata = inferredMetadata;
// search for already-installed bundles.
List<Bundle> existingBundles = findBundlesBySymbolicNameAndVersion(osgiManager, inferredMetadata);
Maybe<Bundle> existingEquivalentBundle = tryFindSameOsgiUrlOrSameContentsBundle(existingBundles, inferredMetadata, zipFile.getFile());
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();
} 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 "+existingBundle);
isBringingExistingOsgiInstalledBundleUnderBrooklynManagement = false;
}
}
if (isBringingExistingOsgiInstalledBundleUnderBrooklynManagement) {
// We were explicitly asked to bring an existing OSGi bundle under management;
// no equivalence check required
result.bundle = existingBundle;
} else {
// 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");
existingBundle.uninstall();
result.bundle = null;
}
}
updating = false;
}
startedInstallation = true;
try (InputStream fin = new FileInputStream(zipFile.getFile())) {
if (!updating) {
if (isBringingExistingOsgiInstalledBundleUnderBrooklynManagement) {
assert result.getBundle()!=null;
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.getFramework().getBundleContext().installBundle(result.getMetadata().getOsgiUniqueUrl(), fin);
}
} else {
result.bundle.update(fin);
}
}
osgiManager.checkCorrectlyInstalled(result.getMetadata(), result.bundle);
final File oldZipFile;
final ManagedBundle oldManagedBundle;
if (!updating) {
oldZipFile = null;
oldManagedBundle = null;
osgiManager.managedBundlesRecord.addManagedBundle(result, zipFile.getFile());
result.code = OsgiBundleInstallationResult.ResultCode.INSTALLED_NEW_BUNDLE;
result.message = "Installed Brooklyn catalog bundle "+result.getMetadata().getVersionedName()+" with ID "+result.getMetadata().getId()+" ["+result.bundle.getBundleId()+"]";
} else {
Pair<File, ManagedBundle> olds = osgiManager.managedBundlesRecord.updateManagedBundleFileAndMetadata(result, zipFile.getFile());
oldZipFile = olds.getLeft();
oldManagedBundle = olds.getRight();
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()+"]";
}
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)
zipFile.deleteIfTemp();
zipFile = null;
// 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
// eg if it doesn't have OSGi deps, or if it doesn't have camp parser,
// or if caller is installing multiple things that depend on each other
// eg rebind code, brooklyn.libraries list -- deferred start allows caller to
// determine whether not to start or to start all after things are installed
Runnable startRunnable = new Runnable() {
private void rollbackBundle() {
if (updating) {
if (oldZipFile==null) {
throw new IllegalStateException("Did not have old ZIP file to install");
}
log.debug("Rolling back bundle "+result.getVersionedName()+" to state "+oldManagedBundle+" from "+oldZipFile);
try {
File zipFileNow = osgiManager.managedBundlesRecord.rollbackManagedBundleFileAndMetadata(result, oldZipFile, oldManagedBundle);
result.bundle.update(new FileInputStream(Preconditions.checkNotNull(zipFileNow, "Couldn't find contents of old version of bundle")));
} catch (Exception e) {
Exceptions.propagateIfFatal(e);
log.error("Error rolling back following failed install of updated "+result.getVersionedName()+"; "
+ "installation will likely be corrupted and correct version should be manually installed.", e);
}
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)");
} else {
log.debug("Uninstalling bundle "+result.getVersionedName()+" (roll back of failed fresh install, no previous version to revert to)");
}
osgiManager.uninstallUploadedBundle(result.getMetadata(), false, isBringingExistingOsgiInstalledBundleUnderBrooklynManagement);
if (!isBlacklistedForPersistence(result.getMetadata())) {
((BasicManagedBundle)result.getMetadata()).setPersistenceNeeded(true);
mgmt().getRebindManager().getChangeListener().onUnmanaged(result.getMetadata());
}
}
}
public void run() {
if (start) {
try {
log.debug("Starting bundle "+result.getVersionedName());
if (!isBlacklistedForPersistence(result.getMetadata())) {
((BasicManagedBundle)result.getMetadata()).setPersistenceNeeded(true);
if (updating) {
mgmt().getRebindManager().getChangeListener().onChanged(result.getMetadata());
} else {
mgmt().getRebindManager().getChangeListener().onManaged(result.getMetadata());
}
}
result.bundle.start();
} catch (BundleException e) {
log.warn("Error starting bundle "+result.getVersionedName()+", uninstalling, restoring any old bundle, then re-throwing error: "+e);
try {
rollbackBundle();
} catch (Throwable t) {
Exceptions.propagateIfFatal(t);
log.warn("Error rolling back "+result.getVersionedName()+" after bundle start problem; server may be in inconsistent state (swallowing this error and propagating installation error): "+Exceptions.collapseText(t), t);
throw Exceptions.propagate(new BundleException("Failure installing and rolling back; server may be in inconsistent state regarding bundle "+result.getVersionedName()+". "
+ "Rollback failure ("+Exceptions.collapseText(t)+") detailed in log. Installation error is: "+Exceptions.collapseText(e), e));
}
throw Exceptions.propagate(e);
}
}
if (loadCatalogBom) {
Iterable<RegisteredType> itemsFromOldBundle = null;
Map<RegisteredType, RegisteredType> itemsReplacedHere = null;
try {
if (updating) {
itemsFromOldBundle = osgiManager.uninstallCatalogItemsFromBundle( result.getVersionedName() );
// (ideally removal and addition would be atomic)
}
itemsReplacedHere = MutableMap.of();
osgiManager.loadBrooklynBundleWithCatalogBom(result.bundle, bomText, force, validateTypes, itemsReplacedHere);
Iterable<RegisteredType> items = mgmt().getTypeRegistry().getMatching(RegisteredTypePredicates.containingBundle(result.getMetadata()));
log.debug("Adding items from bundle "+result.getVersionedName()+": "+items);
for (RegisteredType ci: items) {
result.addType(ci);
}
} catch (Exception e) {
// unable to install new items; rollback bundles
// and reload replaced items
if (CatalogInitialization.isRebindReadOnlyShuttingDown(osgiManager.mgmt)) {
// very likely when RO mode interrupted - ignore
throw Exceptions.propagate(e);
}
log.warn("Error adding Brooklyn items from bundle "+result.getVersionedName()+", uninstalling, restoring any old bundle and items, then re-throwing error: "+Exceptions.collapseText(e));
try {
rollbackBundle();
} catch (Throwable t) {
Exceptions.propagateIfFatal(t);
log.warn("Error rolling back "+result.getVersionedName()+" after catalog install problem; server may be in inconsistent state (swallowing this error and propagating installation error): "+Exceptions.collapseText(t), t);
throw Exceptions.propagate(new BundleException("Failure loading catalog items, and also failed rolling back; server may be in inconsistent state regarding bundle "+result.getVersionedName()+". "
+ "Rollback failure ("+Exceptions.collapseText(t)+") detailed in log. Installation error is: "+Exceptions.collapseText(e), e));
}
if (itemsFromOldBundle!=null) {
// add back all itemsFromOldBundle (when replacing a bundle)
for (RegisteredType oldItem: itemsFromOldBundle) {
if (log.isTraceEnabled()) {
log.trace("RESTORING replaced bundle item "+oldItem+"\n"+ RegisteredTypes.getImplementationDataStringForSpec(oldItem));
}
((BasicBrooklynTypeRegistry)mgmt().getTypeRegistry()).addToLocalUnpersistedTypeRegistry(oldItem, true);
}
}
if (itemsReplacedHere!=null) {
// and restore any items from other bundles (eg wrappers) that were replaced
MutableList<RegisteredType> replaced = MutableList.copyOf(itemsReplacedHere.values());
// in reverse order so if other bundle adds multiple we end up with the real original
Collections.reverse(replaced);
for (RegisteredType oldItem: replaced) {
if (oldItem!=null) {
if (log.isTraceEnabled()) {
log.trace("RESTORING replaced external item "+oldItem+"\n"+RegisteredTypes.getImplementationDataStringForSpec(oldItem));
}
((BasicBrooklynTypeRegistry)mgmt().getTypeRegistry()).addToLocalUnpersistedTypeRegistry(oldItem, true);
}
}
}
throw Exceptions.propagate(e);
}
}
}
};
if (deferredStart) {
result.deferredStart = startRunnable;
log.debug(result.message+" (Brooklyn load deferred)");
} else {
startRunnable.run();
if (!result.typesInstalled.isEmpty()) {
// show fewer info messages, only for 'interesting' and non-deferred installations
// (rebind is deferred, as are tests, but REST is not)
final int MAX_TO_LIST_EXPLICITLY = 5;
Iterable<String> firstN = Iterables.transform(MutableList.copyOf(Iterables.limit(result.typesInstalled, MAX_TO_LIST_EXPLICITLY)),
input -> input.getVersionedName().toString());
log.info(result.message+", items: "+firstN+
(result.typesInstalled.size() > MAX_TO_LIST_EXPLICITLY ? " (and others, "+result.typesInstalled.size()+" total)" : "") );
if (log.isDebugEnabled() && result.typesInstalled.size()>MAX_TO_LIST_EXPLICITLY) {
log.debug(result.message+", all items: "+result.typesInstalled);
}
} else {
log.debug(result.message+" (complete): bundle started and now managed by Brooklyn, though no catalog items found (may have installed other bundles though)");
}
}
return ReferenceWithError.newInstanceWithoutError(result);
} catch (Exception e) {
Exceptions.propagateIfFatal(e);
result.code = startedInstallation ? OsgiBundleInstallationResult.ResultCode.ERROR_LAUNCHING_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();
}
}