You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@brooklyn.apache.org by he...@apache.org on 2017/09/07 15:47:54 UTC

[03/11] brooklyn-server git commit: keep a cache of osgi bundles brooklyn installs, incl 1 bak, and rollback on failure

keep a cache of osgi bundles brooklyn installs, incl 1 bak, and rollback on failure

removes other no-longer-needed temp file

the cache is a temp dir and deleted on brooklyn server close (since we can reload from rebind)


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

Branch: refs/heads/master
Commit: e8e39fd7ef55c833f2b4a735ee591667242e0ab1
Parents: 8127dbb
Author: Alex Heneveld <al...@cloudsoftcorp.com>
Authored: Wed Aug 16 16:40:03 2017 +0100
Committer: Alex Heneveld <al...@cloudsoftcorp.com>
Committed: Thu Aug 17 10:31:23 2017 +0100

----------------------------------------------------------------------
 .../CatalogOsgiVersionMoreEntityRebindTest.java |  17 +--
 .../catalog/internal/BasicBrooklynCatalog.java  |  17 +--
 .../catalog/internal/CatalogBundleLoader.java   |  17 ---
 .../core/mgmt/ha/OsgiArchiveInstaller.java      |  42 +++++--
 .../brooklyn/core/mgmt/ha/OsgiManager.java      | 125 +++++++++++++------
 .../BrooklynMementoPersisterToObjectStore.java  |  20 +--
 .../core/typereg/BasicManagedBundle.java        |  23 ++--
 7 files changed, 155 insertions(+), 106 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/e8e39fd7/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 0bea66c..cb2d2cb 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
@@ -408,12 +408,13 @@ public class CatalogOsgiVersionMoreEntityRebindTest extends AbstractYamlRebindTe
         }
     }
 
-    @Test(groups="Broken")  // AH think not going to support this; see notes in BasicBrooklynCatalog.scanAnnotationsInBundle
-    // it's hard to get the JAR for scanning, and doesn't fit with the OSGi way
-    public void testRebindJavaScanningBundleInCatalog() throws Exception {
-        CatalogScanOsgiTest.installJavaScanningMoreEntitiesV2(mgmt(), this);
-        rebind();
-        RegisteredType item = mgmt().getTypeRegistry().get(OsgiTestResources.BROOKLYN_TEST_MORE_ENTITIES_MORE_ENTITY);
-        Assert.assertNotNull(item, "Scanned item should have been available after rebind");
-    }
+    // could support this now that we have a local cache; but probably not needed; see BasicBrooklynCatalog.scanAnnotationsInBundle
+//    @Test
+//    public void testRebindJavaScanningBundleInCatalog() throws Exception {
+//        CatalogScanOsgiTest.installJavaScanningMoreEntitiesV2(mgmt(), this);
+//        rebind();
+//        RegisteredType item = mgmt().getTypeRegistry().get(OsgiTestResources.BROOKLYN_TEST_MORE_ENTITIES_MORE_ENTITY);
+//        Assert.assertNotNull(item, "Scanned item should have been available after rebind");
+//    }
+    
 }

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/e8e39fd7/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 deaae6f..87725dd 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
@@ -61,7 +61,6 @@ import org.apache.brooklyn.core.mgmt.ha.OsgiManager;
 import org.apache.brooklyn.core.mgmt.internal.CampYamlParser;
 import org.apache.brooklyn.core.mgmt.internal.ManagementContextInternal;
 import org.apache.brooklyn.core.typereg.BasicBrooklynTypeRegistry;
-import org.apache.brooklyn.core.typereg.BasicManagedBundle;
 import org.apache.brooklyn.core.typereg.BasicRegisteredType;
 import org.apache.brooklyn.core.typereg.BasicTypeImplementationPlan;
 import org.apache.brooklyn.core.typereg.BrooklynTypePlanTransformer;
