You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@brooklyn.apache.org by sv...@apache.org on 2017/03/16 09:26:03 UTC

[1/4] brooklyn-server git commit: Fix classRenames for SshPollConfig anonymous classes

Repository: brooklyn-server
Updated Branches:
  refs/heads/master 37c37477b -> b95932a7c


Fix classRenames for SshPollConfig anonymous classes

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

Branch: refs/heads/master
Commit: ad73fcf99676b4c06e30ac6f1ed1093225087f05
Parents: 37c3747
Author: Aled Sage <al...@gmail.com>
Authored: Thu Mar 16 00:04:03 2017 +0000
Committer: Aled Sage <al...@gmail.com>
Committed: Thu Mar 16 00:17:46 2017 +0000

----------------------------------------------------------------------
 .../apache/brooklyn/feed/CommandPollConfig.java | 16 +++++++-
 .../apache/brooklyn/feed/ssh/SshPollConfig.java | 43 ++++++++++++++++++++
 .../deserializingClassRenames.properties        |  4 +-
 3 files changed, 59 insertions(+), 4 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/ad73fcf9/core/src/main/java/org/apache/brooklyn/feed/CommandPollConfig.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/brooklyn/feed/CommandPollConfig.java b/core/src/main/java/org/apache/brooklyn/feed/CommandPollConfig.java
index f8c5b81..f3d8cc4 100644
--- a/core/src/main/java/org/apache/brooklyn/feed/CommandPollConfig.java
+++ b/core/src/main/java/org/apache/brooklyn/feed/CommandPollConfig.java
@@ -41,14 +41,26 @@ import org.apache.brooklyn.util.collections.MutableMap;
 public class CommandPollConfig<T> extends PollConfig<SshPollValue, T, CommandPollConfig<T>> {
 
     private Supplier<String> commandSupplier;
-    private List<Supplier<Map<String,String>>> dynamicEnvironmentSupplier = MutableList.of();
+    protected List<Supplier<Map<String,String>>> dynamicEnvironmentSupplier = MutableList.of();
 
-    public static final Predicate<SshPollValue> DEFAULT_SUCCESS = new Predicate<SshPollValue>() {
+    // TODO Kept in case it's persisted; new code will not use this.
+    // Can't just use deserializingClassRenames.properties, because persistence format
+    // for a static versus anonymous (non-static) inner class is different.
+    private static final Predicate<SshPollValue> unused_DEFAULT_SUCCESS = new Predicate<SshPollValue>() {
         @Override
         public boolean apply(@Nullable SshPollValue input) {
             return input != null && input.getExitStatus() == 0;
         }};
 
+    public static final Predicate<SshPollValue> DEFAULT_SUCCESS = new DefaultSuccessPredicate();
+
+    private static class DefaultSuccessPredicate implements Predicate<SshPollValue> {
+        @Override
+        public boolean apply(@Nullable SshPollValue input) {
+            return input != null && input.getExitStatus() == 0;
+        }
+    }
+    
     public static <T> CommandPollConfig<T> forSensor(AttributeSensor<T> sensor) {
         return new CommandPollConfig<T>(sensor);
     }

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/ad73fcf9/core/src/main/java/org/apache/brooklyn/feed/ssh/SshPollConfig.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/brooklyn/feed/ssh/SshPollConfig.java b/core/src/main/java/org/apache/brooklyn/feed/ssh/SshPollConfig.java
index 859d30f..d59a04f 100644
--- a/core/src/main/java/org/apache/brooklyn/feed/ssh/SshPollConfig.java
+++ b/core/src/main/java/org/apache/brooklyn/feed/ssh/SshPollConfig.java
@@ -18,8 +18,16 @@
  */
 package org.apache.brooklyn.feed.ssh;
 
+import java.util.Map;
+
+import javax.annotation.Nullable;
+
 import org.apache.brooklyn.api.sensor.AttributeSensor;
 import org.apache.brooklyn.feed.CommandPollConfig;
+import org.apache.brooklyn.util.collections.MutableMap;
+
+import com.google.common.base.Predicate;
+import com.google.common.base.Supplier;
 
 /**
  * @deprecated since 0.11.0. Please use {@link CommandPollConfig}.
@@ -38,4 +46,39 @@ public class SshPollConfig<T> extends CommandPollConfig<T> {
     public SshPollConfig<T> self() {
         return this;
     }
+    
+    // TODO Kept in case it's persisted; new code will not use this.
+    @SuppressWarnings("unused")
+    private static final Predicate<SshPollValue> unused_DEFAULT_SUCCESS = new Predicate<SshPollValue>() {
+        @Override
+        public boolean apply(@Nullable SshPollValue input) {
+            return input != null && input.getExitStatus() == 0;
+        }};
+        
+    // TODO Kept in case it's persisted; new code will not use this.
+    // Can't just use deserializingClassRenames.properties, because persistence format
+    // for a static versus anonymous (non-static) inner class is different. 
+    @SuppressWarnings("unused")
+    private void unused_getEnvSupplier() {
+        new Supplier<Map<String,String>>() {
+            @Override
+            public Map<String, String> get() {
+                Map<String,String> result = MutableMap.of();
+                for (Supplier<Map<String, String>> envS: dynamicEnvironmentSupplier) {
+                    if (envS!=null) {
+                        Map<String, String> envM = envS.get();
+                        if (envM!=null) {
+                            mergeEnvMaps(envM, result);
+                        }
+                    }
+                }
+                return result;
+            }
+            private void mergeEnvMaps(Map<String,String> supplied, Map<String,String> target) {
+                if (supplied==null) return;
+                // as the value is a string there is no need to look at deep merge behaviour
+                target.putAll(supplied);
+            }
+        };
+    }
 }

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/ad73fcf9/core/src/main/resources/org/apache/brooklyn/core/mgmt/persist/deserializingClassRenames.properties
----------------------------------------------------------------------
diff --git a/core/src/main/resources/org/apache/brooklyn/core/mgmt/persist/deserializingClassRenames.properties b/core/src/main/resources/org/apache/brooklyn/core/mgmt/persist/deserializingClassRenames.properties
index 0e664cf..992d9e5 100644
--- a/core/src/main/resources/org/apache/brooklyn/core/mgmt/persist/deserializingClassRenames.properties
+++ b/core/src/main/resources/org/apache/brooklyn/core/mgmt/persist/deserializingClassRenames.properties
@@ -1452,5 +1452,5 @@ brooklyn.networking.vclouddirector.NatPredicates
 brooklyn.networking.vclouddirector.NatService                                    : brooklyn.networking.vclouddirector.nat.NatService
 brooklyn.networking.vclouddirector.PortForwardingConfig                          : brooklyn.networking.vclouddirector.nat.PortForwardingConfig
 
-org.apache.brooklyn.feed.ssh.SshFeed$SshPollIdentifier : org.apache.brooklyn.feed.AbstractCommandFeed$CommandPollIdentifier
-org.apache.brooklyn.feed.ssh.SshPollConfig$2 : org.apache.brooklyn.feed.CommandPollConfig$CombiningEnvSupplier
\ No newline at end of file
+org.apache.brooklyn.feed.ssh.SshFeed$SshPollIdentifier                           : org.apache.brooklyn.feed.AbstractCommandFeed$CommandPollIdentifier
+org.apache.brooklyn.feed.ssh.SshPollConfig$CombiningEnvSupplier                  : org.apache.brooklyn.feed.CommandPollConfig$CombiningEnvSupplier


[4/4] brooklyn-server git commit: Closes #598

Posted by sv...@apache.org.
Closes #598

BROOKLYN-453: Fix rebind for classrename with bundle prefix

The historic persisted state added here for testing comes from a clocker.io blueprint.

The "Fix classRenames for SshPollConfig anonymous classes" is me playing it safe, to re-add the anonymous inner classes that might be referenced by customers out there. As the comment in the code says, we can't just use `deserializingClassRenames.properties`, because persistence format for a static versus anonymous (non-static) inner class is different.

For the "Fix xstream deserialising osgi class renames", I discussed this with @neykov first. We both agreed that we don't particularly like the fix, but that at least it's localised and it fixes the problem (which is causing rebind to older versions of Brooklyn to fail). We'd like to revisit this, and see about pushing the calls to the class-renames into `ClassLoaderUtils` perhaps. But that's a separate discussion, not for this PR.


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

Branch: refs/heads/master
Commit: b95932a7c2d7e278d0de2847c3ca7ecc22f0809a
Parents: 37c3747 7ea207f
Author: Svetoslav Neykov <sv...@cloudsoftcorp.com>
Authored: Thu Mar 16 11:25:57 2017 +0200
Committer: Svetoslav Neykov <sv...@cloudsoftcorp.com>
Committed: Thu Mar 16 11:25:57 2017 +0200

----------------------------------------------------------------------
 .../core/mgmt/persist/OsgiClassPrefixer.java    |   2 +-
 .../apache/brooklyn/feed/CommandPollConfig.java |  16 +-
 .../apache/brooklyn/feed/ssh/SshPollConfig.java |  43 +++
 .../util/core/xstream/ClassRenamingMapper.java  | 144 +++++++++-
 .../util/core/xstream/XmlSerializer.java        |   8 +-
 .../deserializingClassRenames.properties        |   4 +-
 .../mgmt/persist/XmlMementoSerializerTest.java  |  99 ++++++-
 .../mgmt/rebind/RebindHistoricSshFeedTest.java  |  95 +++++++
 .../ssh-feed-no-bundle-prefixes-zv7t8bim62      | 268 +++++++++++++++++++
 .../core/mgmt/rebind/ssh-feed-zv7t8bim62        | 268 +++++++++++++++++++
 10 files changed, 936 insertions(+), 11 deletions(-)
----------------------------------------------------------------------



[3/4] brooklyn-server git commit: BROOKLYN-453: Fix xstream deserialising osgi class renames

Posted by sv...@apache.org.
BROOKLYN-453: Fix xstream deserialising osgi class renames

i.e. where the class name in the persisted state includes the
bundle symbolic name as a prefix.


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

Branch: refs/heads/master
Commit: 7ea207f8942370440e16daccc374d93c70bfaa0f
Parents: bcaf76b
Author: Aled Sage <al...@gmail.com>
Authored: Wed Mar 15 19:20:43 2017 +0000
Committer: Aled Sage <al...@gmail.com>
Committed: Thu Mar 16 01:04:42 2017 +0000

----------------------------------------------------------------------
 .../core/mgmt/persist/OsgiClassPrefixer.java    |   2 +-
 .../util/core/xstream/ClassRenamingMapper.java  | 144 ++++++++++++++++++-
 .../util/core/xstream/XmlSerializer.java        |   8 +-
 3 files changed, 148 insertions(+), 6 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/7ea207f8/core/src/main/java/org/apache/brooklyn/core/mgmt/persist/OsgiClassPrefixer.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/brooklyn/core/mgmt/persist/OsgiClassPrefixer.java b/core/src/main/java/org/apache/brooklyn/core/mgmt/persist/OsgiClassPrefixer.java
index 4c4a348..90b7ee6 100644
--- a/core/src/main/java/org/apache/brooklyn/core/mgmt/persist/OsgiClassPrefixer.java
+++ b/core/src/main/java/org/apache/brooklyn/core/mgmt/persist/OsgiClassPrefixer.java
@@ -41,7 +41,7 @@ import com.google.common.base.Optional;
 @Beta
 public class OsgiClassPrefixer {
 
-    private static final String DELIMITER = ":";
+    public static final String DELIMITER = ":";
     
     private final ClassLoaderUtils whiteListRetriever;
     private final Function<Class<?>, Optional<Bundle>> bundleRetriever;

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/7ea207f8/core/src/main/java/org/apache/brooklyn/util/core/xstream/ClassRenamingMapper.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/brooklyn/util/core/xstream/ClassRenamingMapper.java b/core/src/main/java/org/apache/brooklyn/util/core/xstream/ClassRenamingMapper.java
index a30fdfb..f9f68c7 100644
--- a/core/src/main/java/org/apache/brooklyn/util/core/xstream/ClassRenamingMapper.java
+++ b/core/src/main/java/org/apache/brooklyn/util/core/xstream/ClassRenamingMapper.java
@@ -22,32 +22,168 @@ import static com.google.common.base.Preconditions.checkNotNull;
 
 import java.util.Map;
 
+import org.apache.brooklyn.core.mgmt.persist.OsgiClassPrefixer;
 import org.apache.brooklyn.util.guava.Maybe;
 import org.apache.brooklyn.util.javalang.Reflections;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import com.google.common.base.Supplier;
+import com.thoughtworks.xstream.mapper.CannotResolveClassException;
 import com.thoughtworks.xstream.mapper.Mapper;
 import com.thoughtworks.xstream.mapper.MapperWrapper;
 
+/**
+ * An xstream mapper that handles class-renames, so we can rebind to historic persisted state.
+ */
 public class ClassRenamingMapper extends MapperWrapper {
+    
+    /*
+     * TODO There is a strange relationship between this and XmlMementoSerializer$OsgiClassnameMapper.
+     * Should these be perhaps merged?
+     * 
+     * TODO For class-loading on deserialzation, should we push the class-rename logic into 
+     * org.apache.brooklyn.util.core.ClassLoaderUtils instead? Does the xstream mapper do
+     * anything else important, beyond that class-loading responsibility? It's registration
+     * in XmlSerializer makes it look a bit scary: wrapMapperForAllLowLevelMentions().
+     * 
+     * ---
+     * TODO This code feels overly complicated, and deserves a cleanup.
+     * 
+     * The aim is to handle two use-cases in the deserializingClassRenames.properties:
+     * 
+     *  1. A very explicit rename that includes bundle prefixes (e.g. so as to limit scope, or to support 
+     *     moving a class from one bundle to another).
+     *  
+     *  2. Just the class-rename (e.g. `com.acme.Foo: com.acme.Bar`).
+     *     This would rename "acme-bundle:com.acme.Foo" to "acme-bundle:com.acme.Bar".
+     * 
+     * However, to achieve that is fiddly for several reasons:
+     * 
+     *  1. We might be passed qualified or unqualified names (e.g. "com.acme.Foo" or "acme-bundle:com.acme.Foo"),
+     *     depending how old the persisted state is, where OSGi was used previously, and whether 
+     *     whitelabelled bundles were used. 
+     * 
+     *  2. Calling `super.realClass(name)` must return a class that has exactly the same name as 
+     *     was passed in. This is because xstream will subsequently use `Class.forName` which is 
+     *     fussy about that. However, if we're passed "acme-bundle:com.acme.Foo" then we'd expect
+     *     to return a class named "com.acme.Foo". The final classloading in our 
+     *     `XmlMementoSerializer$OsgiClassLoader.findClass()` will handle stripping out the bundle
+     *     name, and using the right bundle.
+     *     
+     *     In the case where we haven't changed the name, then we can leave it up to 
+     *     `XmlMementoSerializer$OsgiClassnameMapper.realClass()` to do sort this out. But if we've 
+     *     done a rename, then unforutnately it's currently this class' responsibility!
+     *     
+     *     That means it has to fallback to calling classLoader.loadClass().
+     *  
+     *  3. As mentioned under the use-cases, the rename could include the full bundle name prefix, 
+     *     or it might just be the classname. We want to handle both, so need to implement yet
+     *     more fallback behaviour.
+     * 
+     * ---
+     * TODO Wanted to pass xstream, rather than Supplier<ClassLoader>, in constructor. However, 
+     * this caused NPE because of how this is constructed from inside 
+     * XmlMementoSerializer.wrapMapperForNormalUsage, called from within an anonymous subclass of XStream!
+     */
+    
     public static final Logger LOG = LoggerFactory.getLogger(ClassRenamingMapper.class);
     
     private final Map<String, String> nameToType;
-
-    public ClassRenamingMapper(Mapper wrapped, Map<String, String> nameToType) {
+    private final Supplier<? extends ClassLoader> classLoaderSupplier;
+    
+    public ClassRenamingMapper(Mapper wrapped, Map<String, String> nameToType, Supplier<? extends ClassLoader> classLoaderSupplier) {
         super(wrapped);
         this.nameToType = checkNotNull(nameToType, "nameToType");
+        this.classLoaderSupplier = checkNotNull(classLoaderSupplier, "classLoaderSupplier");
     }
     
     @Override
     public Class<?> realClass(String elementName) {
+        String elementNamOrig = elementName;
         Maybe<String> elementNameOpt = Reflections.findMappedNameMaybe(nameToType, elementName);
         if (elementNameOpt.isPresent()) {
             LOG.debug("Mapping class '"+elementName+"' to '"+elementNameOpt.get()+"'");
             elementName = elementNameOpt.get();
         }
-        return super.realClass(elementName);
-    }
 
+        CannotResolveClassException tothrow;
+        try {
+            return super.realClass(elementName);
+        } catch (CannotResolveClassException e) {
+            LOG.trace("Failed to load class using super.realClass({}), for orig class {}, attempting fallbacks: {}", new Object[] {elementName, elementNamOrig, e});
+            tothrow = e;
+        }
+        
+        // We didn't do any renaming; just throw the exception. Our responsibilities are done.
+        // See XmlMementoSerializer.OsgiClassnameMapper.
+        
+        if (elementNameOpt.isPresent() && hasBundlePrefix(elementName)) {
+            // We've renamed the class, so can't rely on XmlMementoSerializer$OsgiClassnameMapper.
+            // Workaround for xstream using `Class.forName`, and therefore not liking us stripping
+            // the bundle prefix.
+            try {
+                return classLoaderSupplier.get().loadClass(elementName);
+            } catch (ClassNotFoundException e) {
+                LOG.trace("Fallback loadClass({}) attempt failed (orig class {}): {}", new Object[] {elementName, elementNamOrig, e});
+            }
+        }
+
+        if (hasBundlePrefix(elementNamOrig)) {
+            PrefixAndClass prefixAndClass = splitBundlePrefix(elementNamOrig);
+            Maybe<String> classNameOpt = Reflections.findMappedNameMaybe(nameToType, prefixAndClass.clazz);
+            
+            if (classNameOpt.isPresent()) {
+                if (hasBundlePrefix(classNameOpt.get())) {
+                    // It has been renamed to include a (potentially different!) bundle prefix; use that
+                    elementName = classNameOpt.get();
+                } else {
+                    elementName = joinBundlePrefix(prefixAndClass.prefix, classNameOpt.get());
+                }
+                LOG.debug("Mapping class '"+elementNamOrig+"' to '"+elementName+"'");
+
+                try {
+                    return super.realClass(elementName);
+                } catch (CannotResolveClassException e) {
+                    LOG.trace("Fallback super.realClass({}) attempt failed (orig class {}): {}", new Object[] {elementName, elementNamOrig, e});
+                }
+                
+                // As above, we'll fallback to loadClass because xstream's use of Class.forName doesn't like
+                // the bundle prefix stuff.
+                try {
+                    return classLoaderSupplier.get().loadClass(elementName);
+                } catch (ClassNotFoundException e) {
+                    LOG.trace("Fallback loadClass({}) attempt failed (orig class {}): {}", new Object[] {elementName, elementNamOrig, e});
+                }
+            }
+        }
+        
+        throw tothrow;
+    }
+    
+    private boolean hasBundlePrefix(String type) {
+        return type != null && type.contains(":");
+    }
+    
+    private PrefixAndClass splitBundlePrefix(String type) {
+        int index = type.lastIndexOf(OsgiClassPrefixer.DELIMITER);
+        if (index <= 0) throw new IllegalStateException("'"+type+"' is not in a valid bundle:class format");
+        String prefix = type.substring(0, index);
+        String clazz = type.substring(index + 1);
+        return new PrefixAndClass(prefix, clazz);
+    }
+    
+    private String joinBundlePrefix(String prefix, String clazz) {
+        return checkNotNull(prefix, "prefix") + OsgiClassPrefixer.DELIMITER + checkNotNull(clazz, "clazz");
+    }
+    
+    private static class PrefixAndClass {
+        private final String prefix;
+        private final String clazz;
+        
+        public PrefixAndClass(String prefix, String clazz) {
+            this.prefix = checkNotNull(prefix, "prefix");
+            this.clazz = checkNotNull(clazz, "clazz");
+        }
+    }
 }

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/7ea207f8/core/src/main/java/org/apache/brooklyn/util/core/xstream/XmlSerializer.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/brooklyn/util/core/xstream/XmlSerializer.java b/core/src/main/java/org/apache/brooklyn/util/core/xstream/XmlSerializer.java
index 0ef8943..db8e9c6 100644
--- a/core/src/main/java/org/apache/brooklyn/util/core/xstream/XmlSerializer.java
+++ b/core/src/main/java/org/apache/brooklyn/util/core/xstream/XmlSerializer.java
@@ -31,6 +31,7 @@ import org.apache.brooklyn.util.collections.MutableList;
 import org.apache.brooklyn.util.collections.MutableMap;
 import org.apache.brooklyn.util.collections.MutableSet;
 
+import com.google.common.base.Supplier;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
 import com.thoughtworks.xstream.XStream;
@@ -109,7 +110,12 @@ public class XmlSerializer<T> {
      * See {@link #newCustomJavaClassConverter()}. */
     protected MapperWrapper wrapMapperForAllLowLevelMentions(Mapper next) {
         MapperWrapper result = new CompilerIndependentOuterClassFieldMapper(next);
-        return new ClassRenamingMapper(result, deserializingClassRenames);
+        Supplier<ClassLoader> classLoaderSupplier = new Supplier<ClassLoader>() {
+            @Override public ClassLoader get() {
+                return xstream.getClassLoaderReference().getReference();
+            }
+        };
+        return new ClassRenamingMapper(result, deserializingClassRenames, classLoaderSupplier);
     }
     /** Extension point where sub-classes can add mappers wanted when instances of a class are serialized, 
      * including {@link #wrapMapperForAllLowLevelMentions(Mapper)}, plus any usual domain mappings. */


[2/4] brooklyn-server git commit: Testing rebind of SshFeed (historic state)

Posted by sv...@apache.org.
Testing rebind of SshFeed (historic state)


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

Branch: refs/heads/master
Commit: bcaf76bc8e5825c765d6511541ca5edb3372d6b0
Parents: ad73fcf
Author: Aled Sage <al...@gmail.com>
Authored: Wed Mar 15 19:24:24 2017 +0000
Committer: Aled Sage <al...@gmail.com>
Committed: Thu Mar 16 00:17:54 2017 +0000

----------------------------------------------------------------------
 .../mgmt/persist/XmlMementoSerializerTest.java  |  99 ++++++-
 .../mgmt/rebind/RebindHistoricSshFeedTest.java  |  95 +++++++
 .../ssh-feed-no-bundle-prefixes-zv7t8bim62      | 268 +++++++++++++++++++
 .../core/mgmt/rebind/ssh-feed-zv7t8bim62        | 268 +++++++++++++++++++
 4 files changed, 729 insertions(+), 1 deletion(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/bcaf76bc/core/src/test/java/org/apache/brooklyn/core/mgmt/persist/XmlMementoSerializerTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/org/apache/brooklyn/core/mgmt/persist/XmlMementoSerializerTest.java b/core/src/test/java/org/apache/brooklyn/core/mgmt/persist/XmlMementoSerializerTest.java
index a2ffb16..ed07b07 100644
--- a/core/src/test/java/org/apache/brooklyn/core/mgmt/persist/XmlMementoSerializerTest.java
+++ b/core/src/test/java/org/apache/brooklyn/core/mgmt/persist/XmlMementoSerializerTest.java
@@ -445,7 +445,104 @@ public class XmlMementoSerializerTest {
             }
         }
     }
-    
+
+    // A sanity-check, to confirm that normal loading works (before we look at success/failure of rename tests)
+    @Test
+    public void testNoRenameOsgiClass() throws Exception {
+        String bundlePath = OsgiTestResources.BROOKLYN_TEST_OSGI_ENTITIES_COM_EXAMPLE_PATH;
+        String bundleUrl = "classpath:" + bundlePath;
+        String classname = OsgiTestResources.BROOKLYN_TEST_OSGI_ENTITIES_COM_EXAMPLE_OBJECT;
+        TestResourceUnavailableException.throwIfResourceUnavailable(getClass(), bundlePath);
+        
+        mgmt = LocalManagementContextForTests.builder(true).disableOsgi(false).build();
+        Bundle bundle = installBundle(mgmt, bundleUrl);
+
+        String bundlePrefix = bundle.getSymbolicName();
+        Class<?> osgiObjectClazz = bundle.loadClass(classname);
+        Object obj = Reflections.invokeConstructorFromArgs(osgiObjectClazz, "myval").get();
+
+        serializer = new XmlMementoSerializer<Object>(mgmt.getCatalogClassLoader(),
+                ImmutableMap.<String,String>of());
+        
+        serializer.setLookupContext(new LookupContextImpl(mgmt,
+                ImmutableList.<Entity>of(), ImmutableList.<Location>of(), ImmutableList.<Policy>of(),
+                ImmutableList.<Enricher>of(), ImmutableList.<Feed>of(), ImmutableList.<CatalogItem<?,?>>of(), true));
+
+        // i.e. prepended with bundle name
+        String serializedForm = Joiner.on("\n").join(
+                "<"+bundlePrefix+":"+classname+">",
+                "  <val>myval</val>",
+                "</"+bundlePrefix+":"+classname+">");
+
+        runRenamed(serializedForm, obj, ImmutableMap.<String, String>of());
+    }
+
+    @Test
+    public void testRenamedOsgiClassMovedBundle() throws Exception {
+        String bundlePath = OsgiTestResources.BROOKLYN_TEST_OSGI_ENTITIES_COM_EXAMPLE_PATH;
+        String bundleUrl = "classpath:" + bundlePath;
+        String classname = OsgiTestResources.BROOKLYN_TEST_OSGI_ENTITIES_COM_EXAMPLE_OBJECT;
+        TestResourceUnavailableException.throwIfResourceUnavailable(getClass(), bundlePath);
+        
+        mgmt = LocalManagementContextForTests.builder(true).disableOsgi(false).build();
+        Bundle bundle = installBundle(mgmt, bundleUrl);
+        
+        String oldBundlePrefix = "com.old.symbolicname";
+        
+        String bundlePrefix = bundle.getSymbolicName();
+        Class<?> osgiObjectClazz = bundle.loadClass(classname);
+        Object obj = Reflections.invokeConstructorFromArgs(osgiObjectClazz, "myval").get();
+
+        serializer = new XmlMementoSerializer<Object>(mgmt.getCatalogClassLoader(),
+                ImmutableMap.of(oldBundlePrefix + ":" + classname, bundlePrefix + ":" + classname));
+        
+        serializer.setLookupContext(new LookupContextImpl(mgmt,
+                ImmutableList.<Entity>of(), ImmutableList.<Location>of(), ImmutableList.<Policy>of(),
+                ImmutableList.<Enricher>of(), ImmutableList.<Feed>of(), ImmutableList.<CatalogItem<?,?>>of(), true));
+
+        // i.e. prepended with bundle name
+        String serializedForm = Joiner.on("\n").join(
+                "<"+bundlePrefix+":"+classname+">",
+                "  <val>myval</val>",
+                "</"+bundlePrefix+":"+classname+">");
+
+        runRenamed(serializedForm, obj, ImmutableMap.<String, String>of(
+                bundlePrefix + ":" + classname, oldBundlePrefix + ":" + classname));
+    }
+
+    @Test
+    public void testRenamedOsgiClassWithoutBundlePrefixInRename() throws Exception {
+        String bundlePath = OsgiTestResources.BROOKLYN_TEST_OSGI_ENTITIES_COM_EXAMPLE_PATH;
+        String bundleUrl = "classpath:" + bundlePath;
+        String classname = OsgiTestResources.BROOKLYN_TEST_OSGI_ENTITIES_COM_EXAMPLE_OBJECT;
+        String oldClassname = "com.old.package.name.OldClassName";
+        TestResourceUnavailableException.throwIfResourceUnavailable(getClass(), bundlePath);
+        
+        mgmt = LocalManagementContextForTests.builder(true).disableOsgi(false).build();
+        Bundle bundle = installBundle(mgmt, bundleUrl);
+        
+        String bundlePrefix = bundle.getSymbolicName();
+        
+        Class<?> osgiObjectClazz = bundle.loadClass(classname);
+        Object obj = Reflections.invokeConstructorFromArgs(osgiObjectClazz, "myval").get();
+
+        serializer = new XmlMementoSerializer<Object>(mgmt.getCatalogClassLoader(),
+                ImmutableMap.of(oldClassname, classname));
+        
+        serializer.setLookupContext(new LookupContextImpl(mgmt,
+                ImmutableList.<Entity>of(), ImmutableList.<Location>of(), ImmutableList.<Policy>of(),
+                ImmutableList.<Enricher>of(), ImmutableList.<Feed>of(), ImmutableList.<CatalogItem<?,?>>of(), true));
+
+        // i.e. prepended with bundle name
+        String serializedForm = Joiner.on("\n").join(
+                "<"+bundlePrefix+":"+classname+">",
+                "  <val>myval</val>",
+                "</"+bundlePrefix+":"+classname+">");
+
+        runRenamed(serializedForm, obj, ImmutableMap.<String, String>of(
+                bundlePrefix + ":" + classname, bundlePrefix + ":" + oldClassname));
+    }
+
     // TODO This doesn't get the bundleName - should we expect it to? Is this because of 
     // how we're using Felix? Would it also be true in Karaf?
     @Test(groups="Broken")

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/bcaf76bc/core/src/test/java/org/apache/brooklyn/core/mgmt/rebind/RebindHistoricSshFeedTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/org/apache/brooklyn/core/mgmt/rebind/RebindHistoricSshFeedTest.java b/core/src/test/java/org/apache/brooklyn/core/mgmt/rebind/RebindHistoricSshFeedTest.java
new file mode 100644
index 0000000..548660c
--- /dev/null
+++ b/core/src/test/java/org/apache/brooklyn/core/mgmt/rebind/RebindHistoricSshFeedTest.java
@@ -0,0 +1,95 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.brooklyn.core.mgmt.rebind;
+
+import java.io.File;
+
+import org.apache.brooklyn.api.mgmt.rebind.RebindExceptionHandler;
+import org.apache.brooklyn.api.mgmt.rebind.RebindManager.RebindFailureMode;
+import org.apache.brooklyn.api.objs.BrooklynObjectType;
+import org.apache.brooklyn.core.test.entity.TestApplication;
+import org.apache.brooklyn.util.os.Os;
+import org.apache.brooklyn.util.stream.Streams;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import com.google.common.io.Files;
+
+public class RebindHistoricSshFeedTest extends RebindTestFixtureWithApp {
+
+    @SuppressWarnings("unused")
+    private static final Logger log = LoggerFactory.getLogger(RebindHistoricSshFeedTest.class);
+
+    @Override
+    @BeforeMethod(alwaysRun=true)
+    public void setUp() throws Exception {
+        super.setUp();
+    }
+        
+    // The persisted state contains renamed classes, such as:
+    //   org.apache.brooklyn.feed.ssh.SshFeed$SshPollIdentifier : org.apache.brooklyn.feed.AbstractCommandFeed$CommandPollIdentifier
+    //
+    // These classnames include the bundle prefix (e.g. "org.apache.brooklyn.core:").
+    // Prior to 2017-01-20 (commit dfd4315565c6767ccb16979c8d098717ed2a4853), classes in whitelisted
+    // bundles would not include this prefix.
+    @Test
+    public void testSshFeed_2017_01() throws Exception {
+        addMemento(BrooklynObjectType.FEED, "ssh-feed", "zv7t8bim62");
+        rebind();
+    }
+    
+    // This test is similar to testSshFeed_2017_01, except the persisted state file has been 
+    // hand-crafted to remove the bundle prefixes for "org.apache.brooklyn.*" bundles.
+    @Test
+    public void testFoo_2017_01_withoutBundlePrefixes() throws Exception {
+        addMemento(BrooklynObjectType.FEED, "ssh-feed-no-bundle-prefixes", "zv7t8bim62");
+        rebind();
+    }
+    
+    @Override
+    protected TestApplication rebind() throws Exception {
+        RebindExceptionHandler exceptionHandler = RebindExceptionHandlerImpl.builder()
+                .danglingRefFailureMode(RebindFailureMode.FAIL_AT_END)
+                .rebindFailureMode(RebindFailureMode.FAIL_AT_END)
+                .addConfigFailureMode(RebindFailureMode.FAIL_AT_END)
+                .addPolicyFailureMode(RebindFailureMode.FAIL_AT_END)
+                .loadPolicyFailureMode(RebindFailureMode.FAIL_AT_END)
+                .build();
+        return super.rebind(RebindOptions.create().exceptionHandler(exceptionHandler));
+    }
+    
+    protected void addMemento(BrooklynObjectType type, String label, String id) throws Exception {
+        String mementoFilename = label+"-"+id;
+        String memento = Streams.readFullyString(getClass().getResourceAsStream(mementoFilename));
+        
+        File persistedFile = getPersistanceFile(type, id);
+        Files.write(memento.getBytes(), persistedFile);
+    }
+    
+    protected File getPersistanceFile(BrooklynObjectType type, String id) {
+        String dir;
+        switch (type) {
+            case FEED: dir = "feeds"; break;
+            default: throw new UnsupportedOperationException("type="+type);
+        }
+        return new File(mementoDir, Os.mergePaths(dir, id));
+    }
+}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/bcaf76bc/core/src/test/resources/org/apache/brooklyn/core/mgmt/rebind/ssh-feed-no-bundle-prefixes-zv7t8bim62
----------------------------------------------------------------------
diff --git a/core/src/test/resources/org/apache/brooklyn/core/mgmt/rebind/ssh-feed-no-bundle-prefixes-zv7t8bim62 b/core/src/test/resources/org/apache/brooklyn/core/mgmt/rebind/ssh-feed-no-bundle-prefixes-zv7t8bim62
new file mode 100644
index 0000000..75fda08
--- /dev/null
+++ b/core/src/test/resources/org/apache/brooklyn/core/mgmt/rebind/ssh-feed-no-bundle-prefixes-zv7t8bim62
@@ -0,0 +1,268 @@
+<!--
+Licensed to the Apache Software Foundation (ASF) under one
+or more contributor license agreements.  See the NOTICE file
+distributed with this work for additional information
+regarding copyright ownership.  The ASF licenses this file
+to you under the Apache License, Version 2.0 (the
+"License"); you may not use this file except in compliance
+with the License.  You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing,
+software distributed under the License is distributed on an
+"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+KIND, either express or implied.  See the License for the
+specific language governing permissions and limitations
+under the License.
+-->
+
+<feed>
+  <brooklynVersion>0.11.0-20170126.1332</brooklynVersion>
+  <type>org.apache.brooklyn.feed.ssh.SshFeed</type>
+  <id>zv7t8bim62</id>
+  <displayName>org.apache.brooklyn.feed.ssh.SshFeed</displayName>
+  <tags>
+    <string>SshFeed[ssh[cat /proc/uptime-&gt;machine.uptime], ssh[free | grep Mem:-&gt;machine.../52e0b96f</string>
+  </tags>
+  <uniqueTag>SshFeed[ssh[cat /proc/uptime-&gt;machine.uptime], ssh[free | grep Mem:-&gt;machine.../52e0b96f</uniqueTag>
+  <config>
+    <feed.onlyIfServiceUp type="boolean">false</feed.onlyIfServiceUp>
+    <machine>
+      <null/>
+    </machine>
+    <execAsCommand type="boolean">false</execAsCommand>
+    <polls>
+      <com.google.guava:com.google.common.collect.HashMultimap serialization="custom">
+        <unserializable-parents/>
+        <com.google.guava:com.google.common.collect.HashMultimap>
+          <default/>
+          <int>2</int>
+          <int>4</int>
+          <org.apache.brooklyn.feed.ssh.SshFeed_-SshPollIdentifier>
+            <command class="com.google.guava:com.google.common.base.Suppliers$SupplierOfInstance">
+              <instance class="string">uptime</instance>
+            </command>
+            <env class="org.apache.brooklyn.feed.ssh.SshPollConfig$CombiningEnvSupplier">
+              <dynamicEnvironmentSupplier class="MutableList" serialization="custom">
+                <unserializable-parents/>
+                <list>
+                  <default>
+                    <size>0</size>
+                  </default>
+                  <int>0</int>
+                </list>
+              </dynamicEnvironmentSupplier>
+            </env>
+          </org.apache.brooklyn.feed.ssh.SshFeed_-SshPollIdentifier>
+          <int>1</int>
+          <org.apache.brooklyn.feed.ssh.SshPollConfig>
+            <sensor class="attributeSensor">
+              <type>java.lang.Double</type>
+              <name>machine.loadAverage</name>
+              <description>Current load average</description>
+              <persistence>REQUIRED</persistence>
+            </sensor>
+            <onsuccess class="com.google.guava:com.google.common.base.Functions$ConstantFunction"/>
+            <onfailure class="com.google.guava:com.google.common.base.Functions$ConstantFunction"/>
+            <onexception class="com.google.guava:com.google.common.base.Functions$ConstantFunction" reference="../onfailure"/>
+            <checkSuccess class="org.apache.brooklyn.feed.ssh.SshPollConfig$1"/>
+            <suppressDuplicates>false</suppressDuplicates>
+            <enabled>true</enabled>
+            <period>30000</period>
+            <commandSupplier class="com.google.guava:com.google.common.base.Suppliers$SupplierOfInstance" reference="../../org.apache.brooklyn.feed.ssh.SshFeed_-SshPollIdentifier/command"/>
+            <dynamicEnvironmentSupplier class="MutableList" serialization="custom">
+              <unserializable-parents/>
+              <list>
+                <default>
+                  <size>0</size>
+                </default>
+                <int>0</int>
+              </list>
+            </dynamicEnvironmentSupplier>
+          </org.apache.brooklyn.feed.ssh.SshPollConfig>
+          <org.apache.brooklyn.feed.ssh.SshFeed_-SshPollIdentifier>
+            <command class="com.google.guava:com.google.common.base.Suppliers$SupplierOfInstance">
+              <instance class="string">ps -A -o pcpu</instance>
+            </command>
+            <env class="org.apache.brooklyn.feed.ssh.SshPollConfig$CombiningEnvSupplier">
+              <dynamicEnvironmentSupplier class="MutableList" serialization="custom">
+                <unserializable-parents/>
+                <list>
+                  <default>
+                    <size>0</size>
+                  </default>
+                  <int>0</int>
+                </list>
+              </dynamicEnvironmentSupplier>
+            </env>
+          </org.apache.brooklyn.feed.ssh.SshFeed_-SshPollIdentifier>
+          <int>1</int>
+          <org.apache.brooklyn.feed.ssh.SshPollConfig>
+            <sensor class="attributeSensor">
+              <type>java.lang.Double</type>
+              <name>machine.cpu</name>
+              <description>Current CPU usage</description>
+              <persistence>REQUIRED</persistence>
+            </sensor>
+            <onsuccess class="com.google.guava:com.google.common.base.Functions$ConstantFunction"/>
+            <onfailure class="com.google.guava:com.google.common.base.Functions$ConstantFunction"/>
+            <onexception class="com.google.guava:com.google.common.base.Functions$ConstantFunction" reference="../onfailure"/>
+            <checkSuccess class="org.apache.brooklyn.feed.ssh.SshPollConfig$1" reference="../../org.apache.brooklyn.feed.ssh.SshPollConfig/checkSuccess"/>
+            <suppressDuplicates>false</suppressDuplicates>
+            <enabled>true</enabled>
+            <period>30000</period>
+            <commandSupplier class="com.google.guava:com.google.common.base.Suppliers$SupplierOfInstance" reference="../../org.apache.brooklyn.feed.ssh.SshFeed_-SshPollIdentifier[2]/command"/>
+            <dynamicEnvironmentSupplier class="MutableList" serialization="custom">
+              <unserializable-parents/>
+              <list>
+                <default>
+                  <size>0</size>
+                </default>
+                <int>0</int>
+              </list>
+            </dynamicEnvironmentSupplier>
+          </org.apache.brooklyn.feed.ssh.SshPollConfig>
+          <org.apache.brooklyn.feed.ssh.SshFeed_-SshPollIdentifier>
+            <command class="com.google.guava:com.google.common.base.Suppliers$SupplierOfInstance">
+              <instance class="string">free | grep Mem:</instance>
+            </command>
+            <env class="org.apache.brooklyn.feed.ssh.SshPollConfig$CombiningEnvSupplier">
+              <dynamicEnvironmentSupplier class="MutableList" serialization="custom">
+                <unserializable-parents/>
+                <list>
+                  <default>
+                    <size>0</size>
+                  </default>
+                  <int>0</int>
+                </list>
+              </dynamicEnvironmentSupplier>
+            </env>
+          </org.apache.brooklyn.feed.ssh.SshFeed_-SshPollIdentifier>
+          <int>3</int>
+          <org.apache.brooklyn.feed.ssh.SshPollConfig>
+            <sensor class="attributeSensor">
+              <type>java.lang.Long</type>
+              <name>machine.memory.used</name>
+              <description>Current memory usage</description>
+              <persistence>REQUIRED</persistence>
+            </sensor>
+            <onsuccess class="com.google.guava:com.google.common.base.Functions$ConstantFunction"/>
+            <onfailure class="com.google.guava:com.google.common.base.Functions$ConstantFunction"/>
+            <onexception class="com.google.guava:com.google.common.base.Functions$ConstantFunction" reference="../onfailure"/>
+            <checkSuccess class="org.apache.brooklyn.feed.ssh.SshPollConfig$1" reference="../../org.apache.brooklyn.feed.ssh.SshPollConfig/checkSuccess"/>
+            <suppressDuplicates>false</suppressDuplicates>
+            <enabled>true</enabled>
+            <period>30000</period>
+            <commandSupplier class="com.google.guava:com.google.common.base.Suppliers$SupplierOfInstance" reference="../../org.apache.brooklyn.feed.ssh.SshFeed_-SshPollIdentifier[3]/command"/>
+            <dynamicEnvironmentSupplier class="MutableList" serialization="custom">
+              <unserializable-parents/>
+              <list>
+                <default>
+                  <size>0</size>
+                </default>
+                <int>0</int>
+              </list>
+            </dynamicEnvironmentSupplier>
+          </org.apache.brooklyn.feed.ssh.SshPollConfig>
+          <org.apache.brooklyn.feed.ssh.SshPollConfig>
+            <sensor class="attributeSensor">
+              <type>java.lang.Long</type>
+              <name>machine.memory.free</name>
+              <description>Current free memory</description>
+              <persistence>REQUIRED</persistence>
+            </sensor>
+            <onsuccess class="com.google.guava:com.google.common.base.Functions$ConstantFunction"/>
+            <onfailure class="com.google.guava:com.google.common.base.Functions$ConstantFunction"/>
+            <onexception class="com.google.guava:com.google.common.base.Functions$ConstantFunction" reference="../onfailure"/>
+            <checkSuccess class="org.apache.brooklyn.feed.ssh.SshPollConfig$1" reference="../../org.apache.brooklyn.feed.ssh.SshPollConfig/checkSuccess"/>
+            <suppressDuplicates>false</suppressDuplicates>
+            <enabled>true</enabled>
+            <period>30000</period>
+            <commandSupplier class="com.google.guava:com.google.common.base.Suppliers$SupplierOfInstance">
+              <instance class="string">free | grep Mem:</instance>
+            </commandSupplier>
+            <dynamicEnvironmentSupplier class="MutableList" serialization="custom">
+              <unserializable-parents/>
+              <list>
+                <default>
+                  <size>0</size>
+                </default>
+                <int>0</int>
+              </list>
+            </dynamicEnvironmentSupplier>
+          </org.apache.brooklyn.feed.ssh.SshPollConfig>
+          <org.apache.brooklyn.feed.ssh.SshPollConfig>
+            <sensor class="attributeSensor">
+              <type>java.lang.Long</type>
+              <name>machine.memory.total</name>
+              <description>Total memory</description>
+              <persistence>REQUIRED</persistence>
+            </sensor>
+            <onsuccess class="com.google.guava:com.google.common.base.Functions$ConstantFunction"/>
+            <onfailure class="com.google.guava:com.google.common.base.Functions$ConstantFunction"/>
+            <onexception class="com.google.guava:com.google.common.base.Functions$ConstantFunction" reference="../onfailure"/>
+            <checkSuccess class="org.apache.brooklyn.feed.ssh.SshPollConfig$1" reference="../../org.apache.brooklyn.feed.ssh.SshPollConfig/checkSuccess"/>
+            <suppressDuplicates>false</suppressDuplicates>
+            <enabled>true</enabled>
+            <period>30000</period>
+            <commandSupplier class="com.google.guava:com.google.common.base.Suppliers$SupplierOfInstance">
+              <instance class="string">free | grep Mem:</instance>
+            </commandSupplier>
+            <dynamicEnvironmentSupplier class="MutableList" serialization="custom">
+              <unserializable-parents/>
+              <list>
+                <default>
+                  <size>0</size>
+                </default>
+                <int>0</int>
+              </list>
+            </dynamicEnvironmentSupplier>
+          </org.apache.brooklyn.feed.ssh.SshPollConfig>
+          <org.apache.brooklyn.feed.ssh.SshFeed_-SshPollIdentifier>
+            <command class="com.google.guava:com.google.common.base.Suppliers$SupplierOfInstance">
+              <instance class="string">cat /proc/uptime</instance>
+            </command>
+            <env class="org.apache.brooklyn.feed.ssh.SshPollConfig$CombiningEnvSupplier">
+              <dynamicEnvironmentSupplier class="MutableList" serialization="custom">
+                <unserializable-parents/>
+                <list>
+                  <default>
+                    <size>0</size>
+                  </default>
+                  <int>0</int>
+                </list>
+              </dynamicEnvironmentSupplier>
+            </env>
+          </org.apache.brooklyn.feed.ssh.SshFeed_-SshPollIdentifier>
+          <int>1</int>
+          <org.apache.brooklyn.feed.ssh.SshPollConfig>
+            <sensor class="attributeSensor">
+              <type>org.apache.brooklyn.util.time.Duration</type>
+              <name>machine.uptime</name>
+              <description>Current uptime</description>
+              <persistence>REQUIRED</persistence>
+            </sensor>
+            <onsuccess class="com.google.guava:com.google.common.base.Functions$ConstantFunction"/>
+            <onfailure class="com.google.guava:com.google.common.base.Functions$ConstantFunction"/>
+            <onexception class="com.google.guava:com.google.common.base.Functions$ConstantFunction" reference="../onfailure"/>
+            <checkSuccess class="org.apache.brooklyn.feed.ssh.SshPollConfig$1" reference="../../org.apache.brooklyn.feed.ssh.SshPollConfig/checkSuccess"/>
+            <suppressDuplicates>false</suppressDuplicates>
+            <enabled>true</enabled>
+            <period>30000</period>
+            <commandSupplier class="com.google.guava:com.google.common.base.Suppliers$SupplierOfInstance" reference="../../org.apache.brooklyn.feed.ssh.SshFeed_-SshPollIdentifier[4]/command"/>
+            <dynamicEnvironmentSupplier class="MutableList" serialization="custom">
+              <unserializable-parents/>
+              <list>
+                <default>
+                  <size>0</size>
+                </default>
+                <int>0</int>
+              </list>
+            </dynamicEnvironmentSupplier>
+          </org.apache.brooklyn.feed.ssh.SshPollConfig>
+        </com.google.guava:com.google.common.collect.HashMultimap>
+      </com.google.guava:com.google.common.collect.HashMultimap>
+    </polls>
+  </config>
+</feed>

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/bcaf76bc/core/src/test/resources/org/apache/brooklyn/core/mgmt/rebind/ssh-feed-zv7t8bim62
----------------------------------------------------------------------
diff --git a/core/src/test/resources/org/apache/brooklyn/core/mgmt/rebind/ssh-feed-zv7t8bim62 b/core/src/test/resources/org/apache/brooklyn/core/mgmt/rebind/ssh-feed-zv7t8bim62
new file mode 100644
index 0000000..467aa84
--- /dev/null
+++ b/core/src/test/resources/org/apache/brooklyn/core/mgmt/rebind/ssh-feed-zv7t8bim62
@@ -0,0 +1,268 @@
+<!--
+Licensed to the Apache Software Foundation (ASF) under one
+or more contributor license agreements.  See the NOTICE file
+distributed with this work for additional information
+regarding copyright ownership.  The ASF licenses this file
+to you under the Apache License, Version 2.0 (the
+"License"); you may not use this file except in compliance
+with the License.  You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing,
+software distributed under the License is distributed on an
+"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+KIND, either express or implied.  See the License for the
+specific language governing permissions and limitations
+under the License.
+-->
+
+<feed>
+  <brooklynVersion>0.11.0-20170126.1332</brooklynVersion>
+  <type>org.apache.brooklyn.core:org.apache.brooklyn.feed.ssh.SshFeed</type>
+  <id>zv7t8bim62</id>
+  <displayName>org.apache.brooklyn.feed.ssh.SshFeed</displayName>
+  <tags>
+    <string>SshFeed[ssh[cat /proc/uptime-&gt;machine.uptime], ssh[free | grep Mem:-&gt;machine.../52e0b96f</string>
+  </tags>
+  <uniqueTag>SshFeed[ssh[cat /proc/uptime-&gt;machine.uptime], ssh[free | grep Mem:-&gt;machine.../52e0b96f</uniqueTag>
+  <config>
+    <feed.onlyIfServiceUp type="boolean">false</feed.onlyIfServiceUp>
+    <machine>
+      <null/>
+    </machine>
+    <execAsCommand type="boolean">false</execAsCommand>
+    <polls>
+      <com.google.guava:com.google.common.collect.HashMultimap serialization="custom">
+        <unserializable-parents/>
+        <com.google.guava:com.google.common.collect.HashMultimap>
+          <default/>
+          <int>2</int>
+          <int>4</int>
+          <org.apache.brooklyn.core:org.apache.brooklyn.feed.ssh.SshFeed_-SshPollIdentifier>
+            <command class="com.google.guava:com.google.common.base.Suppliers$SupplierOfInstance">
+              <instance class="string">uptime</instance>
+            </command>
+            <env class="org.apache.brooklyn.core:org.apache.brooklyn.feed.ssh.SshPollConfig$CombiningEnvSupplier">
+              <dynamicEnvironmentSupplier class="MutableList" serialization="custom">
+                <unserializable-parents/>
+                <list>
+                  <default>
+                    <size>0</size>
+                  </default>
+                  <int>0</int>
+                </list>
+              </dynamicEnvironmentSupplier>
+            </env>
+          </org.apache.brooklyn.core:org.apache.brooklyn.feed.ssh.SshFeed_-SshPollIdentifier>
+          <int>1</int>
+          <org.apache.brooklyn.core:org.apache.brooklyn.feed.ssh.SshPollConfig>
+            <sensor class="attributeSensor">
+              <type>java.lang.Double</type>
+              <name>machine.loadAverage</name>
+              <description>Current load average</description>
+              <persistence>REQUIRED</persistence>
+            </sensor>
+            <onsuccess class="com.google.guava:com.google.common.base.Functions$ConstantFunction"/>
+            <onfailure class="com.google.guava:com.google.common.base.Functions$ConstantFunction"/>
+            <onexception class="com.google.guava:com.google.common.base.Functions$ConstantFunction" reference="../onfailure"/>
+            <checkSuccess class="org.apache.brooklyn.core:org.apache.brooklyn.feed.ssh.SshPollConfig$1"/>
+            <suppressDuplicates>false</suppressDuplicates>
+            <enabled>true</enabled>
+            <period>30000</period>
+            <commandSupplier class="com.google.guava:com.google.common.base.Suppliers$SupplierOfInstance" reference="../../org.apache.brooklyn.core:org.apache.brooklyn.feed.ssh.SshFeed_-SshPollIdentifier/command"/>
+            <dynamicEnvironmentSupplier class="MutableList" serialization="custom">
+              <unserializable-parents/>
+              <list>
+                <default>
+                  <size>0</size>
+                </default>
+                <int>0</int>
+              </list>
+            </dynamicEnvironmentSupplier>
+          </org.apache.brooklyn.core:org.apache.brooklyn.feed.ssh.SshPollConfig>
+          <org.apache.brooklyn.core:org.apache.brooklyn.feed.ssh.SshFeed_-SshPollIdentifier>
+            <command class="com.google.guava:com.google.common.base.Suppliers$SupplierOfInstance">
+              <instance class="string">ps -A -o pcpu</instance>
+            </command>
+            <env class="org.apache.brooklyn.core:org.apache.brooklyn.feed.ssh.SshPollConfig$CombiningEnvSupplier">
+              <dynamicEnvironmentSupplier class="MutableList" serialization="custom">
+                <unserializable-parents/>
+                <list>
+                  <default>
+                    <size>0</size>
+                  </default>
+                  <int>0</int>
+                </list>
+              </dynamicEnvironmentSupplier>
+            </env>
+          </org.apache.brooklyn.core:org.apache.brooklyn.feed.ssh.SshFeed_-SshPollIdentifier>
+          <int>1</int>
+          <org.apache.brooklyn.core:org.apache.brooklyn.feed.ssh.SshPollConfig>
+            <sensor class="attributeSensor">
+              <type>java.lang.Double</type>
+              <name>machine.cpu</name>
+              <description>Current CPU usage</description>
+              <persistence>REQUIRED</persistence>
+            </sensor>
+            <onsuccess class="com.google.guava:com.google.common.base.Functions$ConstantFunction"/>
+            <onfailure class="com.google.guava:com.google.common.base.Functions$ConstantFunction"/>
+            <onexception class="com.google.guava:com.google.common.base.Functions$ConstantFunction" reference="../onfailure"/>
+            <checkSuccess class="org.apache.brooklyn.core:org.apache.brooklyn.feed.ssh.SshPollConfig$1" reference="../../org.apache.brooklyn.core:org.apache.brooklyn.feed.ssh.SshPollConfig/checkSuccess"/>
+            <suppressDuplicates>false</suppressDuplicates>
+            <enabled>true</enabled>
+            <period>30000</period>
+            <commandSupplier class="com.google.guava:com.google.common.base.Suppliers$SupplierOfInstance" reference="../../org.apache.brooklyn.core:org.apache.brooklyn.feed.ssh.SshFeed_-SshPollIdentifier[2]/command"/>
+            <dynamicEnvironmentSupplier class="MutableList" serialization="custom">
+              <unserializable-parents/>
+              <list>
+                <default>
+                  <size>0</size>
+                </default>
+                <int>0</int>
+              </list>
+            </dynamicEnvironmentSupplier>
+          </org.apache.brooklyn.core:org.apache.brooklyn.feed.ssh.SshPollConfig>
+          <org.apache.brooklyn.core:org.apache.brooklyn.feed.ssh.SshFeed_-SshPollIdentifier>
+            <command class="com.google.guava:com.google.common.base.Suppliers$SupplierOfInstance">
+              <instance class="string">free | grep Mem:</instance>
+            </command>
+            <env class="org.apache.brooklyn.core:org.apache.brooklyn.feed.ssh.SshPollConfig$CombiningEnvSupplier">
+              <dynamicEnvironmentSupplier class="MutableList" serialization="custom">
+                <unserializable-parents/>
+                <list>
+                  <default>
+                    <size>0</size>
+                  </default>
+                  <int>0</int>
+                </list>
+              </dynamicEnvironmentSupplier>
+            </env>
+          </org.apache.brooklyn.core:org.apache.brooklyn.feed.ssh.SshFeed_-SshPollIdentifier>
+          <int>3</int>
+          <org.apache.brooklyn.core:org.apache.brooklyn.feed.ssh.SshPollConfig>
+            <sensor class="attributeSensor">
+              <type>java.lang.Long</type>
+              <name>machine.memory.used</name>
+              <description>Current memory usage</description>
+              <persistence>REQUIRED</persistence>
+            </sensor>
+            <onsuccess class="com.google.guava:com.google.common.base.Functions$ConstantFunction"/>
+            <onfailure class="com.google.guava:com.google.common.base.Functions$ConstantFunction"/>
+            <onexception class="com.google.guava:com.google.common.base.Functions$ConstantFunction" reference="../onfailure"/>
+            <checkSuccess class="org.apache.brooklyn.core:org.apache.brooklyn.feed.ssh.SshPollConfig$1" reference="../../org.apache.brooklyn.core:org.apache.brooklyn.feed.ssh.SshPollConfig/checkSuccess"/>
+            <suppressDuplicates>false</suppressDuplicates>
+            <enabled>true</enabled>
+            <period>30000</period>
+            <commandSupplier class="com.google.guava:com.google.common.base.Suppliers$SupplierOfInstance" reference="../../org.apache.brooklyn.core:org.apache.brooklyn.feed.ssh.SshFeed_-SshPollIdentifier[3]/command"/>
+            <dynamicEnvironmentSupplier class="MutableList" serialization="custom">
+              <unserializable-parents/>
+              <list>
+                <default>
+                  <size>0</size>
+                </default>
+                <int>0</int>
+              </list>
+            </dynamicEnvironmentSupplier>
+          </org.apache.brooklyn.core:org.apache.brooklyn.feed.ssh.SshPollConfig>
+          <org.apache.brooklyn.core:org.apache.brooklyn.feed.ssh.SshPollConfig>
+            <sensor class="attributeSensor">
+              <type>java.lang.Long</type>
+              <name>machine.memory.free</name>
+              <description>Current free memory</description>
+              <persistence>REQUIRED</persistence>
+            </sensor>
+            <onsuccess class="com.google.guava:com.google.common.base.Functions$ConstantFunction"/>
+            <onfailure class="com.google.guava:com.google.common.base.Functions$ConstantFunction"/>
+            <onexception class="com.google.guava:com.google.common.base.Functions$ConstantFunction" reference="../onfailure"/>
+            <checkSuccess class="org.apache.brooklyn.core:org.apache.brooklyn.feed.ssh.SshPollConfig$1" reference="../../org.apache.brooklyn.core:org.apache.brooklyn.feed.ssh.SshPollConfig/checkSuccess"/>
+            <suppressDuplicates>false</suppressDuplicates>
+            <enabled>true</enabled>
+            <period>30000</period>
+            <commandSupplier class="com.google.guava:com.google.common.base.Suppliers$SupplierOfInstance">
+              <instance class="string">free | grep Mem:</instance>
+            </commandSupplier>
+            <dynamicEnvironmentSupplier class="MutableList" serialization="custom">
+              <unserializable-parents/>
+              <list>
+                <default>
+                  <size>0</size>
+                </default>
+                <int>0</int>
+              </list>
+            </dynamicEnvironmentSupplier>
+          </org.apache.brooklyn.core:org.apache.brooklyn.feed.ssh.SshPollConfig>
+          <org.apache.brooklyn.core:org.apache.brooklyn.feed.ssh.SshPollConfig>
+            <sensor class="attributeSensor">
+              <type>java.lang.Long</type>
+              <name>machine.memory.total</name>
+              <description>Total memory</description>
+              <persistence>REQUIRED</persistence>
+            </sensor>
+            <onsuccess class="com.google.guava:com.google.common.base.Functions$ConstantFunction"/>
+            <onfailure class="com.google.guava:com.google.common.base.Functions$ConstantFunction"/>
+            <onexception class="com.google.guava:com.google.common.base.Functions$ConstantFunction" reference="../onfailure"/>
+            <checkSuccess class="org.apache.brooklyn.core:org.apache.brooklyn.feed.ssh.SshPollConfig$1" reference="../../org.apache.brooklyn.core:org.apache.brooklyn.feed.ssh.SshPollConfig/checkSuccess"/>
+            <suppressDuplicates>false</suppressDuplicates>
+            <enabled>true</enabled>
+            <period>30000</period>
+            <commandSupplier class="com.google.guava:com.google.common.base.Suppliers$SupplierOfInstance">
+              <instance class="string">free | grep Mem:</instance>
+            </commandSupplier>
+            <dynamicEnvironmentSupplier class="MutableList" serialization="custom">
+              <unserializable-parents/>
+              <list>
+                <default>
+                  <size>0</size>
+                </default>
+                <int>0</int>
+              </list>
+            </dynamicEnvironmentSupplier>
+          </org.apache.brooklyn.core:org.apache.brooklyn.feed.ssh.SshPollConfig>
+          <org.apache.brooklyn.core:org.apache.brooklyn.feed.ssh.SshFeed_-SshPollIdentifier>
+            <command class="com.google.guava:com.google.common.base.Suppliers$SupplierOfInstance">
+              <instance class="string">cat /proc/uptime</instance>
+            </command>
+            <env class="org.apache.brooklyn.core:org.apache.brooklyn.feed.ssh.SshPollConfig$CombiningEnvSupplier">
+              <dynamicEnvironmentSupplier class="MutableList" serialization="custom">
+                <unserializable-parents/>
+                <list>
+                  <default>
+                    <size>0</size>
+                  </default>
+                  <int>0</int>
+                </list>
+              </dynamicEnvironmentSupplier>
+            </env>
+          </org.apache.brooklyn.core:org.apache.brooklyn.feed.ssh.SshFeed_-SshPollIdentifier>
+          <int>1</int>
+          <org.apache.brooklyn.core:org.apache.brooklyn.feed.ssh.SshPollConfig>
+            <sensor class="attributeSensor">
+              <type>org.apache.brooklyn.util.time.Duration</type>
+              <name>machine.uptime</name>
+              <description>Current uptime</description>
+              <persistence>REQUIRED</persistence>
+            </sensor>
+            <onsuccess class="com.google.guava:com.google.common.base.Functions$ConstantFunction"/>
+            <onfailure class="com.google.guava:com.google.common.base.Functions$ConstantFunction"/>
+            <onexception class="com.google.guava:com.google.common.base.Functions$ConstantFunction" reference="../onfailure"/>
+            <checkSuccess class="org.apache.brooklyn.core:org.apache.brooklyn.feed.ssh.SshPollConfig$1" reference="../../org.apache.brooklyn.core:org.apache.brooklyn.feed.ssh.SshPollConfig/checkSuccess"/>
+            <suppressDuplicates>false</suppressDuplicates>
+            <enabled>true</enabled>
+            <period>30000</period>
+            <commandSupplier class="com.google.guava:com.google.common.base.Suppliers$SupplierOfInstance" reference="../../org.apache.brooklyn.core:org.apache.brooklyn.feed.ssh.SshFeed_-SshPollIdentifier[4]/command"/>
+            <dynamicEnvironmentSupplier class="MutableList" serialization="custom">
+              <unserializable-parents/>
+              <list>
+                <default>
+                  <size>0</size>
+                </default>
+                <int>0</int>
+              </list>
+            </dynamicEnvironmentSupplier>
+          </org.apache.brooklyn.core:org.apache.brooklyn.feed.ssh.SshPollConfig>
+        </com.google.guava:com.google.common.collect.HashMultimap>
+      </com.google.guava:com.google.common.collect.HashMultimap>
+    </polls>
+  </config>
+</feed>