@@ -112,7 +111,7 @@ import com.google.common.collect.Iterables;
 import com.google.common.collect.Maps;
 
 /* TODO the complex tree-structured catalogs are only useful when we are relying on those separate catalog classloaders
- * to isolate classpaths. with osgi everything is just put into the "manual additions" catalog. */
+ * to isolate classpaths. with osgi everything is just put into the "manual additions" catalog. Deprecate/remove this. */
 public class BasicBrooklynCatalog implements BrooklynCatalog {
     public static final String POLICIES_KEY = "brooklyn.policies";
     public static final String ENRICHERS_KEY = "brooklyn.enrichers";
@@ -1058,25 +1057,21 @@ public class BasicBrooklynCatalog implements BrooklynCatalog {
     }
     
     @SuppressWarnings("unused")  // keep during 0.12.0 until we are decided we won't support this; search for this method name
-    // note that it breaks after rebind since we don't have the JAR -- see notes below
+    // (note that this now could work after rebind since we have the OSGi cache)
     private Collection<CatalogItemDtoAbstract<?, ?>> scanAnnotationsInBundle(ManagementContext mgmt, ManagedBundle containingBundle) {
         CatalogDto dto = CatalogDto.newNamedInstance("Bundle "+containingBundle.getVersionedName().toOsgiString()+" Scanned Catalog", "All annotated Brooklyn entities detected in bundles", "scanning-bundle-"+containingBundle.getVersionedName().toOsgiString());
         CatalogDo subCatalog = new CatalogDo(dto);
         // need access to a JAR to scan this
         String url = null;
-        if (containingBundle instanceof BasicManagedBundle) {
-            File f = ((BasicManagedBundle)containingBundle).getTempLocalFileWhenJustUploaded();
-            if (f!=null) {
-                url = "file:"+f.getAbsolutePath();
-            }
+        File f = ((ManagementContextInternal)mgmt).getOsgiManager().get().getBundleFile(containingBundle);
+        if (f!=null) {
+            url = "file:"+f.getAbsolutePath();
         }
-        // type.getSubPathName(), type, id+".jar", com.google.common.io.Files.asByteSource(f), exceptionHandler);
         if (url==null) {
             url = containingBundle.getUrl();
         }
         if (url==null) {
-            // NOT available after persistence/rebind 
-            // as shown by test in CatalogOsgiVersionMoreEntityRebindTest
+            // should now always be available 
             throw new IllegalArgumentException("Error preparing to scan "+containingBundle.getVersionedName()+": no URL available");
         }
         // org.reflections requires the URL to be "file:" containg ".jar"

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/e8e39fd7/core/src/main/java/org/apache/brooklyn/core/catalog/internal/CatalogBundleLoader.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/brooklyn/core/catalog/internal/CatalogBundleLoader.java b/core/src/main/java/org/apache/brooklyn/core/catalog/internal/CatalogBundleLoader.java
index df2f212..f690df0 100644
--- a/core/src/main/java/org/apache/brooklyn/core/catalog/internal/CatalogBundleLoader.java
+++ b/core/src/main/java/org/apache/brooklyn/core/catalog/internal/CatalogBundleLoader.java
@@ -19,13 +19,10 @@
 
 package org.apache.brooklyn.core.catalog.internal;
 
-import static org.apache.brooklyn.api.catalog.CatalogItem.CatalogItemType.TEMPLATE;
-
 import java.io.IOException;
 import java.io.InputStream;
 import java.net.URL;
 import java.util.Collection;
-import java.util.List;
 import java.util.Map;
 import java.util.Set;
 
@@ -157,18 +154,4 @@ public class CatalogBundleLoader {
         }
     }
 
-    private Iterable<? extends CatalogItem<?, ?>> removeApplications(Iterable<? extends CatalogItem<?, ?>> catalogItems) {
-
-        List<CatalogItem<?, ?>> result = MutableList.of();
-
-        for (CatalogItem<?, ?> item : catalogItems) {
-            if (TEMPLATE.equals(item.getCatalogItemType())) {
-                removeFromCatalog(item);
-            } else {
-                result.add(item);
-            }
-        }
-        return result;
-    }
-
 }

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/e8e39fd7/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 0b018b2..d294ad5 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
@@ -39,6 +39,7 @@ import org.apache.brooklyn.core.mgmt.internal.ManagementContextInternal;
 import org.apache.brooklyn.core.typereg.BasicBrooklynTypeRegistry;
 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.util.collections.MutableList;
 import org.apache.brooklyn.util.collections.MutableMap;
 import org.apache.brooklyn.util.core.ResourceUtils;
@@ -354,21 +355,26 @@ class OsgiArchiveInstaller {
             }
             
             osgiManager.checkCorrectlyInstalled(result.getMetadata(), result.bundle);
-            File oldZipIfSet = ((BasicManagedBundle)result.getMetadata()).getTempLocalFileWhenJustUploaded();
-            ((BasicManagedBundle)result.getMetadata()).setTempLocalFileWhenJustUploaded(zipFile);
-            zipFile = null; // don't close/delete it here, we'll use it for uploading, then it will delete it
+            final File oldZipFile; 
             
             if (!updating) { 
-                osgiManager.managedBundlesRecord.addManagedBundle(result);
+                oldZipFile = null;
+                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());
             } 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());
             }
             log.debug(result.message + " (in osgi container)");
+            // can now delete and close (copy has been made and is available from OsgiManager)
+            zipFile.delete();
+            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;
@@ -384,22 +390,26 @@ class OsgiArchiveInstaller {
             Runnable startRunnable = new Runnable() {
                 private void rollbackBundle() {
                     if (updating) {
-                        if (oldZipIfSet!=null) {
-                            ((BasicManagedBundle)result.getMetadata()).setTempLocalFileWhenJustUploaded(oldZipIfSet);
-                        } else {
-                            // TODO look in persistence
+                        if (oldZipFile==null) {
+                            throw new IllegalStateException("Did not have old ZIP file to install");
                         }
-                        log.debug("Rolling back bundle "+result.getVersionedName()+" to state from "+oldZipIfSet);
+                        log.debug("Rolling back bundle "+result.getVersionedName()+" to state from "+oldZipFile);
+                        osgiManager.managedBundlesRecord.updateManagedBundleFile(result, oldZipFile);
                         try {
-                            result.bundle.update(new FileInputStream(Preconditions.checkNotNull(oldZipIfSet, "Couldn't find contents of old version of bundle")));
+                            result.bundle.update(new FileInputStream(Preconditions.checkNotNull(oldZipFile, "Couldn't find contents of old version of bundle")));
                         } catch (Exception 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);
                         }
                         
+                        ((BasicManagedBundle)result.getMetadata()).setPersistenceNeeded(true);
+                        mgmt().getRebindManager().getChangeListener().onChanged(result.getMetadata());
                     } else {
-                        log.debug("Uninstalling bundle "+result.getVersionedName()+" (rolling back, but no previous version)");
+                        log.debug("Uninstalling bundle "+result.getVersionedName()+" (roll back of failed fresh install, no previous version to revert to)");
                         osgiManager.uninstallUploadedBundle(result.getMetadata());
+                        
+                        ((BasicManagedBundle)result.getMetadata()).setPersistenceNeeded(true);
+                        mgmt().getRebindManager().getChangeListener().onUnmanaged(result.getMetadata());
                     }
                 }
                 public void run() {
@@ -438,6 +448,9 @@ class OsgiArchiveInstaller {
                             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);
                                 }
                             }
@@ -447,7 +460,12 @@ class OsgiArchiveInstaller {
                                 // in reverse order so if other bundle adds multiple we end up with the real original
                                 Collections.reverse(replaced);
                                 for (RegisteredType oldItem: replaced) {
-                                    ((BasicBrooklynTypeRegistry)mgmt().getTypeRegistry()).addToLocalUnpersistedTypeRegistry(oldItem, true);
+                                    if (oldItem!=null) {
+                                        if (log.isTraceEnabled()) {
+                                            log.trace("RESTORING replaced external item "+oldItem+"\n"+RegisteredTypes.getImplementationDataStringForSpec(oldItem));
+                                        }
+                                        ((BasicBrooklynTypeRegistry)mgmt().getTypeRegistry()).addToLocalUnpersistedTypeRegistry(oldItem, true);
+                                    }
                                 }
                             }
                             

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/e8e39fd7/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 18a2b33..111dfcc 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,6 +19,9 @@
 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.net.URL;
 import java.util.Arrays;
@@ -61,6 +64,7 @@ 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;
@@ -99,14 +103,17 @@ public class OsgiManager {
     
     private boolean reuseFramework;
     private Set<Bundle> bundlesAtStartup;
-    private File osgiCacheDir;
+    /** Used by us to store bundle ZIPs; can be deleted between server runs, repopulated by rebind. */
+    private File brooklynBundlesCacheDir;
+    /** Given to OSGi container for use as its framework cache */
+    private File osgiFrameworkCacheDir;
     final ManagedBundlesRecord managedBundlesRecord = new ManagedBundlesRecord();
-    final Map<VersionedName,ManagedBundle> wrapperBundles = MutableMap.of();
     
-    static class ManagedBundlesRecord {
-        private Map<String, ManagedBundle> managedBundlesByUid = MutableMap.of();
-        private Map<VersionedName, String> managedBundlesUidByVersionedName = MutableMap.of();
-        private Map<String, String> managedBundlesUidByUrl = MutableMap.of();
+    class ManagedBundlesRecord {
+        private final Map<String, ManagedBundle> managedBundlesByUid = MutableMap.of();
+        private final Map<VersionedName, String> managedBundlesUidByVersionedName = MutableMap.of();
+        private final Map<String, String> managedBundlesUidByUrl = MutableMap.of();
+        private final Map<VersionedName,ManagedBundle> wrapperBundles = MutableMap.of();
         
         synchronized Map<String, ManagedBundle> getManagedBundles() {
             return ImmutableMap.copyOf(managedBundlesByUid);
@@ -134,7 +141,8 @@ public class OsgiManager {
             managedBundlesUidByUrl.put(url, id);    
         }
         
-        synchronized void addManagedBundle(OsgiBundleInstallationResult result) {
+        synchronized void addManagedBundle(OsgiBundleInstallationResult result, File f) {
+            updateManagedBundleFile(result, f);
             managedBundlesByUid.put(result.getMetadata().getId(), result.getMetadata());
             managedBundlesUidByVersionedName.put(VersionedName.toOsgiVersionedName(result.getMetadata().getVersionedName()), 
                 result.getMetadata().getId());
@@ -142,6 +150,55 @@ public class OsgiManager {
                 managedBundlesUidByUrl.put(result.getMetadata().getUrl(), result.getMetadata().getId());
             }
         }
+
+        private File fileFor(ManagedBundle managedBundle) {
+            return new File(brooklynBundlesCacheDir, managedBundle.getId()+"-"+managedBundle.getVersionedName().toOsgiString()+".jar");
+        }
+        
+        synchronized void addInstalledWrapperBundle(ManagedBundle mb) {
+            wrapperBundles.put(mb.getVersionedName(), mb);
+        }
+        private synchronized void removeInstalledWrapperBundle(ManagedBundle mb) {
+            wrapperBundles.remove(mb.getVersionedName());
+        }
+
+        synchronized boolean remove(ManagedBundle bundleMetadata) {
+            ManagedBundle metadata = managedBundlesRecord.managedBundlesByUid.remove(bundleMetadata.getId());
+            if (metadata==null) {
+                return false;
+            }
+            managedBundlesRecord.managedBundlesUidByVersionedName.remove(bundleMetadata.getVersionedName());
+            managedBundlesRecord.managedBundlesUidByUrl.remove(bundleMetadata.getUrl());
+            removeInstalledWrapperBundle(bundleMetadata);
+            fileFor(bundleMetadata).delete();
+            return true;
+        }
+
+        /** Updates the bundle file associated with the given record, creating and returning a backup if there was already such a file */ 
+        synchronized File updateManagedBundleFile(OsgiBundleInstallationResult result, File fNew) {
+            File fCached = fileFor(result.getMetadata());
+            File fBak = null;
+            if (fCached.exists()) {
+                fBak = new File(fCached.getAbsolutePath()+".bak");
+                if (fBak.equals(fNew)) {
+                    // rolling back
+                    log.debug("Rolling back to back Brooklyn local copy of bundle file "+fCached);
+                    fCached.delete();
+                    fBak.renameTo(fCached);
+                    return null;
+                }
+                log.debug("Replacing and backing up old Brooklyn local copy of bundle file "+fCached);
+                fCached.renameTo(fBak);
+            } else {
+                log.debug("Creating Brooklyn local copy of bundle file "+fCached);
+            }
+            try (FileInputStream fin = new FileInputStream(fNew); FileOutputStream fout = new FileOutputStream(fCached)) {
+                Streams.copy(fin, fout);
+            } catch (IOException e) {
+                throw Exceptions.propagate(e);
+            }
+            return fBak;
+        }
     }
     
     private static AtomicInteger numberOfReusableFrameworksCreated = new AtomicInteger();
@@ -158,6 +215,9 @@ public class OsgiManager {
         }
         
         try {
+            brooklynBundlesCacheDir = Os.newTempDir("brooklyn-osgi-brooklyn-bundles-cache");
+            Os.deleteOnExitRecursively(brooklynBundlesCacheDir);
+            
             if (mgmt.getConfig().getConfig(REUSE_OSGI)) {
                 reuseFramework = true;
                 
@@ -177,19 +237,19 @@ public class OsgiManager {
                     
                     return;
                 }
-                osgiCacheDir = Os.newTempDir("brooklyn-osgi-reusable-container");
-                Os.deleteOnExitRecursively(osgiCacheDir);
+                osgiFrameworkCacheDir = Os.newTempDir("brooklyn-osgi-reusable-container");
+                Os.deleteOnExitRecursively(osgiFrameworkCacheDir);
                 if (numberOfReusableFrameworksCreated.incrementAndGet()%10==0) {
                     log.warn("Possible leak of reusable OSGi containers ("+numberOfReusableFrameworksCreated+" total)");
                 }
                 
             } else {
-                osgiCacheDir = BrooklynServerPaths.getOsgiCacheDirCleanedIfNeeded(mgmt);
+                osgiFrameworkCacheDir = BrooklynServerPaths.getOsgiCacheDirCleanedIfNeeded(mgmt);
             }
             
             // any extra OSGi startup args could go here
-            framework = Osgis.getFramework(osgiCacheDir.getAbsolutePath(), false);
-            log.debug("OSGi framework container created in "+osgiCacheDir+" mgmt node "+mgmt.getManagementNodeId()+
+            framework = Osgis.getFramework(osgiFrameworkCacheDir.getAbsolutePath(), false);
+            log.debug("OSGi framework container created in "+osgiFrameworkCacheDir+" mgmt node "+mgmt.getManagementNodeId()+
                 (reuseFramework ? "(reusable, "+numberOfReusableFrameworksCreated.get()+" total)" : "") );
             
         } catch (Exception e) {
@@ -227,7 +287,7 @@ public class OsgiManager {
                 OSGI_FRAMEWORK_CONTAINERS_FOR_REUSE.add(framework);
             }
             
-        } else if (BrooklynServerPaths.isOsgiCacheForCleaning(mgmt, osgiCacheDir)) {
+        } else if (BrooklynServerPaths.isOsgiCacheForCleaning(mgmt, osgiFrameworkCacheDir)) {
             // See exception reported in https://issues.apache.org/jira/browse/BROOKLYN-72
             // We almost always fail to delete he OSGi temp directory due to a concurrent modification.
             // Therefore keep trying.
@@ -236,18 +296,21 @@ public class OsgiManager {
                     .until(new Callable<Boolean>() {
                         @Override
                         public Boolean call() {
-                            deletionResult.set(Os.deleteRecursively(osgiCacheDir));
+                            deletionResult.set(Os.deleteRecursively(osgiFrameworkCacheDir));
                             return deletionResult.get().wasSuccessful();
                         }})
                     .limitTimeTo(Duration.ONE_SECOND)
                     .backoffTo(Duration.millis(50))
                     .run();
             if (deletionResult.get().getThrowable()!=null) {
-                log.debug("Unable to delete "+osgiCacheDir+" (possibly being modified concurrently?): "+deletionResult.get().getThrowable());
+                log.debug("Unable to delete "+osgiFrameworkCacheDir+" (possibly being modified concurrently?): "+deletionResult.get().getThrowable());
             }
         }
-        osgiCacheDir = null;
+        osgiFrameworkCacheDir = null;
         framework = null;
+        
+        Os.deleteRecursively(brooklynBundlesCacheDir);
+        brooklynBundlesCacheDir = null;
     }
 
     /** Map of bundles by UID */
@@ -310,18 +373,13 @@ public class OsgiManager {
      * Callers should typically fail if anything from this bundle is in use.
      */
     public void uninstallUploadedBundle(ManagedBundle bundleMetadata) {
-        synchronized (managedBundlesRecord) {
-            ManagedBundle metadata = managedBundlesRecord.managedBundlesByUid.remove(bundleMetadata.getId());
-            if (metadata==null) {
-                throw new IllegalStateException("No such bundle registered: "+bundleMetadata);
-            }
-            managedBundlesRecord.managedBundlesUidByVersionedName.remove(bundleMetadata.getVersionedName());
-            managedBundlesRecord.managedBundlesUidByUrl.remove(bundleMetadata.getUrl());
-            removeInstalledWrapperBundle(bundleMetadata);
+        uninstallCatalogItemsFromBundle( bundleMetadata.getVersionedName() );
+        
+        if (!managedBundlesRecord.remove(bundleMetadata)) {
+            throw new IllegalStateException("No such bundle registered: "+bundleMetadata);
         }
         mgmt.getRebindManager().getChangeListener().onUnmanaged(bundleMetadata);
 
-        uninstallCatalogItemsFromBundle( bundleMetadata.getVersionedName() );
         
         Bundle bundle = framework.getBundleContext().getBundle(bundleMetadata.getOsgiUniqueUrl());
         if (bundle==null) {
@@ -590,19 +648,16 @@ public class OsgiManager {
 
     // track wrapper bundles lifecvcle specially, to avoid removing it while it's installing
     public void addInstalledWrapperBundle(ManagedBundle mb) {
-        synchronized (wrapperBundles) {
-            wrapperBundles.put(mb.getVersionedName(), mb);
-        }
-    }
-    public void removeInstalledWrapperBundle(ManagedBundle mb) {
-        synchronized (wrapperBundles) {
-            wrapperBundles.remove(mb.getVersionedName());
-        }
+        managedBundlesRecord.addInstalledWrapperBundle(mb);
     }
     public Collection<ManagedBundle> getInstalledWrapperBundles() {
-        synchronized (wrapperBundles) {
-            return MutableSet.copyOf(wrapperBundles.values());
+        synchronized (managedBundlesRecord) {
+            return MutableSet.copyOf(managedBundlesRecord.wrapperBundles.values());
         }
     }
 
+    public File getBundleFile(ManagedBundle mb) {
+        return managedBundlesRecord.fileFor(mb);
+    }
+
 }

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/e8e39fd7/core/src/main/java/org/apache/brooklyn/core/mgmt/persist/BrooklynMementoPersisterToObjectStore.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/brooklyn/core/mgmt/persist/BrooklynMementoPersisterToObjectStore.java b/core/src/main/java/org/apache/brooklyn/core/mgmt/persist/BrooklynMementoPersisterToObjectStore.java
index e8bb5fc..07abf6b 100644
--- a/core/src/main/java/org/apache/brooklyn/core/mgmt/persist/BrooklynMementoPersisterToObjectStore.java
+++ b/core/src/main/java/org/apache/brooklyn/core/mgmt/persist/BrooklynMementoPersisterToObjectStore.java
@@ -20,7 +20,6 @@ package org.apache.brooklyn.core.mgmt.persist;
 
 import static com.google.common.base.Preconditions.checkNotNull;
 
-import java.io.File;
 import java.io.IOException;
 import java.util.LinkedHashMap;
 import java.util.List;
@@ -76,6 +75,7 @@ import org.apache.brooklyn.util.time.Duration;
 import org.apache.brooklyn.util.time.Time;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
+import org.w3c.dom.NodeList;
 
 import com.google.common.annotations.Beta;
 import com.google.common.base.Objects;
@@ -87,7 +87,6 @@ import com.google.common.util.concurrent.Futures;
 import com.google.common.util.concurrent.ListenableFuture;
 import com.google.common.util.concurrent.ListeningExecutorService;
 import com.google.common.util.concurrent.MoreExecutors;
-import org.w3c.dom.NodeList;
 
 /** Implementation of the {@link BrooklynMementoPersister} backed by a pluggable
  * {@link PersistenceObjectStore} such as a file system or a jclouds object store */
@@ -338,8 +337,11 @@ public class BrooklynMementoPersisterToObjectStore implements BrooklynMementoPer
                     LOG.warn("ID mismatch on "+type.toCamelCase()+", "+id+" from path, "+safeXmlId+" from xml");
                 
                 if (type == BrooklynObjectType.MANAGED_BUNDLE) {
-                    // TODO write to temp file, destroy when loaded
+                    // TODO could R/W to cache space directly, rather than memory copy then extra file copy
                     byte[] jarData = readBytes(contentsSubpath+".jar");
+                    if (jarData==null) {
+                        throw new IllegalStateException("No bundle data for "+contentsSubpath);
+                    }
                     builder.bundleJar(id, ByteSource.wrap(jarData));
                 }
                 builder.put(type, xmlId, contents);
@@ -713,19 +715,17 @@ public class BrooklynMementoPersisterToObjectStore implements BrooklynMementoPer
             }
             
             if (mb instanceof BasicManagedBundle) {
-                final File f = ((BasicManagedBundle)mb).getTempLocalFileWhenJustUploaded();
-                // use the above transient field to know when to upload
-                if (f!=null) {
+                if (((BasicManagedBundle)mb).getPersistenceNeeded()) {
                     futures.add( executor.submit(new Runnable() {
                         @Override
                         public void run() {
-                            if (((BasicManagedBundle)mb).getTempLocalFileWhenJustUploaded()==null) {
+                            if (!((BasicManagedBundle)mb).getPersistenceNeeded()) {
                                 // someone else persisted this (race)
                                 return;
                             }
-                            persist(type.getSubPathName(), type, id+".jar", com.google.common.io.Files.asByteSource(f), exceptionHandler);
-                            ((BasicManagedBundle)mb).setTempLocalFileWhenJustUploaded(null);
-                            f.delete();
+                            persist(type.getSubPathName(), type, id+".jar", com.google.common.io.Files.asByteSource(
+                                ((ManagementContextInternal)mgmt).getOsgiManager().get().getBundleFile(mb)), exceptionHandler);
+                            ((BasicManagedBundle)mb).setPersistenceNeeded(false);
                         } }) );
                 }
             }

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/e8e39fd7/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 19c1699..253f3a6 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
@@ -18,7 +18,6 @@
  */
 package org.apache.brooklyn.core.typereg;
 
-import java.io.File;
 import java.util.Map;
 
 import org.apache.brooklyn.api.catalog.CatalogItem.CatalogBundle;
@@ -32,7 +31,6 @@ import org.apache.brooklyn.core.objs.BrooklynObjectInternal;
 import org.apache.brooklyn.util.osgi.VersionedName;
 import org.apache.brooklyn.util.text.BrooklynVersionSyntax;
 
-import com.google.common.annotations.Beta;
 import com.google.common.base.MoreObjects;
 import com.google.common.base.Objects;
 import com.google.common.base.Preconditions;
@@ -43,7 +41,7 @@ public class BasicManagedBundle extends AbstractBrooklynObject implements Manage
     private String version;
     private String checksum;
     private String url;
-    private transient File localFileWhenJustUploaded;
+    private transient boolean persistenceNeeded = false;
 
     /** Creates an empty one, with an ID, expecting other fields will be populated. */
     public BasicManagedBundle() {}
@@ -103,16 +101,6 @@ public class BasicManagedBundle extends AbstractBrooklynObject implements Manage
         this.url = url;
     }
 
-    /** This is cached on the object when just uploaded, then deleted after it has been persisted. */
-    @Beta
-    public void setTempLocalFileWhenJustUploaded(File localFileWhenJustUploaded) {
-        this.localFileWhenJustUploaded = localFileWhenJustUploaded;
-    }
-    @Beta
-    public File getTempLocalFileWhenJustUploaded() {
-        return localFileWhenJustUploaded;
-    }
-    
     @Override
     public String getOsgiUniqueUrl() {
         return "brooklyn:"+getId();
@@ -204,4 +192,13 @@ public class BasicManagedBundle extends AbstractBrooklynObject implements Manage
     public static ManagedBundle of(CatalogBundle bundleUrl) {
         return new BasicManagedBundle(bundleUrl.getSymbolicName(), bundleUrl.getSuppliedVersionString(), bundleUrl.getUrl());
     }
+
+    public void setPersistenceNeeded(boolean val) {
+        persistenceNeeded |= val;
+    }
+    public boolean getPersistenceNeeded() {
+        return persistenceNeeded;
+        
+    }
+    
 }