You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@brooklyn.apache.org by al...@apache.org on 2014/11/03 16:51:45 UTC

[01/29] git commit: clean up of anonymous internal classes, and some minor extensions to predicates available on Entity and String

Repository: incubator-brooklyn
Updated Branches:
  refs/heads/master 1f73dcf10 -> 5e6b9908e


clean up of anonymous internal classes, and some minor extensions to predicates available on Entity and String


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

Branch: refs/heads/master
Commit: 919eaea0e7d930d453351761fee69d3d45fe3e5d
Parents: dfe323c
Author: Alex Heneveld <al...@cloudsoftcorp.com>
Authored: Mon Oct 20 14:51:17 2014 +0100
Committer: Alex Heneveld <al...@cloudsoftcorp.com>
Committed: Fri Oct 31 09:36:16 2014 -0500

----------------------------------------------------------------------
 .../entity/basic/BasicStartableImpl.java        |   2 +-
 .../brooklyn/entity/basic/EntityPredicates.java | 280 +++++++++++++++++--
 .../brooklyn/entity/trait/StartableMethods.java |   2 +-
 .../entity/basic/EntityPredicatesTest.java      |  13 +-
 .../testing/mocks/NameMatcherGroupImpl.java     |   3 +-
 .../util/collections/CollectionFunctionals.java |  24 ++
 .../brooklyn/util/text/StringPredicates.java    | 181 +++++++++++-
 .../util/text/StringPredicatesTest.java         |  74 +++++
 8 files changed, 538 insertions(+), 41 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/919eaea0/core/src/main/java/brooklyn/entity/basic/BasicStartableImpl.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/entity/basic/BasicStartableImpl.java b/core/src/main/java/brooklyn/entity/basic/BasicStartableImpl.java
index 4c61acd..1e42490 100644
--- a/core/src/main/java/brooklyn/entity/basic/BasicStartableImpl.java
+++ b/core/src/main/java/brooklyn/entity/basic/BasicStartableImpl.java
@@ -79,6 +79,6 @@ public class BasicStartableImpl extends AbstractEntity implements BasicStartable
 
     // TODO make public in StartableMethods
     private static Iterable<Entity> filterStartableManagedEntities(Iterable<Entity> contenders) {
-        return Iterables.filter(contenders, Predicates.and(Predicates.instanceOf(Startable.class), EntityPredicates.managed()));
+        return Iterables.filter(contenders, Predicates.and(Predicates.instanceOf(Startable.class), EntityPredicates.isManaged()));
     }
 }

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/919eaea0/core/src/main/java/brooklyn/entity/basic/EntityPredicates.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/entity/basic/EntityPredicates.java b/core/src/main/java/brooklyn/entity/basic/EntityPredicates.java
index b359b1e..41a214e 100644
--- a/core/src/main/java/brooklyn/entity/basic/EntityPredicates.java
+++ b/core/src/main/java/brooklyn/entity/basic/EntityPredicates.java
@@ -18,6 +18,8 @@
  */
 package brooklyn.entity.basic;
 
+import java.util.Collection;
+
 import javax.annotation.Nullable;
 
 import brooklyn.config.ConfigKey;
@@ -26,17 +28,43 @@ import brooklyn.entity.Entity;
 import brooklyn.entity.Group;
 import brooklyn.event.AttributeSensor;
 import brooklyn.location.Location;
+import brooklyn.util.collections.CollectionFunctionals;
 import brooklyn.util.guava.SerializablePredicate;
+import brooklyn.util.text.StringPredicates;
 
 import com.google.common.base.Objects;
 import com.google.common.base.Predicate;
+import com.google.common.base.Predicates;
 
 @SuppressWarnings("serial")
 public class EntityPredicates {
 
-    // TODO convert these to named classes. but keep the anonymous ones around for deserialization purposes!
+    public static Predicate<Entity> idEqualTo(final String val) {
+        return idSatisfies(Predicates.equalTo(val));
+    }
     
-    public static <T> Predicate<Entity> idEqualTo(final T val) {
+    public static Predicate<Entity> idSatisfies(final Predicate<? super String> condition) {
+        return new IdSatisfies(condition);
+    }
+    
+    protected static class IdSatisfies implements SerializablePredicate<Entity> {
+        protected final Predicate<? super String> condition;
+        protected IdSatisfies(Predicate<? super String> condition) {
+            this.condition = condition;
+        }
+        @Override
+        public boolean apply(@Nullable Entity input) {
+            return (input != null) && condition.apply(input.getId());
+        }
+        @Override
+        public String toString() {
+            return "idSatisfies("+condition+")";
+        }
+    }
+
+    /** @deprecated since 0.7.0 kept only to allow conversion of anonymous inner classes */
+    @SuppressWarnings("unused") @Deprecated 
+    private static <T> Predicate<Entity> idEqualToOld(final T val) {
         return new SerializablePredicate<Entity>() {
             @Override
             public boolean apply(@Nullable Entity input) {
@@ -45,7 +73,34 @@ public class EntityPredicates {
         };
     }
     
-    public static <T> Predicate<Entity> displayNameEqualTo(final T val) {
+    // ---------------------------
+    
+    public static Predicate<Entity> displayNameEqualTo(final String val) {
+        return displayNameSatisfies(Predicates.equalTo(val));
+    }
+    
+    public static Predicate<Entity> displayNameSatisfies(final Predicate<? super String> condition) {
+        return new DisplayNameSatisfies(condition);
+    }
+    
+    protected static class DisplayNameSatisfies implements SerializablePredicate<Entity> {
+        protected final Predicate<? super String> condition;
+        protected DisplayNameSatisfies(Predicate<? super String> condition) {
+            this.condition = condition;
+        }
+        @Override
+        public boolean apply(@Nullable Entity input) {
+            return (input != null) && condition.apply(input.getDisplayName());
+        }
+        @Override
+        public String toString() {
+            return "displayNameSatisfies("+condition+")";
+        }
+    }
+
+    /** @deprecated since 0.7.0 kept only to allow conversion of anonymous inner classes */
+    @SuppressWarnings("unused") @Deprecated 
+    private static <T> Predicate<Entity> displayNameEqualToOld(final T val) {
         return new SerializablePredicate<Entity>() {
             @Override
             public boolean apply(@Nullable Entity input) {
@@ -54,25 +109,57 @@ public class EntityPredicates {
         };
     }
     
-    public static Predicate<Entity> displayNameMatches(final String val) {
-        return new DisplayNameMatches(val);
+    /** @deprecated since 0.7.0 use {@link #displayNameSatisfies(Predicate)} to clarify this is *regex* matching
+     * (passing {@link StringPredicates#matchesRegex(String)} as the predicate) */
+    public static Predicate<Entity> displayNameMatches(final String regex) {
+        return displayNameSatisfies(StringPredicates.matchesRegex(regex));
     }
+
+    /** @deprecated since 0.7.0 kept only to allow conversion of anonymous inner classes */
+    @SuppressWarnings("unused") @Deprecated 
     private static class DisplayNameMatches implements SerializablePredicate<Entity> {
-        private final String val;
-        DisplayNameMatches(String val) {
-            this.val = val;
+        private final String regex;
+        DisplayNameMatches(String regex) {
+            this.regex = regex;
         }
         @Override
         public boolean apply(@Nullable Entity input) {
-            return (input != null && input.getDisplayName() != null) && input.getDisplayName().matches(val);
+            return (input != null && input.getDisplayName() != null) && input.getDisplayName().matches(regex);
         }
         @Override
         public String toString() {
-            return "DisplayNameMatches("+val+")";
+            return "DisplayNameMatches("+regex+")";
         }
     };
     
+    // ---------------------------
+
     public static Predicate<Entity> applicationIdEqualTo(final String val) {
+        return applicationIdSatisfies(Predicates.equalTo(val));
+    }
+
+    public static Predicate<Entity> applicationIdSatisfies(final Predicate<? super String> condition) {
+        return new ApplicationIdSatisfies(condition);
+    }
+
+    protected static class ApplicationIdSatisfies implements SerializablePredicate<Entity> {
+        protected final Predicate<? super String> condition;
+        protected ApplicationIdSatisfies(Predicate<? super String> condition) {
+            this.condition = condition;
+        }
+        @Override
+        public boolean apply(@Nullable Entity input) {
+            return (input != null) && condition.apply(input.getApplicationId());
+        }
+        @Override
+        public String toString() {
+            return "applicationIdSatisfies("+condition+")";
+        }
+    }
+
+    /** @deprecated since 0.7.0 kept only to allow conversion of anonymous inner classes */
+    @SuppressWarnings("unused") @Deprecated 
+    private static Predicate<Entity> applicationIdEqualToOld(final String val) {
         return new SerializablePredicate<Entity>() {
             @Override
             public boolean apply(@Nullable Entity input) {
@@ -81,25 +168,84 @@ public class EntityPredicates {
         };
     }
 
+    // ---------------------------
+    
     public static <T> Predicate<Entity> attributeEqualTo(final AttributeSensor<T> attribute, final T val) {
-        return new SerializablePredicate<Entity>() {
-            @Override
-            public boolean apply(@Nullable Entity input) {
-                return (input != null) && Objects.equal(input.getAttribute(attribute), val);
-            }
-        };
+        return attributeSatisfies(attribute, Predicates.equalTo(val));
     }
     
-    public static <T> Predicate<Entity> attributeNotEqualTo(final AttributeSensor<T> attribute, final T val) {
+    public static <T> Predicate<Entity> attributeSatisfies(final AttributeSensor<T> attribute, final Predicate<T> condition) {
+        return new AttributeSatisfies<T>(attribute, condition);
+    }
+
+    protected static class AttributeSatisfies<T> implements SerializablePredicate<Entity> {
+        protected final AttributeSensor<T> attribute;
+        protected final Predicate<T> condition;
+        private AttributeSatisfies(AttributeSensor<T> attribute, Predicate<T> condition) {
+            this.attribute = attribute;
+            this.condition = condition;
+        }
+        @Override
+        public boolean apply(@Nullable Entity input) {
+            return (input != null) && condition.apply(input.getAttribute(attribute));
+        }
+        @Override
+        public String toString() {
+            return "attributeSatisfies("+attribute.getName()+","+condition+")";
+        }
+    }
+
+    /** @deprecated since 0.7.0 kept only to allow conversion of anonymous inner classes */
+    @SuppressWarnings("unused") @Deprecated 
+    private static <T> Predicate<Entity> attributeEqualToOld(final AttributeSensor<T> attribute, final T val) {
         return new SerializablePredicate<Entity>() {
             @Override
             public boolean apply(@Nullable Entity input) {
-                return (input != null) && !Objects.equal(input.getAttribute(attribute), val);
+                return (input != null) && Objects.equal(input.getAttribute(attribute), val);
             }
         };
     }
     
+
+    // ---------------------------
+
     public static <T> Predicate<Entity> configEqualTo(final ConfigKey<T> configKey, final T val) {
+        return configSatisfies(configKey, Predicates.equalTo(val));
+    }
+
+    public static <T> Predicate<Entity> configSatisfies(final ConfigKey<T> configKey, final Predicate<T> condition) {
+        return new ConfigKeySatisfies<T>(configKey, condition);
+    }
+
+    public static <T> Predicate<Entity> configEqualTo(final HasConfigKey<T> configKey, final T val) {
+        return configEqualTo(configKey.getConfigKey(), val);
+    }
+
+    public static <T> Predicate<Entity> configSatisfies(final HasConfigKey<T> configKey, final Predicate<T> condition) {
+        return new ConfigKeySatisfies<T>(configKey.getConfigKey(), condition);
+    }
+
+    protected static class ConfigKeySatisfies<T> implements SerializablePredicate<Entity> {
+        protected final ConfigKey<T> configKey;
+        protected final Predicate<T> condition;
+        private ConfigKeySatisfies(ConfigKey<T> configKey, Predicate<T> condition) {
+            this.configKey = configKey;
+            this.condition = condition;
+        }
+        @Override
+        public boolean apply(@Nullable Entity input) {
+            return (input != null) && condition.apply(input.getConfig(configKey));
+        }
+        @Override
+        public String toString() {
+            return "configKeySatisfies("+configKey.getName()+","+condition+")";
+        }
+    }
+
+    
+    /** @deprecated since 0.7.0 kept only to allow conversion of anonymous inner classes */
+    @SuppressWarnings("unused") @Deprecated 
+    private static <T> Predicate<Entity> configEqualToOld(final ConfigKey<T> configKey, final T val) {
         return new SerializablePredicate<Entity>() {
             @Override
             public boolean apply(@Nullable Entity input) {
@@ -108,7 +254,9 @@ public class EntityPredicates {
         };
     }
 
-    public static <T> Predicate<Entity> configEqualTo(final HasConfigKey<T> configKey, final T val) {
+    /** @deprecated since 0.7.0 kept only to allow conversion of anonymous inner classes */
+    @SuppressWarnings("unused") @Deprecated 
+    private static <T> Predicate<Entity> configEqualToOld(final HasConfigKey<T> configKey, final T val) {
         return new SerializablePredicate<Entity>() {
             @Override
             public boolean apply(@Nullable Entity input) {
@@ -117,10 +265,35 @@ public class EntityPredicates {
         };
     }
 
+    // ---------------------------
+
     /**
      * Returns a predicate that determines if a given entity is a direct child of this {@code parent}.
      */
     public static <T> Predicate<Entity> isChildOf(final Entity parent) {
+        return new IsChildOf(parent);
+    }
+
+    // if needed, could add parentSatisfies(...)
+    
+    protected static class IsChildOf implements SerializablePredicate<Entity> {
+        protected final Entity parent;
+        protected IsChildOf(Entity parent) {
+            this.parent = parent;
+        }
+        @Override
+        public boolean apply(@Nullable Entity input) {
+            return (input != null) && Objects.equal(input.getParent(), parent);
+        }
+        @Override
+        public String toString() {
+            return "isChildOf("+parent+")";
+        }
+    }
+
+    /** @deprecated since 0.7.0 kept only to allow conversion of anonymous inner classes */
+    @SuppressWarnings("unused") @Deprecated 
+    private static <T> Predicate<Entity> isChildOfOld(final Entity parent) {
         return new SerializablePredicate<Entity>() {
             @Override
             public boolean apply(@Nullable Entity input) {
@@ -129,7 +302,30 @@ public class EntityPredicates {
         };
     }
 
+    // ---------------------------
+    
     public static <T> Predicate<Entity> isMemberOf(final Group group) {
+        return new IsMemberOf(group);
+    }
+
+    protected static class IsMemberOf implements SerializablePredicate<Entity> {
+        protected final Group group;
+        protected IsMemberOf(Group group) {
+            this.group = group;
+        }
+        @Override
+        public boolean apply(@Nullable Entity input) {
+            return (input != null) && group.hasMember(input);
+        }
+        @Override
+        public String toString() {
+            return "isMemberOf("+group+")";
+        }
+    }
+
+    /** @deprecated since 0.7.0 kept only to allow conversion of anonymous inner classes */
+    @SuppressWarnings("unused") @Deprecated 
+    private static <T> Predicate<Entity> isMemberOfOld(final Group group) {
         return new SerializablePredicate<Entity>() {
             @Override
             public boolean apply(@Nullable Entity input) {
@@ -138,10 +334,38 @@ public class EntityPredicates {
         };
     }
 
+    // ---------------------------
+
     /**
      * Create a predicate that matches any entity who has an exact match for the given location
      * (i.e. {@code entity.getLocations().contains(location)}).
      */
+    public static <T> Predicate<Entity> locationsInclude(Location location) {
+        return locationsSatisfy(CollectionFunctionals.contains(location));
+        
+    }
+    
+    public static <T> Predicate<Entity> locationsSatisfy(final Predicate<Collection<Location>> condition) {
+        return new LocationsSatisfy(condition);
+    }
+
+    protected static class LocationsSatisfy implements SerializablePredicate<Entity> {
+        protected final Predicate<Collection<Location>> condition;
+        protected LocationsSatisfy(Predicate<Collection<Location>> condition) {
+            this.condition = condition;
+        }
+        @Override
+        public boolean apply(@Nullable Entity input) {
+            return (input != null) && condition.apply(input.getLocations());
+        }
+        @Override
+        public String toString() {
+            return "locationsSatisfy("+condition+")";
+        }
+    }
+
+    /** @deprecated since 0.7.0 use #locationsInclude */
+    @Deprecated 
     public static <T> Predicate<Entity> withLocation(final Location location) {
         return new SerializablePredicate<Entity>() {
             @Override
@@ -151,6 +375,24 @@ public class EntityPredicates {
         };
     }
     
+    // ---------------------------
+
+    public static <T> Predicate<Entity> isManaged() {
+        return new IsManaged();
+    }
+
+    protected static class IsManaged implements SerializablePredicate<Entity> {
+        @Override
+        public boolean apply(@Nullable Entity input) {
+            return (input != null) && Entities.isManaged(input);
+        }
+        @Override
+        public String toString() {
+            return "isManaged()";
+        }
+    }
+
+    /** @deprecated since 0.7.0 use #isManaged */
     public static <T> Predicate<Entity> managed() {
         return new SerializablePredicate<Entity>() {
             @Override

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/919eaea0/core/src/main/java/brooklyn/entity/trait/StartableMethods.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/entity/trait/StartableMethods.java b/core/src/main/java/brooklyn/entity/trait/StartableMethods.java
index 94a1fdb..8c1b90d 100644
--- a/core/src/main/java/brooklyn/entity/trait/StartableMethods.java
+++ b/core/src/main/java/brooklyn/entity/trait/StartableMethods.java
@@ -69,7 +69,7 @@ public class StartableMethods {
     }
     
     private static <T extends Entity> Iterable<T> filterStartableManagedEntities(Iterable<T> contenders) {
-        return Iterables.filter(contenders, Predicates.and(Predicates.instanceOf(Startable.class), EntityPredicates.managed()));
+        return Iterables.filter(contenders, Predicates.and(Predicates.instanceOf(Startable.class), EntityPredicates.isManaged()));
     }
 
     public static void stopSequentially(Iterable<? extends Startable> entities) {

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/919eaea0/core/src/test/java/brooklyn/entity/basic/EntityPredicatesTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/brooklyn/entity/basic/EntityPredicatesTest.java b/core/src/test/java/brooklyn/entity/basic/EntityPredicatesTest.java
index 549aada..b4c144f 100644
--- a/core/src/test/java/brooklyn/entity/basic/EntityPredicatesTest.java
+++ b/core/src/test/java/brooklyn/entity/basic/EntityPredicatesTest.java
@@ -28,6 +28,7 @@ import brooklyn.entity.BrooklynAppUnitTestSupport;
 import brooklyn.entity.proxying.EntitySpec;
 import brooklyn.location.Location;
 import brooklyn.test.entity.TestEntity;
+import brooklyn.util.text.StringPredicates;
 
 import com.google.common.collect.ImmutableList;
 
@@ -79,8 +80,8 @@ public class EntityPredicatesTest extends BrooklynAppUnitTestSupport {
     }
     
     @Test
-    public void testDisplayNameMatches() throws Exception {
-        assertTrue(EntityPredicates.displayNameMatches(entity.getDisplayName()).apply(entity));
+    public void testDisplayNameSatisfies() throws Exception {
+        assertTrue(EntityPredicates.displayNameSatisfies(StringPredicates.matchesRegex("myd.*me")).apply(entity));
         assertFalse(EntityPredicates.applicationIdEqualTo("wrongname").apply(entity));
     }
     
@@ -101,15 +102,15 @@ public class EntityPredicatesTest extends BrooklynAppUnitTestSupport {
     
     @Test
     public void testManaged() throws Exception {
-        assertTrue(EntityPredicates.managed().apply(entity));
+        assertTrue(EntityPredicates.isManaged().apply(entity));
         Entities.unmanage(entity);
-        assertFalse(EntityPredicates.managed().apply(entity));
+        assertFalse(EntityPredicates.isManaged().apply(entity));
     }
     
     @Test
     public void testWithLocation() throws Exception {
         entity.addLocations(ImmutableList.of(loc));
-        assertTrue(EntityPredicates.withLocation(loc).apply(entity));
-        assertFalse(EntityPredicates.withLocation(loc).apply(app));
+        assertTrue(EntityPredicates.locationsInclude(loc).apply(entity));
+        assertFalse(EntityPredicates.locationsInclude(loc).apply(app));
     }
 }

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/919eaea0/usage/rest-server/src/test/java/brooklyn/rest/testing/mocks/NameMatcherGroupImpl.java
----------------------------------------------------------------------
diff --git a/usage/rest-server/src/test/java/brooklyn/rest/testing/mocks/NameMatcherGroupImpl.java b/usage/rest-server/src/test/java/brooklyn/rest/testing/mocks/NameMatcherGroupImpl.java
index ee1c270..b0b5854 100644
--- a/usage/rest-server/src/test/java/brooklyn/rest/testing/mocks/NameMatcherGroupImpl.java
+++ b/usage/rest-server/src/test/java/brooklyn/rest/testing/mocks/NameMatcherGroupImpl.java
@@ -20,13 +20,14 @@ package brooklyn.rest.testing.mocks;
 
 import brooklyn.entity.basic.DynamicGroupImpl;
 import brooklyn.entity.basic.EntityPredicates;
+import brooklyn.util.text.StringPredicates;
 
 public class NameMatcherGroupImpl extends DynamicGroupImpl implements NameMatcherGroup {
 
     @Override
     public void init() {
         super.init();
-        setConfig(ENTITY_FILTER, EntityPredicates.displayNameMatches(getConfig(NAME_REGEX)));
+        setConfig(ENTITY_FILTER, EntityPredicates.displayNameSatisfies(StringPredicates.matchesRegex(getConfig(NAME_REGEX))));
         rescanEntities();
     }
 }

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/919eaea0/utils/common/src/main/java/brooklyn/util/collections/CollectionFunctionals.java
----------------------------------------------------------------------
diff --git a/utils/common/src/main/java/brooklyn/util/collections/CollectionFunctionals.java b/utils/common/src/main/java/brooklyn/util/collections/CollectionFunctionals.java
index 0a5570d..7b2fe76 100644
--- a/utils/common/src/main/java/brooklyn/util/collections/CollectionFunctionals.java
+++ b/utils/common/src/main/java/brooklyn/util/collections/CollectionFunctionals.java
@@ -19,6 +19,7 @@
 package brooklyn.util.collections;
 
 import java.util.Arrays;
+import java.util.Collection;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
@@ -153,4 +154,27 @@ public class CollectionFunctionals {
         }
     }
 
+    // ---------
+    public static <I,T extends Collection<I>> Predicate<T> contains(I item) {
+        return new CollectionContains<I,T>(item);
+    }
+    
+    private static final class CollectionContains<I,T extends Collection<I>> implements Predicate<T> {
+        private final I item;
+        private CollectionContains(I item) {
+            this.item = item;
+        }
+        @Override
+        public boolean apply(T input) {
+            if (input==null) return false;
+            return input.contains(item);
+        }
+        @Override
+        public String toString() {
+            return "contains("+item+")";
+        }
+    }
+
+
+
 }

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/919eaea0/utils/common/src/main/java/brooklyn/util/text/StringPredicates.java
----------------------------------------------------------------------
diff --git a/utils/common/src/main/java/brooklyn/util/text/StringPredicates.java b/utils/common/src/main/java/brooklyn/util/text/StringPredicates.java
index 210d3af..1f9c574 100644
--- a/utils/common/src/main/java/brooklyn/util/text/StringPredicates.java
+++ b/utils/common/src/main/java/brooklyn/util/text/StringPredicates.java
@@ -33,10 +33,26 @@ import com.google.common.collect.Iterables;
 
 public class StringPredicates {
 
-    /**
-     * @since 0.7.0
-     */
-    public static Predicate<CharSequence> isBlank() {
+    /** predicate form of {@link Strings#isBlank(CharSequence)} */
+    public static <T extends CharSequence> Predicate<T> isBlank() {
+        return new IsBlank<T>();
+    }
+
+    private static final class IsBlank<T extends CharSequence> implements Predicate<T> {
+        @Override
+        public boolean apply(@Nullable CharSequence input) {
+            return Strings.isBlank(input);
+        }
+
+        @Override
+        public String toString() {
+            return "isBlank()";
+        }
+    }
+
+    /** @deprecated since 0.7.0 kept only to allow conversion of anonymous inner classes */
+    @SuppressWarnings("unused") @Deprecated 
+    private static Predicate<CharSequence> isBlankOld() {
         return new Predicate<CharSequence>() {
             @Override
             public boolean apply(@Nullable CharSequence input) {
@@ -49,7 +65,55 @@ public class StringPredicates {
         };
     }
 
-    public static Predicate<CharSequence> containsLiteralCaseInsensitive(final String fragment) {
+    // -----------------
+    
+    public static <T extends CharSequence> Predicate<T> containsLiteralCaseInsensitive(final String fragment) {
+        return new ContainsLiteralCaseInsensitive<T>(fragment);
+    }
+
+    private static final class ContainsLiteralCaseInsensitive<T extends CharSequence> implements Predicate<T> {
+        private final String fragment;
+
+        private ContainsLiteralCaseInsensitive(String fragment) {
+            this.fragment = fragment;
+        }
+
+        @Override
+        public boolean apply(@Nullable CharSequence input) {
+            return Strings.containsLiteralIgnoreCase(input, fragment);
+        }
+
+        @Override
+        public String toString() {
+            return "containsLiteralCaseInsensitive("+fragment+")";
+        }
+    }
+
+    public static <T extends CharSequence> Predicate<T> containsLiteral(final String fragment) {
+        return new ContainsLiteral<T>(fragment);
+    }
+    
+    private static final class ContainsLiteral<T extends CharSequence> implements Predicate<T> {
+        private final String fragment;
+
+        private ContainsLiteral(String fragment) {
+            this.fragment = fragment;
+        }
+
+        @Override
+        public boolean apply(@Nullable CharSequence input) {
+            return Strings.containsLiteral(input, fragment);
+        }
+
+        @Override
+        public String toString() {
+            return "containsLiteral("+fragment+")";
+        }
+    }
+
+    /** @deprecated since 0.7.0 kept only to allow conversion of anonymous inner classes */
+    @SuppressWarnings("unused") @Deprecated 
+    private static Predicate<CharSequence> containsLiteralCaseInsensitiveOld(final String fragment) {
         return new Predicate<CharSequence>() {
             @Override
             public boolean apply(@Nullable CharSequence input) {
@@ -62,7 +126,9 @@ public class StringPredicates {
         };
     }
 
-    public static Predicate<CharSequence> containsLiteral(final String fragment) {
+    /** @deprecated since 0.7.0 kept only to allow conversion of anonymous inner classes */
+    @SuppressWarnings("unused") @Deprecated 
+    private static Predicate<CharSequence> containsLiteralOld(final String fragment) {
         return new Predicate<CharSequence>() {
             @Override
             public boolean apply(@Nullable CharSequence input) {
@@ -75,7 +141,22 @@ public class StringPredicates {
         };
     }
     
-    public static Predicate<CharSequence> containsAllLiterals(final String... fragments) {
+    // -----------------
+    
+    public static <T extends CharSequence> Predicate<T> containsAllLiterals(final String... fragments) {
+        return Predicates.and(Iterables.transform(Arrays.asList(fragments), new ConvertStringToContainsLiteralPredicate()));
+    }
+
+    private static final class ConvertStringToContainsLiteralPredicate implements Function<String, Predicate<CharSequence>> {
+        @Override
+        public Predicate<CharSequence> apply(String input) {
+            return containsLiteral(input);
+        }
+    }
+
+    /** @deprecated since 0.7.0 kept only to allow conversion of anonymous inner classes */
+    @SuppressWarnings("unused") @Deprecated 
+    private static Predicate<CharSequence> containsAllLiteralsOld(final String... fragments) {
         return Predicates.and(Iterables.transform(Arrays.asList(fragments), new Function<String,Predicate<CharSequence>>() {
             @Override
             public Predicate<CharSequence> apply(String input) {
@@ -83,13 +164,38 @@ public class StringPredicates {
             }
         }));
     }
+    
+    // -----------------
 
     public static Predicate<CharSequence> containsRegex(final String regex) {
         // "Pattern" ... what a bad name :)
         return Predicates.containsPattern(regex);
     }
 
-    public static Predicate<CharSequence> startsWith(final String prefix) {
+    // -----------------
+    
+    public static <T extends CharSequence> Predicate<T> startsWith(final String prefix) {
+        return new StartsWith<T>(prefix);
+    }
+
+    private static final class StartsWith<T extends CharSequence> implements Predicate<T> {
+        private final String prefix;
+        private StartsWith(String prefix) {
+            this.prefix = prefix;
+        }
+        @Override
+        public boolean apply(CharSequence input) {
+            return (input != null) && input.toString().startsWith(prefix);
+        }
+        @Override
+        public String toString() {
+            return "startsWith("+prefix+")";
+        }
+    }
+
+    /** @deprecated since 0.7.0 kept only to allow conversion of anonymous inner classes */
+    @SuppressWarnings("unused") @Deprecated 
+    private static Predicate<CharSequence> startsWithOld(final String prefix) {
         return new Predicate<CharSequence>() {
             @Override
             public boolean apply(CharSequence input) {
@@ -98,8 +204,17 @@ public class StringPredicates {
         };
     }
 
-    /** true if the object *is* a string starting with the given prefix */
+    // -----------------
+    
+    /** true if the object *is* a {@link CharSequence} starting with the given prefix */
     public static Predicate<Object> isStringStartingWith(final String prefix) {
+        return Predicates.<Object>and(Predicates.instanceOf(CharSequence.class),
+            Predicates.compose(new StartsWith<String>(prefix), StringFunctions.toStringFunction()));
+    }
+
+    /** @deprecated since 0.7.0 kept only to allow conversion of anonymous inner classes */
+    @SuppressWarnings("unused") @Deprecated 
+    private static Predicate<Object> isStringStartingWithOld(final String prefix) {
         return new Predicate<Object>() {
             @Override
             public boolean apply(Object input) {
@@ -108,11 +223,13 @@ public class StringPredicates {
         };
     }
 
-    public static Predicate<CharSequence> equalToAny(Iterable<String> vals) {
-        return new EqualToAny<CharSequence>(vals);
+    // ---------------
+    
+    public static <T> Predicate<T> equalToAny(Iterable<T> vals) {
+        return new EqualToAny<T>(vals);
     }
 
-    public static class EqualToAny<T> implements Predicate<T>, Serializable {
+    private static class EqualToAny<T> implements Predicate<T>, Serializable {
         private static final long serialVersionUID = 6209304291945204422L;
         private final Set<T> vals;
         
@@ -129,6 +246,44 @@ public class StringPredicates {
         }
     }
 
-    // TODO globs, matches regex, etc ... add as you need them!
+    // -----------
     
+    public static <T extends CharSequence> Predicate<T> matchesRegex(final String regex) {
+        return new MatchesRegex<T>(regex);
+    }
+
+    protected static class MatchesRegex<T extends CharSequence> implements Predicate<T> {
+        protected final String regex;
+        protected MatchesRegex(String regex) {
+            this.regex = regex;
+        }
+        @Override
+        public boolean apply(CharSequence input) {
+            return (input != null) && input.toString().matches(regex);
+        }
+        @Override
+        public String toString() {
+            return "matchesRegex("+regex+")";
+        }
+    }
+    
+    public static <T extends CharSequence> Predicate<T> matchesGlob(final String glob) {
+        return new MatchesGlob<T>(glob);
+    }
+
+    protected static class MatchesGlob<T extends CharSequence> implements Predicate<T> {
+        protected final String glob;
+        protected MatchesGlob(String glob) {
+            this.glob = glob;
+        }
+        @Override
+        public boolean apply(CharSequence input) {
+            return (input != null) && WildcardGlobs.isGlobMatched(glob, input.toString());
+        }
+        @Override
+        public String toString() {
+            return "matchesGlob("+glob+")";
+        }
+    }
+
 }

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/919eaea0/utils/common/src/test/java/brooklyn/util/text/StringPredicatesTest.java
----------------------------------------------------------------------
diff --git a/utils/common/src/test/java/brooklyn/util/text/StringPredicatesTest.java b/utils/common/src/test/java/brooklyn/util/text/StringPredicatesTest.java
new file mode 100644
index 0000000..26738db
--- /dev/null
+++ b/utils/common/src/test/java/brooklyn/util/text/StringPredicatesTest.java
@@ -0,0 +1,74 @@
+/*
+ * 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 brooklyn.util.text;
+
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+import com.google.common.collect.ImmutableSet;
+
+public class StringPredicatesTest {
+
+    @Test
+    public static void testIsBlank() {
+        Assert.assertTrue(StringPredicates.isBlank().apply(""));
+        Assert.assertTrue(StringPredicates.isBlank().apply(" \n\t"));
+        Assert.assertTrue(StringPredicates.isBlank().apply(null));
+        Assert.assertFalse(StringPredicates.isBlank().apply(" hi "));
+    }
+    
+    @Test
+    public static void testContainsLiteral() {
+        Assert.assertTrue(StringPredicates.containsLiteral("xx").apply("texxxt tessst"));
+        Assert.assertFalse(StringPredicates.containsLiteral("xx").apply("text test"));
+        Assert.assertFalse(StringPredicates.containsLiteral("xx").apply("texXxt tessst"));
+        
+        Assert.assertTrue(StringPredicates.containsLiteralCaseInsensitive("xx").apply("texxxt tessst"));
+        Assert.assertFalse(StringPredicates.containsLiteralCaseInsensitive("xx").apply("text test"));
+        Assert.assertTrue(StringPredicates.containsLiteralCaseInsensitive("xx").apply("texXxt tessst"));
+        
+        Assert.assertTrue(StringPredicates.containsAllLiterals("xx", "ss").apply("texxxt tessst"));
+        Assert.assertFalse(StringPredicates.containsAllLiterals("xx", "tt").apply("texxxt tessst"));
+    }
+    
+    @Test
+    public static void testEqualToAny() {
+        Assert.assertTrue(StringPredicates.equalToAny(ImmutableSet.of("1", "2")).apply("2"));
+        Assert.assertFalse(StringPredicates.equalToAny(ImmutableSet.of("1", "2")).apply("3"));
+    }
+    
+    @Test
+    public static void testStartsWith() {
+        Assert.assertTrue(StringPredicates.startsWith("t").apply("test"));
+        Assert.assertFalse(StringPredicates.startsWith("v").apply("test"));
+        
+        Assert.assertTrue(StringPredicates.isStringStartingWith("t").apply("test"));
+        Assert.assertFalse(StringPredicates.isStringStartingWith("t").apply(true));
+    }
+    
+    @Test
+    public static void testMatches() {
+        Assert.assertTrue(StringPredicates.matchesRegex("t.*").apply("test"));
+        Assert.assertFalse(StringPredicates.matchesRegex("v.*").apply("test"));
+        
+        Assert.assertTrue(StringPredicates.matchesGlob("t*").apply("test"));
+        Assert.assertFalse(StringPredicates.matchesGlob("v*").apply("test"));
+    }
+    
+}


[29/29] git commit: This closes #272

Posted by al...@apache.org.
This closes #272


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

Branch: refs/heads/master
Commit: 5e6b9908eb2a23bb0fa6fed0db874225f7e8d56b
Parents: 1f73dcf 1bb1561
Author: Aled Sage <al...@gmail.com>
Authored: Mon Nov 3 15:51:11 2014 +0000
Committer: Aled Sage <al...@gmail.com>
Committed: Mon Nov 3 15:51:11 2014 +0000

----------------------------------------------------------------------
 .../entity/drivers/DriverDependentEntity.java   |   5 +
 .../brooklyn/entity/proxying/EntitySpec.java    |   7 +-
 .../brooklyn/config/BrooklynProperties.java     |   1 +
 .../brooklyn/entity/basic/AbstractGroup.java    |   3 +-
 .../basic/BasicConfigurableEntityFactory.java   |   2 +
 .../entity/basic/BasicParameterType.java        |   3 -
 .../entity/basic/BasicStartableImpl.java        |   2 +-
 .../java/brooklyn/entity/basic/Entities.java    |  12 +
 .../brooklyn/entity/basic/EntityConfigMap.java  |  22 +-
 .../brooklyn/entity/basic/EntityFactory.java    |   4 +-
 .../brooklyn/entity/basic/EntityPredicates.java | 288 +++++++++++-
 .../java/brooklyn/entity/basic/EntityTasks.java |  81 ++++
 .../java/brooklyn/entity/basic/QuorumCheck.java |   9 +-
 .../entity/basic/ServiceStateLogic.java         |   1 +
 .../brooklyn/entity/effector/EffectorTasks.java |   1 +
 .../entity/group/DynamicClusterImpl.java        |  22 +-
 .../entity/proxying/EntityProxyImpl.java        |   6 +
 .../rebind/PeriodicDeltaChangeListener.java     |   6 +-
 .../brooklyn/entity/trait/StartableMethods.java |   2 +-
 .../event/basic/DependentConfiguration.java     | 471 +++++++++++++++----
 .../event/feed/AttributePollHandler.java        |   2 +-
 .../brooklyn/event/feed/ConfigToAttributes.java |   7 +-
 .../location/basic/BasicLocationRegistry.java   |   4 +-
 .../location/basic/HostLocationResolver.java    |   2 +-
 .../management/internal/LocalEntityManager.java |   7 +-
 .../java/brooklyn/util/config/ConfigBag.java    |   2 +-
 .../util/task/DynamicSequentialTask.java        |   2 +
 .../src/main/java/brooklyn/util/task/Tasks.java |  47 ++
 .../brooklyn/entity/basic/EntityConfigTest.java |  14 +-
 .../entity/basic/EntityPredicatesTest.java      |  13 +-
 .../ReflectiveEntityDriverFactoryTest.java      |   5 +
 .../basic/HostLocationResolverTest.java         |   5 +
 .../test/java/brooklyn/util/task/TasksTest.java |  29 ++
 .../loadbalancing/BalanceableContainer.java     |   4 +-
 .../basic/AbstractSoftwareProcessSshDriver.java |  69 +--
 .../brooklyn/entity/basic/SameServerEntity.java |   1 +
 .../entity/basic/SoftwareProcessImpl.java       |   3 +-
 .../entity/brooklynnode/BrooklynCluster.java    |  65 +++
 .../brooklynnode/BrooklynClusterImpl.java       | 116 +++++
 .../brooklynnode/BrooklynEntityMirrorImpl.java  |   2 -
 .../entity/brooklynnode/BrooklynNode.java       |  53 ++-
 .../entity/brooklynnode/BrooklynNodeDriver.java |   2 +
 .../entity/brooklynnode/BrooklynNodeImpl.java   |  49 +-
 .../brooklynnode/BrooklynNodeSshDriver.java     |  45 +-
 .../brooklynnode/RemoteEffectorBuilder.java     |  23 +-
 .../BrooklynClusterUpgradeEffectorBody.java     | 207 ++++++++
 .../BrooklynNodeUpgradeEffectorBody.java        | 187 ++++++++
 .../effector/SelectMasterEffectorBody.java      | 175 +++++++
 .../SetHighAvailabilityModeEffectorBody.java    |  64 +++
 ...SetHighAvailabilityPriorityEffectorBody.java |  55 +++
 .../software/MachineLifecycleEffectorTasks.java |   6 +-
 .../entity/brooklynnode/brooklyn-cluster.yaml   |  33 ++
 .../brooklyn-node-persisting-to-tmp.yaml        |  27 ++
 .../entity/brooklynnode/brooklyn-node.yaml      |  35 ++
 .../entity/brooklynnode/BrooklynNodeTest.java   |   6 +-
 .../brooklynnode/CallbackEntityHttpClient.java  |  95 ++++
 .../entity/brooklynnode/MockBrooklynNode.java   |  68 +++
 .../brooklynnode/SelectMasterEffectorTest.java  | 257 ++++++++++
 .../nosql/couchbase/CouchbaseClusterImpl.java   |   2 +-
 .../BrooklynAssemblyTemplateInstantiator.java   |   3 +-
 usage/cli/src/main/java/brooklyn/cli/Main.java  |   6 +-
 .../brooklyn/launcher/BrooklynLauncher.java     |  16 +-
 .../brooklyn/launcher/BrooklynWebServer.java    |  18 +-
 .../main/java/brooklyn/rest/api/ServerApi.java  |  23 +-
 .../rest/resources/CatalogResource.java         |   2 +-
 .../brooklyn/rest/resources/ServerResource.java |  26 +-
 .../testing/mocks/NameMatcherGroupImpl.java     |   3 +-
 .../util/collections/CollectionFunctionals.java |  61 +++
 .../brooklyn/util/collections/QuorumCheck.java  | 106 +++++
 .../util/exceptions/NotManagedException.java    |  36 ++
 .../exceptions/RuntimeTimeoutException.java     |  36 ++
 .../src/main/java/brooklyn/util/net/Urls.java   |   6 +
 .../java/brooklyn/util/repeat/Repeater.java     |   9 +
 .../java/brooklyn/util/text/KeyValueParser.java |   3 +-
 .../brooklyn/util/text/StringPredicates.java    | 180 ++++++-
 .../main/java/brooklyn/util/time/Duration.java  |  15 +-
 .../util/text/StringPredicatesTest.java         |  74 +++
 77 files changed, 3080 insertions(+), 279 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/5e6b9908/core/src/main/java/brooklyn/entity/basic/ServiceStateLogic.java
----------------------------------------------------------------------

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/5e6b9908/usage/launcher/src/main/java/brooklyn/launcher/BrooklynWebServer.java
----------------------------------------------------------------------


[20/29] git commit: refactor recent changes so that Repeater tasks are in Tasks and EntityTasks reuse cleaned up DependentCongifuration, with DependentConfiguration now relying on an internal class and: * checking for unmanaged (and now this is the defau

Posted by al...@apache.org.
refactor recent changes so that Repeater tasks are in Tasks and EntityTasks reuse cleaned up DependentCongifuration,
with DependentConfiguration now relying on an internal class and:
* checking for unmanaged (and now this is the default)
* supporting a timeout
* ensuring that all values put into the subscription queue are read (previously it could miss a value if subscriptions were updated twice in succession)


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

Branch: refs/heads/master
Commit: 14f17bc94d76693eadafe7d33a34e4c5fad06ff9
Parents: 7f09a80
Author: Alex Heneveld <al...@cloudsoftcorp.com>
Authored: Tue Oct 28 00:42:36 2014 -0700
Committer: Alex Heneveld <al...@cloudsoftcorp.com>
Committed: Fri Oct 31 09:39:50 2014 -0500

----------------------------------------------------------------------
 .../java/brooklyn/entity/basic/EntityTasks.java |  72 +--
 .../event/basic/DependentConfiguration.java     | 468 +++++++++++++++----
 .../src/main/java/brooklyn/util/task/Tasks.java |  28 +-
 .../entity/basic/EntityPredicatesTest.java      |   4 +-
 .../test/java/brooklyn/util/task/TasksTest.java |  29 ++
 .../BrooklynClusterUpgradeEffectorBody.java     |  16 +-
 .../BrooklynNodeUpgradeEffectorBody.java        |   2 +-
 .../util/collections/CollectionFunctionals.java |   2 +-
 .../util/exceptions/NotManagedException.java    |  36 ++
 .../util/exceptions/TimeoutException.java       |  36 ++
 .../java/brooklyn/util/repeat/Repeater.java     |   4 +
 11 files changed, 547 insertions(+), 150 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/14f17bc9/core/src/main/java/brooklyn/entity/basic/EntityTasks.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/entity/basic/EntityTasks.java b/core/src/main/java/brooklyn/entity/basic/EntityTasks.java
index 37114c5..99c5ca3 100644
--- a/core/src/main/java/brooklyn/entity/basic/EntityTasks.java
+++ b/core/src/main/java/brooklyn/entity/basic/EntityTasks.java
@@ -20,44 +20,62 @@ package brooklyn.entity.basic;
 
 import brooklyn.entity.Entity;
 import brooklyn.event.AttributeSensor;
-import brooklyn.management.TaskAdaptable;
+import brooklyn.event.basic.DependentConfiguration;
+import brooklyn.management.Task;
 import brooklyn.util.collections.CollectionFunctionals;
-import brooklyn.util.guava.Functionals;
-import brooklyn.util.repeat.Repeater;
-import brooklyn.util.task.Tasks;
 import brooklyn.util.time.Duration;
 
 import com.google.common.base.Functions;
 import com.google.common.base.Predicate;
-import com.google.common.collect.Iterables;
+import com.google.common.base.Predicates;
 
 /** Generally useful tasks related to entities */
 public class EntityTasks {
 
     /** creates an (unsubmitted) task which waits for the attribute to satisfy the given predicate,
-     * with an optional timeout */
-    public static <T> TaskAdaptable<Boolean> awaitingAttribute(Entity entity, AttributeSensor<T> sensor, Predicate<T> condition, Duration timeout) {
-        return Tasks.awaitingBuilder(Repeater.create("waiting on "+sensor.getName())
-                .backoff(Duration.millis(10), 1.5, Duration.millis(200))
-                .limitTimeTo(timeout==null ? Duration.PRACTICALLY_FOREVER : timeout)
-//                TODO abort if entity is unmanaged
-                .until(Functionals.callable(Functions.forPredicate(EntityPredicates.attributeSatisfies(sensor, condition)), entity)),
-                true)
-            .description("waiting on "+entity+" "+sensor.getName()+" "+condition+
-                (timeout!=null ? ", timeout "+timeout : "")).build();
+     * returning false if it times out or becomes unmanaged */
+    public static <T> Task<Boolean> testingAttributeEventually(Entity entity, AttributeSensor<T> sensor, Predicate<T> condition, Duration timeout) {
+        return DependentConfiguration.builder().attributeWhenReady(entity, sensor)
+            .readiness(condition)
+            .postProcess(Functions.constant(true))
+            .timeout(timeout)
+            .onTimeoutReturn(false)
+            .onUnmanagedReturn(false)
+            .build();
     }
 
-    /** as {@link #awaitingAttribute(Entity, AttributeSensor, Predicate, Duration)} for multiple entities */
-    public static <T> TaskAdaptable<Boolean> awaitingAttribute(Iterable<Entity> entities, AttributeSensor<T> sensor, Predicate<T> condition, Duration timeout) {
-        return Tasks.awaitingBuilder(Repeater.create("waiting on "+sensor.getName())
-                .backoff(Duration.millis(10), 1.5, Duration.millis(200))
-                .limitTimeTo(timeout==null ? Duration.PRACTICALLY_FOREVER : timeout)
-//                TODO abort if entity is unmanaged
-                .until(Functionals.callable(Functions.forPredicate(
-                    CollectionFunctionals.all(EntityPredicates.attributeSatisfies(sensor, condition))), entities)),
-                true)
-            .description("waiting on "+Iterables.size(entities)+", "+sensor.getName()+" "+condition+
-                (timeout!=null ? ", timeout "+timeout : "")+
-                ": "+entities).build();
+    /** creates an (unsubmitted) task which waits for the attribute to satisfy the given predicate,
+     * throwing if it times out or becomes unmanaged */
+    public static <T> Task<Boolean> requiringAttributeEventually(Entity entity, AttributeSensor<T> sensor, Predicate<T> condition, Duration timeout) {
+        return DependentConfiguration.builder().attributeWhenReady(entity, sensor)
+            .readiness(condition)
+            .postProcess(Functions.constant(true))
+            .timeout(timeout)
+            .onTimeoutThrow()
+            .onUnmanagedThrow()
+            .build();
+    }
+
+    /** as {@link #testingAttributeEventually(Entity, AttributeSensor, Predicate, Duration) for multiple entities */
+    public static <T> Task<Boolean> testingAttributeEventually(Iterable<Entity> entities, AttributeSensor<T> sensor, Predicate<T> condition, Duration timeout) {
+        return DependentConfiguration.builder().attributeWhenReadyFromMultiple(entities, sensor, condition)
+            .postProcess(Functions.constant(true))
+            .timeout(timeout)
+            .onTimeoutReturn(false)
+            .onUnmanagedReturn(false)
+            .postProcessFromMultiple(CollectionFunctionals.all(Predicates.equalTo(true)))
+            .build();
     }
+    
+    /** as {@link #requiringAttributeEventually(Entity, AttributeSensor, Predicate, Duration) for multiple entities */
+    public static <T> Task<Boolean> requiringAttributeEventually(Iterable<Entity> entities, AttributeSensor<T> sensor, Predicate<T> condition, Duration timeout) {
+        return DependentConfiguration.builder().attributeWhenReadyFromMultiple(entities, sensor, condition)
+            .postProcess(Functions.constant(true))
+            .timeout(timeout)
+            .onTimeoutThrow()
+            .onUnmanagedThrow()
+            .postProcessFromMultiple(CollectionFunctionals.all(Predicates.equalTo(true)))
+            .build();
+    }
+
 }

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/14f17bc9/core/src/main/java/brooklyn/event/basic/DependentConfiguration.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/event/basic/DependentConfiguration.java b/core/src/main/java/brooklyn/event/basic/DependentConfiguration.java
index de45514..51af110 100644
--- a/core/src/main/java/brooklyn/event/basic/DependentConfiguration.java
+++ b/core/src/main/java/brooklyn/event/basic/DependentConfiguration.java
@@ -19,18 +19,18 @@
 package brooklyn.event.basic;
 
 import static com.google.common.base.Preconditions.checkNotNull;
-import static com.google.common.base.Preconditions.checkState;
 import groovy.lang.Closure;
 
 import java.util.Arrays;
 import java.util.Collection;
 import java.util.Iterator;
+import java.util.LinkedList;
 import java.util.List;
 import java.util.Map;
 import java.util.concurrent.Callable;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.Semaphore;
-import java.util.concurrent.atomic.AtomicReference;
+import java.util.concurrent.TimeUnit;
 
 import javax.annotation.Nullable;
 
@@ -41,6 +41,7 @@ import brooklyn.config.ConfigKey;
 import brooklyn.entity.Entity;
 import brooklyn.entity.basic.Attributes;
 import brooklyn.entity.basic.BrooklynTaskTags;
+import brooklyn.entity.basic.Entities;
 import brooklyn.entity.basic.EntityInternal;
 import brooklyn.entity.basic.EntityLocal;
 import brooklyn.entity.basic.Lifecycle;
@@ -53,9 +54,15 @@ import brooklyn.management.Task;
 import brooklyn.management.TaskAdaptable;
 import brooklyn.management.TaskFactory;
 import brooklyn.util.GroovyJavaMethods;
+import brooklyn.util.collections.CollectionFunctionals;
+import brooklyn.util.collections.MutableList;
 import brooklyn.util.collections.MutableMap;
 import brooklyn.util.exceptions.CompoundRuntimeException;
 import brooklyn.util.exceptions.Exceptions;
+import brooklyn.util.exceptions.NotManagedException;
+import brooklyn.util.exceptions.TimeoutException;
+import brooklyn.util.guava.Functionals;
+import brooklyn.util.guava.Maybe;
 import brooklyn.util.task.BasicExecutionContext;
 import brooklyn.util.task.BasicTask;
 import brooklyn.util.task.DeferredSupplier;
@@ -63,6 +70,9 @@ import brooklyn.util.task.DynamicTasks;
 import brooklyn.util.task.ParallelTask;
 import brooklyn.util.task.TaskInternal;
 import brooklyn.util.task.Tasks;
+import brooklyn.util.text.Strings;
+import brooklyn.util.time.CountdownTimer;
+import brooklyn.util.time.Duration;
 
 import com.google.common.annotations.Beta;
 import com.google.common.base.Function;
@@ -105,7 +115,7 @@ public class DependentConfiguration {
         return attributeWhenReady(source, sensor, readyPredicate);
     }
     
-    /** returns a {@link Task} which blocks until the given sensor on the given source entity gives a value that satisfies ready, then returns that value;
+    /** returns an unsubmitted {@link Task} which blocks until the given sensor on the given source entity gives a value that satisfies ready, then returns that value;
      * particular useful in Entity configuration where config will block until Tasks have a value
      */
     public static <T> Task<T> attributeWhenReady(final Entity source, final AttributeSensor<T> sensor, final Predicate<? super T> ready) {
@@ -141,11 +151,19 @@ public class DependentConfiguration {
         return attributePostProcessedWhenReady(source, sensor, ready, GroovyJavaMethods.<T,V>functionFromClosure(postProcess));
     }
     
+    @SuppressWarnings("unchecked")
     public static <T,V> Task<V> attributePostProcessedWhenReady(final Entity source, final AttributeSensor<T> sensor, final Predicate<? super T> ready, final Function<? super T,V> postProcess) {
-        Builder<T, T> builder = builder().attributeWhenReady(source, sensor);
+        Builder<T,T> builder1 = DependentConfiguration.builder().attributeWhenReady(source, sensor);
+        // messy generics here to support null postProcess; would be nice to disallow that here
+        Builder<T,V> builder;
+        if (postProcess != null) {
+            builder = builder1.postProcess(postProcess);
+        } else {
+            builder = (Builder<T,V>)builder1;
+        }
         if (ready != null) builder.readiness(ready);
-        if (postProcess != null) builder.postProcess(postProcess);
-        return ((Builder)builder).build();
+        
+        return builder.build();
     }
 
     public static <T> T waitInTaskForAttributeReady(Entity source, AttributeSensor<T> sensor, Predicate<? super T> ready) {
@@ -158,49 +176,83 @@ public class DependentConfiguration {
     }
     
     // TODO would be nice to have an easy semantics for whenServiceUp (cf DynamicWebAppClusterImpl.whenServiceUp)
-    // and TODO would be nice to have it stop when source is unmanaged (with ability to define post-processing)
-    // probably using the builder for both of these...
+    
     public static <T> T waitInTaskForAttributeReady(final Entity source, final AttributeSensor<T> sensor, Predicate<? super T> ready, List<AttributeAndSensorCondition<?>> abortConditions, String blockingDetails) {
+        return new WaitInTaskForAttributeReady<T,T>(source, sensor, ready, abortConditions, blockingDetails).call();
+    }
+    
+    protected static class WaitInTaskForAttributeReady<T,V> implements Callable<V> {
+
+        /* This is a change since before Oct 2014. Previously it would continue to poll,
+         * (maybe finding a different error) if the target entity becomes unmanaged. 
+         * Now it actively checks unmanaged by default, and still throws although it might 
+         * now find a different problem. */
+        private final static boolean DEFAULT_IGNORE_UNMANAGED = false;
         
-        T value = source.getAttribute(sensor);
-        final List<Exception> abortionExceptions = Lists.newCopyOnWriteArrayList();
-
-        // return immediately if either the ready predicate or the abort conditions hold
-        if (ready==null) ready = GroovyJavaMethods.truthPredicate();
-        if (ready.apply(value)) return value;
-        for (AttributeAndSensorCondition abortCondition : abortConditions) {
-            Object abortValue = abortCondition.source.getAttribute(abortCondition.sensor);
-            if (abortCondition.predicate.apply(abortValue)) {
-                abortionExceptions.add(new Exception("Abort due to "+abortCondition.source+" -> "+abortCondition.sensor));
-            }
+        final Entity source;
+        final AttributeSensor<T> sensor;
+        final Predicate<? super T> ready;
+        final List<AttributeAndSensorCondition<?>> abortSensorConditions;
+        final String blockingDetails;
+        final Function<? super T,? extends V> postProcess;
+        final Duration timeout;
+        final Maybe<V> onTimeout;
+        final boolean ignoreUnmanaged;
+        final Maybe<V> onUnmanaged;
+        // TODO onError Continue / Throw / Return(V)
+        
+        protected WaitInTaskForAttributeReady(Builder<T, V> builder) {
+            this.source = builder.source;
+            this.sensor = builder.sensor;
+            this.ready = builder.readiness;
+            this.abortSensorConditions = builder.abortSensorConditions;
+            this.blockingDetails = builder.blockingDetails;
+            this.postProcess = builder.postProcess;
+            this.timeout = builder.timeout;
+            this.onTimeout = builder.onTimeout;
+            this.ignoreUnmanaged = builder.ignoreUnmanaged;
+            this.onUnmanaged = builder.onUnmanaged;
         }
-        if (abortionExceptions.size() > 0) {
-            throw new CompoundRuntimeException("Aborted waiting for ready from "+source+" "+sensor, abortionExceptions);
+        
+        private WaitInTaskForAttributeReady(Entity source, AttributeSensor<T> sensor, Predicate<? super T> ready,
+                List<AttributeAndSensorCondition<?>> abortConditions, String blockingDetails) {
+            this.source = source;
+            this.sensor = sensor;
+            this.ready = ready;
+            this.abortSensorConditions = abortConditions;
+            this.blockingDetails = blockingDetails;
+            
+            this.timeout = Duration.PRACTICALLY_FOREVER;
+            this.onTimeout = Maybe.absent();
+            this.ignoreUnmanaged = DEFAULT_IGNORE_UNMANAGED;
+            this.onUnmanaged = Maybe.absent();
+            this.postProcess = null;
         }
 
-        TaskInternal<?> current = (TaskInternal<?>) Tasks.current();
-        if (current == null) throw new IllegalStateException("Should only be invoked in a running task");
-        Entity entity = BrooklynTaskTags.getTargetOrContextEntity(current);
-        if (entity == null) throw new IllegalStateException("Should only be invoked in a running task with an entity tag; "+
-                current+" has no entity tag ("+current.getStatusDetail(false)+")");
-        final AtomicReference<T> data = new AtomicReference<T>();
-        final Semaphore semaphore = new Semaphore(0); // could use Exchanger
-        SubscriptionHandle subscription = null;
-        List<SubscriptionHandle> abortSubscriptions = Lists.newArrayList();
-        try {
-            subscription = ((EntityInternal)entity).getSubscriptionContext().subscribe(source, sensor, new SensorEventListener<T>() {
-                @Override public void onEvent(SensorEvent<T> event) {
-                    data.set(event.getValue());
-                    semaphore.release();
-                }});
-            for (final AttributeAndSensorCondition abortCondition : abortConditions) {
-                abortSubscriptions.add(((EntityInternal)entity).getSubscriptionContext().subscribe(abortCondition.source, abortCondition.sensor, new SensorEventListener<Object>() {
-                    @Override public void onEvent(SensorEvent<Object> event) {
-                        if (abortCondition.predicate.apply(event.getValue())) {
-                            abortionExceptions.add(new Exception("Abort due to "+abortCondition.source+" -> "+abortCondition.sensor));
-                            semaphore.release();
-                        }
-                    }}));
+        @SuppressWarnings("unchecked")
+        protected V postProcess(T value) {
+            if (this.postProcess!=null) return postProcess.apply(value);
+            // if no post-processing assume the types are correct
+            return (V) value;
+        }
+        
+        protected boolean ready(T value) {
+            if (ready!=null) return ready.apply(value);
+            return GroovyJavaMethods.truth(value);
+        }
+        
+        @SuppressWarnings({ "rawtypes", "unchecked" })
+        @Override
+        public V call() {
+            T value = source.getAttribute(sensor);
+
+            // return immediately if either the ready predicate or the abort conditions hold
+            if (ready(value)) return postProcess(value);
+
+            final List<Exception> abortionExceptions = Lists.newCopyOnWriteArrayList();
+            long start = System.currentTimeMillis();
+            
+            for (AttributeAndSensorCondition abortCondition : abortSensorConditions) {
                 Object abortValue = abortCondition.source.getAttribute(abortCondition.sensor);
                 if (abortCondition.predicate.apply(abortValue)) {
                     abortionExceptions.add(new Exception("Abort due to "+abortCondition.source+" -> "+abortCondition.sensor));
@@ -209,31 +261,101 @@ public class DependentConfiguration {
             if (abortionExceptions.size() > 0) {
                 throw new CompoundRuntimeException("Aborted waiting for ready from "+source+" "+sensor, abortionExceptions);
             }
+
+            TaskInternal<?> current = (TaskInternal<?>) Tasks.current();
+            if (current == null) throw new IllegalStateException("Should only be invoked in a running task");
+            Entity entity = BrooklynTaskTags.getTargetOrContextEntity(current);
+            if (entity == null) throw new IllegalStateException("Should only be invoked in a running task with an entity tag; "+
+                current+" has no entity tag ("+current.getStatusDetail(false)+")");
+            
+            final LinkedList<T> publishedValues = new LinkedList<T>();
+            final Semaphore semaphore = new Semaphore(0); // could use Exchanger
+            SubscriptionHandle subscription = null;
+            List<SubscriptionHandle> abortSubscriptions = Lists.newArrayList();
             
-            value = source.getAttribute(sensor);
-            while (!ready.apply(value)) {
-                String prevBlockingDetails = current.setBlockingDetails(blockingDetails);
-                try {
-                    semaphore.acquire();
-                } finally {
-                    current.setBlockingDetails(prevBlockingDetails);
+            try {
+                subscription = ((EntityInternal)entity).getSubscriptionContext().subscribe(source, sensor, new SensorEventListener<T>() {
+                    @Override public void onEvent(SensorEvent<T> event) {
+                        synchronized (publishedValues) { publishedValues.add(event.getValue()); }
+                        semaphore.release();
+                    }});
+                for (final AttributeAndSensorCondition abortCondition : abortSensorConditions) {
+                    abortSubscriptions.add(((EntityInternal)entity).getSubscriptionContext().subscribe(abortCondition.source, abortCondition.sensor, new SensorEventListener<Object>() {
+                        @Override public void onEvent(SensorEvent<Object> event) {
+                            if (abortCondition.predicate.apply(event.getValue())) {
+                                abortionExceptions.add(new Exception("Abort due to "+abortCondition.source+" -> "+abortCondition.sensor));
+                                semaphore.release();
+                            }
+                        }}));
+                    Object abortValue = abortCondition.source.getAttribute(abortCondition.sensor);
+                    if (abortCondition.predicate.apply(abortValue)) {
+                        abortionExceptions.add(new Exception("Abort due to "+abortCondition.source+" -> "+abortCondition.sensor));
+                    }
                 }
-                
                 if (abortionExceptions.size() > 0) {
                     throw new CompoundRuntimeException("Aborted waiting for ready from "+source+" "+sensor, abortionExceptions);
                 }
-                value = data.get();
-            }
-            if (LOG.isDebugEnabled()) LOG.debug("Attribute-ready for {} in entity {}", sensor, source);
-            return value;
-        } catch (InterruptedException e) {
-            throw Exceptions.propagate(e);
-        } finally {
-            if (subscription != null) {
-                ((EntityInternal)entity).getSubscriptionContext().unsubscribe(subscription);
-            }
-            for (SubscriptionHandle handle : abortSubscriptions) {
-                ((EntityInternal)entity).getSubscriptionContext().unsubscribe(handle);
+
+                CountdownTimer timer = timeout!=null ? timeout.countdownTimer() : null;
+                Duration maxPeriod = Duration.millis(200);
+                Duration nextPeriod = Duration.millis(10);
+                while (true) {
+                    // check the source on initial run (could be done outside the loop) 
+                    // and also (optionally) on each iteration in case it is more recent 
+                    value = source.getAttribute(sensor);
+                    if (ready(value)) break;
+
+                    if (timer!=null) {
+                        if (timer.getDurationRemaining().isShorterThan(nextPeriod)) {
+                            nextPeriod = timer.getDurationRemaining();
+                        }
+                        if (timer.isExpired()) {
+                            if (onTimeout.isPresent()) return onTimeout.get();
+                            throw new TimeoutException("Unsatisfied after "+Duration.sinceUtc(start));
+                        }
+                    }
+
+                    String prevBlockingDetails = current.setBlockingDetails(blockingDetails);
+                    try {
+                        if (semaphore.tryAcquire(nextPeriod.toMilliseconds(), TimeUnit.MILLISECONDS)) {
+                            // immediately release so we are available for the next check
+                            semaphore.release();
+                            // if other permits have been made available (e.g. multiple notifications) drain them all as no point running multiple times
+                            semaphore.drainPermits();
+                        }
+                    } finally {
+                        current.setBlockingDetails(prevBlockingDetails);
+                    }
+
+                    // check any subscribed values which have come in first
+                    while (!publishedValues.isEmpty()) {
+                        synchronized (publishedValues) { value = publishedValues.pop(); }
+                        if (ready(value)) break;
+                    }
+
+                    // if unmanaged then ignore the other abort conditions
+                    if (!ignoreUnmanaged && Entities.isNoLongerManaged(entity)) {
+                        if (onTimeout.isPresent()) return onTimeout.get();
+                        throw new NotManagedException(entity);                        
+                    }
+                    
+                    if (abortionExceptions.size() > 0) {
+                        throw new CompoundRuntimeException("Aborted waiting for ready from "+source+" "+sensor, abortionExceptions);
+                    }
+
+                    nextPeriod = nextPeriod.times(2).maximum(maxPeriod);
+                }
+                if (LOG.isDebugEnabled()) LOG.debug("Attribute-ready for {} in entity {}", sensor, source);
+                return postProcess(value);
+            } catch (InterruptedException e) {
+                throw Exceptions.propagate(e);
+            } finally {
+                if (subscription != null) {
+                    ((EntityInternal)entity).getSubscriptionContext().unsubscribe(subscription);
+                }
+                for (SubscriptionHandle handle : abortSubscriptions) {
+                    ((EntityInternal)entity).getSubscriptionContext().unsubscribe(handle);
+                }
             }
         }
     }
@@ -268,6 +390,7 @@ public class DependentConfiguration {
     }
     
     /** @see #transform(Task, Function) */
+    @SuppressWarnings({ "rawtypes" })
     public static <U,T> Task<T> transform(final Map flags, final TaskAdaptable<U> task, final Function<U,T> transformer) {
         return new BasicTask<T>(flags, new Callable<T>() {
             public T call() throws Exception {
@@ -286,19 +409,23 @@ public class DependentConfiguration {
     }
 
     /** @see #transformMultiple(Function, TaskAdaptable...) */
+    @SuppressWarnings({ "unchecked", "rawtypes" })
     public static <U,T> Task<T> transformMultiple(Closure transformer, TaskAdaptable<U> ...tasks) {
         return transformMultiple(GroovyJavaMethods.functionFromClosure(transformer), tasks);
     }
 
     /** @see #transformMultiple(Function, TaskAdaptable...) */
+    @SuppressWarnings({ "unchecked", "rawtypes" })
     public static <U,T> Task<T> transformMultiple(Map flags, Closure transformer, TaskAdaptable<U> ...tasks) {
         return transformMultiple(flags, GroovyJavaMethods.functionFromClosure(transformer), tasks);
     }
     
     /** @see #transformMultiple(Function, TaskAdaptable...) */
+    @SuppressWarnings({ "rawtypes" })
     public static <U,T> Task<T> transformMultiple(Map flags, final Function<List<U>,T> transformer, TaskAdaptable<U> ...tasks) {
         return transformMultiple(flags, transformer, Arrays.asList(tasks));
     }
+    @SuppressWarnings({ "rawtypes" })
     public static <U,T> Task<T> transformMultiple(Map flags, final Function<List<U>,T> transformer, Collection<? extends TaskAdaptable<U>> tasks) {
         if (tasks.size()==1) {
             return transform(flags, Iterables.getOnlyElement(tasks), new Function<U,T>() {
@@ -411,18 +538,26 @@ public class DependentConfiguration {
     public static class ProtoBuilder {
         /**
          * Will wait for the attribute on the given entity.
-         * If that entity report {@link Lifecycle#ON_FIRE} for its {@link Attributes#SERVICE_STATE} then it will abort. 
+         * If that entity reports {@link Lifecycle#ON_FIRE} for its {@link Attributes#SERVICE_STATE} then it will abort. 
          */
         public <T2> Builder<T2,T2> attributeWhenReady(Entity source, AttributeSensor<T2> sensor) {
-            return new Builder<T2,T2>().attributeWhenReady(source, sensor);
+            return new Builder<T2,T2>(source, sensor).abortIfOnFire();
         }
 
-        /** returns a task for parallel execution returning a list of values of the given sensor list on the given entity, 
+        /**
+         * Will wait for the attribute on the given entity, not aborting when it goes {@link Lifecycle#ON_FIRE}.
+         */
+        public <T2> Builder<T2,T2> attributeWhenReadyAllowingOnFire(Entity source, AttributeSensor<T2> sensor) {
+            return new Builder<T2,T2>(source, sensor);
+        }
+
+        /** Constructs a builder for task for parallel execution returning a list of values of the given sensor list on the given entity, 
          * optionally when the values satisfy a given readiness predicate (defaulting to groovy truth if not supplied) */ 
         @Beta
         public <T> MultiBuilder<T, T, List<T>> attributeWhenReadyFromMultiple(Iterable<? extends Entity> sources, AttributeSensor<T> sensor) {
             return attributeWhenReadyFromMultiple(sources, sensor, GroovyJavaMethods.truthPredicate());
         }
+        /** As {@link #attributeWhenReadyFromMultiple(Iterable, AttributeSensor)} with an explicit readiness test. */
         @Beta
         public <T> MultiBuilder<T, T, List<T>> attributeWhenReadyFromMultiple(Iterable<? extends Entity> sources, AttributeSensor<T> sensor, Predicate<? super T> readiness) {
             return new MultiBuilder<T, T, List<T>>(sources, sensor, readiness);
@@ -432,24 +567,33 @@ public class DependentConfiguration {
     /**
      * Builder for producing variants of attributeWhenReady.
      */
-    @SuppressWarnings({ "unchecked", "rawtypes" })
-    @Beta
     public static class Builder<T,V> {
         protected Entity source;
         protected AttributeSensor<T> sensor;
         protected Predicate<? super T> readiness;
         protected Function<? super T, ? extends V> postProcess;
-        protected List<AttributeAndSensorCondition<?>> abortConditions = Lists.newArrayList();
+        protected List<AttributeAndSensorCondition<?>> abortSensorConditions = Lists.newArrayList();
         protected String blockingDetails;
+        protected Duration timeout;
+        protected Maybe<V> onTimeout;
+        protected  boolean ignoreUnmanaged = WaitInTaskForAttributeReady.DEFAULT_IGNORE_UNMANAGED;
+        protected Maybe<V> onUnmanaged;
+
+        protected Builder(Entity source, AttributeSensor<T> sensor) {
+            this.source = source;
+            this.sensor = sensor;
+        }
         
         /**
          * Will wait for the attribute on the given entity.
-         * If that entity report {@link Lifecycle#ON_FIRE} for its {@link Attributes#SERVICE_STATE_ACTUAL} then it will abort. 
+         * If that entity report {@link Lifecycle#ON_FIRE} for its {@link Attributes#SERVICE_STATE_ACTUAL} then it will abort.
+         * @deprecated since 0.7.0 use {@link DependentConfiguration#builder()} then {@link ProtoBuilder#attributeWhenReady(Entity, AttributeSensor)} then {@link #abortIfOnFire()} 
          */
+        @SuppressWarnings({ "unchecked", "rawtypes" })
         public <T2> Builder<T2,T2> attributeWhenReady(Entity source, AttributeSensor<T2> sensor) {
             this.source = checkNotNull(source, "source");
             this.sensor = (AttributeSensor) checkNotNull(sensor, "sensor");
-            abortIf(source, Attributes.SERVICE_STATE_ACTUAL, Predicates.equalTo(Lifecycle.ON_FIRE));
+            abortIfOnFire();
             return (Builder<T2, T2>) this;
         }
         public Builder<T,V> readiness(Closure<Boolean> val) {
@@ -460,10 +604,12 @@ public class DependentConfiguration {
             this.readiness = checkNotNull(val, "ready");
             return this;
         }
+        @SuppressWarnings({ "unchecked", "rawtypes" })
         public <V2> Builder<T,V2> postProcess(Closure<V2> val) {
             this.postProcess = (Function) GroovyJavaMethods.<T,V2>functionFromClosure(checkNotNull(val, "postProcess"));
             return (Builder<T,V2>) this;
         }
+        @SuppressWarnings({ "unchecked", "rawtypes" })
         public <V2> Builder<T,V2> postProcess(final Function<? super T, V2>  val) {
             this.postProcess = (Function) checkNotNull(val, "postProcess");
             return (Builder<T,V2>) this;
@@ -472,28 +618,71 @@ public class DependentConfiguration {
             return abortIf(source, sensor, GroovyJavaMethods.truthPredicate());
         }
         public <T2> Builder<T,V> abortIf(Entity source, AttributeSensor<T2> sensor, Predicate<? super T2> predicate) {
-            abortConditions.add(new AttributeAndSensorCondition<T2>(source, sensor, predicate));
+            abortSensorConditions.add(new AttributeAndSensorCondition<T2>(source, sensor, predicate));
+            return this;
+        }
+        public Builder<T,V> abortIfOnFire() {
+            abortIf(source, Attributes.SERVICE_STATE_ACTUAL, Predicates.equalTo(Lifecycle.ON_FIRE));
             return this;
         }
         public Builder<T,V> blockingDetails(String val) {
             blockingDetails = val;
             return this;
         }
+        /** specifies an optional timeout; by default it waits forever, or until unmanaged or other abort condition */
+        public Builder<T,V> timeout(Duration val) {
+            timeout = val;
+            return this;
+        }
+        public Builder<T,V> onTimeoutReturn(V val) {
+            onTimeout = Maybe.of(val);
+            return this;
+        }
+        public Builder<T,V> onTimeoutThrow() {
+            onTimeout = Maybe.<V>absent();
+            return this;
+        }
+        public Builder<T,V> onUnmanagedReturn(V val) {
+            onUnmanaged = Maybe.of(val);
+            return this;
+        }
+        public Builder<T,V> onUnmanagedThrow() {
+            onUnmanaged = Maybe.<V>absent();
+            return this;
+        }
+        /** @since 0.7.0 included in case old behaviour of not checking whether the entity is managed is required
+         * (I can't see why it is; polling will likely give errors, once it is unmanaged this will never completed,
+         * and before management the current code will continue, so long as there are no other errors) */ @Deprecated
+        public Builder<T,V> onUnmanagedContinue() {
+            ignoreUnmanaged = true;
+            return this;
+        }
+        /** take advantage of the fact that this builder can build multiple times, allowing subclasses 
+         * to change the source along the way */
+        protected Builder<T,V> source(Entity source) {
+            this.source = source;
+            return this;
+        }
+        /** as {@link #source(Entity)} */
+        @SuppressWarnings({ "unchecked", "rawtypes" })
+        protected Builder<T,V> sensor(AttributeSensor<? extends T> sensor) {
+            this.sensor = (AttributeSensor) sensor;
+            return this;
+        }
         public Task<V> build() {
             validate();
-            return new BasicTask<V>(
-                    MutableMap.of("tag", "attributeWhenReady", "displayName", "retrieving sensor "+sensor.getName()+" from "+source.getDisplayName()), 
-                    new Callable<V>() {
-                        @Override public V call() {
-                            T result = waitInTaskForAttributeReady(source, sensor, readiness, abortConditions, blockingDetails);
-                            return postProcess.apply(result);
-                        }
-                    });
+            
+            return Tasks.<V>builder().dynamic(false)
+                .name("waiting on "+sensor.getName())
+                .description("Waiting on sensor "+sensor.getName()+" from "+source)
+                .tag("attributeWhenReady")
+                .body(new WaitInTaskForAttributeReady<T,V>(this))
+                .build();
         }
+        
         public V runNow() {
             validate();
-            T result = waitInTaskForAttributeReady(source, sensor, readiness, abortConditions, blockingDetails);
-            return postProcess.apply(result);
+            return new WaitInTaskForAttributeReady<T,V>(this).call();
         }
         private void validate() {
             checkNotNull(source, "Entity source");
@@ -509,7 +698,12 @@ public class DependentConfiguration {
     @SuppressWarnings({ "unchecked", "rawtypes" })
     @Beta
     public static class MultiBuilder<T, V, V2> {
-        protected List<AttributeAndSensorCondition<?>> multiSource = Lists.newArrayList();
+        protected final String name;
+        protected final String descriptionBase;
+        protected final Builder<T,V> builder;
+        // if desired, the use of this multiSource could allow different conditions; 
+        // but probably an easier API just for the caller to build the parallel task  
+        protected final List<AttributeAndSensorCondition<?>> multiSource = Lists.newArrayList();
         protected Function<? super List<V>, ? extends V2> postProcessFromMultiple;
         
         /** returns a task for parallel execution returning a list of values of the given sensor list on the given entity, 
@@ -520,34 +714,106 @@ public class DependentConfiguration {
         }
         @Beta
         protected MultiBuilder(Iterable<? extends Entity> sources, AttributeSensor<T> sensor, Predicate<? super T> readiness) {
+            builder = new Builder<T,V>(null, sensor);
+            builder.readiness(readiness);
+            
             for (Entity s : checkNotNull(sources, "sources")) {
-                AttributeAndSensorCondition<T> condition = new AttributeAndSensorCondition<T>(s, sensor, readiness);
-                multiSource.add(condition);
+                multiSource.add(new AttributeAndSensorCondition<T>(s, sensor, readiness));
             }
+            this.name = "waiting on "+sensor.getName();
+            this.descriptionBase = "waiting on "+sensor.getName()+" "+readiness
+                +" from "+Iterables.size(sources)+" entit"+Strings.ies(sources);
         }
+        
+        /** Apply post-processing to the entire list of results */
         public <V2b> MultiBuilder<T, V, V2b> postProcessFromMultiple(final Function<? super List<V>, V2b> val) {
-            this.postProcessFromMultiple = (Function) checkNotNull(val, "postProcess");
+            this.postProcessFromMultiple = (Function) checkNotNull(val, "postProcessFromMulitple");
             return (MultiBuilder<T,V, V2b>) this;
         }
+        /** Apply post-processing to the entire list of results 
+         * See {@link CollectionFunctionals#all(Predicate)} and {@link CollectionFunctionals#quorum(brooklyn.util.collections.QuorumCheck, Predicate)
+         * which allow useful arguments. */
+        public MultiBuilder<T, V, Boolean> postProcessFromMultiple(final Predicate<? super List<V>> val) {
+            return postProcessFromMultiple(Functions.forPredicate(val));
+        }
+        
+        public <V1> MultiBuilder<T, V1, V2> postProcess(Closure<V1> val) {
+            builder.postProcess(val);
+            return (MultiBuilder<T, V1, V2>) this;
+        }
+        public <V1> MultiBuilder<T, V1, V2> postProcess(final Function<? super T, V1>  val) {
+            builder.postProcess(val);
+            return (MultiBuilder<T, V1, V2>) this;
+        }
+        public <T2> MultiBuilder<T, V, V2> abortIf(Entity source, AttributeSensor<T2> sensor) {
+            builder.abortIf(source, sensor);
+            return this;
+        }
+        public <T2> MultiBuilder<T, V, V2> abortIf(Entity source, AttributeSensor<T2> sensor, Predicate<? super T2> predicate) {
+            builder.abortIf(source, sensor, predicate);
+            return this;
+        }
+        public MultiBuilder<T, V, V2> abortIfOnFire() {
+            builder.abortIfOnFire();
+            return this;
+        }
+        public MultiBuilder<T, V, V2> blockingDetails(String val) {
+            builder.blockingDetails(val);
+            return this;
+        }
+        public MultiBuilder<T, V, V2> timeout(Duration val) {
+            builder.timeout(val);
+            return this;
+        }
+        public MultiBuilder<T, V, V2> onTimeoutReturn(V val) {
+            builder.onTimeoutReturn(val);
+            return this;
+        }
+        public MultiBuilder<T, V, V2> onTimeoutThrow() {
+            builder.onTimeoutThrow();
+            return this;
+        }
+        public MultiBuilder<T, V, V2> onUnmanagedReturn(V val) {
+            builder.onUnmanagedReturn(val);
+            return this;
+        }
+        public MultiBuilder<T, V, V2> onUnmanagedThrow() {
+            builder.onUnmanagedThrow();
+            return this;
+        }
+        
         public Task<V2> build() {
-            checkState(multiSource.size() > 0, "Entity sources must be set: multiSource=%s", multiSource);
+            List<Task<V>> tasks = MutableList.of();
+            for (AttributeAndSensorCondition<?> source: multiSource) {
+                builder.source(source.source);
+                builder.sensor((AttributeSensor)source.sensor);
+                builder.readiness((Predicate)source.predicate);
+                tasks.add(builder.build());
+            }
+            final Task<List<V>> parallelTask = Tasks.<List<V>>builder().parallel(true).addAll(tasks)
+                .name(name)
+                .description(descriptionBase+
+                    (builder.timeout!=null ? ", timeout "+builder.timeout : ""))
+                .build();
             
-            // TODO Do we really want to try to support the list-of-entities?
-            final Task<List<V>> task = (Task<List<V>>) new ParallelTask<V>(Iterables.transform(multiSource, new Function<AttributeAndSensorCondition<?>, Task<T>>() {
-                @Override public Task<T> apply(AttributeAndSensorCondition<?> it) {
-                    return (Task) builder().attributeWhenReady(it.source, it.sensor).readiness((Predicate)it.predicate).build();
-                }
-            }));
             if (postProcessFromMultiple == null) {
-                return (Task<V2>) task;
+                // V2 should be the right type in normal operations
+                return (Task<V2>) parallelTask;
             } else {
-                return new BasicTask(new Callable<V2>() {
-                    @Override public V2 call() throws Exception {
-                        List<V> prePostProgress = DynamicTasks.queueIfPossible(task).orSubmitAndBlock().getTask().get();
-                        return postProcessFromMultiple.apply(prePostProgress);
-                    }
-                });
+                return Tasks.<V2>builder().name(name).description(descriptionBase)
+                    .tag("attributeWhenReady")
+                    .body(new Callable<V2>() {
+                        @Override public V2 call() throws Exception {
+                            List<V> prePostProgress = DynamicTasks.queue(parallelTask).get();
+                            return DynamicTasks.queue(
+                                Tasks.<V2>builder().name("post-processing").description("Applying "+postProcessFromMultiple)
+                                    .body(Functionals.<List<V>,V2>callable((Function)postProcessFromMultiple, prePostProgress))
+                                    .build()).get();
+                        }
+                    })
+                    .build();
             }
         }
     }
+    
 }

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/14f17bc9/core/src/main/java/brooklyn/util/task/Tasks.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/util/task/Tasks.java b/core/src/main/java/brooklyn/util/task/Tasks.java
index 0edd07b..fd33985 100644
--- a/core/src/main/java/brooklyn/util/task/Tasks.java
+++ b/core/src/main/java/brooklyn/util/task/Tasks.java
@@ -445,16 +445,28 @@ public class Tasks {
             return false;
         }
     }
-    
-    /** creates an (unsubmitted) task which waits for the given repeater, optionally failing if it does not complete with success */
-    public static TaskAdaptable<Boolean> awaiting(Repeater repeater, boolean requireTrue) {
-        return awaitingBuilder(repeater, requireTrue).build();
+
+    /** @return a {@link TaskBuilder} which tests whether the repeater terminates with success in its configured timeframe,
+     * returning true or false depending on whether repeater succeed */
+    public static TaskBuilder<Boolean> testing(Repeater repeater) {
+        return Tasks.<Boolean>builder().body(new WaitForRepeaterCallable(repeater, false))
+            .name("waiting for condition")
+            .description("Testing whether " + getTimeoutString(repeater) + ": "+repeater.getDescription());
     }
 
-    /** creates a partially instantiated builder which waits for the given repeater, optionally failing if it does not complete with success,
-     * for further task customization and then {@link TaskBuilder#build()} */
-    public static TaskBuilder<Boolean> awaitingBuilder(Repeater repeater, boolean requireTrue) {
-        return Tasks.<Boolean>builder().name(repeater.getDescription()).body(new WaitForRepeaterCallable(repeater, requireTrue));
+    /** @return a {@link TaskBuilder} which requires that the repeater terminate with success in its configured timeframe,
+     * throwing if it does not */
+    public static TaskBuilder<?> requiring(Repeater repeater) {
+        return Tasks.<Boolean>builder().body(new WaitForRepeaterCallable(repeater, true))
+            .name("waiting for condition")
+            .description("Requiring " + getTimeoutString(repeater) + ": "+repeater);
+    }
+    
+    private static String getTimeoutString(Repeater repeater) {
+        Duration timeout = repeater.getTimeLimit();
+        if (timeout==null || Duration.PRACTICALLY_FOREVER.equals(timeout))
+            return "eventually";
+        return "in "+timeout;
     }
 
 }

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/14f17bc9/core/src/test/java/brooklyn/entity/basic/EntityPredicatesTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/brooklyn/entity/basic/EntityPredicatesTest.java b/core/src/test/java/brooklyn/entity/basic/EntityPredicatesTest.java
index b4c144f..9394b85 100644
--- a/core/src/test/java/brooklyn/entity/basic/EntityPredicatesTest.java
+++ b/core/src/test/java/brooklyn/entity/basic/EntityPredicatesTest.java
@@ -110,7 +110,7 @@ public class EntityPredicatesTest extends BrooklynAppUnitTestSupport {
     @Test
     public void testWithLocation() throws Exception {
         entity.addLocations(ImmutableList.of(loc));
-        assertTrue(EntityPredicates.locationsInclude(loc).apply(entity));
-        assertFalse(EntityPredicates.locationsInclude(loc).apply(app));
+        assertTrue(EntityPredicates.locationsIncludes(loc).apply(entity));
+        assertFalse(EntityPredicates.locationsIncludes(loc).apply(app));
     }
 }

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/14f17bc9/core/src/test/java/brooklyn/util/task/TasksTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/brooklyn/util/task/TasksTest.java b/core/src/test/java/brooklyn/util/task/TasksTest.java
index 9ee9970..68aa2ea 100644
--- a/core/src/test/java/brooklyn/util/task/TasksTest.java
+++ b/core/src/test/java/brooklyn/util/task/TasksTest.java
@@ -24,6 +24,7 @@ import static org.testng.Assert.assertEquals;
 import java.util.Map;
 import java.util.Set;
 
+import org.testng.Assert;
 import org.testng.annotations.BeforeMethod;
 import org.testng.annotations.Test;
 
@@ -34,12 +35,14 @@ import brooklyn.management.Task;
 import brooklyn.test.entity.TestApplication;
 import brooklyn.test.entity.TestEntity;
 import brooklyn.util.guava.Functionals;
+import brooklyn.util.repeat.Repeater;
 import brooklyn.util.time.Duration;
 
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Iterables;
+import com.google.common.util.concurrent.Callables;
 
 
 public class TasksTest extends BrooklynAppUnitTestSupport {
@@ -127,4 +130,30 @@ public class TasksTest extends BrooklynAppUnitTestSupport {
         assertResolvesValue(v, Object.class, "foo");
     }
 
+    @Test
+    public void testRepeater() throws Exception {
+        Task<?> t;
+        
+        t = Tasks.requiring(Repeater.create().until(Callables.returning(true)).every(Duration.millis(1))).build();
+        app.getExecutionContext().submit(t);
+        t.get(Duration.ONE_SECOND);
+        
+        t = Tasks.testing(Repeater.create().until(Callables.returning(true)).every(Duration.millis(1))).build();
+        app.getExecutionContext().submit(t);
+        Assert.assertEquals(t.get(Duration.ONE_SECOND), true);
+        
+        t = Tasks.requiring(Repeater.create().until(Callables.returning(false)).limitIterationsTo(2).every(Duration.millis(1))).build();
+        app.getExecutionContext().submit(t);
+        try {
+            t.get(Duration.ONE_SECOND);
+            Assert.fail("Should have failed");
+        } catch (Exception e) {
+            // expected
+        }
+
+        t = Tasks.testing(Repeater.create().until(Callables.returning(false)).limitIterationsTo(2).every(Duration.millis(1))).build();
+        app.getExecutionContext().submit(t);
+        Assert.assertEquals(t.get(Duration.ONE_SECOND), false);
+    }
+
 }

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/14f17bc9/software/base/src/main/java/brooklyn/entity/brooklynnode/effector/BrooklynClusterUpgradeEffectorBody.java
----------------------------------------------------------------------
diff --git a/software/base/src/main/java/brooklyn/entity/brooklynnode/effector/BrooklynClusterUpgradeEffectorBody.java b/software/base/src/main/java/brooklyn/entity/brooklynnode/effector/BrooklynClusterUpgradeEffectorBody.java
index b815ee4..48553b3 100644
--- a/software/base/src/main/java/brooklyn/entity/brooklynnode/effector/BrooklynClusterUpgradeEffectorBody.java
+++ b/software/base/src/main/java/brooklyn/entity/brooklynnode/effector/BrooklynClusterUpgradeEffectorBody.java
@@ -22,14 +22,12 @@ import java.io.File;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.HashSet;
-import java.util.Map;
 import java.util.concurrent.Callable;
 import java.util.concurrent.atomic.AtomicBoolean;
 
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-import brooklyn.config.ConfigKey;
 import brooklyn.entity.Effector;
 import brooklyn.entity.Entity;
 import brooklyn.entity.Group;
@@ -91,9 +89,8 @@ public class BrooklynClusterUpgradeEffectorBody extends EffectorBody<Void> imple
             
             specCfg.putAll(ConfigBag.newInstance(parameters.get(EXTRA_CONFIG)).getAllConfigAsConfigKeyMap());
             
-            Map<ConfigKey<?>, Object> cfgLive = memberSpec.getConfigLive();
-            cfgLive.clear();
-            cfgLive.putAll(specCfg.getAllConfigAsConfigKeyMap());
+            memberSpec.clearConfig();
+            memberSpec.configure(specCfg.getAllConfigAsConfigKeyMap());
             // not necessary, but good practice
             entity().setConfig(BrooklynCluster.MEMBER_SPEC, memberSpec);
             log.debug("Upgrading "+entity()+", new "+BrooklynCluster.MEMBER_SPEC+": "+memberSpec+" / "+specCfg);
@@ -101,9 +98,8 @@ public class BrooklynClusterUpgradeEffectorBody extends EffectorBody<Void> imple
             upgrade(parameters);
         } catch (Exception e) {
             log.debug("Upgrading "+entity()+" failed, will rethrow after restoring "+BrooklynCluster.MEMBER_SPEC+" to: "+origSpecCfg);
-            Map<ConfigKey<?>, Object> cfgLive = memberSpec.getConfigLive();
-            cfgLive.clear();
-            cfgLive.putAll(origSpecCfg.getAllConfigAsConfigKeyMap());
+            memberSpec.clearConfig();
+            memberSpec.configure(origSpecCfg.getAllConfigAsConfigKeyMap());
             // not necessary, but good practice
             entity().setConfig(BrooklynCluster.MEMBER_SPEC, memberSpec);
             
@@ -187,7 +183,7 @@ public class BrooklynClusterUpgradeEffectorBody extends EffectorBody<Void> imple
 
         //2. Wait for them to be RUNNING (or at least STARTING to have completed)
         // (should already be the case, because above is synchronous and, we think, it will fail if start does not succeed)
-        DynamicTasks.queue(EntityTasks.awaitingAttribute(newNodes, Attributes.SERVICE_STATE_ACTUAL, 
+        DynamicTasks.queue(EntityTasks.requiringAttributeEventually(newNodes, Attributes.SERVICE_STATE_ACTUAL, 
                 Predicates.not(Predicates.equalTo(Lifecycle.STARTING)), Duration.minutes(30)));
 
         //3. Set HOT_STANDBY in case it is not enabled on the command line ...
@@ -197,7 +193,7 @@ public class BrooklynClusterUpgradeEffectorBody extends EffectorBody<Void> imple
                 newNodes)).asTask().getUnchecked();
         //... and wait until all of the nodes change state
         // TODO fail quicker if state changes to FAILED
-        DynamicTasks.queue(EntityTasks.awaitingAttribute(newNodes, BrooklynNode.MANAGEMENT_NODE_STATE, 
+        DynamicTasks.queue(EntityTasks.requiringAttributeEventually(newNodes, BrooklynNode.MANAGEMENT_NODE_STATE, 
                 Predicates.equalTo(ManagementNodeState.HOT_STANDBY), Duration.FIVE_MINUTES));
 
         //5. Just in case check if all of the nodes are SERVICE_UP (which would rule out ON_FIRE as well)

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/14f17bc9/software/base/src/main/java/brooklyn/entity/brooklynnode/effector/BrooklynNodeUpgradeEffectorBody.java
----------------------------------------------------------------------
diff --git a/software/base/src/main/java/brooklyn/entity/brooklynnode/effector/BrooklynNodeUpgradeEffectorBody.java b/software/base/src/main/java/brooklyn/entity/brooklynnode/effector/BrooklynNodeUpgradeEffectorBody.java
index 299bd5b..00ab7bc 100644
--- a/software/base/src/main/java/brooklyn/entity/brooklynnode/effector/BrooklynNodeUpgradeEffectorBody.java
+++ b/software/base/src/main/java/brooklyn/entity/brooklynnode/effector/BrooklynNodeUpgradeEffectorBody.java
@@ -128,7 +128,7 @@ public class BrooklynNodeUpgradeEffectorBody extends EffectorBody<Void> {
         DynamicTasks.queue(Effectors.invocation(dryRunChild, BrooklynNode.START, ConfigBag.EMPTY));
 
         // 2 confirm hot standby status
-        DynamicTasks.queue(EntityTasks.awaitingAttribute(dryRunChild, BrooklynNode.MANAGEMENT_NODE_STATE, 
+        DynamicTasks.queue(EntityTasks.requiringAttributeEventually(dryRunChild, BrooklynNode.MANAGEMENT_NODE_STATE, 
             Predicates.equalTo(ManagementNodeState.HOT_STANDBY), Duration.FIVE_MINUTES));
 
         // 3 stop new version

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/14f17bc9/utils/common/src/main/java/brooklyn/util/collections/CollectionFunctionals.java
----------------------------------------------------------------------
diff --git a/utils/common/src/main/java/brooklyn/util/collections/CollectionFunctionals.java b/utils/common/src/main/java/brooklyn/util/collections/CollectionFunctionals.java
index a42091e..9ac7202 100644
--- a/utils/common/src/main/java/brooklyn/util/collections/CollectionFunctionals.java
+++ b/utils/common/src/main/java/brooklyn/util/collections/CollectionFunctionals.java
@@ -181,7 +181,7 @@ public class CollectionFunctionals {
     // ---------
     
     public static <T,TT extends Iterable<T>> Predicate<TT> all(Predicate<T> attributeSatisfies) {
-        return new QuorumSatisfies<T, TT>(QuorumChecks.all(), attributeSatisfies);
+        return quorum(QuorumChecks.all(), attributeSatisfies);
     }
 
     public static <T,TT extends Iterable<T>> Predicate<TT> quorum(QuorumCheck quorumCheck, Predicate<T> attributeSatisfies) {

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/14f17bc9/utils/common/src/main/java/brooklyn/util/exceptions/NotManagedException.java
----------------------------------------------------------------------
diff --git a/utils/common/src/main/java/brooklyn/util/exceptions/NotManagedException.java b/utils/common/src/main/java/brooklyn/util/exceptions/NotManagedException.java
new file mode 100644
index 0000000..9550288
--- /dev/null
+++ b/utils/common/src/main/java/brooklyn/util/exceptions/NotManagedException.java
@@ -0,0 +1,36 @@
+/*
+ * 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 brooklyn.util.exceptions;
+
+public class NotManagedException extends IllegalStateException {
+
+    private static final long serialVersionUID = -3359163414517503809L;
+
+    public NotManagedException(Object object) {
+        super(object+" is not managed");
+    }
+    
+    public NotManagedException(String message) {
+        super(message);
+    }
+    
+    public NotManagedException(String message, Throwable cause) {
+        super(message, cause);
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/14f17bc9/utils/common/src/main/java/brooklyn/util/exceptions/TimeoutException.java
----------------------------------------------------------------------
diff --git a/utils/common/src/main/java/brooklyn/util/exceptions/TimeoutException.java b/utils/common/src/main/java/brooklyn/util/exceptions/TimeoutException.java
new file mode 100644
index 0000000..c31512f
--- /dev/null
+++ b/utils/common/src/main/java/brooklyn/util/exceptions/TimeoutException.java
@@ -0,0 +1,36 @@
+/*
+ * 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 brooklyn.util.exceptions;
+
+public class TimeoutException extends IllegalStateException {
+
+    private static final long serialVersionUID = -3359163414517503809L;
+
+    public TimeoutException() {
+        super("timeout");
+    }
+    
+    public TimeoutException(String message) {
+        super(message);
+    }
+    
+    public TimeoutException(String message, Throwable cause) {
+        super(message, cause);
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/14f17bc9/utils/common/src/main/java/brooklyn/util/repeat/Repeater.java
----------------------------------------------------------------------
diff --git a/utils/common/src/main/java/brooklyn/util/repeat/Repeater.java b/utils/common/src/main/java/brooklyn/util/repeat/Repeater.java
index 1e3e188..bd76ea8 100644
--- a/utils/common/src/main/java/brooklyn/util/repeat/Repeater.java
+++ b/utils/common/src/main/java/brooklyn/util/repeat/Repeater.java
@@ -386,5 +386,9 @@ public class Repeater {
     public String getDescription() {
         return description;
     }
+
+    public Duration getTimeLimit() {
+        return timeLimit;
+    }
     
 }


[12/29] git commit: fix swallowed error in dynamic cluster

Posted by al...@apache.org.
fix swallowed error in dynamic cluster


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

Branch: refs/heads/master
Commit: e1cf3b8b2bb09927e6a9e2bafc38bee94af01e73
Parents: 7bae4e6
Author: Alex Heneveld <al...@cloudsoftcorp.com>
Authored: Sat Oct 25 01:45:36 2014 +0100
Committer: Alex Heneveld <al...@cloudsoftcorp.com>
Committed: Fri Oct 31 09:38:19 2014 -0500

----------------------------------------------------------------------
 .../entity/group/DynamicClusterImpl.java        | 22 ++++++++------------
 1 file changed, 9 insertions(+), 13 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/e1cf3b8b/core/src/main/java/brooklyn/entity/group/DynamicClusterImpl.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/entity/group/DynamicClusterImpl.java b/core/src/main/java/brooklyn/entity/group/DynamicClusterImpl.java
index 195348f..bccdf35 100644
--- a/core/src/main/java/brooklyn/entity/group/DynamicClusterImpl.java
+++ b/core/src/main/java/brooklyn/entity/group/DynamicClusterImpl.java
@@ -23,7 +23,6 @@ import static com.google.common.base.Preconditions.checkNotNull;
 
 import java.util.Collection;
 import java.util.Collections;
-import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
 import java.util.NoSuchElementException;
@@ -294,8 +293,8 @@ public class DynamicClusterImpl extends AbstractGroupImpl implements DynamicClus
 
         int initialSize = getConfig(INITIAL_SIZE).intValue();
         int initialQuorumSize = getInitialQuorumSize();
+        Exception internalError = null;
 
-        Exception resizeException = null;
         try {
             resize(initialSize);
         } catch (Exception e) {
@@ -304,18 +303,12 @@ public class DynamicClusterImpl extends AbstractGroupImpl implements DynamicClus
             // But if it was this thread that threw the exception (rather than a sub-task), then need
             // to record that failure here.
             LOG.debug("Error resizing "+this+" to size "+initialSize+" (collecting and handling): "+e, e);
-            resizeException = e;
+            internalError = e;
         }
 
         Iterable<Task<?>> failed = Tasks.failed(Tasks.children(Tasks.current()));
-        Iterator<Task<?>> fi = failed.iterator();
-        boolean noFailed=true, severalFailed=false;
-        if (fi.hasNext()) {
-            noFailed = false;
-            fi.next();
-            if (fi.hasNext())
-                severalFailed = true;
-        }
+        boolean noFailed = Iterables.isEmpty(failed);
+        boolean severalFailed = Iterables.size(failed) > 1;
 
         int currentSize = getCurrentSize().intValue();
         if (currentSize < initialQuorumSize) {
@@ -331,14 +324,17 @@ public class DynamicClusterImpl extends AbstractGroupImpl implements DynamicClus
                     + (initialQuorumSize != initialSize ? " (initial quorum size is " + initialQuorumSize + ")" : "");
             }
             Throwable firstError = Tasks.getError(Maybe.next(failed.iterator()).orNull());
+            if (firstError==null && internalError!=null) {
+                // only use the internal error if there were no nested task failures
+                // (otherwise the internal error should be a wrapper around the nested failures)
+                firstError = internalError;
+            }
             if (firstError!=null) {
                 if (severalFailed) {
                     message += "; first failure is: "+Exceptions.collapseText(firstError);
                 } else {
                     message += ": "+Exceptions.collapseText(firstError);
                 }
-            } else {
-                firstError = resizeException;
             }
             throw new IllegalStateException(message, firstError);
             


[09/29] git commit: clean up process of setting up the new-version cluster

Posted by al...@apache.org.
clean up process of setting up the new-version cluster


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

Branch: refs/heads/master
Commit: 4aa2cc4e5a486207981e59db40c47f4cf9c49a6f
Parents: e1cf3b8
Author: Alex Heneveld <al...@cloudsoftcorp.com>
Authored: Sat Oct 25 01:46:19 2014 +0100
Committer: Alex Heneveld <al...@cloudsoftcorp.com>
Committed: Fri Oct 31 09:38:19 2014 -0500

----------------------------------------------------------------------
 .../brooklynnode/BrooklynClusterImpl.java       |  7 +++-
 .../entity/brooklynnode/brooklyn-cluster.yaml   | 32 ++++++++++++++++++
 .../brooklyn-node-persisting-to-tmp.yaml        | 26 +++++++++++++++
 .../entity/brooklynnode/brooklyn-node.yaml      | 35 ++++++++++++++++++++
 .../entity/brooklynnode/brooklyn-node.yaml      | 34 -------------------
 .../BrooklynAssemblyTemplateInstantiator.java   |  3 +-
 6 files changed, 100 insertions(+), 37 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/4aa2cc4e/software/base/src/main/java/brooklyn/entity/brooklynnode/BrooklynClusterImpl.java
----------------------------------------------------------------------
diff --git a/software/base/src/main/java/brooklyn/entity/brooklynnode/BrooklynClusterImpl.java b/software/base/src/main/java/brooklyn/entity/brooklynnode/BrooklynClusterImpl.java
index dc3ee17..0d1afb6 100644
--- a/software/base/src/main/java/brooklyn/entity/brooklynnode/BrooklynClusterImpl.java
+++ b/software/base/src/main/java/brooklyn/entity/brooklynnode/BrooklynClusterImpl.java
@@ -24,6 +24,7 @@ import java.util.concurrent.Callable;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import brooklyn.config.render.RendererHints;
 import brooklyn.entity.Entity;
 import brooklyn.entity.basic.EntityPredicates;
 import brooklyn.entity.basic.ServiceStateLogic;
@@ -46,7 +47,11 @@ public class BrooklynClusterImpl extends DynamicClusterImpl implements BrooklynC
 
     private static final Logger LOG = LoggerFactory.getLogger(BrooklynClusterImpl.class);
 
-    //TODO set MEMBER_SPEC
+    static {
+        RendererHints.register(MASTER_NODE, RendererHints.namedActionWithUrl());
+    }
+
+    // TODO should we set a default MEMBER_SPEC ?  difficult though because we'd need to set a password
 
     @SuppressWarnings("unused")
     private FunctionFeed scanMaster;

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/4aa2cc4e/software/base/src/main/resources/brooklyn/entity/brooklynnode/brooklyn-cluster.yaml
----------------------------------------------------------------------
diff --git a/software/base/src/main/resources/brooklyn/entity/brooklynnode/brooklyn-cluster.yaml b/software/base/src/main/resources/brooklyn/entity/brooklynnode/brooklyn-cluster.yaml
new file mode 100644
index 0000000..f25a879
--- /dev/null
+++ b/software/base/src/main/resources/brooklyn/entity/brooklynnode/brooklyn-cluster.yaml
@@ -0,0 +1,32 @@
+#
+# 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.
+#
+
+name: Example Brooklyn Cluster
+
+services:
+- type: brooklyn.entity.brooklynnode.BrooklynCluster
+  initialSize: 2
+  memberSpec:
+    $brooklyn:entitySpec: 
+#      type: classpath://brooklyn/entity/brooklynnode/brooklyn-node-persisting-to-tmp.yaml
+      type: brooklyn.entity.brooklynnode.BrooklynNode
+      brooklyn.config:
+        brooklynnode.launch.parameters.extra: --persist auto --persistenceDir /tmp/brooklyn-persistence-example/
+
+# location: localhost

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/4aa2cc4e/software/base/src/main/resources/brooklyn/entity/brooklynnode/brooklyn-node-persisting-to-tmp.yaml
----------------------------------------------------------------------
diff --git a/software/base/src/main/resources/brooklyn/entity/brooklynnode/brooklyn-node-persisting-to-tmp.yaml b/software/base/src/main/resources/brooklyn/entity/brooklynnode/brooklyn-node-persisting-to-tmp.yaml
new file mode 100644
index 0000000..a932e4e
--- /dev/null
+++ b/software/base/src/main/resources/brooklyn/entity/brooklynnode/brooklyn-node-persisting-to-tmp.yaml
@@ -0,0 +1,26 @@
+#
+# 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.
+#
+
+name: Example Persisting Brooklyn Node
+
+services:
+- type: classpath://brooklyn/entity/brooklynnode/brooklyn-node.yaml
+  launchParameters: --persist auto --persistenceDir /tmp/brooklyn-persistence-example/
+
+# location: localhost

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/4aa2cc4e/software/base/src/main/resources/brooklyn/entity/brooklynnode/brooklyn-node.yaml
----------------------------------------------------------------------
diff --git a/software/base/src/main/resources/brooklyn/entity/brooklynnode/brooklyn-node.yaml b/software/base/src/main/resources/brooklyn/entity/brooklynnode/brooklyn-node.yaml
new file mode 100644
index 0000000..55d09f6
--- /dev/null
+++ b/software/base/src/main/resources/brooklyn/entity/brooklynnode/brooklyn-node.yaml
@@ -0,0 +1,35 @@
+#
+# 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.
+#
+
+name: Example Brooklyn Node
+
+services:
+- type: brooklyn.entity.brooklynnode.BrooklynNode
+
+  ## to use a local file, specify something such as the following:   [BROOKLYN_VERSION_BELOW] 
+  # downloadUrl: file:///tmp/brooklyn-dist-0.7.0-SNAPSHOT-dist.tar.gz
+
+  ## if deploying to anything other than localhost you must also configure login details, e.g.:
+  # managementUsername: admin
+  # managementPassword: p4ssw0rd
+  # brooklynLocalPropertiesContents: |
+  #   brooklyn.webconsole.security.users=admin
+  #   brooklyn.webconsole.security.user.admin.password=p4ssw0rd
+
+# location: localhost

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/4aa2cc4e/software/base/src/test/resources/brooklyn/entity/brooklynnode/brooklyn-node.yaml
----------------------------------------------------------------------
diff --git a/software/base/src/test/resources/brooklyn/entity/brooklynnode/brooklyn-node.yaml b/software/base/src/test/resources/brooklyn/entity/brooklynnode/brooklyn-node.yaml
deleted file mode 100644
index bd91f77..0000000
--- a/software/base/src/test/resources/brooklyn/entity/brooklynnode/brooklyn-node.yaml
+++ /dev/null
@@ -1,34 +0,0 @@
-#
-# 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.
-#
-
-name: Example Brooklyn Node
-
-services:
-- type: brooklyn.entity.brooklynnode.BrooklynNode
-
-  ## to use a local file, specify something such as the following:
-  downloadUrl: file:///Users/alex/.m2/repository/org/apache/brooklyn/brooklyn-dist/0.7.0-SNAPSHOT/brooklyn-dist-0.7.0-SNAPSHOT-dist.tar.gz
-  # downloadUrl: file:///tmp/brooklyn-dist-0.7.0-SNAPSHOT-dist.tar.gz
-  
-  ## to persist
-#  launchParameters: --persist auto --persistenceDir /tmp/brooklyn-persistence-example/
-
-
-## NB if deploying to a remote machine you must also supply management{Username,Password} and a brooklyn properties with those values set 
-location: localhost

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/4aa2cc4e/usage/camp/src/main/java/io/brooklyn/camp/brooklyn/spi/creation/BrooklynAssemblyTemplateInstantiator.java
----------------------------------------------------------------------
diff --git a/usage/camp/src/main/java/io/brooklyn/camp/brooklyn/spi/creation/BrooklynAssemblyTemplateInstantiator.java b/usage/camp/src/main/java/io/brooklyn/camp/brooklyn/spi/creation/BrooklynAssemblyTemplateInstantiator.java
index 5f594c4..77d55f5 100644
--- a/usage/camp/src/main/java/io/brooklyn/camp/brooklyn/spi/creation/BrooklynAssemblyTemplateInstantiator.java
+++ b/usage/camp/src/main/java/io/brooklyn/camp/brooklyn/spi/creation/BrooklynAssemblyTemplateInstantiator.java
@@ -32,7 +32,6 @@ import java.io.InputStreamReader;
 import java.io.Reader;
 import java.io.StringReader;
 import java.util.List;
-import java.util.Map;
 import java.util.Map.Entry;
 import java.util.Set;
 
@@ -42,8 +41,8 @@ import org.slf4j.LoggerFactory;
 import brooklyn.camp.brooklyn.api.AssemblyTemplateSpecInstantiator;
 import brooklyn.camp.brooklyn.api.HasBrooklynManagementContext;
 import brooklyn.catalog.CatalogItem;
-import brooklyn.catalog.internal.CatalogUtils;
 import brooklyn.catalog.internal.BasicBrooklynCatalog.BrooklynLoaderTracker;
+import brooklyn.catalog.internal.CatalogUtils;
 import brooklyn.config.BrooklynServerConfig;
 import brooklyn.entity.Application;
 import brooklyn.entity.Entity;


[19/29] git commit: code review for brooklyn upgrade - tidy, fix tests, better state detection

Posted by al...@apache.org.
code review for brooklyn upgrade - tidy, fix tests, better state detection


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

Branch: refs/heads/master
Commit: d3f6e34257091abca114eb59bcb148f1a7308ff8
Parents: 14f17bc
Author: Alex Heneveld <al...@cloudsoftcorp.com>
Authored: Tue Oct 28 00:42:23 2014 -0700
Committer: Alex Heneveld <al...@cloudsoftcorp.com>
Committed: Fri Oct 31 09:39:50 2014 -0500

----------------------------------------------------------------------
 .../brooklyn/entity/proxying/EntitySpec.java    |  12 +-
 .../java/brooklyn/entity/basic/Entities.java    |  12 +
 .../brooklyn/entity/basic/EntityPredicates.java |   8 +-
 .../entity/proxying/EntityProxyImpl.java        |   6 +
 .../event/feed/AttributePollHandler.java        |   2 +-
 .../util/task/DynamicSequentialTask.java        |   2 +
 .../brooklynnode/BrooklynClusterImpl.java       |   9 +-
 .../entity/brooklynnode/BrooklynNodeImpl.java   |  10 +-
 .../brooklynnode/BrooklynNodeSshDriver.java     |   4 +-
 .../BrooklynClusterUpgradeEffectorBody.java     |  64 ++---
 .../BrooklynNodeUpgradeEffectorBody.java        |   2 +-
 .../effector/SelectMasterEffectorBody.java      |  28 +-
 .../entity/brooklynnode/brooklyn-cluster.yaml   |   1 +
 .../entity/brooklynnode/BrooklynNodeTest.java   |   6 +-
 .../brooklynnode/CallbackEntityHttpClient.java  |  95 +++++++
 .../entity/brooklynnode/MockBrooklynNode.java   |  68 +++++
 .../brooklynnode/SelectMasterEffectorTest.java  | 257 ++++++++++++++++++
 .../effector/CallbackEntityHttpClient.java      |  95 -------
 .../effector/SelectMasterEffectorTest.java      | 267 -------------------
 .../brooklynnode/effector/TestHttpEntity.java   |  66 -----
 .../rest/resources/CatalogResource.java         |   2 +-
 .../brooklyn/util/text/StringPredicates.java    |  23 +-
 .../util/text/StringPredicatesTest.java         |   6 +-
 23 files changed, 527 insertions(+), 518 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/d3f6e342/api/src/main/java/brooklyn/entity/proxying/EntitySpec.java
----------------------------------------------------------------------
diff --git a/api/src/main/java/brooklyn/entity/proxying/EntitySpec.java b/api/src/main/java/brooklyn/entity/proxying/EntitySpec.java
index 7d65121..1b22387 100644
--- a/api/src/main/java/brooklyn/entity/proxying/EntitySpec.java
+++ b/api/src/main/java/brooklyn/entity/proxying/EntitySpec.java
@@ -42,7 +42,6 @@ import brooklyn.policy.EnricherSpec;
 import brooklyn.policy.Policy;
 import brooklyn.policy.PolicySpec;
 
-import com.google.common.annotations.Beta;
 import com.google.common.base.Supplier;
 import com.google.common.base.Throwables;
 import com.google.common.collect.Iterables;
@@ -102,7 +101,7 @@ public class EntitySpec<T extends Entity> extends AbstractBrooklynObjectSpec<T,E
     }
     
     /**
-     * Wraps an entity spec so its configuration can be overridden without modifying the 
+     * Copies entity spec so its configuration can be overridden without modifying the 
      * original entity spec.
      */
     public static <T extends Entity> EntitySpec<T> create(EntitySpec<T> spec) {
@@ -230,11 +229,10 @@ public class EntitySpec<T extends Entity> extends AbstractBrooklynObjectSpec<T,E
     public Map<ConfigKey<?>, Object> getConfig() {
         return Collections.unmodifiableMap(config);
     }
-    
-    /** @return Live instance of the config map, for instance in case mass clearances are desired */
-    @Beta
-    public Map<ConfigKey<?>, Object> getConfigLive() {
-        return config;
+
+    /** Clears the config map, removing any config previously set. */
+    public void clearConfig() {
+        config.clear();
     }
         
     public List<PolicySpec<?>> getPolicySpecs() {

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/d3f6e342/core/src/main/java/brooklyn/entity/basic/Entities.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/entity/basic/Entities.java b/core/src/main/java/brooklyn/entity/basic/Entities.java
index 77a676d..7d17b7a 100644
--- a/core/src/main/java/brooklyn/entity/basic/Entities.java
+++ b/core/src/main/java/brooklyn/entity/basic/Entities.java
@@ -22,6 +22,7 @@ import java.io.Closeable;
 import java.io.IOException;
 import java.io.PrintWriter;
 import java.io.Writer;
+import java.lang.reflect.Proxy;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
@@ -51,6 +52,7 @@ import brooklyn.entity.Group;
 import brooklyn.entity.drivers.EntityDriver;
 import brooklyn.entity.drivers.downloads.DownloadResolver;
 import brooklyn.entity.effector.Effectors;
+import brooklyn.entity.proxying.EntityProxyImpl;
 import brooklyn.entity.trait.Startable;
 import brooklyn.entity.trait.StartableMethods;
 import brooklyn.event.AttributeSensor;
@@ -91,6 +93,7 @@ import brooklyn.util.task.system.SystemTasks;
 import brooklyn.util.time.Duration;
 
 import com.google.common.annotations.Beta;
+import com.google.common.annotations.VisibleForTesting;
 import com.google.common.base.Function;
 import com.google.common.base.Preconditions;
 import com.google.common.base.Predicate;
@@ -768,6 +771,15 @@ public class Entities {
         return ((EntityInternal)e).getManagementSupport().isReadOnly();
     }
 
+    /** Unwraps a proxy to retrieve the real item, if available.
+     * <p>
+     * Only intended for use in tests. For normal operations, callers should ensure the method is
+     * available on an interface and accessed via the proxy. */
+    @Beta @VisibleForTesting
+    public static AbstractEntity deproxy(Entity e) {
+        return (AbstractEntity) ((EntityProxyImpl)Proxy.getInvocationHandler(e)).getDelegate();
+    }
+    
     /**
      * Brings this entity under management only if its ancestor is managed.
      * <p>

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/d3f6e342/core/src/main/java/brooklyn/entity/basic/EntityPredicates.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/entity/basic/EntityPredicates.java b/core/src/main/java/brooklyn/entity/basic/EntityPredicates.java
index b81b835..12b656d 100644
--- a/core/src/main/java/brooklyn/entity/basic/EntityPredicates.java
+++ b/core/src/main/java/brooklyn/entity/basic/EntityPredicates.java
@@ -343,7 +343,7 @@ public class EntityPredicates {
      * Create a predicate that matches any entity who has an exact match for the given location
      * (i.e. {@code entity.getLocations().contains(location)}).
      */
-    public static <T> Predicate<Entity> locationsInclude(Location location) {
+    public static <T> Predicate<Entity> locationsIncludes(Location location) {
         return locationsSatisfy(CollectionFunctionals.contains(location));
         
     }
@@ -367,13 +367,13 @@ public class EntityPredicates {
         }
     }
 
-    /** @deprecated since 0.7.0 use {@link #locationsInclude(Location)} */
+    /** @deprecated since 0.7.0 use {@link #locationsIncludes(Location)} */
     @Deprecated 
     public static <T> Predicate<Entity> withLocation(final Location location) {
-        return locationsInclude(location);
+        return locationsIncludes(location);
     }
     
-    /** @deprecated since 0.7.0 use {@link #locationsInclude(Location)}, introduced to allow deserialization of anonymous inner class */
+    /** @deprecated since 0.7.0 use {@link #locationsIncludes(Location)}, introduced to allow deserialization of anonymous inner class */
     @SuppressWarnings("unused") @Deprecated 
     private static <T> Predicate<Entity> withLocationOld(final Location location) {
         return new SerializablePredicate<Entity>() {

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/d3f6e342/core/src/main/java/brooklyn/entity/proxying/EntityProxyImpl.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/entity/proxying/EntityProxyImpl.java b/core/src/main/java/brooklyn/entity/proxying/EntityProxyImpl.java
index 19188ab..4163204 100644
--- a/core/src/main/java/brooklyn/entity/proxying/EntityProxyImpl.java
+++ b/core/src/main/java/brooklyn/entity/proxying/EntityProxyImpl.java
@@ -48,6 +48,7 @@ import brooklyn.util.config.ConfigBag;
 import brooklyn.util.task.DynamicTasks;
 import brooklyn.util.task.TaskTags;
 
+import com.google.common.annotations.VisibleForTesting;
 import com.google.common.base.Objects;
 import com.google.common.collect.Sets;
 
@@ -252,6 +253,11 @@ public class EntityProxyImpl implements java.lang.reflect.InvocationHandler {
         }
     }
     
+    @VisibleForTesting
+    public Entity getDelegate() {
+        return delegate;
+    }
+    
     @Override
     public boolean equals(Object obj) {
         return delegate.equals(obj);

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/d3f6e342/core/src/main/java/brooklyn/event/feed/AttributePollHandler.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/event/feed/AttributePollHandler.java b/core/src/main/java/brooklyn/event/feed/AttributePollHandler.java
index 7252d20..19538b3 100644
--- a/core/src/main/java/brooklyn/event/feed/AttributePollHandler.java
+++ b/core/src/main/java/brooklyn/event/feed/AttributePollHandler.java
@@ -196,7 +196,7 @@ public class AttributePollHandler<V> implements PollHandler<V> {
 
     @SuppressWarnings("unchecked")
     protected void setSensor(Object v) {
-        if (!Entities.isManaged(entity)) {
+        if (Entities.isNoLongerManaged(entity)) {
             if (Tasks.isInterrupted()) return;
             log.warn(""+entity+" is not managed; feed "+this+" setting "+sensor+" to "+v+" at this time is not supported ("+Tasks.current()+")");
         }

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/d3f6e342/core/src/main/java/brooklyn/util/task/DynamicSequentialTask.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/util/task/DynamicSequentialTask.java b/core/src/main/java/brooklyn/util/task/DynamicSequentialTask.java
index 266921b..34d8912 100644
--- a/core/src/main/java/brooklyn/util/task/DynamicSequentialTask.java
+++ b/core/src/main/java/brooklyn/util/task/DynamicSequentialTask.java
@@ -138,6 +138,8 @@ public class DynamicSequentialTask<T> extends BasicTask<T> implements HasTaskChi
         synchronized (jobTransitionLock) {
             if (primaryFinished)
                 throw new IllegalStateException("Cannot add a task to "+this+" which is already finished (trying to add "+t+")");
+            if (secondaryQueueAborted)
+                throw new IllegalStateException("Cannot add a task to "+this+" whose queue has been aborted (trying to add "+t+")");
             secondaryJobsAll.add(t);
             secondaryJobsRemaining.add(t);
             BrooklynTaskTags.addTagsDynamically(t, ManagementContextInternal.SUB_TASK_TAG);

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/d3f6e342/software/base/src/main/java/brooklyn/entity/brooklynnode/BrooklynClusterImpl.java
----------------------------------------------------------------------
diff --git a/software/base/src/main/java/brooklyn/entity/brooklynnode/BrooklynClusterImpl.java b/software/base/src/main/java/brooklyn/entity/brooklynnode/BrooklynClusterImpl.java
index 48c9472..61b42d5 100644
--- a/software/base/src/main/java/brooklyn/entity/brooklynnode/BrooklynClusterImpl.java
+++ b/software/base/src/main/java/brooklyn/entity/brooklynnode/BrooklynClusterImpl.java
@@ -50,9 +50,6 @@ public class BrooklynClusterImpl extends DynamicClusterImpl implements BrooklynC
 
     // TODO should we set a default MEMBER_SPEC ?  difficult though because we'd need to set a password
 
-    @SuppressWarnings("unused")
-    private FunctionFeed scanMaster;
-
     @Override
     public void init() {
         super.init();
@@ -60,12 +57,12 @@ public class BrooklynClusterImpl extends DynamicClusterImpl implements BrooklynC
         getMutableEntityType().addEffector(BrooklynClusterUpgradeEffectorBody.UPGRADE_CLUSTER);
 
         ServiceProblemsLogic.updateProblemsIndicator(this, MASTER_NODE, MSG_NO_MASTER);
-        scanMaster = FunctionFeed.builder()
+        addFeed(FunctionFeed.builder()
                 .entity(this)
                 .poll(new FunctionPollConfig<Object, BrooklynNode>(MASTER_NODE)
                         .period(Duration.ONE_SECOND)
                         .callable(new MasterChildFinder()))
-                .build();
+                .build());
         
         addEnricher( Enrichers.builder().transforming(MASTER_NODE)
             .uniqueTag("master-node-web-uri")
@@ -81,7 +78,7 @@ public class BrooklynClusterImpl extends DynamicClusterImpl implements BrooklynC
         }
     }
 
-    private BrooklynNode findMasterChild() {
+    BrooklynNode findMasterChild() {
         Collection<Entity> masters = FluentIterable.from(getMembers())
                 .filter(EntityPredicates.attributeEqualTo(BrooklynNode.MANAGEMENT_NODE_STATE, ManagementNodeState.MASTER))
                 .toList();

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/d3f6e342/software/base/src/main/java/brooklyn/entity/brooklynnode/BrooklynNodeImpl.java
----------------------------------------------------------------------
diff --git a/software/base/src/main/java/brooklyn/entity/brooklynnode/BrooklynNodeImpl.java b/software/base/src/main/java/brooklyn/entity/brooklynnode/BrooklynNodeImpl.java
index fac22ee..395949d 100644
--- a/software/base/src/main/java/brooklyn/entity/brooklynnode/BrooklynNodeImpl.java
+++ b/software/base/src/main/java/brooklyn/entity/brooklynnode/BrooklynNodeImpl.java
@@ -108,6 +108,11 @@ public class BrooklynNodeImpl extends SoftwareProcessImpl implements BrooklynNod
     }
 
     @Override
+    protected void preStart() {
+        ServiceNotUpLogic.clearNotUpIndicator(this, SHUTDOWN.getName());
+    }
+    
+    @Override
     protected void preStop() {
         super.preStop();
 
@@ -233,11 +238,6 @@ public class BrooklynNodeImpl extends SoftwareProcessImpl implements BrooklynNod
 
     }
 
-    @Override
-    protected void preStart() {
-        ServiceNotUpLogic.clearNotUpIndicator(this, SHUTDOWN.getName());
-    }
-    
     public static class StopNodeButLeaveAppsEffectorBody extends EffectorBody<Void> implements StopNodeButLeaveAppsEffector {
         public static final Effector<Void> STOP_NODE_BUT_LEAVE_APPS = Effectors.effector(BrooklynNode.STOP_NODE_BUT_LEAVE_APPS).impl(new StopNodeButLeaveAppsEffectorBody()).build();
 

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/d3f6e342/software/base/src/main/java/brooklyn/entity/brooklynnode/BrooklynNodeSshDriver.java
----------------------------------------------------------------------
diff --git a/software/base/src/main/java/brooklyn/entity/brooklynnode/BrooklynNodeSshDriver.java b/software/base/src/main/java/brooklyn/entity/brooklynnode/BrooklynNodeSshDriver.java
index d5d9c01..1fbf694 100644
--- a/software/base/src/main/java/brooklyn/entity/brooklynnode/BrooklynNodeSshDriver.java
+++ b/software/base/src/main/java/brooklyn/entity/brooklynnode/BrooklynNodeSshDriver.java
@@ -224,7 +224,7 @@ public class BrooklynNodeSshDriver extends JavaSoftwareProcessSshDriver implemen
         }
         
         String cmd = entity.getConfig(BrooklynNode.EXTRA_CUSTOMIZATION_SCRIPT);
-        if (!Strings.isBlank(cmd)) {
+        if (Strings.isNonBlank(cmd)) {
             DynamicTasks.queueIfPossible( SshEffectorTasks.ssh(cmd).summary("Bespoke BrooklynNode customization script")
                 .requiringExitCodeZero() )
                 .orSubmitAndBlock(getEntity());
@@ -285,7 +285,7 @@ public class BrooklynNodeSshDriver extends JavaSoftwareProcessSshDriver implemen
         if (getEntity().getConfig(BrooklynNode.NO_SHUTDOWN_ON_EXIT)) {
             cmd += " --noShutdownOnExit ";
         }
-        if (!Strings.isBlank(getEntity().getConfig(BrooklynNode.EXTRA_LAUNCH_PARAMETERS))) {
+        if (Strings.isNonBlank(getEntity().getConfig(BrooklynNode.EXTRA_LAUNCH_PARAMETERS))) {
             cmd += " "+getEntity().getConfig(BrooklynNode.EXTRA_LAUNCH_PARAMETERS);
         }
         cmd += format(" >> %s/console 2>&1 </dev/null &", getRunDir());

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/d3f6e342/software/base/src/main/java/brooklyn/entity/brooklynnode/effector/BrooklynClusterUpgradeEffectorBody.java
----------------------------------------------------------------------
diff --git a/software/base/src/main/java/brooklyn/entity/brooklynnode/effector/BrooklynClusterUpgradeEffectorBody.java b/software/base/src/main/java/brooklyn/entity/brooklynnode/effector/BrooklynClusterUpgradeEffectorBody.java
index 48553b3..9fac5a4 100644
--- a/software/base/src/main/java/brooklyn/entity/brooklynnode/effector/BrooklynClusterUpgradeEffectorBody.java
+++ b/software/base/src/main/java/brooklyn/entity/brooklynnode/effector/BrooklynClusterUpgradeEffectorBody.java
@@ -21,7 +21,6 @@ package brooklyn.entity.brooklynnode.effector;
 import java.io.File;
 import java.util.Collection;
 import java.util.Collections;
-import java.util.HashSet;
 import java.util.concurrent.Callable;
 import java.util.concurrent.atomic.AtomicBoolean;
 
@@ -49,7 +48,6 @@ import brooklyn.management.ha.HighAvailabilityMode;
 import brooklyn.management.ha.ManagementNodeState;
 import brooklyn.util.collections.MutableMap;
 import brooklyn.util.config.ConfigBag;
-import brooklyn.util.exceptions.Exceptions;
 import brooklyn.util.net.Urls;
 import brooklyn.util.task.DynamicTasks;
 import brooklyn.util.task.Tasks;
@@ -67,7 +65,7 @@ public class BrooklynClusterUpgradeEffectorBody extends EffectorBody<Void> imple
     public static final Effector<Void> UPGRADE_CLUSTER = Effectors.effector(UpgradeClusterEffector.UPGRADE_CLUSTER)
         .impl(new BrooklynClusterUpgradeEffectorBody()).build();
 
-    private AtomicBoolean upgradeInProgress = new AtomicBoolean();
+    private final AtomicBoolean upgradeInProgress = new AtomicBoolean();
 
     @Override
     public Void call(ConfigBag parameters) {
@@ -75,37 +73,36 @@ public class BrooklynClusterUpgradeEffectorBody extends EffectorBody<Void> imple
             throw new IllegalStateException("An upgrade is already in progress.");
         }
 
-        EntitySpec<?> memberSpec = entity().getConfig(BrooklynCluster.MEMBER_SPEC);
-        Preconditions.checkNotNull(memberSpec, BrooklynCluster.MEMBER_SPEC.getName() + " is required for " + UpgradeClusterEffector.class.getName());
+        EntitySpec<?> origMemberSpec = entity().getConfig(BrooklynCluster.MEMBER_SPEC);
+        Preconditions.checkNotNull(origMemberSpec, BrooklynCluster.MEMBER_SPEC.getName() + " is required for " + UpgradeClusterEffector.class.getName());
 
-        ConfigBag specCfg = ConfigBag.newInstance( memberSpec.getConfig() );
-        ConfigBag origSpecCfg = ConfigBag.newInstanceCopying(specCfg);
-        log.debug("Upgrading "+entity()+", changing "+BrooklynCluster.MEMBER_SPEC+" from "+memberSpec+" / "+origSpecCfg);
-        
+        log.debug("Upgrading "+entity()+", changing "+BrooklynCluster.MEMBER_SPEC+" from "+origMemberSpec+" / "+origMemberSpec.getConfig());
+
+        boolean success = false;
         try {
             String newDownloadUrl = parameters.get(DOWNLOAD_URL);
-            specCfg.putIfNotNull(DOWNLOAD_URL, newDownloadUrl);
-            specCfg.put(BrooklynNode.DISTRO_UPLOAD_URL, inferUploadUrl(newDownloadUrl));
             
-            specCfg.putAll(ConfigBag.newInstance(parameters.get(EXTRA_CONFIG)).getAllConfigAsConfigKeyMap());
+            EntitySpec<?> newMemberSpec = EntitySpec.create(origMemberSpec);
             
-            memberSpec.clearConfig();
-            memberSpec.configure(specCfg.getAllConfigAsConfigKeyMap());
-            // not necessary, but good practice
-            entity().setConfig(BrooklynCluster.MEMBER_SPEC, memberSpec);
-            log.debug("Upgrading "+entity()+", new "+BrooklynCluster.MEMBER_SPEC+": "+memberSpec+" / "+specCfg);
+            ConfigBag newConfig = ConfigBag.newInstance();
+            newConfig.putIfNotNull(DOWNLOAD_URL, newDownloadUrl);
+            newConfig.put(BrooklynNode.DISTRO_UPLOAD_URL, inferUploadUrl(newDownloadUrl));
+            newConfig.putAll(ConfigBag.newInstance(parameters.get(EXTRA_CONFIG)).getAllConfigAsConfigKeyMap());
+            newMemberSpec.configure(newConfig.getAllConfigAsConfigKeyMap());
             
-            upgrade(parameters);
-        } catch (Exception e) {
-            log.debug("Upgrading "+entity()+" failed, will rethrow after restoring "+BrooklynCluster.MEMBER_SPEC+" to: "+origSpecCfg);
-            memberSpec.clearConfig();
-            memberSpec.configure(origSpecCfg.getAllConfigAsConfigKeyMap());
-            // not necessary, but good practice
-            entity().setConfig(BrooklynCluster.MEMBER_SPEC, memberSpec);
+            entity().setConfig(BrooklynCluster.MEMBER_SPEC, newMemberSpec);
             
-            throw Exceptions.propagate(e);
+            log.debug("Upgrading "+entity()+", new "+BrooklynCluster.MEMBER_SPEC+": "+newMemberSpec+" / "+newMemberSpec.getConfig()+" (adding: "+newConfig+")");
             
+            upgrade(parameters);
+
+            success = true;
         } finally {
+            if (!success) {
+                log.debug("Upgrading "+entity()+" failed, will rethrow after restoring "+BrooklynCluster.MEMBER_SPEC+" to: "+origMemberSpec);
+                entity().setConfig(BrooklynCluster.MEMBER_SPEC, origMemberSpec);
+            }
+            
             upgradeInProgress.set(false);
         }
         return null;
@@ -135,11 +132,15 @@ public class BrooklynClusterUpgradeEffectorBody extends EffectorBody<Void> imple
                 new IllegalStateException("Persistence does not appear to be enabled at this cluster. "
                 + "Cluster upgrade will not succeed unless a custom launch script enables it.")) );
         }
+       
+        //TODO we'd like to disable these nodes as standby targets, ie in some 'hot standby but not available for failover' mode
+        //currently if failover happens to a new node, assumptions below may fail and the cluster may require manual repair
 
         //1. Initially create a single node to check if it will launch successfully
         TaskAdaptable<Collection<Entity>> initialNodeTask = DynamicTasks.queue(newCreateNodesTask(1, "Creating first upgraded version node"));
 
         //2. If everything is OK with the first node launch the rest as well
+        @SuppressWarnings("unused")
         TaskAdaptable<Collection<Entity>> remainingNodesTask = DynamicTasks.queue(newCreateNodesTask(initialClusterSize - 1, "Creating remaining upgraded version nodes ("+(initialClusterSize - 1)+")"));
 
         //3. Once we have all nodes running without errors switch master
@@ -151,13 +152,9 @@ public class BrooklynClusterUpgradeEffectorBody extends EffectorBody<Void> imple
         //   For members that were created meanwhile - they will be using the new version already. If the new version
         //   isn't good then they will fail to start as well, forcing the policies to retry (and succeed once the
         //   URL is reverted).
-        //TODO can get into problem state if more old nodes are created; better might be to set the
-        //version on this cluster before the above select-master call, and then delete any which are running the old
-        //version (would require tracking the version number at the entity)
-        HashSet<Entity> oldMembers = new HashSet<Entity>(initialMembers);
-        oldMembers.removeAll(remainingNodesTask.asTask().getUnchecked());
-        oldMembers.removeAll(initialNodeTask.asTask().getUnchecked());
-        DynamicTasks.queue(Effectors.invocation(BrooklynNode.STOP_NODE_BUT_LEAVE_APPS, Collections.emptyMap(), oldMembers)).asTask().getUnchecked();
+        
+        //any other nodes created via other means should also be using the new spec, so initialMembers will be all the old version nodes
+        DynamicTasks.queue(Effectors.invocation(BrooklynNode.STOP_NODE_BUT_LEAVE_APPS, Collections.emptyMap(), initialMembers)).asTask().getUnchecked();
     }
 
     private TaskAdaptable<Collection<Entity>> newCreateNodesTask(int size, String name) {
@@ -187,6 +184,7 @@ public class BrooklynClusterUpgradeEffectorBody extends EffectorBody<Void> imple
                 Predicates.not(Predicates.equalTo(Lifecycle.STARTING)), Duration.minutes(30)));
 
         //3. Set HOT_STANDBY in case it is not enabled on the command line ...
+        // TODO support via EntitySpec
         DynamicTasks.queue(Effectors.invocation(
                 BrooklynNode.SET_HIGH_AVAILABILITY_MODE,
                 MutableMap.of(SetHighAvailabilityModeEffector.MODE, HighAvailabilityMode.HOT_STANDBY), 
@@ -196,6 +194,8 @@ public class BrooklynClusterUpgradeEffectorBody extends EffectorBody<Void> imple
         DynamicTasks.queue(EntityTasks.requiringAttributeEventually(newNodes, BrooklynNode.MANAGEMENT_NODE_STATE, 
                 Predicates.equalTo(ManagementNodeState.HOT_STANDBY), Duration.FIVE_MINUTES));
 
+        // TODO also check that the nodes created all report the original master, in case persistence changes it
+        
         //5. Just in case check if all of the nodes are SERVICE_UP (which would rule out ON_FIRE as well)
         Collection<Entity> failedNodes = Collections2.filter(newNodes, EntityPredicates.attributeEqualTo(BrooklynNode.SERVICE_UP, Boolean.FALSE));
         if (!failedNodes.isEmpty()) {

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/d3f6e342/software/base/src/main/java/brooklyn/entity/brooklynnode/effector/BrooklynNodeUpgradeEffectorBody.java
----------------------------------------------------------------------
diff --git a/software/base/src/main/java/brooklyn/entity/brooklynnode/effector/BrooklynNodeUpgradeEffectorBody.java b/software/base/src/main/java/brooklyn/entity/brooklynnode/effector/BrooklynNodeUpgradeEffectorBody.java
index 00ab7bc..22afa15 100644
--- a/software/base/src/main/java/brooklyn/entity/brooklynnode/effector/BrooklynNodeUpgradeEffectorBody.java
+++ b/software/base/src/main/java/brooklyn/entity/brooklynnode/effector/BrooklynNodeUpgradeEffectorBody.java
@@ -170,7 +170,7 @@ public class BrooklynNodeUpgradeEffectorBody extends EffectorBody<Void> {
     @Beta
     static boolean isPersistenceModeEnabled(Entity entity) {
         // TODO when there are PERSIST* options in BrooklynNode, look at them here!
-        // or, better, have a sensor for persistence
+        // or, even better, make a REST call to check persistence
         String params = null;
         if (entity instanceof BrooklynCluster) {
             EntitySpec<?> spec = entity.getConfig(BrooklynCluster.MEMBER_SPEC);

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/d3f6e342/software/base/src/main/java/brooklyn/entity/brooklynnode/effector/SelectMasterEffectorBody.java
----------------------------------------------------------------------
diff --git a/software/base/src/main/java/brooklyn/entity/brooklynnode/effector/SelectMasterEffectorBody.java b/software/base/src/main/java/brooklyn/entity/brooklynnode/effector/SelectMasterEffectorBody.java
index 40d65a7..30f5f2d 100644
--- a/software/base/src/main/java/brooklyn/entity/brooklynnode/effector/SelectMasterEffectorBody.java
+++ b/software/base/src/main/java/brooklyn/entity/brooklynnode/effector/SelectMasterEffectorBody.java
@@ -45,6 +45,7 @@ import brooklyn.util.task.DynamicTasks;
 import brooklyn.util.time.Duration;
 
 import com.google.api.client.util.Preconditions;
+import com.google.common.base.Objects;
 import com.google.common.collect.Iterables;
 
 public class SelectMasterEffectorBody extends EffectorBody<Void> implements SelectMasterEffector {
@@ -84,20 +85,21 @@ public class SelectMasterEffectorBody extends EffectorBody<Void> implements Sele
         final Entity newMaster = getMember(newMasterId);
 
         //1. Increase the priority of the node we wish to become master
-        setNodePriority(newMaster, HA_MASTER_PRIORITY);
+        toggleNodePriority(newMaster, HA_MASTER_PRIORITY);
 
-        //2. Denote the existing master so a new election takes place
+        //2. Demote the existing master so a new election takes place
         try {
-            //If no master was yet selected, at least wait to see
-            //if the new master will be what we expect.
+            // this allows the finally block to run even on failure
+            DynamicTasks.swallowChildrenFailures();
+            
             if (oldMaster != null) {
-                setNodeState(oldMaster, HighAvailabilityMode.HOT_STANDBY);
+                demoteOldMaster(oldMaster, HighAvailabilityMode.HOT_STANDBY);
             }
 
             waitMasterHandover(oldMaster, newMaster);
-        } finally {
+        } finally { 
             //3. Revert the priority of the node once it has become master
-            setNodePriority(newMaster, HA_STANDBY_PRIORITY);
+            toggleNodePriority(newMaster, HA_STANDBY_PRIORITY);
         }
 
         checkMasterSelected(newMaster);
@@ -105,13 +107,13 @@ public class SelectMasterEffectorBody extends EffectorBody<Void> implements Sele
 
     private void waitMasterHandover(final Entity oldMaster, final Entity newMaster) {
         boolean masterChanged = Repeater.create()
-            .backoff(Duration.millis(500), 1.2, Duration.FIVE_SECONDS)
+            .backoff(Duration.millis(50), 1.5, Duration.FIVE_SECONDS)
             .limitTimeTo(Duration.ONE_MINUTE)
             .until(new Callable<Boolean>() {
                 @Override
                 public Boolean call() throws Exception {
                     Entity master = getMasterNode();
-                    return master != oldMaster && master != null;
+                    return !Objects.equal(master, oldMaster) && master != null;
                 }
             })
             .run();
@@ -120,7 +122,7 @@ public class SelectMasterEffectorBody extends EffectorBody<Void> implements Sele
         }
     }
 
-    private void setNodeState(Entity oldMaster, HighAvailabilityMode mode) {
+    private void demoteOldMaster(Entity oldMaster, HighAvailabilityMode mode) {
         ManagementNodeState oldState = DynamicTasks.queue(
                 Effectors.invocation(
                         oldMaster,
@@ -134,17 +136,17 @@ public class SelectMasterEffectorBody extends EffectorBody<Void> implements Sele
         }
     }
 
-    private void setNodePriority(Entity newMaster, int newPriority) {
+    private void toggleNodePriority(Entity node, int newPriority) {
         Integer oldPriority = DynamicTasks.queue(
                 Effectors.invocation(
-                    newMaster,
+                    node,
                     BrooklynNode.SET_HIGH_AVAILABILITY_PRIORITY,
                     MutableMap.of(SetHighAvailabilityPriorityEffector.PRIORITY, newPriority))
             ).asTask().getUnchecked();
 
         Integer expectedPriority = (newPriority == HA_MASTER_PRIORITY ? HA_STANDBY_PRIORITY : HA_MASTER_PRIORITY);
         if (oldPriority != expectedPriority) {
-            LOG.warn("The previous HA priority on node " + newMaster.getId() + " was " + oldPriority +
+            LOG.warn("The previous HA priority on node " + node.getId() + " was " + oldPriority +
                     ", while the expected value is " + expectedPriority + " (while setting priority " +
                     newPriority + ").");
         }

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/d3f6e342/software/base/src/main/resources/brooklyn/entity/brooklynnode/brooklyn-cluster.yaml
----------------------------------------------------------------------
diff --git a/software/base/src/main/resources/brooklyn/entity/brooklynnode/brooklyn-cluster.yaml b/software/base/src/main/resources/brooklyn/entity/brooklynnode/brooklyn-cluster.yaml
index f25a879..cb32596 100644
--- a/software/base/src/main/resources/brooklyn/entity/brooklynnode/brooklyn-cluster.yaml
+++ b/software/base/src/main/resources/brooklyn/entity/brooklynnode/brooklyn-cluster.yaml
@@ -27,6 +27,7 @@ services:
 #      type: classpath://brooklyn/entity/brooklynnode/brooklyn-node-persisting-to-tmp.yaml
       type: brooklyn.entity.brooklynnode.BrooklynNode
       brooklyn.config:
+        # persistence location must be the same for all nodes; if anywhere other than localhost configure a shared obj store or nfs mount
         brooklynnode.launch.parameters.extra: --persist auto --persistenceDir /tmp/brooklyn-persistence-example/
 
 # location: localhost

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/d3f6e342/software/base/src/test/java/brooklyn/entity/brooklynnode/BrooklynNodeTest.java
----------------------------------------------------------------------
diff --git a/software/base/src/test/java/brooklyn/entity/brooklynnode/BrooklynNodeTest.java b/software/base/src/test/java/brooklyn/entity/brooklynnode/BrooklynNodeTest.java
index 21e3f34..d1a9390 100644
--- a/software/base/src/test/java/brooklyn/entity/brooklynnode/BrooklynNodeTest.java
+++ b/software/base/src/test/java/brooklyn/entity/brooklynnode/BrooklynNodeTest.java
@@ -58,14 +58,14 @@ public class BrooklynNodeTest {
     
     @Test
     public void testGeneratesCorrectSnapshotDownload() throws Exception {
-        String version = "0.7.0-SNAPSHOT"; // BROOKLYN_VERSION
+        String version = "0.0.1-SNAPSHOT";
         String expectedUrl = "https://repository.apache.org/service/local/artifact/maven/redirect?r=snapshots&g=org.apache.brooklyn&v="+version+"&a=brooklyn-dist&c=dist&e=tar.gz";
         runTestGeneratesCorrectDownloadUrl(version, expectedUrl);
     }
     
     @Test
     public void testGeneratesCorrectReleaseDownload() throws Exception {
-        String version = "0.7.0";
+        String version = "0.0.1";
         String expectedUrl = "http://search.maven.org/remotecontent?filepath=org/apache/brooklyn/brooklyn-dist/"+version+"/brooklyn-dist-"+version+"-dist.tar.gz";
         runTestGeneratesCorrectDownloadUrl(version, expectedUrl);
     }
@@ -74,7 +74,7 @@ public class BrooklynNodeTest {
         // TODO Using BrooklynNodeImpl directly, because want to instantiate a BroolynNodeSshDriver.
         //      Really want to make that easier to test, without going through "wrong" code path for creating entity.
         BrooklynNodeImpl entity = new BrooklynNodeImpl();
-        entity.configure(MutableMap.of("version", version));
+        entity.configure(BrooklynNode.SUGGESTED_VERSION, version);
         entity.setParent(app);
         Entities.manage(entity);
         ConfigToAttributes.apply(entity);

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/d3f6e342/software/base/src/test/java/brooklyn/entity/brooklynnode/CallbackEntityHttpClient.java
----------------------------------------------------------------------
diff --git a/software/base/src/test/java/brooklyn/entity/brooklynnode/CallbackEntityHttpClient.java b/software/base/src/test/java/brooklyn/entity/brooklynnode/CallbackEntityHttpClient.java
new file mode 100644
index 0000000..737e4f1
--- /dev/null
+++ b/software/base/src/test/java/brooklyn/entity/brooklynnode/CallbackEntityHttpClient.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 brooklyn.entity.brooklynnode;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.http.HttpStatus;
+import org.apache.http.client.methods.HttpGet;
+import org.apache.http.client.methods.HttpPost;
+
+import brooklyn.entity.Entity;
+import brooklyn.entity.brooklynnode.EntityHttpClient;
+import brooklyn.util.http.HttpTool.HttpClientBuilder;
+import brooklyn.util.http.HttpToolResponse;
+
+import com.google.common.base.Function;
+
+public class CallbackEntityHttpClient implements EntityHttpClient {
+    public static class Request {
+        private Entity entity;
+        private String method;
+        private String path;
+        private Map<String, String> params;
+        public Request(Entity entity, String method, String path, Map<String, String> params) {
+            this.entity = entity;
+            this.method = method;
+            this.path = path;
+            this.params = params;
+        }
+        public Entity getEntity() {
+            return entity;
+        }
+        public String getMethod() {
+            return method;
+        }
+        public String getPath() {
+            return path;
+        }
+        public Map<String, String> getParams() {
+            return params;
+        }
+    }
+    private Function<Request, String> callback;
+    private Entity entity;
+
+    public CallbackEntityHttpClient(Entity entity, Function<Request, String> callback) {
+        this.entity = entity;
+        this.callback = callback;
+    }
+
+    @Override
+    public HttpClientBuilder getHttpClientForBrooklynNode() {
+        throw new IllegalStateException("Method call not expected");
+    }
+
+    @Override
+    public HttpToolResponse get(String path) {
+        String result = callback.apply(new Request(entity, HttpGet.METHOD_NAME, path, Collections.<String, String>emptyMap()));
+        return new HttpToolResponse(HttpStatus.SC_OK, null, result.getBytes(), 0, 0, 0);
+    }
+
+    @Override
+    public HttpToolResponse post(String path, Map<String, String> headers, byte[] body) {
+        throw new IllegalStateException("Method call not expected");
+    }
+
+    @Override
+    public HttpToolResponse post(String path, Map<String, String> headers, Map<String, String> formParams) {
+        String result = callback.apply(new Request(entity, HttpPost.METHOD_NAME, path, formParams));
+        return new HttpToolResponse(HttpStatus.SC_OK, Collections.<String, List<String>>emptyMap(), result.getBytes(), 0, 0, 0);
+    }
+    
+    @Override
+    public HttpToolResponse delete(String path, Map<String, String> headers) {
+        throw new IllegalStateException("Method call not expected");
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/d3f6e342/software/base/src/test/java/brooklyn/entity/brooklynnode/MockBrooklynNode.java
----------------------------------------------------------------------
diff --git a/software/base/src/test/java/brooklyn/entity/brooklynnode/MockBrooklynNode.java b/software/base/src/test/java/brooklyn/entity/brooklynnode/MockBrooklynNode.java
new file mode 100644
index 0000000..c75131e
--- /dev/null
+++ b/software/base/src/test/java/brooklyn/entity/brooklynnode/MockBrooklynNode.java
@@ -0,0 +1,68 @@
+/*
+ * 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 brooklyn.entity.brooklynnode;
+
+import java.util.Collection;
+
+import brooklyn.config.ConfigKey;
+import brooklyn.entity.basic.AbstractEntity;
+import brooklyn.entity.basic.ConfigKeys;
+import brooklyn.entity.brooklynnode.BrooklynNode;
+import brooklyn.entity.brooklynnode.EntityHttpClient;
+import brooklyn.entity.brooklynnode.CallbackEntityHttpClient.Request;
+import brooklyn.entity.brooklynnode.effector.SetHighAvailabilityModeEffectorBody;
+import brooklyn.entity.brooklynnode.effector.SetHighAvailabilityPriorityEffectorBody;
+import brooklyn.event.AttributeSensor;
+import brooklyn.event.basic.BasicAttributeSensor;
+import brooklyn.location.Location;
+
+import com.google.common.base.Function;
+import com.google.common.reflect.TypeToken;
+
+public class MockBrooklynNode extends AbstractEntity implements BrooklynNode {
+    @SuppressWarnings("serial")
+    public static final ConfigKey<Function<Request, String>> HTTP_CLIENT_CALLBACK = ConfigKeys.newConfigKey(new TypeToken<Function<Request, String>>(){}, "httpClientCallback");
+    public static final AttributeSensor<Integer> HA_PRIORITY = new BasicAttributeSensor<Integer>(Integer.class, "priority");
+    
+    @Override
+    public void init() {
+        super.init();
+        getMutableEntityType().addEffector(SetHighAvailabilityPriorityEffectorBody.SET_HIGH_AVAILABILITY_PRIORITY);
+        getMutableEntityType().addEffector(SetHighAvailabilityModeEffectorBody.SET_HIGH_AVAILABILITY_MODE);
+        setAttribute(HA_PRIORITY, 0);
+    }
+
+    @Override
+    public EntityHttpClient http() {
+        return new CallbackEntityHttpClient(this, getConfig(HTTP_CLIENT_CALLBACK));
+    }
+
+    @Override
+    public void start(Collection<? extends Location> locations) {
+    }
+
+    @Override
+    public void stop() {
+    }
+
+    @Override
+    public void restart() {
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/d3f6e342/software/base/src/test/java/brooklyn/entity/brooklynnode/SelectMasterEffectorTest.java
----------------------------------------------------------------------
diff --git a/software/base/src/test/java/brooklyn/entity/brooklynnode/SelectMasterEffectorTest.java b/software/base/src/test/java/brooklyn/entity/brooklynnode/SelectMasterEffectorTest.java
new file mode 100644
index 0000000..84b763d
--- /dev/null
+++ b/software/base/src/test/java/brooklyn/entity/brooklynnode/SelectMasterEffectorTest.java
@@ -0,0 +1,257 @@
+/*
+ * 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 brooklyn.entity.brooklynnode;
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertTrue;
+import static org.testng.Assert.fail;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.Callable;
+
+import org.apache.http.client.methods.HttpPost;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.testng.Assert;
+import org.testng.annotations.AfterMethod;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import brooklyn.entity.BrooklynAppUnitTestSupport;
+import brooklyn.entity.Entity;
+import brooklyn.entity.Group;
+import brooklyn.entity.basic.Entities;
+import brooklyn.entity.basic.EntityLocal;
+import brooklyn.entity.brooklynnode.BrooklynCluster.SelectMasterEffector;
+import brooklyn.entity.brooklynnode.CallbackEntityHttpClient.Request;
+import brooklyn.entity.effector.Effectors;
+import brooklyn.entity.group.DynamicCluster;
+import brooklyn.entity.proxying.EntitySpec;
+import brooklyn.event.feed.AttributePollHandler;
+import brooklyn.event.feed.DelegatingPollHandler;
+import brooklyn.event.feed.Poller;
+import brooklyn.management.ha.ManagementNodeState;
+import brooklyn.test.EntityTestUtils;
+import brooklyn.util.collections.MutableList;
+import brooklyn.util.time.Duration;
+
+import com.google.common.base.Function;
+import com.google.common.base.Objects;
+import com.google.common.collect.ImmutableMap;
+
+public class SelectMasterEffectorTest extends BrooklynAppUnitTestSupport {
+    private static final Logger LOG = LoggerFactory.getLogger(BrooklynClusterImpl.class);
+
+    protected BrooklynCluster cluster;
+    protected HttpCallback http; 
+    protected Poller<Void> poller;
+
+    @BeforeMethod(alwaysRun=true)
+    public void setUp() throws Exception {
+        super.setUp();
+
+        // because the effector calls wait for a state change, use a separate thread to drive that 
+        poller = new Poller<Void>((EntityLocal)app, false);
+        poller.scheduleAtFixedRate(
+            new Callable<Void>() {
+                @Override
+                public Void call() throws Exception {
+                    masterFailoverIfNeeded();
+                    return null;
+                }
+            },
+            new DelegatingPollHandler<Void>(Collections.<AttributePollHandler<? super Void>>emptyList()),
+            Duration.millis(20));
+        poller.start();
+    }
+
+    @Override
+    protected void setUpApp() {
+        super.setUpApp();
+        http = new HttpCallback();
+        cluster = app.createAndManageChild(EntitySpec.create(BrooklynCluster.class)
+            .location(app.newLocalhostProvisioningLocation())
+            .configure(BrooklynCluster.MEMBER_SPEC, EntitySpec.create(BrooklynNode.class)
+                .impl(MockBrooklynNode.class)
+                .configure(MockBrooklynNode.HTTP_CLIENT_CALLBACK, http)));
+    }
+    
+    @AfterMethod(alwaysRun=true)
+    public void tearDown() throws Exception {
+        poller.stop();
+        super.tearDown();
+    }
+
+    @Test
+    public void testInvalidNewMasterIdFails() {
+        try {
+            selectMaster(cluster, "1234");
+            fail("Non-existend entity ID provided.");
+        } catch (Exception e) {
+            assertTrue(e.toString().contains("1234 is not an ID of brooklyn node in this cluster"));
+        }
+    }
+
+    @Test(groups="Integration") // because slow, due to sensor feeds
+    public void testSelectMasterAfterChange() {
+        List<Entity> nodes = makeTwoNodes();
+        EntityTestUtils.assertAttributeEqualsEventually(cluster, BrooklynCluster.MASTER_NODE, (BrooklynNode)nodes.get(0));
+
+        selectMaster(cluster, nodes.get(1).getId());
+        checkMaster(cluster, nodes.get(1));
+    }
+
+    @Test
+    public void testFindMaster() {
+        List<Entity> nodes = makeTwoNodes();
+        Assert.assertEquals(((BrooklynClusterImpl)Entities.deproxy(cluster)).findMasterChild(), nodes.get(0));
+    }
+    
+    @Test(groups="Integration") // because slow, due to sensor feeds
+    public void testSelectMasterFailsAtChangeState() {
+        http.setFailAtStateChange(true);
+
+        List<Entity> nodes = makeTwoNodes();
+        
+        EntityTestUtils.assertAttributeEqualsEventually(cluster, BrooklynCluster.MASTER_NODE, (BrooklynNode)nodes.get(0));
+
+        try {
+            selectMaster(cluster, nodes.get(1).getId());
+            fail("selectMaster should have failed");
+        } catch (Exception e) {
+            // expected
+        }
+        checkMaster(cluster, nodes.get(0));
+    }
+
+    private List<Entity> makeTwoNodes() {
+        List<Entity> nodes = MutableList.copyOf(cluster.resizeByDelta(2));
+        setManagementState(nodes.get(0), ManagementNodeState.MASTER);
+        setManagementState(nodes.get(1), ManagementNodeState.HOT_STANDBY);
+        return nodes;
+    }
+
+    private void checkMaster(Group cluster, Entity node) {
+        assertEquals(node.getAttribute(BrooklynNode.MANAGEMENT_NODE_STATE), ManagementNodeState.MASTER);
+        assertEquals(cluster.getAttribute(BrooklynCluster.MASTER_NODE), node);
+        for (Entity member : cluster.getMembers()) {
+            if (member != node) {
+                assertEquals(member.getAttribute(BrooklynNode.MANAGEMENT_NODE_STATE), ManagementNodeState.HOT_STANDBY);
+            }
+            assertEquals((int)member.getAttribute(MockBrooklynNode.HA_PRIORITY), 0);
+        }
+    }
+
+    private static class HttpCallback implements Function<CallbackEntityHttpClient.Request, String> {
+        private enum State {
+            INITIAL,
+            PROMOTED
+        }
+        private State state = State.INITIAL;
+        private boolean failAtStateChange;
+
+        @Override
+        public String apply(Request input) {
+            if ("/v1/server/ha/state".equals(input.getPath())) {
+                if (failAtStateChange) {
+                    throw new RuntimeException("Testing failure at changing node state");
+                }
+
+                checkRequest(input, HttpPost.METHOD_NAME, "/v1/server/ha/state", "mode", "HOT_STANDBY");
+                Entity entity = input.getEntity();
+                EntityTestUtils.assertAttributeEquals(entity, BrooklynNode.MANAGEMENT_NODE_STATE, ManagementNodeState.MASTER);
+                EntityTestUtils.assertAttributeEquals(entity, MockBrooklynNode.HA_PRIORITY, 0);
+
+                setManagementState(entity, ManagementNodeState.HOT_STANDBY);
+
+                return "MASTER";
+            } else {
+                switch(state) {
+                case INITIAL:
+                    checkRequest(input, HttpPost.METHOD_NAME, "/v1/server/ha/priority", "priority", "1");
+                    state = State.PROMOTED;
+                    setPriority(input.getEntity(), Integer.parseInt(input.getParams().get("priority")));
+                    return "0";
+                case PROMOTED:
+                    checkRequest(input, HttpPost.METHOD_NAME, "/v1/server/ha/priority", "priority", "0");
+                    state = State.INITIAL;
+                    setPriority(input.getEntity(), Integer.parseInt(input.getParams().get("priority")));
+                    return "1";
+                default: throw new IllegalStateException("Illegal call at state " + state + ". Request = " + input.getMethod() + " " + input.getPath());
+                }
+            }
+        }
+
+        public void checkRequest(Request input, String methodName, String path, String key, String value) {
+            if (!input.getMethod().equals(methodName) || !input.getPath().equals(path)) {
+                throw new IllegalStateException("Request doesn't match expected state. Expected = " + input.getMethod() + " " + input.getPath() + ". " +
+                        "Actual = " + methodName + " " + path);
+            }
+
+            String inputValue = input.getParams().get(key);
+            if(!Objects.equal(value, inputValue)) {
+                throw new IllegalStateException("Request doesn't match expected parameter " + methodName + " " + path + ". Parameter " + key + 
+                    " expected = " + value + ", actual = " + inputValue);
+            }
+        }
+
+        public void setFailAtStateChange(boolean failAtStateChange) {
+            this.failAtStateChange = failAtStateChange;
+        }
+
+    }
+
+    private void masterFailoverIfNeeded() {
+        if (!Entities.isManaged(cluster)) return;
+        if (cluster.getAttribute(BrooklynCluster.MASTER_NODE) == null) {
+            Collection<Entity> members = cluster.getMembers();
+            if (members.size() > 0) {
+                for (Entity member : members) {
+                    if (member.getAttribute(MockBrooklynNode.HA_PRIORITY) == 1) {
+                        masterFailover(member);
+                        return;
+                    }
+                }
+                masterFailover(members.iterator().next());
+            }
+        }
+    }
+
+    private void masterFailover(Entity member) {
+        LOG.debug("Master failover to " + member);
+        setManagementState(member, ManagementNodeState.MASTER);
+        EntityTestUtils.assertAttributeEqualsEventually(cluster, BrooklynCluster.MASTER_NODE, (BrooklynNode)member);
+        return;
+    }
+
+    public static void setManagementState(Entity entity, ManagementNodeState state) {
+        ((EntityLocal)entity).setAttribute(BrooklynNode.MANAGEMENT_NODE_STATE, state);
+    }
+
+    public static void setPriority(Entity entity, int priority) {
+        ((EntityLocal)entity).setAttribute(MockBrooklynNode.HA_PRIORITY, priority);
+    }
+
+    private void selectMaster(DynamicCluster cluster, String id) {
+        app.getExecutionContext().submit(Effectors.invocation(cluster, BrooklynCluster.SELECT_MASTER, ImmutableMap.of(SelectMasterEffector.NEW_MASTER_ID.getName(), id))).asTask().getUnchecked();
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/d3f6e342/software/base/src/test/java/brooklyn/entity/brooklynnode/effector/CallbackEntityHttpClient.java
----------------------------------------------------------------------
diff --git a/software/base/src/test/java/brooklyn/entity/brooklynnode/effector/CallbackEntityHttpClient.java b/software/base/src/test/java/brooklyn/entity/brooklynnode/effector/CallbackEntityHttpClient.java
deleted file mode 100644
index 78eef1e..0000000
--- a/software/base/src/test/java/brooklyn/entity/brooklynnode/effector/CallbackEntityHttpClient.java
+++ /dev/null
@@ -1,95 +0,0 @@
-/*
- * 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 brooklyn.entity.brooklynnode.effector;
-
-import java.util.Collections;
-import java.util.List;
-import java.util.Map;
-
-import org.apache.http.HttpStatus;
-import org.apache.http.client.methods.HttpGet;
-import org.apache.http.client.methods.HttpPost;
-
-import brooklyn.entity.Entity;
-import brooklyn.entity.brooklynnode.EntityHttpClient;
-import brooklyn.util.http.HttpTool.HttpClientBuilder;
-import brooklyn.util.http.HttpToolResponse;
-
-import com.google.common.base.Function;
-
-public class CallbackEntityHttpClient implements EntityHttpClient {
-    public static class Request {
-        private Entity entity;
-        private String method;
-        private String path;
-        private Map<String, String> params;
-        public Request(Entity entity, String method, String path, Map<String, String> params) {
-            this.entity = entity;
-            this.method = method;
-            this.path = path;
-            this.params = params;
-        }
-        public Entity getEntity() {
-            return entity;
-        }
-        public String getMethod() {
-            return method;
-        }
-        public String getPath() {
-            return path;
-        }
-        public Map<String, String> getParams() {
-            return params;
-        }
-    }
-    private Function<Request, String> callback;
-    private Entity entity;
-
-    public CallbackEntityHttpClient(Entity entity, Function<Request, String> callback) {
-        this.entity = entity;
-        this.callback = callback;
-    }
-
-    @Override
-    public HttpClientBuilder getHttpClientForBrooklynNode() {
-        throw new IllegalStateException("Method call not expected");
-    }
-
-    @Override
-    public HttpToolResponse get(String path) {
-        String result = callback.apply(new Request(entity, HttpGet.METHOD_NAME, path, Collections.<String, String>emptyMap()));
-        return new HttpToolResponse(HttpStatus.SC_OK, null, result.getBytes(), 0, 0, 0);
-    }
-
-    @Override
-    public HttpToolResponse post(String path, Map<String, String> headers, byte[] body) {
-        throw new IllegalStateException("Method call not expected");
-    }
-
-    @Override
-    public HttpToolResponse post(String path, Map<String, String> headers, Map<String, String> formParams) {
-        String result = callback.apply(new Request(entity, HttpPost.METHOD_NAME, path, formParams));
-        return new HttpToolResponse(HttpStatus.SC_OK, Collections.<String, List<String>>emptyMap(), result.getBytes(), 0, 0, 0);
-    }
-    
-    @Override
-    public HttpToolResponse delete(String path, Map<String, String> headers) {
-        throw new IllegalStateException("Method call not expected");
-    }
-}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/d3f6e342/software/base/src/test/java/brooklyn/entity/brooklynnode/effector/SelectMasterEffectorTest.java
----------------------------------------------------------------------
diff --git a/software/base/src/test/java/brooklyn/entity/brooklynnode/effector/SelectMasterEffectorTest.java b/software/base/src/test/java/brooklyn/entity/brooklynnode/effector/SelectMasterEffectorTest.java
deleted file mode 100644
index 6036507..0000000
--- a/software/base/src/test/java/brooklyn/entity/brooklynnode/effector/SelectMasterEffectorTest.java
+++ /dev/null
@@ -1,267 +0,0 @@
-/*
- * 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 brooklyn.entity.brooklynnode.effector;
-
-import static org.testng.Assert.assertEquals;
-import static org.testng.Assert.assertTrue;
-import static org.testng.Assert.fail;
-
-import java.util.Collection;
-import java.util.Collections;
-import java.util.concurrent.Callable;
-
-import org.apache.http.client.methods.HttpPost;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.testng.annotations.AfterMethod;
-import org.testng.annotations.BeforeMethod;
-import org.testng.annotations.Test;
-
-import brooklyn.entity.Entity;
-import brooklyn.entity.Group;
-import brooklyn.entity.basic.ApplicationBuilder;
-import brooklyn.entity.basic.BasicApplication;
-import brooklyn.entity.basic.EntityLocal;
-import brooklyn.entity.brooklynnode.BrooklynCluster;
-import brooklyn.entity.brooklynnode.BrooklynCluster.SelectMasterEffector;
-import brooklyn.entity.brooklynnode.BrooklynClusterImpl;
-import brooklyn.entity.brooklynnode.BrooklynNode;
-import brooklyn.entity.brooklynnode.effector.CallbackEntityHttpClient.Request;
-import brooklyn.entity.effector.Effectors;
-import brooklyn.entity.group.DynamicCluster;
-import brooklyn.entity.proxying.EntitySpec;
-import brooklyn.event.feed.AttributePollHandler;
-import brooklyn.event.feed.DelegatingPollHandler;
-import brooklyn.event.feed.Poller;
-import brooklyn.event.feed.function.FunctionFeed;
-import brooklyn.management.ManagementContext;
-import brooklyn.management.ha.ManagementNodeState;
-import brooklyn.test.EntityTestUtils;
-import brooklyn.test.entity.LocalManagementContextForTests;
-import brooklyn.util.task.BasicExecutionContext;
-import brooklyn.util.task.BasicExecutionManager;
-import brooklyn.util.time.Duration;
-
-import com.google.common.base.Function;
-import com.google.common.base.Objects;
-import com.google.common.collect.ImmutableMap;
-import com.google.common.collect.Iterables;
-
-public class SelectMasterEffectorTest {
-    private static final Logger LOG = LoggerFactory.getLogger(BrooklynClusterImpl.class);
-
-    protected ManagementContext mgmt;
-    protected BasicApplication app;
-    protected BasicExecutionContext ec;
-    protected BrooklynCluster cluster;
-    protected FunctionFeed scanMaster;
-    protected Poller<Void> poller; 
-
-    @BeforeMethod
-    public void setUp() {
-        mgmt = new LocalManagementContextForTests();
-        EntitySpec<BasicApplication> appSpec = EntitySpec.create(BasicApplication.class)
-                .child(EntitySpec.create(BrooklynCluster.class));
-        app = ApplicationBuilder.newManagedApp(appSpec, mgmt);
-        cluster = (BrooklynCluster)Iterables.getOnlyElement(app.getChildren());
-
-        BasicExecutionManager em = new BasicExecutionManager("mycontext");
-        ec = new BasicExecutionContext(em);
-
-        poller = new Poller<Void>((EntityLocal)app, false);
-        poller.scheduleAtFixedRate(
-            new Callable<Void>() {
-                @Override
-                public Void call() throws Exception {
-                    masterFailoverIfNeeded();
-                    return null;
-                }
-            },
-            new DelegatingPollHandler<Void>(Collections.<AttributePollHandler<? super Void>>emptyList()),
-            Duration.millis(200));
-        poller.start();
-    }
-
-    @AfterMethod
-    public void tearDown() {
-        poller.stop();
-    }
-
-    @Test
-    public void testInvalidNewMasterIdFails() {
-        try {
-            BrooklynCluster cluster = app.addChild(EntitySpec.create(BrooklynCluster.class));
-            selectMaster(cluster, "1234");
-            fail("Non-existend entity ID provided.");
-        } catch (Exception e) {
-            assertTrue(e.toString().contains("1234 is not an ID of brooklyn node in this cluster"));
-        }
-    }
-
-    @Test
-    public void testSelectMaster() {
-        HttpCallback cb = new HttpCallback();
-        BrooklynNode node1 = cluster.addMemberChild(EntitySpec.create(BrooklynNode.class)
-                .impl(TestHttpEntity.class)
-                .configure(TestHttpEntity.HTTP_CLIENT_CALLBACK, cb));
-        BrooklynNode node2 = cluster.addMemberChild(EntitySpec.create(BrooklynNode.class)
-                .impl(TestHttpEntity.class)
-                .configure(TestHttpEntity.HTTP_CLIENT_CALLBACK, cb));
-
-        cluster.addMemberChild(node1);
-        cluster.addMemberChild(node2);
-
-        setManagementState(node1, ManagementNodeState.MASTER);
-        EntityTestUtils.assertAttributeEqualsEventually(cluster, BrooklynCluster.MASTER_NODE, node1);
-
-        selectMaster(cluster, node2.getId());
-        checkMaster(cluster, node2);
-    }
-
-    @Test(groups="WIP")
-    //after throwing an exception in HttpCallback tasks are no longer executed, why?
-    public void testSelectMasterFailsAtChangeState() {
-        HttpCallback cb = new HttpCallback();
-        cb.setFailAtStateChange(true);
-
-        BrooklynNode node1 = cluster.addMemberChild(EntitySpec.create(BrooklynNode.class)
-                .impl(TestHttpEntity.class)
-                .configure(TestHttpEntity.HTTP_CLIENT_CALLBACK, cb));
-        BrooklynNode node2 = cluster.addMemberChild(EntitySpec.create(BrooklynNode.class)
-                .impl(TestHttpEntity.class)
-                .configure(TestHttpEntity.HTTP_CLIENT_CALLBACK, cb));
-
-        cluster.addMemberChild(node1);
-        cluster.addMemberChild(node2);
-
-        setManagementState(node1, ManagementNodeState.MASTER);
-        EntityTestUtils.assertAttributeEqualsEventually(cluster, BrooklynCluster.MASTER_NODE, node1);
-
-        selectMaster(cluster, node2.getId());
-        checkMaster(cluster, node1);
-    }
-
-    private void checkMaster(Group cluster, Entity node) {
-        assertEquals(node.getAttribute(BrooklynNode.MANAGEMENT_NODE_STATE), ManagementNodeState.MASTER);
-        assertEquals(cluster.getAttribute(BrooklynCluster.MASTER_NODE), node);
-        for (Entity member : cluster.getMembers()) {
-            if (member != node) {
-                assertEquals(member.getAttribute(BrooklynNode.MANAGEMENT_NODE_STATE), ManagementNodeState.HOT_STANDBY);
-            }
-            assertEquals((int)member.getAttribute(TestHttpEntity.HA_PRIORITY), 0);
-        }
-    }
-
-    private static class HttpCallback implements Function<CallbackEntityHttpClient.Request, String> {
-        private enum State {
-            INITIAL,
-            PROMOTED
-        }
-        private State state = State.INITIAL;
-        private boolean failAtStateChange;
-
-        @Override
-        public String apply(Request input) {
-            if ("/v1/server/ha/state".equals(input.getPath())) {
-                if (failAtStateChange) {
-                    throw new RuntimeException("Testing failure at chaning node state");
-                }
-
-                checkRequest(input, HttpPost.METHOD_NAME, "/v1/server/ha/state", "mode", "HOT_STANDBY");
-                Entity entity = input.getEntity();
-                EntityTestUtils.assertAttributeEquals(entity, BrooklynNode.MANAGEMENT_NODE_STATE, ManagementNodeState.MASTER);
-                EntityTestUtils.assertAttributeEquals(entity, TestHttpEntity.HA_PRIORITY, 0);
-
-                setManagementState(entity, ManagementNodeState.HOT_STANDBY);
-
-                return "MASTER";
-            } else {
-                switch(state) {
-                case INITIAL:
-                    checkRequest(input, HttpPost.METHOD_NAME, "/v1/server/ha/priority", "priority", "1");
-                    state = State.PROMOTED;
-                    setPriority(input.getEntity(), Integer.parseInt(input.getParams().get("priority")));
-                    return "0";
-                case PROMOTED:
-                    checkRequest(input, HttpPost.METHOD_NAME, "/v1/server/ha/priority", "priority", "0");
-                    state = State.INITIAL;
-                    setPriority(input.getEntity(), Integer.parseInt(input.getParams().get("priority")));
-                    return "1";
-                default: throw new IllegalStateException("Illegal call at state " + state + ". Request = " + input.getMethod() + " " + input.getPath());
-                }
-            }
-        }
-
-        public void checkRequest(Request input, String methodName, String path, String... keyValue) {
-            if (!input.getMethod().equals(methodName) || !input.getPath().equals(path)) {
-                throw new IllegalStateException("Request doesn't match expected state. Expected = " + input.getMethod() + " " + input.getPath() + ". " +
-                        "Actual = " + methodName + " " + path);
-            }
-            for(int i = 0; i < keyValue.length / 2; i++) {
-                String key = keyValue[i];
-                String value = keyValue[i+1];
-                String inputValue = input.getParams().get(key);
-                if(!Objects.equal(value, inputValue)) {
-                    throw new IllegalStateException("Request doesn't match expected parameter " + methodName + " " + path + ". Parameter " + key + 
-                            " expected = " + value + ", actual = " + inputValue);
-                }
-            }
-        }
-
-        public void setFailAtStateChange(boolean failAtStateChange) {
-            this.failAtStateChange = failAtStateChange;
-        }
-
-    }
-
-    private void masterFailoverIfNeeded() {
-        if (cluster.getAttribute(BrooklynCluster.MASTER_NODE) == null) {
-            Collection<Entity> members = cluster.getMembers();
-            if (members.size() > 0) {
-                for (Entity member : members) {
-                    if (member.getAttribute(TestHttpEntity.HA_PRIORITY) == 1) {
-                        masterFailover(member);
-                        return;
-                    }
-                }
-                masterFailover(members.iterator().next());
-            }
-        }
-    }
-
-    private void masterFailover(Entity member) {
-        LOG.debug("Master failover to " + member);
-        setManagementState(member, ManagementNodeState.MASTER);
-        EntityTestUtils.assertAttributeEqualsEventually(cluster, BrooklynCluster.MASTER_NODE, (BrooklynNode)member);
-        return;
-    }
-
-    public static void setManagementState(Entity entity, ManagementNodeState state) {
-        ((EntityLocal)entity).setAttribute(BrooklynNode.MANAGEMENT_NODE_STATE, state);
-    }
-
-    public static void setPriority(Entity entity, int priority) {
-        ((EntityLocal)entity).setAttribute(TestHttpEntity.HA_PRIORITY, priority);
-    }
-
-    private void selectMaster(DynamicCluster cluster, String id) {
-        ec.submit(Effectors.invocation(cluster, BrooklynCluster.SELECT_MASTER, ImmutableMap.of(SelectMasterEffector.NEW_MASTER_ID.getName(), id))).asTask().getUnchecked();
-    }
-
-}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/d3f6e342/software/base/src/test/java/brooklyn/entity/brooklynnode/effector/TestHttpEntity.java
----------------------------------------------------------------------
diff --git a/software/base/src/test/java/brooklyn/entity/brooklynnode/effector/TestHttpEntity.java b/software/base/src/test/java/brooklyn/entity/brooklynnode/effector/TestHttpEntity.java
deleted file mode 100644
index 335f7f7..0000000
--- a/software/base/src/test/java/brooklyn/entity/brooklynnode/effector/TestHttpEntity.java
+++ /dev/null
@@ -1,66 +0,0 @@
-/*
- * 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 brooklyn.entity.brooklynnode.effector;
-
-import java.util.Collection;
-
-import brooklyn.config.ConfigKey;
-import brooklyn.entity.basic.AbstractEntity;
-import brooklyn.entity.basic.ConfigKeys;
-import brooklyn.entity.brooklynnode.BrooklynNode;
-import brooklyn.entity.brooklynnode.EntityHttpClient;
-import brooklyn.entity.brooklynnode.effector.CallbackEntityHttpClient.Request;
-import brooklyn.event.AttributeSensor;
-import brooklyn.event.basic.BasicAttributeSensor;
-import brooklyn.location.Location;
-
-import com.google.common.base.Function;
-import com.google.common.reflect.TypeToken;
-
-public class TestHttpEntity extends AbstractEntity implements BrooklynNode {
-    @SuppressWarnings("serial")
-    public static final ConfigKey<Function<Request, String>> HTTP_CLIENT_CALLBACK = ConfigKeys.newConfigKey(new TypeToken<Function<Request, String>>(){}, "httpClientCallback");
-    public static final AttributeSensor<Integer> HA_PRIORITY = new BasicAttributeSensor<Integer>(Integer.class, "priority");
-    
-    @Override
-    public void init() {
-        super.init();
-        getMutableEntityType().addEffector(SetHighAvailabilityPriorityEffectorBody.SET_HIGH_AVAILABILITY_PRIORITY);
-        getMutableEntityType().addEffector(SetHighAvailabilityModeEffectorBody.SET_HIGH_AVAILABILITY_MODE);
-        setAttribute(HA_PRIORITY, 0);
-    }
-
-    @Override
-    public EntityHttpClient http() {
-        return new CallbackEntityHttpClient(this, getConfig(HTTP_CLIENT_CALLBACK));
-    }
-
-    @Override
-    public void start(Collection<? extends Location> locations) {
-    }
-
-    @Override
-    public void stop() {
-    }
-
-    @Override
-    public void restart() {
-    }
-
-}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/d3f6e342/usage/rest-server/src/main/java/brooklyn/rest/resources/CatalogResource.java
----------------------------------------------------------------------
diff --git a/usage/rest-server/src/main/java/brooklyn/rest/resources/CatalogResource.java b/usage/rest-server/src/main/java/brooklyn/rest/resources/CatalogResource.java
index 53ef4cf..0ba8b8c 100644
--- a/usage/rest-server/src/main/java/brooklyn/rest/resources/CatalogResource.java
+++ b/usage/rest-server/src/main/java/brooklyn/rest/resources/CatalogResource.java
@@ -185,7 +185,7 @@ public class CatalogResource extends AbstractBrooklynRestResource implements Cat
         if (Strings.isNonEmpty(regex))
             filters.add(CatalogPredicates.xml(StringPredicates.containsRegex(regex)));
         if (Strings.isNonEmpty(fragment))
-            filters.add(CatalogPredicates.xml(StringPredicates.containsLiteralCaseInsensitive(fragment)));
+            filters.add(CatalogPredicates.xml(StringPredicates.containsLiteralIgnoreCase(fragment)));
 
         return FluentIterable.from(brooklyn().getCatalog().getCatalogItems())
                 .filter(Predicates.and(filters))

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/d3f6e342/utils/common/src/main/java/brooklyn/util/text/StringPredicates.java
----------------------------------------------------------------------
diff --git a/utils/common/src/main/java/brooklyn/util/text/StringPredicates.java b/utils/common/src/main/java/brooklyn/util/text/StringPredicates.java
index 1f9c574..15a306a 100644
--- a/utils/common/src/main/java/brooklyn/util/text/StringPredicates.java
+++ b/utils/common/src/main/java/brooklyn/util/text/StringPredicates.java
@@ -20,6 +20,7 @@ package brooklyn.util.text;
 
 import java.io.Serializable;
 import java.util.Arrays;
+import java.util.List;
 import java.util.Set;
 
 import javax.annotation.Nullable;
@@ -30,6 +31,7 @@ import com.google.common.base.Function;
 import com.google.common.base.Predicate;
 import com.google.common.base.Predicates;
 import com.google.common.collect.Iterables;
+import com.google.common.collect.Lists;
 
 public class StringPredicates {
 
@@ -67,14 +69,14 @@ public class StringPredicates {
 
     // -----------------
     
-    public static <T extends CharSequence> Predicate<T> containsLiteralCaseInsensitive(final String fragment) {
-        return new ContainsLiteralCaseInsensitive<T>(fragment);
+    public static <T extends CharSequence> Predicate<T> containsLiteralIgnoreCase(final String fragment) {
+        return new ContainsLiteralIgnoreCase<T>(fragment);
     }
 
-    private static final class ContainsLiteralCaseInsensitive<T extends CharSequence> implements Predicate<T> {
+    private static final class ContainsLiteralIgnoreCase<T extends CharSequence> implements Predicate<T> {
         private final String fragment;
 
-        private ContainsLiteralCaseInsensitive(String fragment) {
+        private ContainsLiteralIgnoreCase(String fragment) {
             this.fragment = fragment;
         }
 
@@ -144,14 +146,11 @@ public class StringPredicates {
     // -----------------
     
     public static <T extends CharSequence> Predicate<T> containsAllLiterals(final String... fragments) {
-        return Predicates.and(Iterables.transform(Arrays.asList(fragments), new ConvertStringToContainsLiteralPredicate()));
-    }
-
-    private static final class ConvertStringToContainsLiteralPredicate implements Function<String, Predicate<CharSequence>> {
-        @Override
-        public Predicate<CharSequence> apply(String input) {
-            return containsLiteral(input);
+        List<Predicate<CharSequence>> fragmentPredicates = Lists.newArrayList();
+        for (String fragment : fragments) {
+            fragmentPredicates.add(containsLiteral(fragment));
         }
+        return Predicates.and(fragmentPredicates);
     }
 
     /** @deprecated since 0.7.0 kept only to allow conversion of anonymous inner classes */
@@ -209,7 +208,7 @@ public class StringPredicates {
     /** true if the object *is* a {@link CharSequence} starting with the given prefix */
     public static Predicate<Object> isStringStartingWith(final String prefix) {
         return Predicates.<Object>and(Predicates.instanceOf(CharSequence.class),
-            Predicates.compose(new StartsWith<String>(prefix), StringFunctions.toStringFunction()));
+            Predicates.compose(startsWith(prefix), StringFunctions.toStringFunction()));
     }
 
     /** @deprecated since 0.7.0 kept only to allow conversion of anonymous inner classes */

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/d3f6e342/utils/common/src/test/java/brooklyn/util/text/StringPredicatesTest.java
----------------------------------------------------------------------
diff --git a/utils/common/src/test/java/brooklyn/util/text/StringPredicatesTest.java b/utils/common/src/test/java/brooklyn/util/text/StringPredicatesTest.java
index 26738db..a27900c 100644
--- a/utils/common/src/test/java/brooklyn/util/text/StringPredicatesTest.java
+++ b/utils/common/src/test/java/brooklyn/util/text/StringPredicatesTest.java
@@ -39,9 +39,9 @@ public class StringPredicatesTest {
         Assert.assertFalse(StringPredicates.containsLiteral("xx").apply("text test"));
         Assert.assertFalse(StringPredicates.containsLiteral("xx").apply("texXxt tessst"));
         
-        Assert.assertTrue(StringPredicates.containsLiteralCaseInsensitive("xx").apply("texxxt tessst"));
-        Assert.assertFalse(StringPredicates.containsLiteralCaseInsensitive("xx").apply("text test"));
-        Assert.assertTrue(StringPredicates.containsLiteralCaseInsensitive("xx").apply("texXxt tessst"));
+        Assert.assertTrue(StringPredicates.containsLiteralIgnoreCase("xx").apply("texxxt tessst"));
+        Assert.assertFalse(StringPredicates.containsLiteralIgnoreCase("xx").apply("text test"));
+        Assert.assertTrue(StringPredicates.containsLiteralIgnoreCase("xx").apply("texXxt tessst"));
         
         Assert.assertTrue(StringPredicates.containsAllLiterals("xx", "ss").apply("texxxt tessst"));
         Assert.assertFalse(StringPredicates.containsAllLiterals("xx", "tt").apply("texxxt tessst"));


[04/29] git commit: minor cleanups to software, allowing start with no locations, and including stdin for driver.execute commands

Posted by al...@apache.org.
minor cleanups to software, allowing start with no locations, and including stdin for driver.execute commands


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

Branch: refs/heads/master
Commit: d66c4401fe02ae06e3d66221a37823affc7e0fc4
Parents: 919eaea
Author: Alex Heneveld <al...@cloudsoftcorp.com>
Authored: Thu Oct 23 08:38:55 2014 +0100
Committer: Alex Heneveld <al...@cloudsoftcorp.com>
Committed: Fri Oct 31 09:38:18 2014 -0500

----------------------------------------------------------------------
 .../basic/AbstractSoftwareProcessSshDriver.java   | 18 ++++++++++++++++--
 .../software/MachineLifecycleEffectorTasks.java   |  6 +++++-
 2 files changed, 21 insertions(+), 3 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/d66c4401/software/base/src/main/java/brooklyn/entity/basic/AbstractSoftwareProcessSshDriver.java
----------------------------------------------------------------------
diff --git a/software/base/src/main/java/brooklyn/entity/basic/AbstractSoftwareProcessSshDriver.java b/software/base/src/main/java/brooklyn/entity/basic/AbstractSoftwareProcessSshDriver.java
index 9bee075..8eed782 100644
--- a/software/base/src/main/java/brooklyn/entity/basic/AbstractSoftwareProcessSshDriver.java
+++ b/software/base/src/main/java/brooklyn/entity/basic/AbstractSoftwareProcessSshDriver.java
@@ -51,6 +51,8 @@ import brooklyn.util.os.Os;
 import brooklyn.util.ssh.BashCommands;
 import brooklyn.util.stream.KnownSizeInputStream;
 import brooklyn.util.stream.ReaderInputStream;
+import brooklyn.util.stream.Streams;
+import brooklyn.util.task.DynamicTasks;
 import brooklyn.util.task.Tasks;
 import brooklyn.util.text.StringPredicates;
 import brooklyn.util.text.Strings;
@@ -275,6 +277,8 @@ public abstract class AbstractSoftwareProcessSshDriver extends AbstractSoftwareP
     @SuppressWarnings({ "rawtypes", "unchecked" })
     @Override
     public int execute(Map flags2, List<String> script, String summaryForLogging) {
+        // TODO replace with SshEffectorTasks.ssh ?; remove the use of flags
+        
         Map flags = Maps.newLinkedHashMap();
         if (!flags2.containsKey(IGNORE_ENTITY_SSH_FLAGS)) {
             flags.putAll(getSshFlags());
@@ -286,6 +290,10 @@ public abstract class AbstractSoftwareProcessSshDriver extends AbstractSoftwareP
             if (environment!=null) {
                 Tasks.addTagDynamically(BrooklynTaskTags.tagForEnvStream(BrooklynTaskTags.STREAM_ENV, environment));
             }
+            if (BrooklynTaskTags.stream(Tasks.current(), BrooklynTaskTags.STREAM_STDIN)==null) {
+                Tasks.addTagDynamically(BrooklynTaskTags.tagForStreamSoft(BrooklynTaskTags.STREAM_STDIN, 
+                    Streams.byteArrayOfString(Strings.join(script, "\n"))));
+            }
             if (BrooklynTaskTags.stream(Tasks.current(), BrooklynTaskTags.STREAM_STDOUT)==null) {
                 ByteArrayOutputStream stdout = new ByteArrayOutputStream();
                 Tasks.addTagDynamically(BrooklynTaskTags.tagForStreamSoft(BrooklynTaskTags.STREAM_STDOUT, stdout));
@@ -313,11 +321,15 @@ public abstract class AbstractSoftwareProcessSshDriver extends AbstractSoftwareP
     public void copyInstallResources() {
         getLocation().acquireMutex("installing "+elvis(entity,this),  "installation lock at host for files and templates");
         try {
-            // Override environment variables for this simple command. Otherwise sub-classes might
+            // Ensure environment variables are not looked up here, otherwise sub-classes might
             // lookup port numbers and fail with ugly error if port is not set; better to wait
             // until in Entity's code (e.g. customize) where such checks are done explicitly.
-            execute(ImmutableMap.of("env", ImmutableMap.of()), ImmutableList.of("mkdir -p " + getInstallDir()), "create-install-dir");
+            DynamicTasks.queue(SshEffectorTasks.ssh("mkdir -p " + getInstallDir()).summary("create install directory")
+                .requiringExitCodeZero()).get();
 
+            // TODO see comment in copyResource, that should be queued as a task like the above
+            // (better reporting in activities console)
+            
             Map<String, String> installFiles = entity.getConfig(SoftwareProcess.INSTALL_FILES);
             if (installFiles != null && installFiles.size() > 0) {
                 for (String source : installFiles.keySet()) {
@@ -462,6 +474,7 @@ public abstract class AbstractSoftwareProcessSshDriver extends AbstractSoftwareP
         return copyResource(MutableMap.of(), resource, target, createParentDir);
     }
 
+    @SuppressWarnings({ "rawtypes", "unchecked" })
     public int copyResource(Map sshFlags, String source, String target) {
         return copyResource(sshFlags, source, target, false);
     }
@@ -478,6 +491,7 @@ public abstract class AbstractSoftwareProcessSshDriver extends AbstractSoftwareP
      */
     @SuppressWarnings({ "rawtypes", "unchecked" })
     public int copyResource(Map<Object,Object> sshFlags, String source, String target, boolean createParentDir) {
+        // TODO use SshTasks.put instead, better logging
         Map flags = Maps.newLinkedHashMap();
         if (!sshFlags.containsKey(IGNORE_ENTITY_SSH_FLAGS)) {
             flags.putAll(getSshFlags());

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/d66c4401/software/base/src/main/java/brooklyn/entity/software/MachineLifecycleEffectorTasks.java
----------------------------------------------------------------------
diff --git a/software/base/src/main/java/brooklyn/entity/software/MachineLifecycleEffectorTasks.java b/software/base/src/main/java/brooklyn/entity/software/MachineLifecycleEffectorTasks.java
index 2921c36..3aac2a7 100644
--- a/software/base/src/main/java/brooklyn/entity/software/MachineLifecycleEffectorTasks.java
+++ b/software/base/src/main/java/brooklyn/entity/software/MachineLifecycleEffectorTasks.java
@@ -21,6 +21,7 @@ package brooklyn.entity.software;
 import java.io.Serializable;
 import java.util.Arrays;
 import java.util.Collection;
+import java.util.Collections;
 import java.util.List;
 import java.util.Map;
 import java.util.concurrent.Callable;
@@ -152,7 +153,10 @@ public abstract class MachineLifecycleEffectorTasks {
             @Override
             public Void call(ConfigBag parameters) {
                 Collection<? extends Location> locations = parameters.get(LOCATIONS);
-                Preconditions.checkNotNull(locations, "locations");
+                if (locations==null) {
+                    // null/empty will mean to inherit from parent
+                    locations = Collections.emptyList();
+                }
                 start(locations);
                 return null;
             }


[21/29] git commit: use more rules to infer the subpath inside a brooklyn archive

Posted by al...@apache.org.
use more rules to infer the subpath inside a brooklyn archive


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

Branch: refs/heads/master
Commit: 6dfd17894739dc24e0198932e49218ec7dfcf8c9
Parents: d768efa
Author: Alex Heneveld <al...@cloudsoftcorp.com>
Authored: Thu Oct 30 21:18:07 2014 -0500
Committer: Alex Heneveld <al...@cloudsoftcorp.com>
Committed: Fri Oct 31 09:39:51 2014 -0500

----------------------------------------------------------------------
 .../entity/brooklynnode/BrooklynNode.java       |  3 +-
 .../brooklynnode/BrooklynNodeSshDriver.java     | 35 ++++++++++++++++++--
 2 files changed, 35 insertions(+), 3 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/6dfd1789/software/base/src/main/java/brooklyn/entity/brooklynnode/BrooklynNode.java
----------------------------------------------------------------------
diff --git a/software/base/src/main/java/brooklyn/entity/brooklynnode/BrooklynNode.java b/software/base/src/main/java/brooklyn/entity/brooklynnode/BrooklynNode.java
index a0f0032..20626f9 100644
--- a/software/base/src/main/java/brooklyn/entity/brooklynnode/BrooklynNode.java
+++ b/software/base/src/main/java/brooklyn/entity/brooklynnode/BrooklynNode.java
@@ -87,7 +87,8 @@ public interface BrooklynNode extends SoftwareProcess, UsesJava {
     ConfigKey<String> SUBPATH_IN_ARCHIVE = ConfigKeys.newStringConfigKey("brooklynnode.download.archive.subpath",
         "Path to the main directory in the archive being supplied for installation; "
         + "to use the root of an archive, specify '.'; "
-        + "default value if left blank is the appropriate value for brooklyn,"
+        + "default value taken based on download URL (e.g. 'name' for 'http://path/name.tgz' or 'http://path/name-dist.tgz') "
+        + "falling back to an appropriate value for brooklyn, "
         + "e.g. 'brooklyn-"+BrooklynVersion.INSTANCE.getVersion()+"'", null);
 
     @SetFromFlag("managementUser")

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/6dfd1789/software/base/src/main/java/brooklyn/entity/brooklynnode/BrooklynNodeSshDriver.java
----------------------------------------------------------------------
diff --git a/software/base/src/main/java/brooklyn/entity/brooklynnode/BrooklynNodeSshDriver.java b/software/base/src/main/java/brooklyn/entity/brooklynnode/BrooklynNodeSshDriver.java
index bd33b88..add3f8f 100644
--- a/software/base/src/main/java/brooklyn/entity/brooklynnode/BrooklynNodeSshDriver.java
+++ b/software/base/src/main/java/brooklyn/entity/brooklynnode/BrooklynNodeSshDriver.java
@@ -31,6 +31,7 @@ import java.util.Map;
 
 import brooklyn.entity.basic.Entities;
 import brooklyn.entity.brooklynnode.BrooklynNode.ExistingFileBehaviour;
+import brooklyn.entity.drivers.downloads.DownloadSubstituters;
 import brooklyn.entity.java.JavaSoftwareProcessSshDriver;
 import brooklyn.entity.software.SshEffectorTasks;
 import brooklyn.location.basic.SshMachineLocation;
@@ -76,14 +77,44 @@ public class BrooklynNodeSshDriver extends JavaSoftwareProcessSshDriver implemen
     
     @Override
     protected String getInstallLabelExtraSalt() {
-        return Identifiers.makeIdFromHash(Objects.hashCode(entity.getConfig(BrooklynNode.DOWNLOAD_URL), entity.getConfig(BrooklynNode.DISTRO_UPLOAD_URL)));
+        String downloadUrl = entity.getConfig(BrooklynNode.DOWNLOAD_URL);
+        String uploadUrl = entity.getConfig(BrooklynNode.DISTRO_UPLOAD_URL);
+        if (Objects.equal(downloadUrl, BrooklynNode.DOWNLOAD_URL.getConfigKey().getDefaultValue()) &&
+                Objects.equal(uploadUrl, BrooklynNode.DISTRO_UPLOAD_URL.getDefaultValue())) {
+            // if both are at the default value, then no salt
+            return null;
+        }
+        return Identifiers.makeIdFromHash(Objects.hashCode(downloadUrl, uploadUrl));
     }
 
     @Override
     public void preInstall() {
         resolver = Entities.newDownloader(this);
         String subpath = entity.getConfig(BrooklynNode.SUBPATH_IN_ARCHIVE);
-        if (Strings.isBlank(subpath)) subpath = format("brooklyn-%s", getVersion());
+        if (subpath==null) {
+            // assume the dir name is `basename-VERSION` where download link is `basename-VERSION-dist.tar.gz`
+            String uploadUrl = entity.getConfig(BrooklynNode.DISTRO_UPLOAD_URL);
+            String origDownloadName = uploadUrl;
+            if (origDownloadName==null) { 
+                String downloadUrlTemplate = entity.getAttribute(BrooklynNode.DOWNLOAD_URL);
+                if (downloadUrlTemplate!=null) {
+                    // BasicDownloadResolver makes it crazy hard to get the template-evaluated value of DOWNLOAD_URL
+                    origDownloadName = DownloadSubstituters.substitute(downloadUrlTemplate, DownloadSubstituters.getBasicEntitySubstitutions(this));
+                }
+            }
+            if (origDownloadName!=null) {
+                origDownloadName = Urls.getBasename(origDownloadName);
+                String downloadName = origDownloadName;
+                downloadName = Strings.removeFromEnd(downloadName, ".tar.gz");
+                downloadName = Strings.removeFromEnd(downloadName, ".tgz");
+                downloadName = Strings.removeFromEnd(downloadName, ".zip");
+                if (!downloadName.equals(origDownloadName)) {
+                    downloadName = Strings.removeFromEnd(downloadName, "-dist");
+                    subpath = downloadName;
+                }
+            }
+        }
+        if (subpath==null) subpath = format("brooklyn-%s", getVersion());
         setExpandedInstallDir(Os.mergePaths(getInstallDir(), resolver.getUnpackedDirectoryName(subpath)));
     }
 


[25/29] git commit: change test logic to match EntityConfigMap behaviour for inheritance

Posted by al...@apache.org.
change test logic to match EntityConfigMap behaviour for inheritance

two implications of recent changes to EntityConfigMap caused failures in these tests,
i think the new behaviour is desired:
* any unknown flags/config set at parent remain is now NOT interpreted as flags at children
* getAllConfig() includes unknown config


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

Branch: refs/heads/master
Commit: ebd446291d47dd72b494a7c9d24ace5c5cc2ae41
Parents: 139822a
Author: Alex Heneveld <al...@cloudsoftcorp.com>
Authored: Thu Oct 30 22:54:12 2014 -0500
Committer: Alex Heneveld <al...@cloudsoftcorp.com>
Committed: Fri Oct 31 09:39:51 2014 -0500

----------------------------------------------------------------------
 .../java/brooklyn/entity/basic/EntityConfigTest.java  | 14 +++++++++-----
 1 file changed, 9 insertions(+), 5 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/ebd44629/core/src/test/java/brooklyn/entity/basic/EntityConfigTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/brooklyn/entity/basic/EntityConfigTest.java b/core/src/test/java/brooklyn/entity/basic/EntityConfigTest.java
index 3296941..da27c61 100644
--- a/core/src/test/java/brooklyn/entity/basic/EntityConfigTest.java
+++ b/core/src/test/java/brooklyn/entity/basic/EntityConfigTest.java
@@ -83,7 +83,7 @@ public class EntityConfigTest {
         EntityInternal entity = managementContext.getEntityManager().createEntity(EntitySpec.create(MyEntity.class)
                 .configure("notThere", "notThereVal"));
         
-        assertEquals(entity.getAllConfig(), ImmutableMap.of());
+        assertEquals(entity.getAllConfig(), ImmutableMap.of(ConfigKeys.newConfigKey(Object.class, "notThere"), "notThereVal"));
         assertEquals(entity.getAllConfigBag().getAllConfig(), ImmutableMap.of("notThere", "notThereVal"));
         assertEquals(entity.getLocalConfigBag().getAllConfig(), ImmutableMap.of("notThere", "notThereVal"));
     }
@@ -98,8 +98,10 @@ public class EntityConfigTest {
         EntityInternal child = managementContext.getEntityManager().createEntity(EntitySpec.create(MyChildEntity.class)
                 .parent(entity));
 
-        assertEquals(child.getAllConfig(), ImmutableMap.of(MyChildEntity.MY_CHILD_CONFIG, "myval1", MyChildEntity.MY_CHILD_CONFIG_WITH_FLAGNAME, "myval2"));
-        assertEquals(child.getAllConfigBag().getAllConfig(), ImmutableMap.of("mychildentity.myconfig", "myval1", "mychildentity.myconfigwithflagname", "myval2", "notThere", "notThereVal"));
+        assertEquals(child.getAllConfig(), ImmutableMap.of(MyChildEntity.MY_CHILD_CONFIG, "myval1", 
+            ConfigKeys.newConfigKey(Object.class, "mychildconfigflagname"), "myval2",
+            ConfigKeys.newConfigKey(Object.class, "notThere"), "notThereVal"));
+        assertEquals(child.getAllConfigBag().getAllConfig(), ImmutableMap.of("mychildentity.myconfig", "myval1", "mychildconfigflagname", "myval2", "notThere", "notThereVal"));
         assertEquals(child.getLocalConfigBag().getAllConfig(), ImmutableMap.of());
     }
     
@@ -127,7 +129,8 @@ public class EntityConfigTest {
                 .configure("mychildentity.myconfigwithflagname", "overrideMyval")
                 .configure("notThere", "overrideNotThereVal"));
 
-        assertEquals(child.getAllConfig(), ImmutableMap.of(MyChildEntity.MY_CHILD_CONFIG_WITH_FLAGNAME, "overrideMyval"));
+        assertEquals(child.getAllConfig(), ImmutableMap.of(MyChildEntity.MY_CHILD_CONFIG_WITH_FLAGNAME, "overrideMyval",
+            ConfigKeys.newConfigKey(Object.class, "notThere"), "overrideNotThereVal"));
         assertEquals(child.getAllConfigBag().getAllConfig(), ImmutableMap.of("mychildentity.myconfigwithflagname", "overrideMyval", "notThere", "overrideNotThereVal"));
         assertEquals(child.getLocalConfigBag().getAllConfig(), ImmutableMap.of("mychildentity.myconfigwithflagname", "overrideMyval", "notThere", "overrideNotThereVal"));
     }
@@ -135,7 +138,8 @@ public class EntityConfigTest {
     @Test
     public void testChildCanOverrideConfigUsingFlagName() throws Exception {
         EntityInternal entity = managementContext.getEntityManager().createEntity(EntitySpec.create(MyEntity.class)
-                .configure("mychildconfigflagname", "myval"));
+                .configure(MyChildEntity.MY_CHILD_CONFIG_WITH_FLAGNAME, "myval"));
+        assertEquals(entity.getAllConfig(), ImmutableMap.of(MyChildEntity.MY_CHILD_CONFIG_WITH_FLAGNAME, "myval"));
 
         EntityInternal child = managementContext.getEntityManager().createEntity(EntitySpec.create(MyChildEntity.class)
                 .parent(entity)


[14/29] git commit: add apache header to new java to kill the RAT problem

Posted by al...@apache.org.
add apache header to new java to kill the RAT problem


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

Branch: refs/heads/master
Commit: d87e1179da4c164424eb63d65d2ff9a93112a667
Parents: d94ed9a
Author: Alex Heneveld <al...@cloudsoftcorp.com>
Authored: Mon Oct 27 00:42:16 2014 -0700
Committer: Alex Heneveld <al...@cloudsoftcorp.com>
Committed: Fri Oct 31 09:38:20 2014 -0500

----------------------------------------------------------------------
 .../java/brooklyn/entity/basic/EntityTasks.java   | 18 ++++++++++++++++++
 1 file changed, 18 insertions(+)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/d87e1179/core/src/main/java/brooklyn/entity/basic/EntityTasks.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/entity/basic/EntityTasks.java b/core/src/main/java/brooklyn/entity/basic/EntityTasks.java
index fd0a63a..37114c5 100644
--- a/core/src/main/java/brooklyn/entity/basic/EntityTasks.java
+++ b/core/src/main/java/brooklyn/entity/basic/EntityTasks.java
@@ -1,3 +1,21 @@
+/*
+ * 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 brooklyn.entity.basic;
 
 import brooklyn.entity.Entity;


[02/29] git commit: add an upgrade effector for the BrooklynNode, and a sample yaml, and improve lifecycle so that stop_* methods destroy the VM and set a STOPPING state

Posted by al...@apache.org.
add an upgrade effector for the BrooklynNode, and a sample yaml, and improve lifecycle so that stop_* methods destroy the VM and set a STOPPING state


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

Branch: refs/heads/master
Commit: dfe323c0cb9f1cd0817a85365d6a760b2f013104
Parents: 6f895aa
Author: Alex Heneveld <al...@cloudsoftcorp.com>
Authored: Tue Oct 21 19:12:09 2014 +0100
Committer: Alex Heneveld <al...@cloudsoftcorp.com>
Committed: Fri Oct 31 09:36:16 2014 -0500

----------------------------------------------------------------------
 .../basic/BasicConfigurableEntityFactory.java   |   2 +
 .../entity/basic/BasicParameterType.java        |   3 -
 .../brooklyn/entity/basic/EntityFactory.java    |   4 +-
 .../basic/AbstractSoftwareProcessSshDriver.java |   2 +-
 .../entity/brooklynnode/BrooklynNode.java       |   5 +-
 .../entity/brooklynnode/BrooklynNodeImpl.java   |  28 +++--
 .../brooklynnode/BrooklynUpgradeEffector.java   | 126 +++++++++++++++++++
 .../entity/brooklynnode/brooklyn-node.yaml      |  26 ++++
 8 files changed, 177 insertions(+), 19 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/dfe323c0/core/src/main/java/brooklyn/entity/basic/BasicConfigurableEntityFactory.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/entity/basic/BasicConfigurableEntityFactory.java b/core/src/main/java/brooklyn/entity/basic/BasicConfigurableEntityFactory.java
index 6ecedc3..5d1d892 100644
--- a/core/src/main/java/brooklyn/entity/basic/BasicConfigurableEntityFactory.java
+++ b/core/src/main/java/brooklyn/entity/basic/BasicConfigurableEntityFactory.java
@@ -32,6 +32,8 @@ import brooklyn.entity.Entity;
 import com.google.common.base.Objects;
 import com.google.common.base.Throwables;
 
+/** @deprecated since 0.7.0; use EntitySpec instead, as per {@link EntityFactory} javadoc */
+@Deprecated
 public class BasicConfigurableEntityFactory<T extends Entity> extends AbstractConfigurableEntityFactory<T> {
     private transient Class<? extends T> clazz;
     private final String clazzName;

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/dfe323c0/core/src/main/java/brooklyn/entity/basic/BasicParameterType.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/entity/basic/BasicParameterType.java b/core/src/main/java/brooklyn/entity/basic/BasicParameterType.java
index 5645d9d..81c5d24 100644
--- a/core/src/main/java/brooklyn/entity/basic/BasicParameterType.java
+++ b/core/src/main/java/brooklyn/entity/basic/BasicParameterType.java
@@ -25,9 +25,6 @@ import brooklyn.entity.ParameterType;
 
 import com.google.common.base.Objects;
 
-/**
- * TODO javadoc
- */
 public class BasicParameterType<T> implements ParameterType<T> {
     private static final long serialVersionUID = -5521879180483663919L;
     

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/dfe323c0/core/src/main/java/brooklyn/entity/basic/EntityFactory.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/entity/basic/EntityFactory.java b/core/src/main/java/brooklyn/entity/basic/EntityFactory.java
index cf06a1d..8e93d12 100644
--- a/core/src/main/java/brooklyn/entity/basic/EntityFactory.java
+++ b/core/src/main/java/brooklyn/entity/basic/EntityFactory.java
@@ -18,10 +18,10 @@
  */
 package brooklyn.entity.basic;
 
-import brooklyn.entity.Entity;
-
 import java.util.Map;
 
+import brooklyn.entity.Entity;
+
 /**
  * A Factory for creating entities.
  *

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/dfe323c0/software/base/src/main/java/brooklyn/entity/basic/AbstractSoftwareProcessSshDriver.java
----------------------------------------------------------------------
diff --git a/software/base/src/main/java/brooklyn/entity/basic/AbstractSoftwareProcessSshDriver.java b/software/base/src/main/java/brooklyn/entity/basic/AbstractSoftwareProcessSshDriver.java
index e6d02fa..9bee075 100644
--- a/software/base/src/main/java/brooklyn/entity/basic/AbstractSoftwareProcessSshDriver.java
+++ b/software/base/src/main/java/brooklyn/entity/basic/AbstractSoftwareProcessSshDriver.java
@@ -184,7 +184,7 @@ public abstract class AbstractSoftwareProcessSshDriver extends AbstractSoftwareP
     }
     
     protected void setInstallLabel() {
-        if (getEntity().getConfigRaw(SoftwareProcess.INSTALL_UNIQUE_LABEL, true).isPresent()) return; 
+        if (getEntity().getConfigRaw(SoftwareProcess.INSTALL_UNIQUE_LABEL, true).isPresentAndNonNull()) return; 
         getEntity().setConfig(SoftwareProcess.INSTALL_UNIQUE_LABEL, 
             getEntity().getEntityType().getSimpleName()+
             (Strings.isNonBlank(getVersion()) ? "_"+getVersion() : "")+

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/dfe323c0/software/base/src/main/java/brooklyn/entity/brooklynnode/BrooklynNode.java
----------------------------------------------------------------------
diff --git a/software/base/src/main/java/brooklyn/entity/brooklynnode/BrooklynNode.java b/software/base/src/main/java/brooklyn/entity/brooklynnode/BrooklynNode.java
index 6208813..16fdca2 100644
--- a/software/base/src/main/java/brooklyn/entity/brooklynnode/BrooklynNode.java
+++ b/software/base/src/main/java/brooklyn/entity/brooklynnode/BrooklynNode.java
@@ -248,7 +248,6 @@ public interface BrooklynNode extends SoftwareProcess, UsesJava {
         ConfigKey<Duration> REQUEST_TIMEOUT = ConfigKeys.newConfigKey(Duration.class, "requestTimeout", "Maximum time to block the request for the shutdown to finish, 0 to wait infinitely");
         ConfigKey<Duration> DELAY_FOR_HTTP_RETURN = ConfigKeys.newConfigKey(Duration.class, "delayForHttpReturn", "The delay before exiting the process, to permit the REST response to be returned");
         Effector<Void> SHUTDOWN = Effectors.effector(Void.class, "shutdown")
-            .description("Shutdown the remote brooklyn instance")
             .description("Shutdown the remote brooklyn instance (stops via the REST API only; leaves any VM)")
             .parameter(STOP_APPS_FIRST)
             .parameter(FORCE_SHUTDOWN_ON_ERROR)
@@ -263,7 +262,7 @@ public interface BrooklynNode extends SoftwareProcess, UsesJava {
     public interface StopNodeButLeaveAppsEffector {
         ConfigKey<Duration> TIMEOUT = ConfigKeys.newConfigKey(Duration.class, "timeout", "How long to wait before giving up on stopping the node", Duration.ONE_HOUR);
         Effector<Void> STOP_NODE_BUT_LEAVE_APPS = Effectors.effector(Void.class, "stopNodeButLeaveApps")
-                .description("Stop the node but if it was managing other applications, leave them running")
+                .description("Stop the Brooklyn process, and any VM created, and unmanage this entity; but if it was managing other applications, leave them running")
                 .parameter(TIMEOUT)
                 .buildAbstract();
     }
@@ -273,7 +272,7 @@ public interface BrooklynNode extends SoftwareProcess, UsesJava {
     public interface StopNodeAndKillAppsEffector {
         ConfigKey<Duration> TIMEOUT = ConfigKeys.newConfigKey(Duration.class, "timeout", "How long to wait before giving up on stopping the node", Duration.ONE_HOUR);
         Effector<Void> STOP_NODE_AND_KILL_APPS = Effectors.effector(Void.class, "stopNodeAndKillApps")
-                .description("Stop all apps managed by the node and shutdown the node")
+                .description("Stop all apps managed by the Brooklyn process, stop the process, and any VM created, and unmanage this entity")
                 .parameter(TIMEOUT)
                 .buildAbstract();
     }

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/dfe323c0/software/base/src/main/java/brooklyn/entity/brooklynnode/BrooklynNodeImpl.java
----------------------------------------------------------------------
diff --git a/software/base/src/main/java/brooklyn/entity/brooklynnode/BrooklynNodeImpl.java b/software/base/src/main/java/brooklyn/entity/brooklynnode/BrooklynNodeImpl.java
index 630563d..6416970 100644
--- a/software/base/src/main/java/brooklyn/entity/brooklynnode/BrooklynNodeImpl.java
+++ b/software/base/src/main/java/brooklyn/entity/brooklynnode/BrooklynNodeImpl.java
@@ -36,6 +36,7 @@ import brooklyn.entity.basic.Attributes;
 import brooklyn.entity.basic.Entities;
 import brooklyn.entity.basic.EntityPredicates;
 import brooklyn.entity.basic.Lifecycle;
+import brooklyn.entity.basic.ServiceStateLogic;
 import brooklyn.entity.basic.ServiceStateLogic.ServiceNotUpLogic;
 import brooklyn.entity.basic.SoftwareProcessImpl;
 import brooklyn.entity.brooklynnode.effector.SetHAModeEffectorBody;
@@ -102,6 +103,7 @@ public class BrooklynNodeImpl extends SoftwareProcessImpl implements BrooklynNod
         getMutableEntityType().addEffector(StopNodeAndKillAppsEffectorBody.STOP_NODE_AND_KILL_APPS);
         getMutableEntityType().addEffector(SetHAPriorityEffectorBody.SET_HA_PRIORITY);
         getMutableEntityType().addEffector(SetHAModeEffectorBody.SET_HA_MODE);
+        getMutableEntityType().addEffector(BrooklynUpgradeEffector.UPGRADE);
     }
 
     @Override
@@ -191,13 +193,13 @@ public class BrooklynNodeImpl extends SoftwareProcessImpl implements BrooklynNod
 
         @Override
         public Void call(ConfigBag parameters) {
-            Map<String, String> formParams = new MutableMap<String, String>()
-                    .addIfNotNull("stopAppsFirst", toNullableString(parameters.get(STOP_APPS_FIRST)))
-                    .addIfNotNull("forceShutdownOnError", toNullableString(parameters.get(FORCE_SHUTDOWN_ON_ERROR)))
-                    .addIfNotNull("shutdownTimeout", toNullableString(parameters.get(SHUTDOWN_TIMEOUT)))
-                    .addIfNotNull("requestTimeout", toNullableString(parameters.get(REQUEST_TIMEOUT)))
-                    .addIfNotNull("delayForHttpReturn", toNullableString(parameters.get(DELAY_FOR_HTTP_RETURN)));
+            MutableMap<String, String> formParams = MutableMap.of();
+            Lifecycle initialState = entity().getAttribute(Attributes.SERVICE_STATE_ACTUAL);
+            ServiceStateLogic.setExpectedState(entity(), Lifecycle.STOPPING);
+            for (ConfigKey<?> k: new ConfigKey<?>[] { STOP_APPS_FIRST, FORCE_SHUTDOWN_ON_ERROR, SHUTDOWN_TIMEOUT, REQUEST_TIMEOUT, DELAY_FOR_HTTP_RETURN })
+                formParams.addIfNotNull(k.getName(), toNullableString(parameters.get(k)));
             try {
+                log.debug("Shutting down "+entity()+" with "+formParams);
                 HttpToolResponse resp = ((BrooklynNode)entity()).http()
                     .post("/v1/server/shutdown",
                         ImmutableMap.of("Brooklyn-Allow-Non-Master-Access", "true"),
@@ -207,14 +209,15 @@ public class BrooklynNodeImpl extends SoftwareProcessImpl implements BrooklynNod
                 }
             } catch (Exception e) {
                 Exceptions.propagateIfFatal(e);
-                Lifecycle state = entity().getAttribute(Attributes.SERVICE_STATE_ACTUAL);
-                if (state!=Lifecycle.RUNNING) {
+                ServiceStateLogic.setExpectedState(entity(), Lifecycle.ON_FIRE);
+                if (initialState!=Lifecycle.RUNNING) {
                     // ignore failure in this task if the node is not currently running
                     Tasks.markInessential();
                 }
-                throw new PropagatedRuntimeException("Error shutting down remote node "+entity()+" (in state "+state+"): "+Exceptions.collapseText(e), e);
+                throw new PropagatedRuntimeException("Error shutting down remote node "+entity()+" (in state "+initialState+"): "+Exceptions.collapseText(e), e);
             }
-            ServiceNotUpLogic.updateNotUpIndicator(entity(), "brooklynnode.shutdown", "Shutdown of remote node has completed successfuly");
+            ServiceNotUpLogic.updateNotUpIndicator(entity(), SHUTDOWN.getName(), "Shutdown of remote node has completed successfuly");
+            ServiceStateLogic.setExpectedState(entity(), Lifecycle.STOPPED);
             return null;
         }
 
@@ -228,6 +231,11 @@ public class BrooklynNodeImpl extends SoftwareProcessImpl implements BrooklynNod
 
     }
 
+    @Override
+    protected void preStart() {
+        ServiceNotUpLogic.clearNotUpIndicator(this, SHUTDOWN.getName());
+    }
+    
     public static class StopNodeButLeaveAppsEffectorBody extends EffectorBody<Void> implements StopNodeButLeaveAppsEffector {
         public static final Effector<Void> STOP_NODE_BUT_LEAVE_APPS = Effectors.effector(BrooklynNode.STOP_NODE_BUT_LEAVE_APPS).impl(new StopNodeButLeaveAppsEffectorBody()).build();
 

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/dfe323c0/software/base/src/main/java/brooklyn/entity/brooklynnode/BrooklynUpgradeEffector.java
----------------------------------------------------------------------
diff --git a/software/base/src/main/java/brooklyn/entity/brooklynnode/BrooklynUpgradeEffector.java b/software/base/src/main/java/brooklyn/entity/brooklynnode/BrooklynUpgradeEffector.java
new file mode 100644
index 0000000..08da8d4
--- /dev/null
+++ b/software/base/src/main/java/brooklyn/entity/brooklynnode/BrooklynUpgradeEffector.java
@@ -0,0 +1,126 @@
+/*
+ * 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 brooklyn.entity.brooklynnode;
+
+import java.util.Map;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import brooklyn.config.ConfigKey;
+import brooklyn.entity.Effector;
+import brooklyn.entity.basic.Entities;
+import brooklyn.entity.basic.EntityInternal;
+import brooklyn.entity.basic.SoftwareProcess;
+import brooklyn.entity.effector.EffectorBody;
+import brooklyn.entity.effector.Effectors;
+import brooklyn.entity.proxying.EntitySpec;
+import brooklyn.entity.software.SshEffectorTasks;
+import brooklyn.event.basic.MapConfigKey;
+import brooklyn.util.config.ConfigBag;
+import brooklyn.util.net.Urls;
+import brooklyn.util.task.DynamicTasks;
+import brooklyn.util.task.Tasks;
+
+import com.google.common.base.Preconditions;
+import com.google.common.reflect.TypeToken;
+
+@SuppressWarnings("serial")
+public class BrooklynUpgradeEffector {
+
+    private static final Logger log = LoggerFactory.getLogger(BrooklynUpgradeEffector.class);
+    
+    public static final ConfigKey<String> DOWNLOAD_URL = BrooklynNode.DOWNLOAD_URL.getConfigKey();
+    public static final ConfigKey<Map<String,Object>> EXTRA_CONFIG = MapConfigKey.builder(new TypeToken<Map<String,Object>>() {}).name("extraConfig").description("Additional new config to set on this entity as part of upgrading").build();
+
+    public static final Effector<Void> UPGRADE = Effectors.effector(Void.class, "upgrade")
+        .description("Changes the Brooklyn build used to run this node, by spawning a dry-run node then copying the installed files across")
+        .parameter(BrooklynNode.SUGGESTED_VERSION).parameter(DOWNLOAD_URL).parameter(EXTRA_CONFIG)
+        .impl(new UpgradeImpl()).build();
+    
+    public static class UpgradeImpl extends EffectorBody<Void> {
+        @Override
+        public Void call(ConfigBag parametersO) {
+            ConfigBag parameters = ConfigBag.newInstanceCopying(parametersO);
+            
+            /*
+             * all parameters are passed to children, apart from EXTRA_CONFIG
+             * whose value (as a map) is so passed; it provides an easy way to set extra config in the gui.
+             * (IOW a key-value mapping can be passed either inside EXTRA_CONFIG or as a sibling to EXTRA_CONFIG)  
+             */
+            if (parameters.containsKey(EXTRA_CONFIG)) {
+                Map<String, Object> extra = parameters.get(EXTRA_CONFIG);
+                parameters.remove(EXTRA_CONFIG);
+                parameters.putAll(extra);
+            }
+            log.debug(this+" upgrading, using "+parameters);
+            entity().getConfigMap().addToLocalBag(parameters.getAllConfig());
+
+            // 1 add new brooklyn version entity as child (so uses same machine), with same config apart from things in parameters
+            final BrooklynNode dryRunChild = entity().addChild(EntitySpec.create(BrooklynNode.class).configure(parameters.getAllConfig())
+                .displayName("Upgraded Version Dry-Run Node")
+                // TODO enforce hot-standby
+                .configure(BrooklynNode.INSTALL_DIR, BrooklynNode.INSTALL_DIR.getConfigKey().getDefaultValue())
+                .configure(BrooklynNode.INSTALL_UNIQUE_LABEL, BrooklynNode.INSTALL_UNIQUE_LABEL.getDefaultValue()));
+            Entities.manage(dryRunChild);
+            final String versionUid = dryRunChild.getId();
+            ((EntityInternal)dryRunChild).setDisplayName("Upgraded Version Dry-Run Node ("+versionUid+")");
+
+            DynamicTasks.queue(Effectors.invocation(dryRunChild, BrooklynNode.START, ConfigBag.EMPTY));
+            
+            // 2 confirm hot standby status
+            // TODO poll, wait for HOT_STANDBY; error if anything else (other than STARTING)
+//            status = dryRun.getAttribute(BrooklynNode.STATUS);
+
+            // 3 stop new version
+            // 4 stop old version
+            DynamicTasks.queue(Tasks.builder().name("shutdown original and transient nodes")
+                .add(Effectors.invocation(dryRunChild, BrooklynNode.SHUTDOWN, ConfigBag.EMPTY))
+                .add(Effectors.invocation(entity(), BrooklynNode.SHUTDOWN, ConfigBag.EMPTY))
+                .build());
+            
+            // 5 move old files, and move new files
+            DynamicTasks.queue(Tasks.builder().name("setup new version").body(new Runnable() {
+                @Override
+                public void run() {
+                    String runDir = entity().getAttribute(SoftwareProcess.RUN_DIR);
+                    String bkDir = Urls.mergePaths(runDir, "..", Urls.getBasename(runDir)+"-backups", versionUid);
+                    String dryRunDir = Preconditions.checkNotNull(dryRunChild.getAttribute(SoftwareProcess.RUN_DIR));
+                    log.debug(this+" storing backup of previous version in "+bkDir);
+                    DynamicTasks.queue(SshEffectorTasks.ssh(
+                        "cd "+runDir,
+                        "mkdir -p "+bkDir,
+                        "mv * "+bkDir,
+                        "cd "+dryRunDir,
+                        "mv * "+runDir
+                        ).summary("move files"));
+                }
+            }).build());
+
+            // 6 start this entity, running the new version
+            DynamicTasks.queue(Effectors.invocation(entity(), BrooklynNode.START, ConfigBag.EMPTY));
+            
+            DynamicTasks.waitForLast();
+            Entities.unmanage(dryRunChild);
+            
+            return null;
+        }
+    }
+    
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/dfe323c0/software/base/src/test/resources/brooklyn/entity/brooklynnode/brooklyn-node.yaml
----------------------------------------------------------------------
diff --git a/software/base/src/test/resources/brooklyn/entity/brooklynnode/brooklyn-node.yaml b/software/base/src/test/resources/brooklyn/entity/brooklynnode/brooklyn-node.yaml
new file mode 100644
index 0000000..a53e9de
--- /dev/null
+++ b/software/base/src/test/resources/brooklyn/entity/brooklynnode/brooklyn-node.yaml
@@ -0,0 +1,26 @@
+#
+# 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.
+#
+
+services:
+- type: brooklyn.entity.brooklynnode.BrooklynNode
+  ## to use a local file, specify something such as the following:
+  # downloadUrl: ~/.m2/repository/io/brooklyn/brooklyn-dist/0.7.0-SNAPSHOT/brooklyn-dist-0.7.0-SNAPSHOT-dist.tar.gz
+  # downloadUrl: file:///tmp/brooklyn-dist-0.7.0-SNAPSHOT-dist.tar.gz
+
+location: localhost


[18/29] git commit: remove renderer hint for MASTER_NODE -- it is not needed as type Entity gets this automatically, plus it was broken (it needs a special post processing); and add an enricher to publish at the cluster the url for that node

Posted by al...@apache.org.
remove renderer hint for MASTER_NODE -- it is not needed as type Entity gets this automatically, plus it was broken (it needs a special post processing);
and add an enricher to publish at the cluster the url for that node


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

Branch: refs/heads/master
Commit: 82666ea148a2ec72e428c64084574af9fd67f0fa
Parents: 294c217
Author: Alex Heneveld <al...@cloudsoftcorp.com>
Authored: Sun Oct 26 14:22:11 2014 +0000
Committer: Alex Heneveld <al...@cloudsoftcorp.com>
Committed: Fri Oct 31 09:38:20 2014 -0500

----------------------------------------------------------------------
 .../entity/brooklynnode/BrooklynClusterImpl.java    | 16 +++++++++-------
 1 file changed, 9 insertions(+), 7 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/82666ea1/software/base/src/main/java/brooklyn/entity/brooklynnode/BrooklynClusterImpl.java
----------------------------------------------------------------------
diff --git a/software/base/src/main/java/brooklyn/entity/brooklynnode/BrooklynClusterImpl.java b/software/base/src/main/java/brooklyn/entity/brooklynnode/BrooklynClusterImpl.java
index 4247ff3..48c9472 100644
--- a/software/base/src/main/java/brooklyn/entity/brooklynnode/BrooklynClusterImpl.java
+++ b/software/base/src/main/java/brooklyn/entity/brooklynnode/BrooklynClusterImpl.java
@@ -24,13 +24,14 @@ import java.util.concurrent.Callable;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-import brooklyn.config.render.RendererHints;
+import brooklyn.enricher.Enrichers;
 import brooklyn.entity.Entity;
+import brooklyn.entity.basic.EntityFunctions;
 import brooklyn.entity.basic.EntityPredicates;
 import brooklyn.entity.basic.ServiceStateLogic;
 import brooklyn.entity.basic.ServiceStateLogic.ServiceProblemsLogic;
-import brooklyn.entity.brooklynnode.effector.SelectMasterEffectorBody;
 import brooklyn.entity.brooklynnode.effector.BrooklynClusterUpgradeEffectorBody;
+import brooklyn.entity.brooklynnode.effector.SelectMasterEffectorBody;
 import brooklyn.entity.group.DynamicClusterImpl;
 import brooklyn.event.feed.function.FunctionFeed;
 import brooklyn.event.feed.function.FunctionPollConfig;
@@ -47,11 +48,6 @@ public class BrooklynClusterImpl extends DynamicClusterImpl implements BrooklynC
 
     private static final Logger LOG = LoggerFactory.getLogger(BrooklynClusterImpl.class);
 
-    static {
-        // XXX not needed or wanted
-        RendererHints.register(MASTER_NODE, RendererHints.namedActionWithUrl());
-    }
-
     // TODO should we set a default MEMBER_SPEC ?  difficult though because we'd need to set a password
 
     @SuppressWarnings("unused")
@@ -70,6 +66,12 @@ public class BrooklynClusterImpl extends DynamicClusterImpl implements BrooklynC
                         .period(Duration.ONE_SECOND)
                         .callable(new MasterChildFinder()))
                 .build();
+        
+        addEnricher( Enrichers.builder().transforming(MASTER_NODE)
+            .uniqueTag("master-node-web-uri")
+            .publishing(BrooklynNode.WEB_CONSOLE_URI)
+            .computing(EntityFunctions.attribute(BrooklynNode.WEB_CONSOLE_URI))
+            .build() );
     }
 
     private final class MasterChildFinder implements Callable<BrooklynNode> {


[15/29] git commit: have deprecated predicates methods point to new classes, use a private unused to preserve the anonymous inner (code review)

Posted by al...@apache.org.
have deprecated predicates methods point to new classes, use a private unused to preserve the anonymous inner (code review)


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

Branch: refs/heads/master
Commit: ec3701a8bb931c719dad366d762ae609080941e2
Parents: d87e117
Author: Alex Heneveld <al...@cloudsoftcorp.com>
Authored: Mon Oct 27 06:09:12 2014 -0700
Committer: Alex Heneveld <al...@cloudsoftcorp.com>
Committed: Fri Oct 31 09:38:20 2014 -0500

----------------------------------------------------------------------
 .../brooklyn/entity/basic/EntityPredicates.java     | 16 ++++++++++++++--
 1 file changed, 14 insertions(+), 2 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/ec3701a8/core/src/main/java/brooklyn/entity/basic/EntityPredicates.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/entity/basic/EntityPredicates.java b/core/src/main/java/brooklyn/entity/basic/EntityPredicates.java
index 41a214e..0531441 100644
--- a/core/src/main/java/brooklyn/entity/basic/EntityPredicates.java
+++ b/core/src/main/java/brooklyn/entity/basic/EntityPredicates.java
@@ -364,9 +364,15 @@ public class EntityPredicates {
         }
     }
 
-    /** @deprecated since 0.7.0 use #locationsInclude */
+    /** @deprecated since 0.7.0 use {@link #locationsInclude(Location)} */
     @Deprecated 
     public static <T> Predicate<Entity> withLocation(final Location location) {
+        return locationsInclude(location);
+    }
+    
+    /** @deprecated since 0.7.0 use {@link #locationsInclude(Location)}, introduced to allow deserialization of anonymous inner class */
+    @SuppressWarnings("unused") @Deprecated 
+    private static <T> Predicate<Entity> withLocationOld(final Location location) {
         return new SerializablePredicate<Entity>() {
             @Override
             public boolean apply(@Nullable Entity input) {
@@ -392,8 +398,14 @@ public class EntityPredicates {
         }
     }
 
-    /** @deprecated since 0.7.0 use #isManaged */
+    /** @deprecated since 0.7.0 use {@link #isManaged()} */ @Deprecated
     public static <T> Predicate<Entity> managed() {
+        return isManaged();
+    }
+
+    /** @deprecated since 0.7.0 use {@link #isManaged()}, introduced to allow deserialization of anonymous inner class */
+    @SuppressWarnings("unused") @Deprecated
+    private static <T> Predicate<Entity> managedOld() {
         return new SerializablePredicate<Entity>() {
             @Override
             public boolean apply(@Nullable Entity input) {


[24/29] git commit: don't ignore explicit --port if https selected

Posted by al...@apache.org.
don't ignore explicit --port if https selected


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

Branch: refs/heads/master
Commit: 4de0dc91e2021d97c0d21cc4b0b03b8b647ace53
Parents: 6dfd178
Author: Alex Heneveld <al...@cloudsoftcorp.com>
Authored: Thu Oct 30 22:30:32 2014 -0500
Committer: Alex Heneveld <al...@cloudsoftcorp.com>
Committed: Fri Oct 31 09:39:51 2014 -0500

----------------------------------------------------------------------
 .../java/brooklyn/launcher/BrooklynLauncher.java  | 10 ++++------
 .../java/brooklyn/launcher/BrooklynWebServer.java | 18 +++++++++++++-----
 2 files changed, 17 insertions(+), 11 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/4de0dc91/usage/launcher/src/main/java/brooklyn/launcher/BrooklynLauncher.java
----------------------------------------------------------------------
diff --git a/usage/launcher/src/main/java/brooklyn/launcher/BrooklynLauncher.java b/usage/launcher/src/main/java/brooklyn/launcher/BrooklynLauncher.java
index 158c7c4..d9cde0c 100644
--- a/usage/launcher/src/main/java/brooklyn/launcher/BrooklynLauncher.java
+++ b/usage/launcher/src/main/java/brooklyn/launcher/BrooklynLauncher.java
@@ -142,7 +142,7 @@ public class BrooklynLauncher {
     
     private boolean startWebApps = true;
     private boolean startBrooklynNode = false;
-    private PortRange port = PortRanges.fromString("8081+");
+    private PortRange port = null;
     private InetAddress bindAddress = null;
     private InetAddress publicAddress = null;
     private Map<String,String> webApps = new LinkedHashMap<String,String>();
@@ -321,16 +321,14 @@ public class BrooklynLauncher {
     }
 
     /** 
-     * Specifies the port where the web console (and any additional webapps specified) will listen; 
-     * default "8081+" being the first available >= 8081.
+     * As {@link #webconsolePort(PortRange)} taking a single port
      */ 
     public BrooklynLauncher webconsolePort(int port) {
         return webconsolePort(PortRanges.fromInteger(port));
     }
 
     /**
-     * Specifies the port where the web console (and any additional webapps specified) will listen;
-     * default "8081+" being the first available >= 8081.
+     * As {@link #webconsolePort(PortRange)} taking a string range
      */
     public BrooklynLauncher webconsolePort(String port) {
         return webconsolePort(PortRanges.fromString(port));
@@ -338,7 +336,7 @@ public class BrooklynLauncher {
 
     /**
      * Specifies the port where the web console (and any additional webapps specified) will listen;
-     * default "8081+" being the first available >= 8081.
+     * default "8081+" (or "8443+" for https) being the first available >= 8081.
      */ 
     public BrooklynLauncher webconsolePort(PortRange port) {
         this.port = port;

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/4de0dc91/usage/launcher/src/main/java/brooklyn/launcher/BrooklynWebServer.java
----------------------------------------------------------------------
diff --git a/usage/launcher/src/main/java/brooklyn/launcher/BrooklynWebServer.java b/usage/launcher/src/main/java/brooklyn/launcher/BrooklynWebServer.java
index 1d62fad..2561272 100644
--- a/usage/launcher/src/main/java/brooklyn/launcher/BrooklynWebServer.java
+++ b/usage/launcher/src/main/java/brooklyn/launcher/BrooklynWebServer.java
@@ -109,8 +109,12 @@ public class BrooklynWebServer {
 
     private WebAppContext rootContext;
     
+    /** base port to use, for http if enabled or else https; if not set, it uses httpPort or httpsPort */
+    @SetFromFlag("port")
+    protected PortRange requestedPort = null;
+    
     @SetFromFlag
-    protected PortRange port = PortRanges.fromString("8081+");
+    protected PortRange httpPort = PortRanges.fromString("8081+");
     @SetFromFlag
     protected PortRange httpsPort = PortRanges.fromString("8443+");
     
@@ -206,7 +210,7 @@ public class BrooklynWebServer {
     public BrooklynWebServer setPort(Object port) {
         if (getActualPort()>0)
             throw new IllegalStateException("Can't set port after port has been assigned to server (using "+getActualPort()+")");
-        this.port = TypeCoercions.coerce(port, PortRange.class);
+        this.requestedPort = TypeCoercions.coerce(port, PortRange.class);
         return this;
     }
 
@@ -222,7 +226,7 @@ public class BrooklynWebServer {
     }
     
     public PortRange getRequestedPort() {
-        return port;
+        return requestedPort;
     }
     
     /** returns port where this is running, or -1 if not yet known */
@@ -328,9 +332,13 @@ public class BrooklynWebServer {
         if (server != null) throw new IllegalStateException(""+this+" already running");
 
         if (actualPort == -1){
-            actualPort = LocalhostMachineProvisioningLocation.obtainPort(getAddress(), getHttpsEnabled()?httpsPort:port);
+            PortRange portRange = requestedPort;
+            if (portRange==null) {
+                portRange = getHttpsEnabled()? httpsPort : httpPort;
+            }
+            actualPort = LocalhostMachineProvisioningLocation.obtainPort(getAddress(), portRange);
             if (actualPort == -1) 
-                throw new IllegalStateException("Unable to provision port for web console (wanted "+(getHttpsEnabled()?httpsPort:port)+")");
+                throw new IllegalStateException("Unable to provision port for web console (wanted "+portRange+")");
         }
 
         server = new Server(new InetSocketAddress(bindAddress, actualPort));


[08/29] git commit: more QuorumCheck to util package, using it for new CollectionFunctionals defining a predicate which applies a predicate to a collection

Posted by al...@apache.org.
more QuorumCheck to util package, using it for new CollectionFunctionals defining a predicate which applies a predicate to a collection


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

Branch: refs/heads/master
Commit: 78d8cc32682a85bc61fd509c488f6dfd6487e1a2
Parents: 4aa2cc4
Author: Alex Heneveld <al...@cloudsoftcorp.com>
Authored: Sun Oct 26 14:11:56 2014 +0000
Committer: Alex Heneveld <al...@cloudsoftcorp.com>
Committed: Fri Oct 31 09:38:19 2014 -0500

----------------------------------------------------------------------
 .../brooklyn/entity/basic/AbstractGroup.java    |   3 +-
 .../java/brooklyn/entity/basic/EntityTasks.java |  45 ++++++++
 .../java/brooklyn/entity/basic/QuorumCheck.java |   9 +-
 .../entity/basic/ServiceStateLogic.java         |   1 +
 .../brooklyn/entity/effector/EffectorTasks.java |   1 +
 .../src/main/java/brooklyn/util/task/Tasks.java |  35 ++++++
 .../loadbalancing/BalanceableContainer.java     |   4 +-
 .../brooklyn/entity/basic/SameServerEntity.java |   1 +
 .../nosql/couchbase/CouchbaseClusterImpl.java   |   2 +-
 .../util/collections/CollectionFunctionals.java |  37 +++++++
 .../brooklyn/util/collections/QuorumCheck.java  | 106 +++++++++++++++++++
 11 files changed, 239 insertions(+), 5 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/78d8cc32/core/src/main/java/brooklyn/entity/basic/AbstractGroup.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/entity/basic/AbstractGroup.java b/core/src/main/java/brooklyn/entity/basic/AbstractGroup.java
index 41a3cc2..aeafc12 100644
--- a/core/src/main/java/brooklyn/entity/basic/AbstractGroup.java
+++ b/core/src/main/java/brooklyn/entity/basic/AbstractGroup.java
@@ -23,11 +23,12 @@ import java.util.Collection;
 import brooklyn.config.ConfigKey;
 import brooklyn.entity.Entity;
 import brooklyn.entity.Group;
-import brooklyn.entity.basic.QuorumCheck.QuorumChecks;
 import brooklyn.entity.basic.ServiceStateLogic.ComputeServiceIndicatorsFromChildrenAndMembers;
 import brooklyn.entity.trait.Changeable;
 import brooklyn.event.AttributeSensor;
 import brooklyn.event.basic.Sensors;
+import brooklyn.util.collections.QuorumCheck;
+import brooklyn.util.collections.QuorumCheck.QuorumChecks;
 
 import com.google.common.base.Predicate;
 import com.google.common.reflect.TypeToken;

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/78d8cc32/core/src/main/java/brooklyn/entity/basic/EntityTasks.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/entity/basic/EntityTasks.java b/core/src/main/java/brooklyn/entity/basic/EntityTasks.java
new file mode 100644
index 0000000..fd0a63a
--- /dev/null
+++ b/core/src/main/java/brooklyn/entity/basic/EntityTasks.java
@@ -0,0 +1,45 @@
+package brooklyn.entity.basic;
+
+import brooklyn.entity.Entity;
+import brooklyn.event.AttributeSensor;
+import brooklyn.management.TaskAdaptable;
+import brooklyn.util.collections.CollectionFunctionals;
+import brooklyn.util.guava.Functionals;
+import brooklyn.util.repeat.Repeater;
+import brooklyn.util.task.Tasks;
+import brooklyn.util.time.Duration;
+
+import com.google.common.base.Functions;
+import com.google.common.base.Predicate;
+import com.google.common.collect.Iterables;
+
+/** Generally useful tasks related to entities */
+public class EntityTasks {
+
+    /** creates an (unsubmitted) task which waits for the attribute to satisfy the given predicate,
+     * with an optional timeout */
+    public static <T> TaskAdaptable<Boolean> awaitingAttribute(Entity entity, AttributeSensor<T> sensor, Predicate<T> condition, Duration timeout) {
+        return Tasks.awaitingBuilder(Repeater.create("waiting on "+sensor.getName())
+                .backoff(Duration.millis(10), 1.5, Duration.millis(200))
+                .limitTimeTo(timeout==null ? Duration.PRACTICALLY_FOREVER : timeout)
+//                TODO abort if entity is unmanaged
+                .until(Functionals.callable(Functions.forPredicate(EntityPredicates.attributeSatisfies(sensor, condition)), entity)),
+                true)
+            .description("waiting on "+entity+" "+sensor.getName()+" "+condition+
+                (timeout!=null ? ", timeout "+timeout : "")).build();
+    }
+
+    /** as {@link #awaitingAttribute(Entity, AttributeSensor, Predicate, Duration)} for multiple entities */
+    public static <T> TaskAdaptable<Boolean> awaitingAttribute(Iterable<Entity> entities, AttributeSensor<T> sensor, Predicate<T> condition, Duration timeout) {
+        return Tasks.awaitingBuilder(Repeater.create("waiting on "+sensor.getName())
+                .backoff(Duration.millis(10), 1.5, Duration.millis(200))
+                .limitTimeTo(timeout==null ? Duration.PRACTICALLY_FOREVER : timeout)
+//                TODO abort if entity is unmanaged
+                .until(Functionals.callable(Functions.forPredicate(
+                    CollectionFunctionals.all(EntityPredicates.attributeSatisfies(sensor, condition))), entities)),
+                true)
+            .description("waiting on "+Iterables.size(entities)+", "+sensor.getName()+" "+condition+
+                (timeout!=null ? ", timeout "+timeout : "")+
+                ": "+entities).build();
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/78d8cc32/core/src/main/java/brooklyn/entity/basic/QuorumCheck.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/entity/basic/QuorumCheck.java b/core/src/main/java/brooklyn/entity/basic/QuorumCheck.java
index 1c07e95..6fffb59 100644
--- a/core/src/main/java/brooklyn/entity/basic/QuorumCheck.java
+++ b/core/src/main/java/brooklyn/entity/basic/QuorumCheck.java
@@ -23,8 +23,11 @@ import java.io.Serializable;
 /**
  * For checking if a group/cluster is quorate. That is, whether the group has sufficient
  * healthy members.
+ * @deprecated since 0.7.0 use {@link brooklyn.util.collections.QuorumCheck}. 
+ * but keep this for a while as old quorum checks might be persisted. 
  */
-public interface QuorumCheck {
+@Deprecated
+public interface QuorumCheck extends brooklyn.util.collections.QuorumCheck {
 
     /**
      * @param sizeHealthy Number of healthy members
@@ -71,6 +74,10 @@ public interface QuorumCheck {
         }
     }
     
+    /** @deprecated since 0.7.0 use {@link brooklyn.util.collections.QuorumCheck}. 
+    * but keep this until we have a transition defined. 
+    */
+    @Deprecated
     public static class NumericQuorumCheck implements QuorumCheck, Serializable {
         private static final long serialVersionUID = -5090669237460159621L;
         

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/78d8cc32/core/src/main/java/brooklyn/entity/basic/ServiceStateLogic.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/entity/basic/ServiceStateLogic.java b/core/src/main/java/brooklyn/entity/basic/ServiceStateLogic.java
index b54e732..f9b4541 100644
--- a/core/src/main/java/brooklyn/entity/basic/ServiceStateLogic.java
+++ b/core/src/main/java/brooklyn/entity/basic/ServiceStateLogic.java
@@ -49,6 +49,7 @@ import brooklyn.util.collections.CollectionFunctionals;
 import brooklyn.util.collections.MutableList;
 import brooklyn.util.collections.MutableMap;
 import brooklyn.util.collections.MutableSet;
+import brooklyn.util.collections.QuorumCheck;
 import brooklyn.util.guava.Functionals;
 import brooklyn.util.guava.Maybe;
 import brooklyn.util.repeat.Repeater;

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/78d8cc32/core/src/main/java/brooklyn/entity/effector/EffectorTasks.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/entity/effector/EffectorTasks.java b/core/src/main/java/brooklyn/entity/effector/EffectorTasks.java
index c82eb46..98e3348 100644
--- a/core/src/main/java/brooklyn/entity/effector/EffectorTasks.java
+++ b/core/src/main/java/brooklyn/entity/effector/EffectorTasks.java
@@ -47,6 +47,7 @@ import com.google.common.annotations.Beta;
 import com.google.common.base.Preconditions;
 
 /**
+ * Miscellaneous tasks which are useful in effectors.
  * @since 0.6.0
  */
 @Beta

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/78d8cc32/core/src/main/java/brooklyn/util/task/Tasks.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/util/task/Tasks.java b/core/src/main/java/brooklyn/util/task/Tasks.java
index 6131c78..0edd07b 100644
--- a/core/src/main/java/brooklyn/util/task/Tasks.java
+++ b/core/src/main/java/brooklyn/util/task/Tasks.java
@@ -37,6 +37,8 @@ import brooklyn.management.TaskAdaptable;
 import brooklyn.management.TaskFactory;
 import brooklyn.management.TaskQueueingContext;
 import brooklyn.util.exceptions.Exceptions;
+import brooklyn.util.exceptions.ReferenceWithError;
+import brooklyn.util.repeat.Repeater;
 import brooklyn.util.time.CountdownTimer;
 import brooklyn.util.time.Duration;
 import brooklyn.util.time.Time;
@@ -421,5 +423,38 @@ public class Tasks {
         if (t==null) return false;
         return t.isCancelled();
     }
+
+    private static class WaitForRepeaterCallable implements Callable<Boolean> {
+        protected Repeater repeater;
+        protected boolean requireTrue;
+
+        public WaitForRepeaterCallable(Repeater repeater, boolean requireTrue) {
+            this.repeater = repeater;
+            this.requireTrue = requireTrue;
+        }
+
+        @Override
+        public Boolean call() {
+            ReferenceWithError<Boolean> result = repeater.runKeepingError();
+            if (Boolean.TRUE.equals(result.getWithoutError()))
+                return true;
+            if (result.hasError()) 
+                throw Exceptions.propagate(result.getError());
+            if (requireTrue)
+                throw new IllegalStateException("timeout - "+repeater.getDescription());
+            return false;
+        }
+    }
     
+    /** creates an (unsubmitted) task which waits for the given repeater, optionally failing if it does not complete with success */
+    public static TaskAdaptable<Boolean> awaiting(Repeater repeater, boolean requireTrue) {
+        return awaitingBuilder(repeater, requireTrue).build();
+    }
+
+    /** creates a partially instantiated builder which waits for the given repeater, optionally failing if it does not complete with success,
+     * for further task customization and then {@link TaskBuilder#build()} */
+    public static TaskBuilder<Boolean> awaitingBuilder(Repeater repeater, boolean requireTrue) {
+        return Tasks.<Boolean>builder().name(repeater.getDescription()).body(new WaitForRepeaterCallable(repeater, requireTrue));
+    }
+
 }

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/78d8cc32/policy/src/main/java/brooklyn/policy/loadbalancing/BalanceableContainer.java
----------------------------------------------------------------------
diff --git a/policy/src/main/java/brooklyn/policy/loadbalancing/BalanceableContainer.java b/policy/src/main/java/brooklyn/policy/loadbalancing/BalanceableContainer.java
index 9a7ecb1..5e94369 100644
--- a/policy/src/main/java/brooklyn/policy/loadbalancing/BalanceableContainer.java
+++ b/policy/src/main/java/brooklyn/policy/loadbalancing/BalanceableContainer.java
@@ -24,9 +24,9 @@ import brooklyn.config.ConfigKey;
 import brooklyn.entity.Entity;
 import brooklyn.entity.basic.AbstractGroup;
 import brooklyn.entity.basic.ConfigKeys;
-import brooklyn.entity.basic.QuorumCheck;
-import brooklyn.entity.basic.QuorumCheck.QuorumChecks;
 import brooklyn.event.basic.BasicNotificationSensor;
+import brooklyn.util.collections.QuorumCheck;
+import brooklyn.util.collections.QuorumCheck.QuorumChecks;
 
 /**
  * Contains worker items that can be moved between this container and others to effect load balancing.

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/78d8cc32/software/base/src/main/java/brooklyn/entity/basic/SameServerEntity.java
----------------------------------------------------------------------
diff --git a/software/base/src/main/java/brooklyn/entity/basic/SameServerEntity.java b/software/base/src/main/java/brooklyn/entity/basic/SameServerEntity.java
index 09a2cc4..e10a3b9 100644
--- a/software/base/src/main/java/brooklyn/entity/basic/SameServerEntity.java
+++ b/software/base/src/main/java/brooklyn/entity/basic/SameServerEntity.java
@@ -29,6 +29,7 @@ import brooklyn.event.AttributeSensor;
 import brooklyn.event.basic.BasicAttributeSensor;
 import brooklyn.location.MachineProvisioningLocation;
 import brooklyn.util.collections.MutableMap;
+import brooklyn.util.collections.QuorumCheck;
 import brooklyn.util.flags.SetFromFlag;
 
 import com.google.common.reflect.TypeToken;

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/78d8cc32/software/nosql/src/main/java/brooklyn/entity/nosql/couchbase/CouchbaseClusterImpl.java
----------------------------------------------------------------------
diff --git a/software/nosql/src/main/java/brooklyn/entity/nosql/couchbase/CouchbaseClusterImpl.java b/software/nosql/src/main/java/brooklyn/entity/nosql/couchbase/CouchbaseClusterImpl.java
index f509c91..7c2a496 100644
--- a/software/nosql/src/main/java/brooklyn/entity/nosql/couchbase/CouchbaseClusterImpl.java
+++ b/software/nosql/src/main/java/brooklyn/entity/nosql/couchbase/CouchbaseClusterImpl.java
@@ -38,7 +38,6 @@ import brooklyn.entity.Entity;
 import brooklyn.entity.basic.Attributes;
 import brooklyn.entity.basic.Entities;
 import brooklyn.entity.basic.EntityInternal;
-import brooklyn.entity.basic.QuorumCheck;
 import brooklyn.entity.basic.ServiceStateLogic;
 import brooklyn.entity.effector.Effectors;
 import brooklyn.entity.group.AbstractMembershipTrackingPolicy;
@@ -55,6 +54,7 @@ import brooklyn.location.access.BrooklynAccessUtils;
 import brooklyn.policy.PolicySpec;
 import brooklyn.util.collections.CollectionFunctionals;
 import brooklyn.util.collections.MutableSet;
+import brooklyn.util.collections.QuorumCheck;
 import brooklyn.util.exceptions.Exceptions;
 import brooklyn.util.guava.Functionals;
 import brooklyn.util.guava.IfFunctions;

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/78d8cc32/utils/common/src/main/java/brooklyn/util/collections/CollectionFunctionals.java
----------------------------------------------------------------------
diff --git a/utils/common/src/main/java/brooklyn/util/collections/CollectionFunctionals.java b/utils/common/src/main/java/brooklyn/util/collections/CollectionFunctionals.java
index 7b2fe76..a42091e 100644
--- a/utils/common/src/main/java/brooklyn/util/collections/CollectionFunctionals.java
+++ b/utils/common/src/main/java/brooklyn/util/collections/CollectionFunctionals.java
@@ -26,8 +26,11 @@ import java.util.Set;
 
 import javax.annotation.Nullable;
 
+import brooklyn.util.collections.QuorumCheck.QuorumChecks;
+
 import com.google.common.base.Function;
 import com.google.common.base.Functions;
+import com.google.common.base.Preconditions;
 import com.google.common.base.Predicate;
 import com.google.common.base.Predicates;
 import com.google.common.base.Supplier;
@@ -175,6 +178,40 @@ public class CollectionFunctionals {
         }
     }
 
+    // ---------
+    
+    public static <T,TT extends Iterable<T>> Predicate<TT> all(Predicate<T> attributeSatisfies) {
+        return new QuorumSatisfies<T, TT>(QuorumChecks.all(), attributeSatisfies);
+    }
+
+    public static <T,TT extends Iterable<T>> Predicate<TT> quorum(QuorumCheck quorumCheck, Predicate<T> attributeSatisfies) {
+        return new QuorumSatisfies<T, TT>(quorumCheck, attributeSatisfies);
+    }
+
+
+    private static final class QuorumSatisfies<I,T extends Iterable<I>> implements Predicate<T> {
+        private final Predicate<I> itemCheck;
+        private final QuorumCheck quorumCheck;
+        private QuorumSatisfies(QuorumCheck quorumCheck, Predicate<I> itemCheck) {
+            this.itemCheck = Preconditions.checkNotNull(itemCheck, "itemCheck");
+            this.quorumCheck = Preconditions.checkNotNull(quorumCheck, "quorumCheck");
+        }
+        @Override
+        public boolean apply(T input) {
+            if (input==null) return false;
+            int sizeHealthy = 0, totalSize = 0;
+            for (I item: input) {
+                totalSize++;
+                if (itemCheck.apply(item)) sizeHealthy++;
+            }
+            return quorumCheck.isQuorate(sizeHealthy, totalSize);
+        }
+        @Override
+        public String toString() {
+            return quorumCheck.toString()+"("+itemCheck+")";
+        }
+    }
+
 
 
 }

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/78d8cc32/utils/common/src/main/java/brooklyn/util/collections/QuorumCheck.java
----------------------------------------------------------------------
diff --git a/utils/common/src/main/java/brooklyn/util/collections/QuorumCheck.java b/utils/common/src/main/java/brooklyn/util/collections/QuorumCheck.java
new file mode 100644
index 0000000..98bfddc
--- /dev/null
+++ b/utils/common/src/main/java/brooklyn/util/collections/QuorumCheck.java
@@ -0,0 +1,106 @@
+/*
+ * 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 brooklyn.util.collections;
+
+import java.io.Serializable;
+
+/**
+ * For checking if a group/cluster is quorate. That is, whether the group has sufficient
+ * healthy members.
+ */
+public interface QuorumCheck {
+
+    /**
+     * @param sizeHealthy Number of healthy members
+     * @param totalSize   Total number of members one would expect to be healthy (i.e. ignoring stopped members)
+     * @return            Whether this group is healthy
+     */
+    public boolean isQuorate(int sizeHealthy, int totalSize);
+
+    public static class QuorumChecks {
+        /**
+         * Checks that all members that should be up are up (i.e. ignores stopped nodes).
+         */
+        public static QuorumCheck all() {
+            return new NumericQuorumCheck(0, 1.0, false, "all");
+        }
+        /**
+         * Checks all members that should be up are up, and that there is at least one such member.
+         */
+        public static QuorumCheck allAndAtLeastOne() {
+            return new NumericQuorumCheck(1, 1.0, false, "atLeastOne");
+        }
+        /**
+         * Requires at least one member that should be up is up.
+         */
+        public static QuorumCheck atLeastOne() {
+            return new NumericQuorumCheck(1, 0.0, false);
+        }
+        /**
+         * Requires at least one member to be up if the total size is non-zero.
+         * i.e. okay if empty, or if non-empty and something is healthy, but not okay if not-empty and nothing is healthy.
+         * "Empty" means that no members are supposed to be up  (e.g. there may be stopped members).
+         */
+        public static QuorumCheck atLeastOneUnlessEmpty() {
+            return new NumericQuorumCheck(1, 0.0, true);
+        }
+        /**
+         * Always "healthy"
+         */
+        public static QuorumCheck alwaysTrue() {
+            return new NumericQuorumCheck(0, 0.0, true);
+        }
+        public static QuorumCheck newInstance(int minRequiredSize, double minRequiredRatio, boolean allowEmpty) {
+            return new NumericQuorumCheck(minRequiredSize, minRequiredRatio, allowEmpty);
+        }
+    }
+    
+    public static class NumericQuorumCheck implements QuorumCheck, Serializable {
+        private static final long serialVersionUID = -5090669237460159621L;
+        
+        protected final int minRequiredSize;
+        protected final double minRequiredRatio;
+        protected final boolean allowEmpty;
+        protected final String name;
+
+        public NumericQuorumCheck(int minRequiredSize, double minRequiredRatio, boolean allowEmpty) {
+            this(minRequiredSize, minRequiredRatio, allowEmpty, null);
+        }
+        public NumericQuorumCheck(int minRequiredSize, double minRequiredRatio, boolean allowEmpty, String name) {
+            this.minRequiredSize = minRequiredSize;
+            this.minRequiredRatio = minRequiredRatio;
+            this.allowEmpty = allowEmpty;
+            this.name = name;
+        }
+        
+        @Override
+        public boolean isQuorate(int sizeHealthy, int totalSize) {
+            if (allowEmpty && totalSize==0) return true;
+            if (sizeHealthy < minRequiredSize) return false;
+            if (sizeHealthy < totalSize*minRequiredRatio-0.000000001) return false;
+            return true;
+        }
+        
+        @Override
+        public String toString() {
+            return "QuorumCheck[require="+minRequiredSize+","+((int)100*minRequiredRatio)+"%"+(allowEmpty ? "|0" : "")+"]";
+        }
+    }
+    
+}


[23/29] git commit: comments and fixes in EntityConfigKey for handling structured keys

Posted by al...@apache.org.
comments and fixes in EntityConfigKey for handling structured keys


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

Branch: refs/heads/master
Commit: 0323f480f359a5165f2f7d8f912f619ac5fcd50c
Parents: ebd4462
Author: Alex Heneveld <al...@cloudsoftcorp.com>
Authored: Thu Oct 30 23:00:30 2014 -0500
Committer: Alex Heneveld <al...@cloudsoftcorp.com>
Committed: Fri Oct 31 09:39:51 2014 -0500

----------------------------------------------------------------------
 .../main/java/brooklyn/entity/basic/EntityConfigMap.java  | 10 ++++++++--
 1 file changed, 8 insertions(+), 2 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/0323f480/core/src/main/java/brooklyn/entity/basic/EntityConfigMap.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/entity/basic/EntityConfigMap.java b/core/src/main/java/brooklyn/entity/basic/EntityConfigMap.java
index 27131e2..1f531df 100644
--- a/core/src/main/java/brooklyn/entity/basic/EntityConfigMap.java
+++ b/core/src/main/java/brooklyn/entity/basic/EntityConfigMap.java
@@ -66,7 +66,9 @@ public class EntityConfigMap implements ConfigMap {
     private final Map<ConfigKey<?>,Object> ownConfig;
     private final Map<ConfigKey<?>,Object> inheritedConfig = Collections.synchronizedMap(new LinkedHashMap<ConfigKey<?>, Object>());
     // TODO do we really want to have *both* bags and maps for these?  danger that they get out of synch.
-    // have added some logic (Oct 2014) so that the same changes are applied to both, in most places at least
+    // have added some logic (Oct 2014) so that the same changes are applied to both, in most places at least;
+    // i (alex) think we should prefer ConfigBag (the input keys don't matter, it is more a question of retrieval keys),
+    // but first we need ConfigBag to support StructuredConfigKeys 
     private final ConfigBag localConfigBag;
     private final ConfigBag inheritedConfigBag;
 
@@ -205,10 +207,14 @@ public class EntityConfigMap implements ConfigMap {
         Object oldVal;
         if (key instanceof StructuredConfigKey) {
             oldVal = ((StructuredConfigKey)key).applyValueToMap(val, ownConfig);
+            // TODO ConfigBag does not handle structured config keys; quick fix is to remove (and should also remove any subkeys;
+            // as it stands if someone set string a.b.c in the config bag then removed structured key a.b, then got a.b.c they'd get a vale);
+            // long term fix is to support structured config keys in ConfigBag, at which point i think we could remove ownConfig altogether
+            localConfigBag.remove(key);
         } else {
             oldVal = ownConfig.put(key, val);
+            localConfigBag.put((ConfigKey<Object>)key, v);
         }
-        localConfigBag.put((ConfigKey<Object>)key, v);
         entity.refreshInheritedConfigOfChildren();
         return oldVal;
     }


[22/29] git commit: really clear the old install dir and label, so we really copy in the new version

Posted by al...@apache.org.
really clear the old install dir and label, so we really copy in the new version

fixes inconsistencies in EntityConfigMap configBag and maps, and exposes hooks to get driver and to clear cached install-related values in the driver


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

Branch: refs/heads/master
Commit: d768efab312ed3f7bb8aeb532fb6f7509702553d
Parents: 724ee57
Author: Alex Heneveld <al...@cloudsoftcorp.com>
Authored: Thu Oct 30 20:34:52 2014 -0500
Committer: Alex Heneveld <al...@cloudsoftcorp.com>
Committed: Fri Oct 31 09:39:51 2014 -0500

----------------------------------------------------------------------
 .../entity/drivers/DriverDependentEntity.java   |  5 ++
 .../brooklyn/entity/basic/EntityConfigMap.java  | 16 ++++--
 .../brooklyn/event/feed/ConfigToAttributes.java |  7 +--
 .../ReflectiveEntityDriverFactoryTest.java      |  5 ++
 .../basic/AbstractSoftwareProcessSshDriver.java | 51 +++++++++-----------
 .../entity/basic/SoftwareProcessImpl.java       |  3 +-
 .../entity/brooklynnode/BrooklynNodeDriver.java |  2 +
 .../brooklynnode/BrooklynNodeSshDriver.java     |  7 +++
 .../BrooklynNodeUpgradeEffectorBody.java        | 14 +++---
 .../brooklyn-node-persisting-to-tmp.yaml        |  5 +-
 10 files changed, 72 insertions(+), 43 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/d768efab/api/src/main/java/brooklyn/entity/drivers/DriverDependentEntity.java
----------------------------------------------------------------------
diff --git a/api/src/main/java/brooklyn/entity/drivers/DriverDependentEntity.java b/api/src/main/java/brooklyn/entity/drivers/DriverDependentEntity.java
index b225439..93cb889 100644
--- a/api/src/main/java/brooklyn/entity/drivers/DriverDependentEntity.java
+++ b/api/src/main/java/brooklyn/entity/drivers/DriverDependentEntity.java
@@ -18,6 +18,8 @@
  */
 package brooklyn.entity.drivers;
 
+import javax.annotation.Nullable;
+
 import brooklyn.entity.Entity;
 
 /**
@@ -28,4 +30,7 @@ import brooklyn.entity.Entity;
 public interface DriverDependentEntity<D extends EntityDriver> extends Entity {
 
     Class<D> getDriverInterface();
+    
+    @Nullable D getDriver();
+    
 }

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/d768efab/core/src/main/java/brooklyn/entity/basic/EntityConfigMap.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/entity/basic/EntityConfigMap.java b/core/src/main/java/brooklyn/entity/basic/EntityConfigMap.java
index 4b9654b..27131e2 100644
--- a/core/src/main/java/brooklyn/entity/basic/EntityConfigMap.java
+++ b/core/src/main/java/brooklyn/entity/basic/EntityConfigMap.java
@@ -65,6 +65,8 @@ public class EntityConfigMap implements ConfigMap {
      */
     private final Map<ConfigKey<?>,Object> ownConfig;
     private final Map<ConfigKey<?>,Object> inheritedConfig = Collections.synchronizedMap(new LinkedHashMap<ConfigKey<?>, Object>());
+    // TODO do we really want to have *both* bags and maps for these?  danger that they get out of synch.
+    // have added some logic (Oct 2014) so that the same changes are applied to both, in most places at least
     private final ConfigBag localConfigBag;
     private final ConfigBag inheritedConfigBag;
 
@@ -153,7 +155,7 @@ public class EntityConfigMap implements ConfigMap {
         return Maybe.absent();
     }
     
-    /** returns the config visible at this entity, local and inherited (preferring local) */
+    /** an immutable copy of the config visible at this entity, local and inherited (preferring local) */
     public Map<ConfigKey<?>,Object> getAllConfig() {
         Map<ConfigKey<?>,Object> result = new LinkedHashMap<ConfigKey<?>,Object>(inheritedConfig.size()+ownConfig.size());
         result.putAll(inheritedConfig);
@@ -161,14 +163,14 @@ public class EntityConfigMap implements ConfigMap {
         return Collections.unmodifiableMap(result);
     }
 
-    /** returns the config defined at this entity, ie not inherited */
+    /** an immutable copy of the config defined at this entity, ie not inherited */
     public Map<ConfigKey<?>,Object> getLocalConfig() {
         Map<ConfigKey<?>,Object> result = new LinkedHashMap<ConfigKey<?>,Object>(ownConfig.size());
         result.putAll(ownConfig);
         return Collections.unmodifiableMap(result);
     }
     
-    /** returns the config visible at this entity, local and inherited (preferring local), including those that did not match config keys */
+    /** Creates an immutable copy of the config visible at this entity, local and inherited (preferring local), including those that did not match config keys */
     public ConfigBag getAllConfigBag() {
         return ConfigBag.newInstanceCopying(localConfigBag)
                 .putAll(ownConfig)
@@ -177,13 +179,14 @@ public class EntityConfigMap implements ConfigMap {
                 .seal();
     }
 
-    /** returns the config defined at this entity, ie not inherited, including those that did not match config keys */
+    /** Creates an immutable copy of the config defined at this entity, ie not inherited, including those that did not match config keys */
     public ConfigBag getLocalConfigBag() {
         return ConfigBag.newInstanceCopying(localConfigBag)
                 .putAll(ownConfig)
                 .seal();
     }
 
+    @SuppressWarnings("unchecked")
     public Object setConfig(ConfigKey<?> key, Object v) {
         Object val;
         if ((v instanceof Future) || (v instanceof DeferredSupplier)) {
@@ -205,13 +208,16 @@ public class EntityConfigMap implements ConfigMap {
         } else {
             oldVal = ownConfig.put(key, val);
         }
+        localConfigBag.put((ConfigKey<Object>)key, v);
         entity.refreshInheritedConfigOfChildren();
         return oldVal;
     }
     
     public void setLocalConfig(Map<ConfigKey<?>, ? extends Object> vals) {
         ownConfig.clear();
+        localConfigBag.clear();
         ownConfig.putAll(vals);
+        localConfigBag.putAll(vals);
     }
     
     public void setInheritedConfig(Map<ConfigKey<?>, ? extends Object> vals, ConfigBag configBagVals) {
@@ -258,6 +264,8 @@ public class EntityConfigMap implements ConfigMap {
     
     public void addToLocalBag(Map<String,?> vals) {
         localConfigBag.putAll(vals);
+        // quick fix for problem that ownConfig can get out of synch
+        ownConfig.putAll(localConfigBag.getAllConfigAsConfigKeyMap());
     }
 
     public void clearInheritedConfig() {

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/d768efab/core/src/main/java/brooklyn/event/feed/ConfigToAttributes.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/event/feed/ConfigToAttributes.java b/core/src/main/java/brooklyn/event/feed/ConfigToAttributes.java
index 0dcaf49..c35e59b 100644
--- a/core/src/main/java/brooklyn/event/feed/ConfigToAttributes.java
+++ b/core/src/main/java/brooklyn/event/feed/ConfigToAttributes.java
@@ -25,7 +25,7 @@ import brooklyn.event.basic.TemplatedStringAttributeSensorAndConfigKey;
 import brooklyn.management.ManagementContext;
 
 
-/** simple config adapter for setting config-attributes from config values */ 
+/** Simple config adapter for setting {@link AttributeSensorAndConfigKey} sensor values from the config value or config default */ 
 public class ConfigToAttributes {
 
     //normally just applied once, statically, not registered...
@@ -38,7 +38,8 @@ public class ConfigToAttributes {
     }
 
     /**
-     * for selectively applying once (e.g. sub-classes of DynamicWebAppCluster that don't want to set HTTP_PORT etc!)
+     * Convenience for ensuring an individual sensor is set from its config key
+     * (e.g. sub-classes of DynamicWebAppCluster that don't want to set HTTP_PORT etc!)
      */
     public static <T> T apply(EntityLocal entity, AttributeSensorAndConfigKey<?,T> key) {
         T v = entity.getAttribute(key);
@@ -49,7 +50,7 @@ public class ConfigToAttributes {
     }
 
     /**
-     * For transforming a config value (e.g. processing a {@link TemplatedStringAttributeSensorAndConfigKey}),
+     * Convenience for transforming a config value (e.g. processing a {@link TemplatedStringAttributeSensorAndConfigKey}),
      * outside of the context of an entity.
      */
     public static <T> T transform(ManagementContext managementContext, AttributeSensorAndConfigKey<?,T> key) {

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/d768efab/core/src/test/java/brooklyn/entity/drivers/ReflectiveEntityDriverFactoryTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/brooklyn/entity/drivers/ReflectiveEntityDriverFactoryTest.java b/core/src/test/java/brooklyn/entity/drivers/ReflectiveEntityDriverFactoryTest.java
index 96f5c38..1eb97ab 100644
--- a/core/src/test/java/brooklyn/entity/drivers/ReflectiveEntityDriverFactoryTest.java
+++ b/core/src/test/java/brooklyn/entity/drivers/ReflectiveEntityDriverFactoryTest.java
@@ -65,6 +65,11 @@ public class ReflectiveEntityDriverFactoryTest {
         public Class<D> getDriverInterface() {
             return clazz;
         }
+        
+        @Override
+        public D getDriver() {
+            throw new UnsupportedOperationException();
+        }
     }
     
     public static interface MyDriver extends EntityDriver {

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/d768efab/software/base/src/main/java/brooklyn/entity/basic/AbstractSoftwareProcessSshDriver.java
----------------------------------------------------------------------
diff --git a/software/base/src/main/java/brooklyn/entity/basic/AbstractSoftwareProcessSshDriver.java b/software/base/src/main/java/brooklyn/entity/basic/AbstractSoftwareProcessSshDriver.java
index 8eed782..ffab96b 100644
--- a/software/base/src/main/java/brooklyn/entity/basic/AbstractSoftwareProcessSshDriver.java
+++ b/software/base/src/main/java/brooklyn/entity/basic/AbstractSoftwareProcessSshDriver.java
@@ -20,7 +20,6 @@ package brooklyn.entity.basic;
 
 import static brooklyn.util.JavaGroovyEquivalents.elvis;
 import static brooklyn.util.JavaGroovyEquivalents.groovyTruth;
-import static com.google.common.base.Preconditions.checkNotNull;
 
 import java.io.ByteArrayOutputStream;
 import java.io.File;
@@ -80,10 +79,11 @@ public abstract class AbstractSoftwareProcessSshDriver extends AbstractSoftwareP
     public static final Logger log = LoggerFactory.getLogger(AbstractSoftwareProcessSshDriver.class);
     public static final Logger logSsh = LoggerFactory.getLogger(BrooklynLogging.SSH_IO);
 
-    // we cache these in case the entity becomes unmanaged
+    // we cache these for efficiency and in case the entity becomes unmanaged
     private volatile String installDir;
     private volatile String runDir;
     private volatile String expandedInstallDir;
+    private final Object installDirSetupMutex = new Object();
 
     protected volatile DownloadResolver resolver;
     
@@ -160,33 +160,31 @@ public abstract class AbstractSoftwareProcessSshDriver extends AbstractSoftwareP
     public String getInstallDir() {
         if (installDir != null) return installDir;
 
-        String existingVal = getEntity().getAttribute(SoftwareProcess.INSTALL_DIR);
-        if (Strings.isNonBlank(existingVal)) { // e.g. on rebind
-            installDir = existingVal;
-            return installDir;
-        }
-        
-        setInstallLabel();
-        
-        // deprecated in 0.7.0
-        Maybe<Object> minstallDir = getEntity().getConfigRaw(SoftwareProcess.INSTALL_DIR, true);
-        if (!minstallDir.isPresent() || minstallDir.get()==null) {
-            String installBasedir = ((EntityInternal)entity).getManagementContext().getConfig().getFirst("brooklyn.dirs.install");
-            if (installBasedir != null) {
-                log.warn("Using legacy 'brooklyn.dirs.install' setting for "+entity+"; may be removed in future versions.");
-                installDir = Os.mergePathsUnix(installBasedir, getEntityVersionLabel()+"_"+entity.getId());
-                installDir = Os.tidyPath(installDir);
-                getEntity().setAttribute(SoftwareProcess.INSTALL_DIR, installDir);
-                return installDir;
+        synchronized (installDirSetupMutex) {
+            // previously we looked at sensor value, but we shouldn't as it might have been converted from the config key value
+            // *before* we computed the install label, or that label may have changed since previous install; now force a recompute
+            setInstallLabel();
+
+            // deprecated in 0.7.0 - "brooklyn.dirs.install" is no longer supported
+            Maybe<Object> minstallDir = getEntity().getConfigRaw(SoftwareProcess.INSTALL_DIR, false);
+            if (!minstallDir.isPresent() || minstallDir.get()==null) {
+                String installBasedir = ((EntityInternal)entity).getManagementContext().getConfig().getFirst("brooklyn.dirs.install");
+                if (installBasedir != null) {
+                    log.warn("Using legacy 'brooklyn.dirs.install' setting for "+entity+"; may be removed in future versions.");
+                    setInstallDir(Os.tidyPath(Os.mergePathsUnix(installBasedir, getEntityVersionLabel()+"_"+entity.getId())));
+                    return installDir;
+                }
             }
-        }
 
-        setInstallDir(Os.tidyPath(ConfigToAttributes.apply(getEntity(), SoftwareProcess.INSTALL_DIR)));
-        return installDir;
+            // set it null first so that we force a recompute
+            setInstallDir(null);
+            setInstallDir(Os.tidyPath(ConfigToAttributes.apply(getEntity(), SoftwareProcess.INSTALL_DIR)));
+            return installDir;
+        }
     }
     
     protected void setInstallLabel() {
-        if (getEntity().getConfigRaw(SoftwareProcess.INSTALL_UNIQUE_LABEL, true).isPresentAndNonNull()) return; 
+        if (getEntity().getConfigRaw(SoftwareProcess.INSTALL_UNIQUE_LABEL, false).isPresentAndNonNull()) return; 
         getEntity().setConfig(SoftwareProcess.INSTALL_UNIQUE_LABEL, 
             getEntity().getEntityType().getSimpleName()+
             (Strings.isNonBlank(getVersion()) ? "_"+getVersion() : "")+
@@ -236,12 +234,12 @@ public abstract class AbstractSoftwareProcessSshDriver extends AbstractSoftwareP
     }
 
     public void setExpandedInstallDir(String val) {
-        checkNotNull(val, "expandedInstallDir");
         String oldVal = getEntity().getAttribute(SoftwareProcess.EXPANDED_INSTALL_DIR);
         if (Strings.isNonBlank(oldVal) && !oldVal.equals(val)) {
             log.info("Resetting expandedInstallDir (to "+val+" from "+oldVal+") for "+getEntity());
         }
         
+        expandedInstallDir = val;
         getEntity().setAttribute(SoftwareProcess.EXPANDED_INSTALL_DIR, val);
     }
     
@@ -250,8 +248,7 @@ public abstract class AbstractSoftwareProcessSshDriver extends AbstractSoftwareP
         
         String untidiedVal = ConfigToAttributes.apply(getEntity(), SoftwareProcess.EXPANDED_INSTALL_DIR);
         if (Strings.isNonBlank(untidiedVal)) {
-            expandedInstallDir = Os.tidyPath(untidiedVal);
-            entity.setAttribute(SoftwareProcess.INSTALL_DIR, expandedInstallDir);
+            setExpandedInstallDir(Os.tidyPath(untidiedVal));
             return expandedInstallDir;
         } else {
             throw new IllegalStateException("expandedInstallDir is null; most likely install was not called for "+getEntity());

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/d768efab/software/base/src/main/java/brooklyn/entity/basic/SoftwareProcessImpl.java
----------------------------------------------------------------------
diff --git a/software/base/src/main/java/brooklyn/entity/basic/SoftwareProcessImpl.java b/software/base/src/main/java/brooklyn/entity/basic/SoftwareProcessImpl.java
index 9080121..aad9fa5 100644
--- a/software/base/src/main/java/brooklyn/entity/basic/SoftwareProcessImpl.java
+++ b/software/base/src/main/java/brooklyn/entity/basic/SoftwareProcessImpl.java
@@ -105,11 +105,12 @@ public abstract class SoftwareProcessImpl extends AbstractEntity implements Soft
         return getAttribute(PROVISIONING_LOCATION);
     }
     
+    @Override
     public SoftwareProcessDriver getDriver() {
         return driver;
     }
 
-      protected SoftwareProcessDriver newDriver(MachineLocation loc){
+    protected SoftwareProcessDriver newDriver(MachineLocation loc){
         EntityDriverManager entityDriverManager = getManagementContext().getEntityDriverManager();
         return (SoftwareProcessDriver)entityDriverManager.build(this, loc);
     }

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/d768efab/software/base/src/main/java/brooklyn/entity/brooklynnode/BrooklynNodeDriver.java
----------------------------------------------------------------------
diff --git a/software/base/src/main/java/brooklyn/entity/brooklynnode/BrooklynNodeDriver.java b/software/base/src/main/java/brooklyn/entity/brooklynnode/BrooklynNodeDriver.java
index d87cbf2..df27e1f 100644
--- a/software/base/src/main/java/brooklyn/entity/brooklynnode/BrooklynNodeDriver.java
+++ b/software/base/src/main/java/brooklyn/entity/brooklynnode/BrooklynNodeDriver.java
@@ -22,4 +22,6 @@ import brooklyn.entity.java.JavaSoftwareProcessDriver;
 
 public interface BrooklynNodeDriver extends JavaSoftwareProcessDriver {
 
+    void clearInstallDir();
+
 }

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/d768efab/software/base/src/main/java/brooklyn/entity/brooklynnode/BrooklynNodeSshDriver.java
----------------------------------------------------------------------
diff --git a/software/base/src/main/java/brooklyn/entity/brooklynnode/BrooklynNodeSshDriver.java b/software/base/src/main/java/brooklyn/entity/brooklynnode/BrooklynNodeSshDriver.java
index 1fbf694..bd33b88 100644
--- a/software/base/src/main/java/brooklyn/entity/brooklynnode/BrooklynNodeSshDriver.java
+++ b/software/base/src/main/java/brooklyn/entity/brooklynnode/BrooklynNodeSshDriver.java
@@ -88,6 +88,12 @@ public class BrooklynNodeSshDriver extends JavaSoftwareProcessSshDriver implemen
     }
 
     @Override
+    public void clearInstallDir() {
+        super.setInstallDir(null);
+        super.setExpandedInstallDir(null);
+    }
+    
+    @Override
     public void install() {
         String uploadUrl = entity.getConfig(BrooklynNode.DISTRO_UPLOAD_URL);
         
@@ -231,6 +237,7 @@ public class BrooklynNodeSshDriver extends JavaSoftwareProcessSshDriver implemen
         }
     }
 
+    @SuppressWarnings("deprecation")
     @Override
     public void launch() {
         String app = getEntity().getAttribute(BrooklynNode.APP);

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/d768efab/software/base/src/main/java/brooklyn/entity/brooklynnode/effector/BrooklynNodeUpgradeEffectorBody.java
----------------------------------------------------------------------
diff --git a/software/base/src/main/java/brooklyn/entity/brooklynnode/effector/BrooklynNodeUpgradeEffectorBody.java b/software/base/src/main/java/brooklyn/entity/brooklynnode/effector/BrooklynNodeUpgradeEffectorBody.java
index 22afa15..37ded07 100644
--- a/software/base/src/main/java/brooklyn/entity/brooklynnode/effector/BrooklynNodeUpgradeEffectorBody.java
+++ b/software/base/src/main/java/brooklyn/entity/brooklynnode/effector/BrooklynNodeUpgradeEffectorBody.java
@@ -32,6 +32,8 @@ import brooklyn.entity.basic.EntityTasks;
 import brooklyn.entity.basic.SoftwareProcess;
 import brooklyn.entity.brooklynnode.BrooklynCluster;
 import brooklyn.entity.brooklynnode.BrooklynNode;
+import brooklyn.entity.brooklynnode.BrooklynNodeDriver;
+import brooklyn.entity.drivers.DriverDependentEntity;
 import brooklyn.entity.effector.EffectorBody;
 import brooklyn.entity.effector.Effectors;
 import brooklyn.entity.proxying.EntitySpec;
@@ -43,7 +45,6 @@ import brooklyn.util.config.ConfigBag;
 import brooklyn.util.net.Urls;
 import brooklyn.util.task.DynamicTasks;
 import brooklyn.util.task.Tasks;
-import brooklyn.util.text.Identifiers;
 import brooklyn.util.text.Strings;
 import brooklyn.util.time.Duration;
 
@@ -106,11 +107,9 @@ public class BrooklynNodeUpgradeEffectorBody extends EffectorBody<Void> {
         // TODO could support 'dry_run_only' parameter, with optional resumption tasks (eg new dynamic effector)
 
         // 1 add new brooklyn version entity as child (so uses same machine), with same config apart from things in parameters
-        final BrooklynNode dryRunChild = entity().addChild(EntitySpec.create(BrooklynNode.class).configure(parameters.getAllConfig())
+        final BrooklynNode dryRunChild = entity().addChild(EntitySpec.create(BrooklynNode.class)
             .displayName("Upgraded Version Dry-Run Node")
-            // force dir and label back to their defaults (do not piggy back on what may have already been installed)
-            .configure(BrooklynNode.INSTALL_DIR, BrooklynNode.INSTALL_DIR.getConfigKey().getDefaultValue())
-            .configure(BrooklynNode.INSTALL_UNIQUE_LABEL, "upgrade-tmp-"+Identifiers.makeRandomId(8))
+            // install dir and label are recomputed because they are not inherited, and download_url will normally be different
             .configure(parameters.getAllConfig()));
 
         //force this to start as hot-standby
@@ -155,8 +154,11 @@ public class BrooklynNodeUpgradeEffectorBody extends EffectorBody<Void> {
                     ).summary("move files"));
             }
         }).build());
-
+        
+        DynamicTasks.waitForLast();
+        entity().setConfig(SoftwareProcess.INSTALL_UNIQUE_LABEL, null);
         entity().getConfigMap().addToLocalBag(parameters.getAllConfig());
+        ((BrooklynNodeDriver)((DriverDependentEntity<?>)entity()).getDriver()).clearInstallDir();
 
         // 6 start this entity, running the new version
         DynamicTasks.queue(Effectors.invocation(entity(), BrooklynNode.START, ConfigBag.EMPTY));

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/d768efab/software/base/src/main/resources/brooklyn/entity/brooklynnode/brooklyn-node-persisting-to-tmp.yaml
----------------------------------------------------------------------
diff --git a/software/base/src/main/resources/brooklyn/entity/brooklynnode/brooklyn-node-persisting-to-tmp.yaml b/software/base/src/main/resources/brooklyn/entity/brooklynnode/brooklyn-node-persisting-to-tmp.yaml
index a932e4e..afb53e7 100644
--- a/software/base/src/main/resources/brooklyn/entity/brooklynnode/brooklyn-node-persisting-to-tmp.yaml
+++ b/software/base/src/main/resources/brooklyn/entity/brooklynnode/brooklyn-node-persisting-to-tmp.yaml
@@ -21,6 +21,7 @@ name: Example Persisting Brooklyn Node
 
 services:
 - type: classpath://brooklyn/entity/brooklynnode/brooklyn-node.yaml
-  launchParameters: --persist auto --persistenceDir /tmp/brooklyn-persistence-example/
+  brooklyn.config:
+    brooklynnode.launch.parameters.extra: --persist auto --persistenceDir /tmp/brooklyn-persistence-example/
 
-# location: localhost
+location: localhost


[05/29] git commit: broolyn cluster upgrade should take extra config, and then set them as config after we've failed over, and has nice names of phases, plus refactor/tidy

Posted by al...@apache.org.
broolyn cluster upgrade should take extra config, and then set them as config after we've failed over, and has nice names of phases, plus refactor/tidy


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

Branch: refs/heads/master
Commit: 294c2176ce258078c7e61ed38eeac8013d45a7a8
Parents: 78d8cc3
Author: Alex Heneveld <al...@cloudsoftcorp.com>
Authored: Sun Oct 26 14:12:33 2014 +0000
Committer: Alex Heneveld <al...@cloudsoftcorp.com>
Committed: Fri Oct 31 09:38:19 2014 -0500

----------------------------------------------------------------------
 .../brooklyn/entity/proxying/EntitySpec.java    |   7 +
 .../entity/brooklynnode/BrooklynCluster.java    |  15 +-
 .../brooklynnode/BrooklynClusterImpl.java       |   1 +
 .../BrooklynClusterUpgradeEffectorBody.java     | 150 ++++++++++---------
 .../BrooklynNodeUpgradeEffectorBody.java        |  81 ++++------
 5 files changed, 126 insertions(+), 128 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/294c2176/api/src/main/java/brooklyn/entity/proxying/EntitySpec.java
----------------------------------------------------------------------
diff --git a/api/src/main/java/brooklyn/entity/proxying/EntitySpec.java b/api/src/main/java/brooklyn/entity/proxying/EntitySpec.java
index e8acea2..7d65121 100644
--- a/api/src/main/java/brooklyn/entity/proxying/EntitySpec.java
+++ b/api/src/main/java/brooklyn/entity/proxying/EntitySpec.java
@@ -42,6 +42,7 @@ import brooklyn.policy.EnricherSpec;
 import brooklyn.policy.Policy;
 import brooklyn.policy.PolicySpec;
 
+import com.google.common.annotations.Beta;
 import com.google.common.base.Supplier;
 import com.google.common.base.Throwables;
 import com.google.common.collect.Iterables;
@@ -229,6 +230,12 @@ public class EntitySpec<T extends Entity> extends AbstractBrooklynObjectSpec<T,E
     public Map<ConfigKey<?>, Object> getConfig() {
         return Collections.unmodifiableMap(config);
     }
+    
+    /** @return Live instance of the config map, for instance in case mass clearances are desired */
+    @Beta
+    public Map<ConfigKey<?>, Object> getConfigLive() {
+        return config;
+    }
         
     public List<PolicySpec<?>> getPolicySpecs() {
         return policySpecs;

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/294c2176/software/base/src/main/java/brooklyn/entity/brooklynnode/BrooklynCluster.java
----------------------------------------------------------------------
diff --git a/software/base/src/main/java/brooklyn/entity/brooklynnode/BrooklynCluster.java b/software/base/src/main/java/brooklyn/entity/brooklynnode/BrooklynCluster.java
index 30a46bd..7b5c095 100644
--- a/software/base/src/main/java/brooklyn/entity/brooklynnode/BrooklynCluster.java
+++ b/software/base/src/main/java/brooklyn/entity/brooklynnode/BrooklynCluster.java
@@ -18,10 +18,12 @@
  */
 package brooklyn.entity.brooklynnode;
 
+import java.util.Map;
+
 import brooklyn.config.ConfigKey;
 import brooklyn.entity.Effector;
 import brooklyn.entity.basic.ConfigKeys;
-import brooklyn.entity.basic.SoftwareProcess;
+import brooklyn.entity.brooklynnode.effector.BrooklynNodeUpgradeEffectorBody;
 import brooklyn.entity.effector.Effectors;
 import brooklyn.entity.group.DynamicCluster;
 import brooklyn.entity.proxying.ImplementedBy;
@@ -45,9 +47,16 @@ public interface BrooklynCluster extends DynamicCluster {
     public static final Effector<Void> SELECT_MASTER = SelectMasterEffector.SELECT_MASTER;
 
     public interface UpgradeClusterEffector {
+        public static final ConfigKey<String> DOWNLOAD_URL = BrooklynNode.DOWNLOAD_URL.getConfigKey();
+        public static final ConfigKey<Map<String,Object>> EXTRA_CONFIG = BrooklynNodeUpgradeEffectorBody.EXTRA_CONFIG;
+
         Effector<Void> UPGRADE_CLUSTER = Effectors.effector(Void.class, "upgradeCluster")
-                .description("Upgrade the cluster with new distribution version")
-                .parameter(SoftwareProcess.DOWNLOAD_URL.getConfigKey())
+                .description("Upgrade the cluster with new distribution version, "
+                    + "by provisioning new nodes with the new version, failing over, "
+                    + "and then deprovisioning the original nodes")
+                .parameter(BrooklynNode.SUGGESTED_VERSION)
+                .parameter(DOWNLOAD_URL)
+                .parameter(EXTRA_CONFIG)
                 .buildAbstract();
     }
 

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/294c2176/software/base/src/main/java/brooklyn/entity/brooklynnode/BrooklynClusterImpl.java
----------------------------------------------------------------------
diff --git a/software/base/src/main/java/brooklyn/entity/brooklynnode/BrooklynClusterImpl.java b/software/base/src/main/java/brooklyn/entity/brooklynnode/BrooklynClusterImpl.java
index 0d1afb6..4247ff3 100644
--- a/software/base/src/main/java/brooklyn/entity/brooklynnode/BrooklynClusterImpl.java
+++ b/software/base/src/main/java/brooklyn/entity/brooklynnode/BrooklynClusterImpl.java
@@ -48,6 +48,7 @@ public class BrooklynClusterImpl extends DynamicClusterImpl implements BrooklynC
     private static final Logger LOG = LoggerFactory.getLogger(BrooklynClusterImpl.class);
 
     static {
+        // XXX not needed or wanted
         RendererHints.register(MASTER_NODE, RendererHints.namedActionWithUrl());
     }
 

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/294c2176/software/base/src/main/java/brooklyn/entity/brooklynnode/effector/BrooklynClusterUpgradeEffectorBody.java
----------------------------------------------------------------------
diff --git a/software/base/src/main/java/brooklyn/entity/brooklynnode/effector/BrooklynClusterUpgradeEffectorBody.java b/software/base/src/main/java/brooklyn/entity/brooklynnode/effector/BrooklynClusterUpgradeEffectorBody.java
index 8215a0b..331f363 100644
--- a/software/base/src/main/java/brooklyn/entity/brooklynnode/effector/BrooklynClusterUpgradeEffectorBody.java
+++ b/software/base/src/main/java/brooklyn/entity/brooklynnode/effector/BrooklynClusterUpgradeEffectorBody.java
@@ -23,13 +23,19 @@ import java.util.Collection;
 import java.util.Collections;
 import java.util.HashSet;
 import java.util.Map;
+import java.util.concurrent.Callable;
 import java.util.concurrent.atomic.AtomicBoolean;
 
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
 import brooklyn.config.ConfigKey;
 import brooklyn.entity.Effector;
 import brooklyn.entity.Entity;
 import brooklyn.entity.Group;
+import brooklyn.entity.basic.Attributes;
 import brooklyn.entity.basic.EntityPredicates;
+import brooklyn.entity.basic.EntityTasks;
 import brooklyn.entity.basic.Lifecycle;
 import brooklyn.entity.brooklynnode.BrooklynCluster;
 import brooklyn.entity.brooklynnode.BrooklynCluster.SelectMasterEffector;
@@ -40,25 +46,28 @@ import brooklyn.entity.effector.EffectorBody;
 import brooklyn.entity.effector.Effectors;
 import brooklyn.entity.group.DynamicCluster;
 import brooklyn.entity.proxying.EntitySpec;
-import brooklyn.event.AttributeSensor;
+import brooklyn.management.TaskAdaptable;
 import brooklyn.management.ha.HighAvailabilityMode;
 import brooklyn.management.ha.ManagementNodeState;
 import brooklyn.util.collections.MutableMap;
 import brooklyn.util.config.ConfigBag;
 import brooklyn.util.exceptions.Exceptions;
 import brooklyn.util.net.Urls;
-import brooklyn.util.repeat.Repeater;
 import brooklyn.util.task.DynamicTasks;
 import brooklyn.util.task.Tasks;
 import brooklyn.util.time.Duration;
 
 import com.google.api.client.util.Preconditions;
-import com.google.common.base.Predicate;
+import com.google.common.base.Predicates;
 import com.google.common.collect.Collections2;
 import com.google.common.collect.Iterables;
 
 public class BrooklynClusterUpgradeEffectorBody extends EffectorBody<Void> implements UpgradeClusterEffector {
-    public static final Effector<Void> UPGRADE_CLUSTER = Effectors.effector(UpgradeClusterEffector.UPGRADE_CLUSTER).impl(new BrooklynClusterUpgradeEffectorBody()).build();
+    
+    private static final Logger log = LoggerFactory.getLogger(BrooklynClusterUpgradeEffectorBody.class);
+    
+    public static final Effector<Void> UPGRADE_CLUSTER = Effectors.effector(UpgradeClusterEffector.UPGRADE_CLUSTER)
+        .impl(new BrooklynClusterUpgradeEffectorBody()).build();
 
     private AtomicBoolean upgradeInProgress = new AtomicBoolean();
 
@@ -71,19 +80,35 @@ public class BrooklynClusterUpgradeEffectorBody extends EffectorBody<Void> imple
         EntitySpec<?> memberSpec = entity().getConfig(BrooklynCluster.MEMBER_SPEC);
         Preconditions.checkNotNull(memberSpec, BrooklynCluster.MEMBER_SPEC.getName() + " is required for " + UpgradeClusterEffector.class.getName());
 
-        Map<ConfigKey<?>, Object> specCfg = memberSpec.getConfig();
-        String oldDownloadUrl = (String) specCfg.get(BrooklynNode.DOWNLOAD_URL);
-        String oldUploadUrl = (String) specCfg.get(BrooklynNode.DISTRO_UPLOAD_URL);
-        String newDownloadUrl = parameters.get(BrooklynNode.DOWNLOAD_URL.getConfigKey());
-        String newUploadUrl = inferUploadUrl(newDownloadUrl);
+        ConfigBag specCfg = ConfigBag.newInstance( memberSpec.getConfig() );
+        ConfigBag origSpecCfg = ConfigBag.newInstanceCopying(specCfg);
+        log.debug("Upgrading "+entity()+", changing "+BrooklynCluster.MEMBER_SPEC+" from "+memberSpec+" / "+origSpecCfg);
+        
         try {
-            memberSpec.configure(BrooklynNode.DOWNLOAD_URL, newUploadUrl);
-            memberSpec.configure(BrooklynNode.DISTRO_UPLOAD_URL, newUploadUrl);
+            String newDownloadUrl = parameters.get(DOWNLOAD_URL);
+            specCfg.putIfNotNull(DOWNLOAD_URL, newDownloadUrl);
+            specCfg.put(BrooklynNode.DISTRO_UPLOAD_URL, inferUploadUrl(newDownloadUrl));
+            
+            specCfg.putAll(ConfigBag.newInstance(parameters.get(EXTRA_CONFIG)).getAllConfigAsConfigKeyMap());
+            
+            Map<ConfigKey<?>, Object> cfgLive = memberSpec.getConfigLive();
+            cfgLive.clear();
+            cfgLive.putAll(specCfg.getAllConfigAsConfigKeyMap());
+            // not necessary, but good practice
+            entity().setConfig(BrooklynCluster.MEMBER_SPEC, memberSpec);
+            log.debug("Upgrading "+entity()+", new "+BrooklynCluster.MEMBER_SPEC+": "+memberSpec+" / "+specCfg);
+            
             upgrade(parameters);
         } catch (Exception e) {
-            memberSpec.configure(BrooklynNode.DOWNLOAD_URL, oldDownloadUrl);
-            memberSpec.configure(BrooklynNode.DISTRO_UPLOAD_URL, oldUploadUrl);
+            log.debug("Upgrading "+entity()+" failed, will rethrow after restoring "+BrooklynCluster.MEMBER_SPEC+" to: "+origSpecCfg);
+            Map<ConfigKey<?>, Object> cfgLive = memberSpec.getConfigLive();
+            cfgLive.clear();
+            cfgLive.putAll(origSpecCfg.getAllConfigAsConfigKeyMap());
+            // not necessary, but good practice
+            entity().setConfig(BrooklynCluster.MEMBER_SPEC, memberSpec);
+            
             throw Exceptions.propagate(e);
+            
         } finally {
             upgradeInProgress.set(false);
         }
@@ -91,6 +116,7 @@ public class BrooklynClusterUpgradeEffectorBody extends EffectorBody<Void> imple
     }
 
     private String inferUploadUrl(String newDownloadUrl) {
+        if (newDownloadUrl==null) return null;
         boolean isLocal = "file".equals(Urls.getProtocol(newDownloadUrl)) || new File(newDownloadUrl).exists();
         if (isLocal) {
             return newDownloadUrl;
@@ -99,22 +125,30 @@ public class BrooklynClusterUpgradeEffectorBody extends EffectorBody<Void> imple
         }
     }
 
-    private void upgrade(ConfigBag parameters) {
-        //TODO might be worth separating each step in a task for better UI
-        //TODO currently this will fight with auto-scaler policies; you should turn them off
+    protected void upgrade(ConfigBag parameters) {
+        //TODO currently this will fight with auto-scaler policies; they must be turned off for upgrade to work
 
         Group cluster = (Group)entity();
         Collection<Entity> initialMembers = cluster.getMembers();
         int initialClusterSize = initialMembers.size();
+        
+        if (!BrooklynNodeUpgradeEffectorBody.isPersistenceModeEnabled(cluster)) {
+            // would could try a `forcePersistNow`, but that's sloppy; 
+            // for now, require HA/persistence for upgrading 
+            DynamicTasks.queue( Tasks.warning("Check persistence", 
+                new IllegalStateException("Persistence does not appear to be enabled at this cluster. "
+                + "Cluster upgrade will not succeed unless a custom launch script enables it.")) );
+        }
 
         //1. Initially create a single node to check if it will launch successfully
-        Entity initialNode = Iterables.getOnlyElement(createNodes(1));
+        TaskAdaptable<Collection<Entity>> initialNodeTask = DynamicTasks.queue(newCreateNodesTask(1, "Creating first upgraded version node"));
 
         //2. If everything is OK with the first node launch the rest as well
-        Collection<Entity> remainingNodes = createNodes(initialClusterSize - 1);
+        TaskAdaptable<Collection<Entity>> remainingNodesTask = DynamicTasks.queue(newCreateNodesTask(initialClusterSize - 1, "Creating remaining upgraded version nodes ("+(initialClusterSize - 1)+")"));
 
         //3. Once we have all nodes running without errors switch master
-        DynamicTasks.queue(Effectors.invocation(cluster, BrooklynCluster.SELECT_MASTER, MutableMap.of(SelectMasterEffector.NEW_MASTER_ID, initialNode.getId()))).asTask().getUnchecked();
+        DynamicTasks.queue(Effectors.invocation(cluster, BrooklynCluster.SELECT_MASTER, MutableMap.of(SelectMasterEffector.NEW_MASTER_ID, 
+            Iterables.getOnlyElement(initialNodeTask.asTask().getUnchecked()).getId()))).asTask().getUnchecked();
 
         //4. Stop the nodes which were running at the start of the upgrade call, but keep them around.
         //   Should we create a quarantine-like zone for old stopped version?
@@ -125,34 +159,46 @@ public class BrooklynClusterUpgradeEffectorBody extends EffectorBody<Void> imple
         //version on this cluster before the above select-master call, and then delete any which are running the old
         //version (would require tracking the version number at the entity)
         HashSet<Entity> oldMembers = new HashSet<Entity>(initialMembers);
-        oldMembers.removeAll(remainingNodes);
-        oldMembers.remove(initialNode);
+        oldMembers.removeAll(remainingNodesTask.asTask().getUnchecked());
+        oldMembers.removeAll(initialNodeTask.asTask().getUnchecked());
         DynamicTasks.queue(Effectors.invocation(BrooklynNode.STOP_NODE_BUT_LEAVE_APPS, Collections.emptyMap(), oldMembers)).asTask().getUnchecked();
     }
 
-    private Collection<Entity> createNodes(int nodeCnt) {
+    private TaskAdaptable<Collection<Entity>> newCreateNodesTask(int size, String name) {
+        return Tasks.<Collection<Entity>>builder().name(name).body(new CreateNodesCallable(size)).build();
+    }
+
+    protected class CreateNodesCallable implements Callable<Collection<Entity>> {
+        private final int size;
+        public CreateNodesCallable(int size) {
+            this.size = size;
+        }
+        @Override
+        public Collection<Entity> call() throws Exception {
+            return createNodes(size);
+        }
+    }
+
+    protected Collection<Entity> createNodes(int nodeCnt) {
         DynamicCluster cluster = (DynamicCluster)entity();
 
         //1. Create the nodes
         Collection<Entity> newNodes = cluster.resizeByDelta(nodeCnt);
 
-        //2. Wait for them to be RUNNING
-        waitAttributeNotEqualTo(
-                newNodes,
-                BrooklynNode.SERVICE_STATE_ACTUAL, Lifecycle.STARTING);
+        //2. Wait for them to be RUNNING (or at least STARTING to have completed)
+        // (should already be the case, because above is synchronous and, we think, it will fail if start does not succeed)
+        DynamicTasks.queue(EntityTasks.awaitingAttribute(newNodes, Attributes.SERVICE_STATE_ACTUAL, 
+                Predicates.not(Predicates.equalTo(Lifecycle.STARTING)), Duration.minutes(30)));
 
         //3. Set HOT_STANDBY in case it is not enabled on the command line ...
         DynamicTasks.queue(Effectors.invocation(
                 BrooklynNode.SET_HA_MODE,
                 MutableMap.of(SetHAModeEffector.MODE, HighAvailabilityMode.HOT_STANDBY), 
                 newNodes)).asTask().getUnchecked();
-
-        //4. ... and wait until all of the nodes change state
-        //TODO if the REST call is blocking this is not needed
-        waitAttributeEqualTo(
-                newNodes,
-                BrooklynNode.MANAGEMENT_NODE_STATE,
-                ManagementNodeState.HOT_STANDBY);
+        //... and wait until all of the nodes change state
+        // TODO fail quicker if state changes to FAILED
+        DynamicTasks.queue(EntityTasks.awaitingAttribute(newNodes, BrooklynNode.MANAGEMENT_NODE_STATE, 
+                Predicates.equalTo(ManagementNodeState.HOT_STANDBY), Duration.FIVE_MINUTES));
 
         //5. Just in case check if all of the nodes are SERVICE_UP (which would rule out ON_FIRE as well)
         Collection<Entity> failedNodes = Collections2.filter(newNodes, EntityPredicates.attributeEqualTo(BrooklynNode.SERVICE_UP, Boolean.FALSE));
@@ -162,42 +208,4 @@ public class BrooklynClusterUpgradeEffectorBody extends EffectorBody<Void> imple
         return newNodes;
     }
 
-    private <T> void waitAttributeEqualTo(Collection<Entity> nodes, AttributeSensor<T> sensor, T value) {
-        waitPredicate(
-                nodes, 
-                EntityPredicates.attributeEqualTo(sensor, value),
-                "Waiting for nodes " + nodes + ", sensor " + sensor + " to be " + value,
-                "Timeout while waiting for nodes " + nodes + ", sensor " + sensor + " to change to " + value);
-    }
-
-    private <T> void waitAttributeNotEqualTo(Collection<Entity> nodes, AttributeSensor<T> sensor, T value) {
-        waitPredicate(
-                nodes, 
-                EntityPredicates.attributeNotEqualTo(sensor, value),
-                "Waiting for nodes " + nodes + ", sensor " + sensor + " to change from " + value,
-                "Timeout while waiting for nodes " + nodes + ", sensor " + sensor + " to change from " + value);
-    }
-
-    private <T extends Entity> void waitPredicate(Collection<T> nodes, Predicate<T> waitPredicate, String blockingMsg, String errorMsg) {
-        Tasks.setBlockingDetails(blockingMsg);
-        boolean pollSuccess = Repeater.create(blockingMsg)
-            .backoff(Duration.ONE_SECOND, 1.2, Duration.TEN_SECONDS)
-            .limitTimeTo(Duration.ONE_HOUR)
-            .until(nodes, allMatch(waitPredicate))
-            .run();
-        Tasks.resetBlockingDetails();
-
-        if (!pollSuccess) {
-            throw new IllegalStateException(errorMsg);
-        }
-    }
-
-    public static <T> Predicate<Collection<T>> allMatch(final Predicate<T> predicate) {
-        return new Predicate<Collection<T>>() {
-            @Override
-            public boolean apply(Collection<T> input) {
-                return Iterables.all(input, predicate);
-            }
-        };
-    }
 }

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/294c2176/software/base/src/main/java/brooklyn/entity/brooklynnode/effector/BrooklynNodeUpgradeEffectorBody.java
----------------------------------------------------------------------
diff --git a/software/base/src/main/java/brooklyn/entity/brooklynnode/effector/BrooklynNodeUpgradeEffectorBody.java b/software/base/src/main/java/brooklyn/entity/brooklynnode/effector/BrooklynNodeUpgradeEffectorBody.java
index a6433aa..299bd5b 100644
--- a/software/base/src/main/java/brooklyn/entity/brooklynnode/effector/BrooklynNodeUpgradeEffectorBody.java
+++ b/software/base/src/main/java/brooklyn/entity/brooklynnode/effector/BrooklynNodeUpgradeEffectorBody.java
@@ -19,7 +19,6 @@
 package brooklyn.entity.brooklynnode.effector;
 
 import java.util.Map;
-import java.util.concurrent.Callable;
 
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -29,33 +28,27 @@ import brooklyn.entity.Effector;
 import brooklyn.entity.Entity;
 import brooklyn.entity.basic.Entities;
 import brooklyn.entity.basic.EntityInternal;
-import brooklyn.entity.basic.EntityPredicates;
+import brooklyn.entity.basic.EntityTasks;
 import brooklyn.entity.basic.SoftwareProcess;
+import brooklyn.entity.brooklynnode.BrooklynCluster;
 import brooklyn.entity.brooklynnode.BrooklynNode;
 import brooklyn.entity.effector.EffectorBody;
 import brooklyn.entity.effector.Effectors;
 import brooklyn.entity.proxying.EntitySpec;
 import brooklyn.entity.software.SshEffectorTasks;
-import brooklyn.event.AttributeSensor;
 import brooklyn.event.basic.MapConfigKey;
-import brooklyn.management.TaskAdaptable;
 import brooklyn.management.ha.HighAvailabilityMode;
 import brooklyn.management.ha.ManagementNodeState;
 import brooklyn.util.config.ConfigBag;
-import brooklyn.util.exceptions.Exceptions;
-import brooklyn.util.exceptions.ReferenceWithError;
-import brooklyn.util.guava.Functionals;
 import brooklyn.util.net.Urls;
-import brooklyn.util.repeat.Repeater;
 import brooklyn.util.task.DynamicTasks;
 import brooklyn.util.task.Tasks;
 import brooklyn.util.text.Identifiers;
 import brooklyn.util.text.Strings;
 import brooklyn.util.time.Duration;
 
-import com.google.common.base.Functions;
+import com.google.common.annotations.Beta;
 import com.google.common.base.Preconditions;
-import com.google.common.base.Predicate;
 import com.google.common.base.Predicates;
 import com.google.common.reflect.TypeToken;
 
@@ -70,21 +63,28 @@ public class BrooklynNodeUpgradeEffectorBody extends EffectorBody<Void> {
     private static final Logger log = LoggerFactory.getLogger(BrooklynNodeUpgradeEffectorBody.class);
     
     public static final ConfigKey<String> DOWNLOAD_URL = BrooklynNode.DOWNLOAD_URL.getConfigKey();
-    public static final ConfigKey<Map<String,Object>> EXTRA_CONFIG = MapConfigKey.builder(new TypeToken<Map<String,Object>>() {}).name("extraConfig").description("Additional new config to set on this entity as part of upgrading").build();
+    public static final ConfigKey<Map<String,Object>> EXTRA_CONFIG = MapConfigKey.builder(new TypeToken<Map<String,Object>>() {})
+        .name("extraConfig")
+        .description("Additional new config to set on the BrooklynNode as part of upgrading")
+        .build();
 
     public static final Effector<Void> UPGRADE = Effectors.effector(Void.class, "upgrade")
-        .description("Changes the Brooklyn build used to run this node, by spawning a dry-run node then copying the installed files across. "
+        .description("Changes the Brooklyn build used to run this node, "
+            + "by spawning a dry-run node then copying the installed files across. "
             + "This node must be running for persistence for in-place upgrading to work.")
-        .parameter(BrooklynNode.SUGGESTED_VERSION).parameter(DOWNLOAD_URL).parameter(EXTRA_CONFIG)
+        .parameter(BrooklynNode.SUGGESTED_VERSION)
+        .parameter(DOWNLOAD_URL)
+        .parameter(EXTRA_CONFIG)
         .impl(new BrooklynNodeUpgradeEffectorBody()).build();
     
     @Override
     public Void call(ConfigBag parametersO) {
         if (!isPersistenceModeEnabled(entity())) {
             // would could try a `forcePersistNow`, but that's sloppy; 
-            // for now, require HA/persistence for upgrading 
-            DynamicTasks.queue( Tasks.warning("Persistence does not appear to be enabled at this node. "
-                + "In-place upgrade is unlikely to succeed.", null) );
+            // for now, require HA/persistence for upgrading
+            DynamicTasks.queue( Tasks.warning("Check persistence", 
+                new IllegalStateException("Persistence does not appear to be enabled at this cluster. "
+                + "In-place node upgrade will not succeed unless a custom launch script enables it.")) );
         }
 
         ConfigBag parameters = ConfigBag.newInstanceCopying(parametersO);
@@ -114,6 +114,7 @@ public class BrooklynNodeUpgradeEffectorBody extends EffectorBody<Void> {
             .configure(parameters.getAllConfig()));
 
         //force this to start as hot-standby
+        // TODO alternatively could use REST API as in BrooklynClusterUpgradeEffectorBody
         String launchParameters = dryRunChild.getConfig(BrooklynNode.EXTRA_LAUNCH_PARAMETERS);
         if (Strings.isBlank(launchParameters)) launchParameters = "";
         else launchParameters += " ";
@@ -127,8 +128,8 @@ public class BrooklynNodeUpgradeEffectorBody extends EffectorBody<Void> {
         DynamicTasks.queue(Effectors.invocation(dryRunChild, BrooklynNode.START, ConfigBag.EMPTY));
 
         // 2 confirm hot standby status
-        DynamicTasks.queue(newWaitForAttributeTask(dryRunChild, BrooklynNode.MANAGEMENT_NODE_STATE, 
-            Predicates.equalTo(ManagementNodeState.HOT_STANDBY), Duration.TWO_MINUTES));
+        DynamicTasks.queue(EntityTasks.awaitingAttribute(dryRunChild, BrooklynNode.MANAGEMENT_NODE_STATE, 
+            Predicates.equalTo(ManagementNodeState.HOT_STANDBY), Duration.FIVE_MINUTES));
 
         // 3 stop new version
         // 4 stop old version
@@ -166,47 +167,19 @@ public class BrooklynNodeUpgradeEffectorBody extends EffectorBody<Void> {
         return null;
     }
 
-    private boolean isPersistenceModeEnabled(EntityInternal entity) {
+    @Beta
+    static boolean isPersistenceModeEnabled(Entity entity) {
         // TODO when there are PERSIST* options in BrooklynNode, look at them here!
         // or, better, have a sensor for persistence
-        String params = entity.getConfig(BrooklynNode.EXTRA_LAUNCH_PARAMETERS);
+        String params = null;
+        if (entity instanceof BrooklynCluster) {
+            EntitySpec<?> spec = entity.getConfig(BrooklynCluster.MEMBER_SPEC);
+            params = Strings.toString( spec.getConfig().get(BrooklynNode.EXTRA_LAUNCH_PARAMETERS) );
+        }
+        if (params==null) params = entity.getConfig(BrooklynNode.EXTRA_LAUNCH_PARAMETERS);
         if (params==null) return false;
         if (params.indexOf("persist")==0) return false;
         return true;
     }
 
-    private static class WaitForRepeaterCallable implements Callable<Boolean> {
-        protected Repeater repeater;
-        protected boolean requireTrue;
-
-        public WaitForRepeaterCallable(Repeater repeater, boolean requireTrue) {
-            this.repeater = repeater;
-            this.requireTrue = requireTrue;
-        }
-
-        @Override
-        public Boolean call() {
-            ReferenceWithError<Boolean> result = repeater.runKeepingError();
-            if (Boolean.TRUE.equals(result.getWithoutError()))
-                return true;
-            if (result.hasError()) 
-                throw Exceptions.propagate(result.getError());
-            if (requireTrue)
-                throw new IllegalStateException("timeout - "+repeater.getDescription());
-            return false;
-        }
-    }
-
-    private static <T> TaskAdaptable<Boolean> newWaitForAttributeTask(Entity node, AttributeSensor<T> sensor, Predicate<T> condition, Duration timeout) {
-        return awaiting( Repeater.create("waiting on "+node+" "+sensor.getName()+" "+condition)
-                    .backoff(Duration.millis(10), 1.5, Duration.millis(200))
-                    .limitTimeTo(timeout==null ? Duration.PRACTICALLY_FOREVER : timeout)
-                    .until(Functionals.callable(Functions.forPredicate(EntityPredicates.attributeSatisfies(sensor, condition)), node)),
-                    true);
-    }
-
-    private static TaskAdaptable<Boolean> awaiting(Repeater repeater, boolean requireTrue) {
-        return Tasks.<Boolean>builder().name(repeater.getDescription()).body(new WaitForRepeaterCallable(repeater, requireTrue)).build();
-    }
-
 }


[11/29] git commit: add support for extra CLI parameters to BrooklynNode, use those to run in persistence mode, and final fixes to the in-place upgrade effector

Posted by al...@apache.org.
add support for extra CLI parameters to BrooklynNode, use those to run in persistence mode, and final fixes to the in-place upgrade effector


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

Branch: refs/heads/master
Commit: 634b66b9fe423a599146fc7d9c0e7bdb75cb5e9c
Parents: 6432a12
Author: Alex Heneveld <al...@cloudsoftcorp.com>
Authored: Fri Oct 24 23:52:37 2014 +0100
Committer: Alex Heneveld <al...@cloudsoftcorp.com>
Committed: Fri Oct 31 09:38:19 2014 -0500

----------------------------------------------------------------------
 .../entity/brooklynnode/BrooklynNode.java       |  4 +++
 .../brooklynnode/BrooklynNodeSshDriver.java     |  3 ++
 .../brooklynnode/BrooklynUpgradeEffector.java   | 33 +++++++++++++++++---
 .../entity/brooklynnode/brooklyn-node.yaml      |  7 +++++
 .../brooklyn/launcher/BrooklynLauncher.java     |  6 ++--
 5 files changed, 44 insertions(+), 9 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/634b66b9/software/base/src/main/java/brooklyn/entity/brooklynnode/BrooklynNode.java
----------------------------------------------------------------------
diff --git a/software/base/src/main/java/brooklyn/entity/brooklynnode/BrooklynNode.java b/software/base/src/main/java/brooklyn/entity/brooklynnode/BrooklynNode.java
index 3d8683f..9bf385e 100644
--- a/software/base/src/main/java/brooklyn/entity/brooklynnode/BrooklynNode.java
+++ b/software/base/src/main/java/brooklyn/entity/brooklynnode/BrooklynNode.java
@@ -120,6 +120,10 @@ public interface BrooklynNode extends SoftwareProcess, UsesJava {
         "Path to the script to launch Brooklyn / the app relative to the subpath in the archive, defaulting to 'bin/brooklyn'", 
         "bin/brooklyn");
 
+    @SetFromFlag("launchParameters")
+    ConfigKey<String> EXTRA_LAUNCH_PARAMETERS = ConfigKeys.newStringConfigKey("brooklynnode.launch.parameters.extra",
+        "Launch parameters passed on the CLI, in addition to 'launch' and parameters implied by other config keys (and placed afterwards on the command line)");
+
     @SetFromFlag("launchCommandCreatesPidFile")
     ConfigKey<Boolean> LAUNCH_COMMAND_CREATES_PID_FILE = ConfigKeys.newBooleanConfigKey("brooklynnode.launch.command.pid.updated",
         "Whether the launch script creates/updates the PID file, if not the entity will do so, "

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/634b66b9/software/base/src/main/java/brooklyn/entity/brooklynnode/BrooklynNodeSshDriver.java
----------------------------------------------------------------------
diff --git a/software/base/src/main/java/brooklyn/entity/brooklynnode/BrooklynNodeSshDriver.java b/software/base/src/main/java/brooklyn/entity/brooklynnode/BrooklynNodeSshDriver.java
index cbe1535..d5d9c01 100644
--- a/software/base/src/main/java/brooklyn/entity/brooklynnode/BrooklynNodeSshDriver.java
+++ b/software/base/src/main/java/brooklyn/entity/brooklynnode/BrooklynNodeSshDriver.java
@@ -285,6 +285,9 @@ public class BrooklynNodeSshDriver extends JavaSoftwareProcessSshDriver implemen
         if (getEntity().getConfig(BrooklynNode.NO_SHUTDOWN_ON_EXIT)) {
             cmd += " --noShutdownOnExit ";
         }
+        if (!Strings.isBlank(getEntity().getConfig(BrooklynNode.EXTRA_LAUNCH_PARAMETERS))) {
+            cmd += " "+getEntity().getConfig(BrooklynNode.EXTRA_LAUNCH_PARAMETERS);
+        }
         cmd += format(" >> %s/console 2>&1 </dev/null &", getRunDir());
         
         log.info("Starting brooklyn on {} using command {}", getMachine(), cmd);

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/634b66b9/software/base/src/main/java/brooklyn/entity/brooklynnode/BrooklynUpgradeEffector.java
----------------------------------------------------------------------
diff --git a/software/base/src/main/java/brooklyn/entity/brooklynnode/BrooklynUpgradeEffector.java b/software/base/src/main/java/brooklyn/entity/brooklynnode/BrooklynUpgradeEffector.java
index a55f077..3dc2015 100644
--- a/software/base/src/main/java/brooklyn/entity/brooklynnode/BrooklynUpgradeEffector.java
+++ b/software/base/src/main/java/brooklyn/entity/brooklynnode/BrooklynUpgradeEffector.java
@@ -49,6 +49,7 @@ import brooklyn.util.repeat.Repeater;
 import brooklyn.util.task.DynamicTasks;
 import brooklyn.util.task.Tasks;
 import brooklyn.util.text.Identifiers;
+import brooklyn.util.text.Strings;
 import brooklyn.util.time.Duration;
 
 import com.google.common.base.Functions;
@@ -58,8 +59,10 @@ import com.google.common.base.Predicates;
 import com.google.common.reflect.TypeToken;
 
 @SuppressWarnings("serial")
-/** Upgrades a brooklyn node in-place on the box, by creating a child brooklyn node and ensuring it can rebind in HOT_STANDBY;
- * requires the target node to have persistence enabled 
+/** Upgrades a brooklyn node in-place on the box, 
+ * by creating a child brooklyn node and ensuring it can rebind in HOT_STANDBY
+ * <p>
+ * Requires the target node to have persistence enabled. 
  */
 public class BrooklynUpgradeEffector {
 
@@ -69,13 +72,21 @@ public class BrooklynUpgradeEffector {
     public static final ConfigKey<Map<String,Object>> EXTRA_CONFIG = MapConfigKey.builder(new TypeToken<Map<String,Object>>() {}).name("extraConfig").description("Additional new config to set on this entity as part of upgrading").build();
 
     public static final Effector<Void> UPGRADE = Effectors.effector(Void.class, "upgrade")
-        .description("Changes the Brooklyn build used to run this node, by spawning a dry-run node then copying the installed files across")
+        .description("Changes the Brooklyn build used to run this node, by spawning a dry-run node then copying the installed files across. "
+            + "This node must be running for persistence for in-place upgrading to work.")
         .parameter(BrooklynNode.SUGGESTED_VERSION).parameter(DOWNLOAD_URL).parameter(EXTRA_CONFIG)
         .impl(new UpgradeImpl()).build();
     
     public static class UpgradeImpl extends EffectorBody<Void> {
         @Override
         public Void call(ConfigBag parametersO) {
+            if (!isPersistenceModeEnabled(entity())) {
+                // would could try a `forcePersistNow`, but that's sloppy; 
+                // for now, require HA/persistence for upgrading 
+                DynamicTasks.queue( Tasks.warning("Persistence does not appear to be enabled at this node. "
+                    + "In-place upgrade is unlikely to succeed.", null) );
+            }
+            
             ConfigBag parameters = ConfigBag.newInstanceCopying(parametersO);
             
             /*
@@ -103,8 +114,11 @@ public class BrooklynUpgradeEffector {
                 .configure(parameters.getAllConfig()));
             
             //force this to start as hot-standby
-            String launchCommand = dryRunChild.getConfig(BrooklynNode.LAUNCH_COMMAND);
-            ((EntityInternal)dryRunChild).setConfig(BrooklynNode.LAUNCH_COMMAND, launchCommand + " --highAvailability "+HighAvailabilityMode.HOT_STANDBY);
+            String launchParameters = dryRunChild.getConfig(BrooklynNode.EXTRA_LAUNCH_PARAMETERS);
+            if (Strings.isBlank(launchParameters)) launchParameters = "";
+            else launchParameters += " ";
+            launchParameters += "--highAvailability "+HighAvailabilityMode.HOT_STANDBY;
+            ((EntityInternal)dryRunChild).setConfig(BrooklynNode.EXTRA_LAUNCH_PARAMETERS, launchParameters);
             
             Entities.manage(dryRunChild);
             final String dryRunNodeUid = dryRunChild.getId();
@@ -152,6 +166,15 @@ public class BrooklynUpgradeEffector {
             return null;
         }
 
+        private boolean isPersistenceModeEnabled(EntityInternal entity) {
+            // TODO when there are PERSIST* options in BrooklynNode, look at them here!
+            // or, better, have a sensor for persistence
+            String params = entity.getConfig(BrooklynNode.EXTRA_LAUNCH_PARAMETERS);
+            if (params==null) return false;
+            if (params.indexOf("persist")==0) return false;
+            return true;
+        }
+
     }
 
     private static class WaitForRepeaterCallable implements Callable<Boolean> {

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/634b66b9/software/base/src/test/resources/brooklyn/entity/brooklynnode/brooklyn-node.yaml
----------------------------------------------------------------------
diff --git a/software/base/src/test/resources/brooklyn/entity/brooklynnode/brooklyn-node.yaml b/software/base/src/test/resources/brooklyn/entity/brooklynnode/brooklyn-node.yaml
index 9e6300f..bd91f77 100644
--- a/software/base/src/test/resources/brooklyn/entity/brooklynnode/brooklyn-node.yaml
+++ b/software/base/src/test/resources/brooklyn/entity/brooklynnode/brooklyn-node.yaml
@@ -17,11 +17,18 @@
 # under the License.
 #
 
+name: Example Brooklyn Node
+
 services:
 - type: brooklyn.entity.brooklynnode.BrooklynNode
+
   ## to use a local file, specify something such as the following:
   downloadUrl: file:///Users/alex/.m2/repository/org/apache/brooklyn/brooklyn-dist/0.7.0-SNAPSHOT/brooklyn-dist-0.7.0-SNAPSHOT-dist.tar.gz
   # downloadUrl: file:///tmp/brooklyn-dist-0.7.0-SNAPSHOT-dist.tar.gz
+  
+  ## to persist
+#  launchParameters: --persist auto --persistenceDir /tmp/brooklyn-persistence-example/
+
 
 ## NB if deploying to a remote machine you must also supply management{Username,Password} and a brooklyn properties with those values set 
 location: localhost

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/634b66b9/usage/launcher/src/main/java/brooklyn/launcher/BrooklynLauncher.java
----------------------------------------------------------------------
diff --git a/usage/launcher/src/main/java/brooklyn/launcher/BrooklynLauncher.java b/usage/launcher/src/main/java/brooklyn/launcher/BrooklynLauncher.java
index 598d176..158c7c4 100644
--- a/usage/launcher/src/main/java/brooklyn/launcher/BrooklynLauncher.java
+++ b/usage/launcher/src/main/java/brooklyn/launcher/BrooklynLauncher.java
@@ -19,9 +19,6 @@
 package brooklyn.launcher;
 
 import static com.google.common.base.Preconditions.checkNotNull;
-
-import brooklyn.entity.rebind.transformer.CompoundTransformer;
-import brooklyn.management.ha.ManagementPlaneSyncRecord;
 import io.brooklyn.camp.CampPlatform;
 import io.brooklyn.camp.brooklyn.BrooklynCampPlatformLauncherNoServer;
 import io.brooklyn.camp.brooklyn.spi.creation.BrooklynAssemblyTemplateInstantiator;
@@ -68,6 +65,7 @@ import brooklyn.entity.rebind.persister.FileBasedObjectStore;
 import brooklyn.entity.rebind.persister.PersistMode;
 import brooklyn.entity.rebind.persister.PersistenceObjectStore;
 import brooklyn.entity.rebind.persister.jclouds.JcloudsBlobStoreBasedObjectStore;
+import brooklyn.entity.rebind.transformer.CompoundTransformer;
 import brooklyn.entity.trait.Startable;
 import brooklyn.internal.BrooklynFeatureEnablement;
 import brooklyn.launcher.config.StopWhichAppsOnShutdown;
@@ -81,6 +79,7 @@ import brooklyn.management.ha.HighAvailabilityManager;
 import brooklyn.management.ha.HighAvailabilityManagerImpl;
 import brooklyn.management.ha.HighAvailabilityMode;
 import brooklyn.management.ha.ManagementNodeState;
+import brooklyn.management.ha.ManagementPlaneSyncRecord;
 import brooklyn.management.ha.ManagementPlaneSyncRecordPersister;
 import brooklyn.management.ha.ManagementPlaneSyncRecordPersisterToObjectStore;
 import brooklyn.management.internal.LocalManagementContext;
@@ -104,7 +103,6 @@ import brooklyn.util.time.Time;
 
 import com.google.common.base.Splitter;
 import com.google.common.base.Stopwatch;
-import com.google.common.base.Throwables;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.Lists;
 import com.google.common.collect.Maps;


[28/29] git commit: decode the URL as per @neykov's comment

Posted by al...@apache.org.
decode the URL as per @neykov's comment

(i think in case it is of the form my%20file.txt )


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

Branch: refs/heads/master
Commit: 1bb15610cb5dd3419c8cdb8395d6dda0238adcab
Parents: 0323f48
Author: Alex Heneveld <al...@cloudsoftcorp.com>
Authored: Sat Nov 1 11:26:22 2014 +0000
Committer: Alex Heneveld <al...@cloudsoftcorp.com>
Committed: Sat Nov 1 11:26:52 2014 +0000

----------------------------------------------------------------------
 .../entity/brooklynnode/BrooklynNodeSshDriver.java      | 12 +++++-------
 utils/common/src/main/java/brooklyn/util/net/Urls.java  |  6 ++++++
 2 files changed, 11 insertions(+), 7 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/1bb15610/software/base/src/main/java/brooklyn/entity/brooklynnode/BrooklynNodeSshDriver.java
----------------------------------------------------------------------
diff --git a/software/base/src/main/java/brooklyn/entity/brooklynnode/BrooklynNodeSshDriver.java b/software/base/src/main/java/brooklyn/entity/brooklynnode/BrooklynNodeSshDriver.java
index add3f8f..efc9546 100644
--- a/software/base/src/main/java/brooklyn/entity/brooklynnode/BrooklynNodeSshDriver.java
+++ b/software/base/src/main/java/brooklyn/entity/brooklynnode/BrooklynNodeSshDriver.java
@@ -95,14 +95,12 @@ public class BrooklynNodeSshDriver extends JavaSoftwareProcessSshDriver implemen
             // assume the dir name is `basename-VERSION` where download link is `basename-VERSION-dist.tar.gz`
             String uploadUrl = entity.getConfig(BrooklynNode.DISTRO_UPLOAD_URL);
             String origDownloadName = uploadUrl;
-            if (origDownloadName==null) { 
-                String downloadUrlTemplate = entity.getAttribute(BrooklynNode.DOWNLOAD_URL);
-                if (downloadUrlTemplate!=null) {
-                    // BasicDownloadResolver makes it crazy hard to get the template-evaluated value of DOWNLOAD_URL
-                    origDownloadName = DownloadSubstituters.substitute(downloadUrlTemplate, DownloadSubstituters.getBasicEntitySubstitutions(this));
-                }
-            }
+            if (origDownloadName==null) 
+                origDownloadName = entity.getAttribute(BrooklynNode.DOWNLOAD_URL);
             if (origDownloadName!=null) {
+                // BasicDownloadResolver makes it crazy hard to get the template-evaluated value of DOWNLOAD_URL
+                origDownloadName = DownloadSubstituters.substitute(origDownloadName, DownloadSubstituters.getBasicEntitySubstitutions(this));
+                origDownloadName = Urls.decode(origDownloadName);
                 origDownloadName = Urls.getBasename(origDownloadName);
                 String downloadName = origDownloadName;
                 downloadName = Strings.removeFromEnd(downloadName, ".tar.gz");

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/1bb15610/utils/common/src/main/java/brooklyn/util/net/Urls.java
----------------------------------------------------------------------
diff --git a/utils/common/src/main/java/brooklyn/util/net/Urls.java b/utils/common/src/main/java/brooklyn/util/net/Urls.java
index ea11095..927051a 100644
--- a/utils/common/src/main/java/brooklyn/util/net/Urls.java
+++ b/utils/common/src/main/java/brooklyn/util/net/Urls.java
@@ -23,6 +23,7 @@ import java.net.MalformedURLException;
 import java.net.URI;
 import java.net.URISyntaxException;
 import java.net.URL;
+import java.net.URLDecoder;
 import java.net.URLEncoder;
 
 import javax.annotation.Nullable;
@@ -142,6 +143,11 @@ public class Urls {
     public static String encode(String text) {
         return URLEncoder.encode(text);
     }
+    /** As {@link #encode(String)} */
+    @SuppressWarnings("deprecation")
+    public static String decode(String text) {
+        return URLDecoder.decode(text);
+    }
 
     /** returns the protocol (e.g. http) if one appears to be specified, or else null;
      * 'protocol' here should consist of 2 or more _letters_ only followed by a colon


[17/29] git commit: rename HAMode to HighAvailabilityMode, and same for priority (this brings us in line with convention on treatment of abbrs being camel case, preferring HttpRestApi over HTTPRESTAPI, but without the weirdness of HaMode; easy enough to

Posted by al...@apache.org.
rename HAMode to HighAvailabilityMode, and same for priority
(this brings us in line with convention on treatment of abbrs being camel case,
preferring HttpRestApi over HTTPRESTAPI, but without the weirdness of HaMode; easy enough to be explicit on this API, imho)


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

Branch: refs/heads/master
Commit: 38c79ad0ff82384a86bc5e42e9ddebdc523f1fe2
Parents: 82666ea
Author: Alex Heneveld <al...@cloudsoftcorp.com>
Authored: Sun Oct 26 17:26:14 2014 +0000
Committer: Alex Heneveld <al...@cloudsoftcorp.com>
Committed: Fri Oct 31 09:38:20 2014 -0500

----------------------------------------------------------------------
 .../entity/brooklynnode/BrooklynNode.java       | 16 ++---
 .../entity/brooklynnode/BrooklynNodeImpl.java   |  8 +--
 .../BrooklynClusterUpgradeEffectorBody.java     |  6 +-
 .../effector/SelectMasterEffectorBody.java      | 12 ++--
 .../effector/SetHAModeEffectorBody.java         | 64 --------------------
 .../effector/SetHAPriorityEffectorBody.java     | 55 -----------------
 .../SetHighAvailabilityModeEffectorBody.java    | 64 ++++++++++++++++++++
 ...SetHighAvailabilityPriorityEffectorBody.java | 55 +++++++++++++++++
 .../brooklynnode/effector/TestHttpEntity.java   |  4 +-
 9 files changed, 142 insertions(+), 142 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/38c79ad0/software/base/src/main/java/brooklyn/entity/brooklynnode/BrooklynNode.java
----------------------------------------------------------------------
diff --git a/software/base/src/main/java/brooklyn/entity/brooklynnode/BrooklynNode.java b/software/base/src/main/java/brooklyn/entity/brooklynnode/BrooklynNode.java
index 9bf385e..e125971 100644
--- a/software/base/src/main/java/brooklyn/entity/brooklynnode/BrooklynNode.java
+++ b/software/base/src/main/java/brooklyn/entity/brooklynnode/BrooklynNode.java
@@ -283,25 +283,25 @@ public interface BrooklynNode extends SoftwareProcess, UsesJava {
 
     public static final Effector<Void> STOP_NODE_AND_KILL_APPS = StopNodeAndKillAppsEffector.STOP_NODE_AND_KILL_APPS;
 
-    public interface SetHAPriorityEffector {
+    public interface SetHighAvailabilityPriorityEffector {
         ConfigKey<Integer> PRIORITY = ConfigKeys.newIntegerConfigKey("priority", "HA priority");
-        Effector<Integer> SET_HA_PRIORITY = Effectors.effector(Integer.class, "setHAPriotity")
-                .description("Set HA priority on the node, returns the old priority")
+        Effector<Integer> SET_HIGH_AVAILABILITY_PRIORITY = Effectors.effector(Integer.class, "setHighAvailabilityPriority")
+                .description("Set the HA priority on the node, returning the old priority")
                 .parameter(PRIORITY)
                 .buildAbstract();
     }
 
-    public static final Effector<Integer> SET_HA_PRIORITY = SetHAPriorityEffector.SET_HA_PRIORITY;
+    public static final Effector<Integer> SET_HIGH_AVAILABILITY_PRIORITY = SetHighAvailabilityPriorityEffector.SET_HIGH_AVAILABILITY_PRIORITY;
 
-    public interface SetHAModeEffector {
+    public interface SetHighAvailabilityModeEffector {
         ConfigKey<HighAvailabilityMode> MODE = ConfigKeys.newConfigKey(HighAvailabilityMode.class, "mode", "HA mode");
-        Effector<ManagementNodeState> SET_HA_MODE = Effectors.effector(ManagementNodeState.class, "setHAMode")
-                .description("Set HA mode on the node, returns the existing state")
+        Effector<ManagementNodeState> SET_HIGH_AVAILABILITY_MODE = Effectors.effector(ManagementNodeState.class, "setHighAvailabilityMode")
+                .description("Set the HA mode on the node, returning the existing state")
                 .parameter(MODE)
                 .buildAbstract();
     }
 
-    public static final Effector<ManagementNodeState> SET_HA_MODE = SetHAModeEffector.SET_HA_MODE;
+    public static final Effector<ManagementNodeState> SET_HIGH_AVAILABILITY_MODE = SetHighAvailabilityModeEffector.SET_HIGH_AVAILABILITY_MODE;
 
     public EntityHttpClient http();
 }

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/38c79ad0/software/base/src/main/java/brooklyn/entity/brooklynnode/BrooklynNodeImpl.java
----------------------------------------------------------------------
diff --git a/software/base/src/main/java/brooklyn/entity/brooklynnode/BrooklynNodeImpl.java b/software/base/src/main/java/brooklyn/entity/brooklynnode/BrooklynNodeImpl.java
index 5bdbe2d..946d3fd 100644
--- a/software/base/src/main/java/brooklyn/entity/brooklynnode/BrooklynNodeImpl.java
+++ b/software/base/src/main/java/brooklyn/entity/brooklynnode/BrooklynNodeImpl.java
@@ -40,8 +40,8 @@ import brooklyn.entity.basic.ServiceStateLogic;
 import brooklyn.entity.basic.ServiceStateLogic.ServiceNotUpLogic;
 import brooklyn.entity.basic.SoftwareProcessImpl;
 import brooklyn.entity.brooklynnode.effector.BrooklynNodeUpgradeEffectorBody;
-import brooklyn.entity.brooklynnode.effector.SetHAModeEffectorBody;
-import brooklyn.entity.brooklynnode.effector.SetHAPriorityEffectorBody;
+import brooklyn.entity.brooklynnode.effector.SetHighAvailabilityModeEffectorBody;
+import brooklyn.entity.brooklynnode.effector.SetHighAvailabilityPriorityEffectorBody;
 import brooklyn.entity.effector.EffectorBody;
 import brooklyn.entity.effector.Effectors;
 import brooklyn.event.feed.ConfigToAttributes;
@@ -102,8 +102,8 @@ public class BrooklynNodeImpl extends SoftwareProcessImpl implements BrooklynNod
         getMutableEntityType().addEffector(ShutdownEffectorBody.SHUTDOWN);
         getMutableEntityType().addEffector(StopNodeButLeaveAppsEffectorBody.STOP_NODE_BUT_LEAVE_APPS);
         getMutableEntityType().addEffector(StopNodeAndKillAppsEffectorBody.STOP_NODE_AND_KILL_APPS);
-        getMutableEntityType().addEffector(SetHAPriorityEffectorBody.SET_HA_PRIORITY);
-        getMutableEntityType().addEffector(SetHAModeEffectorBody.SET_HA_MODE);
+        getMutableEntityType().addEffector(SetHighAvailabilityPriorityEffectorBody.SET_HIGH_AVAILABILITY_PRIORITY);
+        getMutableEntityType().addEffector(SetHighAvailabilityModeEffectorBody.SET_HIGH_AVAILABILITY_MODE);
         getMutableEntityType().addEffector(BrooklynNodeUpgradeEffectorBody.UPGRADE);
     }
 

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/38c79ad0/software/base/src/main/java/brooklyn/entity/brooklynnode/effector/BrooklynClusterUpgradeEffectorBody.java
----------------------------------------------------------------------
diff --git a/software/base/src/main/java/brooklyn/entity/brooklynnode/effector/BrooklynClusterUpgradeEffectorBody.java b/software/base/src/main/java/brooklyn/entity/brooklynnode/effector/BrooklynClusterUpgradeEffectorBody.java
index 331f363..b815ee4 100644
--- a/software/base/src/main/java/brooklyn/entity/brooklynnode/effector/BrooklynClusterUpgradeEffectorBody.java
+++ b/software/base/src/main/java/brooklyn/entity/brooklynnode/effector/BrooklynClusterUpgradeEffectorBody.java
@@ -41,7 +41,7 @@ import brooklyn.entity.brooklynnode.BrooklynCluster;
 import brooklyn.entity.brooklynnode.BrooklynCluster.SelectMasterEffector;
 import brooklyn.entity.brooklynnode.BrooklynCluster.UpgradeClusterEffector;
 import brooklyn.entity.brooklynnode.BrooklynNode;
-import brooklyn.entity.brooklynnode.BrooklynNode.SetHAModeEffector;
+import brooklyn.entity.brooklynnode.BrooklynNode.SetHighAvailabilityModeEffector;
 import brooklyn.entity.effector.EffectorBody;
 import brooklyn.entity.effector.Effectors;
 import brooklyn.entity.group.DynamicCluster;
@@ -192,8 +192,8 @@ public class BrooklynClusterUpgradeEffectorBody extends EffectorBody<Void> imple
 
         //3. Set HOT_STANDBY in case it is not enabled on the command line ...
         DynamicTasks.queue(Effectors.invocation(
-                BrooklynNode.SET_HA_MODE,
-                MutableMap.of(SetHAModeEffector.MODE, HighAvailabilityMode.HOT_STANDBY), 
+                BrooklynNode.SET_HIGH_AVAILABILITY_MODE,
+                MutableMap.of(SetHighAvailabilityModeEffector.MODE, HighAvailabilityMode.HOT_STANDBY), 
                 newNodes)).asTask().getUnchecked();
         //... and wait until all of the nodes change state
         // TODO fail quicker if state changes to FAILED

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/38c79ad0/software/base/src/main/java/brooklyn/entity/brooklynnode/effector/SelectMasterEffectorBody.java
----------------------------------------------------------------------
diff --git a/software/base/src/main/java/brooklyn/entity/brooklynnode/effector/SelectMasterEffectorBody.java b/software/base/src/main/java/brooklyn/entity/brooklynnode/effector/SelectMasterEffectorBody.java
index 255b27c..40d65a7 100644
--- a/software/base/src/main/java/brooklyn/entity/brooklynnode/effector/SelectMasterEffectorBody.java
+++ b/software/base/src/main/java/brooklyn/entity/brooklynnode/effector/SelectMasterEffectorBody.java
@@ -32,8 +32,8 @@ import brooklyn.entity.basic.EntityPredicates;
 import brooklyn.entity.brooklynnode.BrooklynCluster;
 import brooklyn.entity.brooklynnode.BrooklynCluster.SelectMasterEffector;
 import brooklyn.entity.brooklynnode.BrooklynNode;
-import brooklyn.entity.brooklynnode.BrooklynNode.SetHAModeEffector;
-import brooklyn.entity.brooklynnode.BrooklynNode.SetHAPriorityEffector;
+import brooklyn.entity.brooklynnode.BrooklynNode.SetHighAvailabilityModeEffector;
+import brooklyn.entity.brooklynnode.BrooklynNode.SetHighAvailabilityPriorityEffector;
 import brooklyn.entity.effector.EffectorBody;
 import brooklyn.entity.effector.Effectors;
 import brooklyn.management.ha.HighAvailabilityMode;
@@ -124,8 +124,8 @@ public class SelectMasterEffectorBody extends EffectorBody<Void> implements Sele
         ManagementNodeState oldState = DynamicTasks.queue(
                 Effectors.invocation(
                         oldMaster,
-                        BrooklynNode.SET_HA_MODE,
-                        MutableMap.of(SetHAModeEffector.MODE, mode))
+                        BrooklynNode.SET_HIGH_AVAILABILITY_MODE,
+                        MutableMap.of(SetHighAvailabilityModeEffector.MODE, mode))
             ).asTask().getUnchecked();
 
         if (oldState != ManagementNodeState.MASTER) {
@@ -138,8 +138,8 @@ public class SelectMasterEffectorBody extends EffectorBody<Void> implements Sele
         Integer oldPriority = DynamicTasks.queue(
                 Effectors.invocation(
                     newMaster,
-                    BrooklynNode.SET_HA_PRIORITY,
-                    MutableMap.of(SetHAPriorityEffector.PRIORITY, newPriority))
+                    BrooklynNode.SET_HIGH_AVAILABILITY_PRIORITY,
+                    MutableMap.of(SetHighAvailabilityPriorityEffector.PRIORITY, newPriority))
             ).asTask().getUnchecked();
 
         Integer expectedPriority = (newPriority == HA_MASTER_PRIORITY ? HA_STANDBY_PRIORITY : HA_MASTER_PRIORITY);

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/38c79ad0/software/base/src/main/java/brooklyn/entity/brooklynnode/effector/SetHAModeEffectorBody.java
----------------------------------------------------------------------
diff --git a/software/base/src/main/java/brooklyn/entity/brooklynnode/effector/SetHAModeEffectorBody.java b/software/base/src/main/java/brooklyn/entity/brooklynnode/effector/SetHAModeEffectorBody.java
deleted file mode 100644
index 36a51d0..0000000
--- a/software/base/src/main/java/brooklyn/entity/brooklynnode/effector/SetHAModeEffectorBody.java
+++ /dev/null
@@ -1,64 +0,0 @@
-/*
- * 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 brooklyn.entity.brooklynnode.effector;
-
-import org.apache.http.HttpStatus;
-
-import brooklyn.entity.Effector;
-import brooklyn.entity.brooklynnode.BrooklynNode;
-import brooklyn.entity.brooklynnode.BrooklynNode.SetHAModeEffector;
-import brooklyn.entity.brooklynnode.EntityHttpClient;
-import brooklyn.entity.effector.EffectorBody;
-import brooklyn.entity.effector.Effectors;
-import brooklyn.event.feed.http.HttpValueFunctions;
-import brooklyn.event.feed.http.JsonFunctions;
-import brooklyn.management.ha.HighAvailabilityMode;
-import brooklyn.management.ha.ManagementNodeState;
-import brooklyn.util.config.ConfigBag;
-import brooklyn.util.guava.Functionals;
-import brooklyn.util.http.HttpToolResponse;
-import brooklyn.util.javalang.Enums;
-
-import com.google.api.client.util.Preconditions;
-import com.google.common.base.Function;
-import com.google.common.collect.ImmutableMap;
-
-public class SetHAModeEffectorBody extends EffectorBody<ManagementNodeState> implements SetHAModeEffector {
-    public static final Effector<ManagementNodeState> SET_HA_MODE = Effectors.effector(SetHAModeEffector.SET_HA_MODE).impl(new SetHAModeEffectorBody()).build();
-
-    @Override
-    public ManagementNodeState call(ConfigBag parameters) {
-        HighAvailabilityMode mode = parameters.get(MODE);
-        Preconditions.checkNotNull(mode, MODE.getName() + " parameter is required");
-
-        EntityHttpClient httpClient = ((BrooklynNode)entity()).http();
-        HttpToolResponse resp = httpClient.post("/v1/server/ha/state", 
-                ImmutableMap.of("Brooklyn-Allow-Non-Master-Access", "true"),
-                ImmutableMap.of("mode", mode.toString()));
-
-        if (resp.getResponseCode() == HttpStatus.SC_OK) {
-            Function<HttpToolResponse, ManagementNodeState> parseRespone = Functionals.chain(
-                    Functionals.chain(HttpValueFunctions.jsonContents(), JsonFunctions.cast(String.class)),
-                    Enums.fromStringFunction(ManagementNodeState.class));
-            return parseRespone.apply(resp);
-        } else {
-            throw new IllegalStateException("Unexpected response code: " + resp.getResponseCode() + "\n" + resp.getContentAsString());
-        }
-    }
-}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/38c79ad0/software/base/src/main/java/brooklyn/entity/brooklynnode/effector/SetHAPriorityEffectorBody.java
----------------------------------------------------------------------
diff --git a/software/base/src/main/java/brooklyn/entity/brooklynnode/effector/SetHAPriorityEffectorBody.java b/software/base/src/main/java/brooklyn/entity/brooklynnode/effector/SetHAPriorityEffectorBody.java
deleted file mode 100644
index 94a961c..0000000
--- a/software/base/src/main/java/brooklyn/entity/brooklynnode/effector/SetHAPriorityEffectorBody.java
+++ /dev/null
@@ -1,55 +0,0 @@
-/*
- * 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 brooklyn.entity.brooklynnode.effector;
-
-import org.apache.http.HttpStatus;
-
-import brooklyn.entity.Effector;
-import brooklyn.entity.brooklynnode.BrooklynNode;
-import brooklyn.entity.brooklynnode.BrooklynNode.SetHAPriorityEffector;
-import brooklyn.entity.brooklynnode.EntityHttpClient;
-import brooklyn.entity.effector.EffectorBody;
-import brooklyn.entity.effector.Effectors;
-import brooklyn.util.config.ConfigBag;
-import brooklyn.util.http.HttpToolResponse;
-
-import com.google.api.client.util.Preconditions;
-import com.google.common.collect.ImmutableMap;
-
-public class SetHAPriorityEffectorBody extends EffectorBody<Integer> implements SetHAPriorityEffector {
-    public static final Effector<Integer> SET_HA_PRIORITY = Effectors.effector(SetHAPriorityEffector.SET_HA_PRIORITY).impl(new SetHAPriorityEffectorBody()).build();
-
-    @Override
-    public Integer call(ConfigBag parameters) {
-        Integer priority = parameters.get(PRIORITY);
-        Preconditions.checkNotNull(priority, PRIORITY.getName() + " parameter is required");
-
-        EntityHttpClient httpClient = ((BrooklynNode)entity()).http();
-        HttpToolResponse resp = httpClient.post("/v1/server/ha/priority",
-            ImmutableMap.of("Brooklyn-Allow-Non-Master-Access", "true"),
-            ImmutableMap.of("priority", priority.toString()));
-
-        if (resp.getResponseCode() == HttpStatus.SC_OK) {
-            return Integer.valueOf(resp.getContentAsString());
-        } else {
-            throw new IllegalStateException("Unexpected response code: " + resp.getResponseCode() + "\n" + resp.getContentAsString());
-        }
-    }
-
-}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/38c79ad0/software/base/src/main/java/brooklyn/entity/brooklynnode/effector/SetHighAvailabilityModeEffectorBody.java
----------------------------------------------------------------------
diff --git a/software/base/src/main/java/brooklyn/entity/brooklynnode/effector/SetHighAvailabilityModeEffectorBody.java b/software/base/src/main/java/brooklyn/entity/brooklynnode/effector/SetHighAvailabilityModeEffectorBody.java
new file mode 100644
index 0000000..3337257
--- /dev/null
+++ b/software/base/src/main/java/brooklyn/entity/brooklynnode/effector/SetHighAvailabilityModeEffectorBody.java
@@ -0,0 +1,64 @@
+/*
+ * 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 brooklyn.entity.brooklynnode.effector;
+
+import org.apache.http.HttpStatus;
+
+import brooklyn.entity.Effector;
+import brooklyn.entity.brooklynnode.BrooklynNode;
+import brooklyn.entity.brooklynnode.BrooklynNode.SetHighAvailabilityModeEffector;
+import brooklyn.entity.brooklynnode.EntityHttpClient;
+import brooklyn.entity.effector.EffectorBody;
+import brooklyn.entity.effector.Effectors;
+import brooklyn.event.feed.http.HttpValueFunctions;
+import brooklyn.event.feed.http.JsonFunctions;
+import brooklyn.management.ha.HighAvailabilityMode;
+import brooklyn.management.ha.ManagementNodeState;
+import brooklyn.util.config.ConfigBag;
+import brooklyn.util.guava.Functionals;
+import brooklyn.util.http.HttpToolResponse;
+import brooklyn.util.javalang.Enums;
+
+import com.google.api.client.util.Preconditions;
+import com.google.common.base.Function;
+import com.google.common.collect.ImmutableMap;
+
+public class SetHighAvailabilityModeEffectorBody extends EffectorBody<ManagementNodeState> implements SetHighAvailabilityModeEffector {
+    public static final Effector<ManagementNodeState> SET_HIGH_AVAILABILITY_MODE = Effectors.effector(SetHighAvailabilityModeEffector.SET_HIGH_AVAILABILITY_MODE).impl(new SetHighAvailabilityModeEffectorBody()).build();
+
+    @Override
+    public ManagementNodeState call(ConfigBag parameters) {
+        HighAvailabilityMode mode = parameters.get(MODE);
+        Preconditions.checkNotNull(mode, MODE.getName() + " parameter is required");
+
+        EntityHttpClient httpClient = ((BrooklynNode)entity()).http();
+        HttpToolResponse resp = httpClient.post("/v1/server/ha/state", 
+                ImmutableMap.of("Brooklyn-Allow-Non-Master-Access", "true"),
+                ImmutableMap.of("mode", mode.toString()));
+
+        if (resp.getResponseCode() == HttpStatus.SC_OK) {
+            Function<HttpToolResponse, ManagementNodeState> parseRespone = Functionals.chain(
+                    Functionals.chain(HttpValueFunctions.jsonContents(), JsonFunctions.cast(String.class)),
+                    Enums.fromStringFunction(ManagementNodeState.class));
+            return parseRespone.apply(resp);
+        } else {
+            throw new IllegalStateException("Unexpected response code: " + resp.getResponseCode() + "\n" + resp.getContentAsString());
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/38c79ad0/software/base/src/main/java/brooklyn/entity/brooklynnode/effector/SetHighAvailabilityPriorityEffectorBody.java
----------------------------------------------------------------------
diff --git a/software/base/src/main/java/brooklyn/entity/brooklynnode/effector/SetHighAvailabilityPriorityEffectorBody.java b/software/base/src/main/java/brooklyn/entity/brooklynnode/effector/SetHighAvailabilityPriorityEffectorBody.java
new file mode 100644
index 0000000..927a139
--- /dev/null
+++ b/software/base/src/main/java/brooklyn/entity/brooklynnode/effector/SetHighAvailabilityPriorityEffectorBody.java
@@ -0,0 +1,55 @@
+/*
+ * 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 brooklyn.entity.brooklynnode.effector;
+
+import org.apache.http.HttpStatus;
+
+import brooklyn.entity.Effector;
+import brooklyn.entity.brooklynnode.BrooklynNode;
+import brooklyn.entity.brooklynnode.BrooklynNode.SetHighAvailabilityPriorityEffector;
+import brooklyn.entity.brooklynnode.EntityHttpClient;
+import brooklyn.entity.effector.EffectorBody;
+import brooklyn.entity.effector.Effectors;
+import brooklyn.util.config.ConfigBag;
+import brooklyn.util.http.HttpToolResponse;
+
+import com.google.api.client.util.Preconditions;
+import com.google.common.collect.ImmutableMap;
+
+public class SetHighAvailabilityPriorityEffectorBody extends EffectorBody<Integer> implements SetHighAvailabilityPriorityEffector {
+    public static final Effector<Integer> SET_HIGH_AVAILABILITY_PRIORITY = Effectors.effector(SetHighAvailabilityPriorityEffector.SET_HIGH_AVAILABILITY_PRIORITY).impl(new SetHighAvailabilityPriorityEffectorBody()).build();
+
+    @Override
+    public Integer call(ConfigBag parameters) {
+        Integer priority = parameters.get(PRIORITY);
+        Preconditions.checkNotNull(priority, PRIORITY.getName() + " parameter is required");
+
+        EntityHttpClient httpClient = ((BrooklynNode)entity()).http();
+        HttpToolResponse resp = httpClient.post("/v1/server/ha/priority",
+            ImmutableMap.of("Brooklyn-Allow-Non-Master-Access", "true"),
+            ImmutableMap.of("priority", priority.toString()));
+
+        if (resp.getResponseCode() == HttpStatus.SC_OK) {
+            return Integer.valueOf(resp.getContentAsString());
+        } else {
+            throw new IllegalStateException("Unexpected response code: " + resp.getResponseCode() + "\n" + resp.getContentAsString());
+        }
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/38c79ad0/software/base/src/test/java/brooklyn/entity/brooklynnode/effector/TestHttpEntity.java
----------------------------------------------------------------------
diff --git a/software/base/src/test/java/brooklyn/entity/brooklynnode/effector/TestHttpEntity.java b/software/base/src/test/java/brooklyn/entity/brooklynnode/effector/TestHttpEntity.java
index 259a271..335f7f7 100644
--- a/software/base/src/test/java/brooklyn/entity/brooklynnode/effector/TestHttpEntity.java
+++ b/software/base/src/test/java/brooklyn/entity/brooklynnode/effector/TestHttpEntity.java
@@ -41,8 +41,8 @@ public class TestHttpEntity extends AbstractEntity implements BrooklynNode {
     @Override
     public void init() {
         super.init();
-        getMutableEntityType().addEffector(SetHAPriorityEffectorBody.SET_HA_PRIORITY);
-        getMutableEntityType().addEffector(SetHAModeEffectorBody.SET_HA_MODE);
+        getMutableEntityType().addEffector(SetHighAvailabilityPriorityEffectorBody.SET_HIGH_AVAILABILITY_PRIORITY);
+        getMutableEntityType().addEffector(SetHighAvailabilityModeEffectorBody.SET_HIGH_AVAILABILITY_MODE);
         setAttribute(HA_PRIORITY, 0);
     }
 


[03/29] git commit: BrooklynNode cluster + upgrade effector

Posted by al...@apache.org.
BrooklynNode cluster + upgrade effector

Also effectors to:
  * select master in the cluster
  * set HA priority
  * set HA state


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

Branch: refs/heads/master
Commit: 6f895aafe49cc8046cc043c17bb38cbbb4018da4
Parents: 63f29bd
Author: Svetoslav Neykov <sv...@cloudsoftcorp.com>
Authored: Wed Oct 22 11:51:19 2014 +0300
Committer: Alex Heneveld <al...@cloudsoftcorp.com>
Committed: Fri Oct 31 09:36:16 2014 -0500

----------------------------------------------------------------------
 .../brooklyn/entity/basic/EntityPredicates.java |   9 +
 .../entity/brooklynnode/BrooklynCluster.java    |  56 ++++
 .../brooklynnode/BrooklynClusterImpl.java       | 110 ++++++++
 .../brooklynnode/BrooklynEntityMirrorImpl.java  |   2 -
 .../entity/brooklynnode/BrooklynNode.java       |  30 ++-
 .../entity/brooklynnode/BrooklynNodeImpl.java   |  16 +-
 .../effector/SelectMasterEffectorBody.java      | 173 ++++++++++++
 .../effector/SetHAModeEffectorBody.java         |  64 +++++
 .../effector/SetHAPriorityEffectorBody.java     |  55 ++++
 .../effector/UpgradeClusterEffectorBody.java    | 199 ++++++++++++++
 .../effector/CallbackEntityHttpClient.java      |  90 +++++++
 .../effector/SelectMasterEffectorTest.java      | 267 +++++++++++++++++++
 .../brooklynnode/effector/TestHttpEntity.java   |  66 +++++
 .../main/java/brooklyn/rest/api/ServerApi.java  |  23 +-
 .../brooklyn/rest/resources/ServerResource.java |  26 +-
 15 files changed, 1178 insertions(+), 8 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/6f895aaf/core/src/main/java/brooklyn/entity/basic/EntityPredicates.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/entity/basic/EntityPredicates.java b/core/src/main/java/brooklyn/entity/basic/EntityPredicates.java
index e0db9e9..b359b1e 100644
--- a/core/src/main/java/brooklyn/entity/basic/EntityPredicates.java
+++ b/core/src/main/java/brooklyn/entity/basic/EntityPredicates.java
@@ -90,6 +90,15 @@ public class EntityPredicates {
         };
     }
     
+    public static <T> Predicate<Entity> attributeNotEqualTo(final AttributeSensor<T> attribute, final T val) {
+        return new SerializablePredicate<Entity>() {
+            @Override
+            public boolean apply(@Nullable Entity input) {
+                return (input != null) && !Objects.equal(input.getAttribute(attribute), val);
+            }
+        };
+    }
+    
     public static <T> Predicate<Entity> configEqualTo(final ConfigKey<T> configKey, final T val) {
         return new SerializablePredicate<Entity>() {
             @Override

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/6f895aaf/software/base/src/main/java/brooklyn/entity/brooklynnode/BrooklynCluster.java
----------------------------------------------------------------------
diff --git a/software/base/src/main/java/brooklyn/entity/brooklynnode/BrooklynCluster.java b/software/base/src/main/java/brooklyn/entity/brooklynnode/BrooklynCluster.java
new file mode 100644
index 0000000..a3c2157
--- /dev/null
+++ b/software/base/src/main/java/brooklyn/entity/brooklynnode/BrooklynCluster.java
@@ -0,0 +1,56 @@
+/*
+ * 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 brooklyn.entity.brooklynnode;
+
+import brooklyn.config.ConfigKey;
+import brooklyn.entity.Effector;
+import brooklyn.entity.basic.ConfigKeys;
+import brooklyn.entity.basic.SoftwareProcess;
+import brooklyn.entity.effector.Effectors;
+import brooklyn.entity.group.DynamicCluster;
+import brooklyn.entity.proxying.ImplementedBy;
+import brooklyn.event.AttributeSensor;
+import brooklyn.event.basic.BasicAttributeSensor;
+
+@ImplementedBy(BrooklynClusterImpl.class)
+public interface BrooklynCluster extends DynamicCluster {
+    public static final AttributeSensor<BrooklynNode> MASTER_NODE = new BasicAttributeSensor<BrooklynNode>(
+            BrooklynNode.class, "brooklyncluster.master", "Pointer to the child node with MASTER state in the cluster");
+
+    public interface SelectMasterEffector {
+        ConfigKey<String> NEW_MASTER_ID = ConfigKeys.newStringConfigKey(
+                "brooklyncluster.new_master_id", "The ID of the node to become master", null);
+        Effector<Void> SELECT_MASTER = Effectors.effector(Void.class, "selectMaster")
+                .description("Select a new master in the cluster")
+                .parameter(NEW_MASTER_ID)
+                .buildAbstract();
+    }
+
+    public static final Effector<Void> SELECT_MASTER = SelectMasterEffector.SELECT_MASTER;
+
+    public interface UpgradeClusterEffector {
+        Effector<Void> UPGRADE_CLUSTER = Effectors.effector(Void.class, "upgradeCluster")
+                .description("Upgrade the cluster with new distribution version")
+                .parameter(SoftwareProcess.DOWNLOAD_URL.getConfigKey())
+                .buildAbstract();
+    }
+
+    public static final Effector<Void> UPGRADE_CLUSTER = UpgradeClusterEffector.UPGRADE_CLUSTER;
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/6f895aaf/software/base/src/main/java/brooklyn/entity/brooklynnode/BrooklynClusterImpl.java
----------------------------------------------------------------------
diff --git a/software/base/src/main/java/brooklyn/entity/brooklynnode/BrooklynClusterImpl.java b/software/base/src/main/java/brooklyn/entity/brooklynnode/BrooklynClusterImpl.java
new file mode 100644
index 0000000..bfaf33c
--- /dev/null
+++ b/software/base/src/main/java/brooklyn/entity/brooklynnode/BrooklynClusterImpl.java
@@ -0,0 +1,110 @@
+/*
+ * 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 brooklyn.entity.brooklynnode;
+
+import java.util.Collection;
+import java.util.concurrent.Callable;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import brooklyn.entity.Entity;
+import brooklyn.entity.basic.EntityPredicates;
+import brooklyn.entity.basic.ServiceStateLogic.ServiceNotUpLogic;
+import brooklyn.entity.brooklynnode.effector.SelectMasterEffectorBody;
+import brooklyn.entity.brooklynnode.effector.UpgradeClusterEffectorBody;
+import brooklyn.entity.group.DynamicClusterImpl;
+import brooklyn.event.feed.function.FunctionFeed;
+import brooklyn.event.feed.function.FunctionPollConfig;
+import brooklyn.management.ha.ManagementNodeState;
+import brooklyn.util.time.Duration;
+
+import com.google.common.collect.FluentIterable;
+import com.google.common.collect.Iterables;
+
+public class BrooklynClusterImpl extends DynamicClusterImpl implements BrooklynCluster {
+
+    private static final String MSG_NO_MASTER = "No master node in cluster";
+
+    private static final Logger LOG = LoggerFactory.getLogger(BrooklynClusterImpl.class);
+
+    //TODO set MEMBER_SPEC
+
+    private FunctionFeed scanMaster;
+
+    @Override
+    public void init() {
+        super.init();
+        getMutableEntityType().addEffector(SelectMasterEffectorBody.SELECT_MASTER);
+        getMutableEntityType().addEffector(UpgradeClusterEffectorBody.UPGRADE_CLUSTER);
+
+        ServiceNotUpLogic.updateNotUpIndicator(this, MASTER_NODE, MSG_NO_MASTER);
+        scanMaster = FunctionFeed.builder()
+                .entity(this)
+                .poll(new FunctionPollConfig<Object, BrooklynNode>(MASTER_NODE)
+                        .period(Duration.ONE_SECOND)
+                        .callable(new Callable<BrooklynNode>() {
+                                @Override
+                                public BrooklynNode call() throws Exception {
+                                    return findMasterChild();
+                                }
+                            }))
+                .build();
+    }
+
+    private BrooklynNode findMasterChild() {
+        Collection<Entity> masters = FluentIterable.from(getMembers())
+                .filter(EntityPredicates.attributeEqualTo(BrooklynNode.MANAGEMENT_NODE_STATE, ManagementNodeState.MASTER))
+                .toList();
+
+        if (masters.size() == 0) {
+            ServiceNotUpLogic.updateNotUpIndicator(this, MASTER_NODE, MSG_NO_MASTER);
+            return null;
+        } else if (masters.size() == 1) {
+            ServiceNotUpLogic.clearNotUpIndicator(this, MASTER_NODE);
+            return (BrooklynNode)Iterables.getOnlyElement(masters);
+        } else if (masters.size() == 2) {
+            //Probably hit a window where we have a new master
+            //its BrooklynNode picked it up, but the BrooklynNode
+            //for the old master hasn't refreshed its state yet.
+            //Just pick one of them, should sort itself out in next update.
+            LOG.warn("Two masters detected, probably a handover just occured: " + masters);
+
+            //Don't clearNotUpIndicator - if there were no masters previously
+            //why have two now.
+
+            return (BrooklynNode)Iterables.getOnlyElement(masters);
+        } else {
+            //Set on fire?
+            String msg = "Multiple (>=3) master nodes in cluster: " + masters;
+            LOG.error(msg);
+            throw new IllegalStateException(msg);
+        }
+    }
+
+    @Override
+    public void stop() {
+        super.stop();
+
+        if (scanMaster != null && scanMaster.isActivated()) {
+            scanMaster.stop();
+        }
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/6f895aaf/software/base/src/main/java/brooklyn/entity/brooklynnode/BrooklynEntityMirrorImpl.java
----------------------------------------------------------------------
diff --git a/software/base/src/main/java/brooklyn/entity/brooklynnode/BrooklynEntityMirrorImpl.java b/software/base/src/main/java/brooklyn/entity/brooklynnode/BrooklynEntityMirrorImpl.java
index a71402f..b0fb728 100644
--- a/software/base/src/main/java/brooklyn/entity/brooklynnode/BrooklynEntityMirrorImpl.java
+++ b/software/base/src/main/java/brooklyn/entity/brooklynnode/BrooklynEntityMirrorImpl.java
@@ -25,8 +25,6 @@ import java.util.concurrent.Callable;
 import javax.annotation.Nullable;
 
 import org.apache.http.HttpStatus;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
 
 import brooklyn.entity.Effector;
 import brooklyn.entity.basic.AbstractEntity;

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/6f895aaf/software/base/src/main/java/brooklyn/entity/brooklynnode/BrooklynNode.java
----------------------------------------------------------------------
diff --git a/software/base/src/main/java/brooklyn/entity/brooklynnode/BrooklynNode.java b/software/base/src/main/java/brooklyn/entity/brooklynnode/BrooklynNode.java
index 16b46c6..6208813 100644
--- a/software/base/src/main/java/brooklyn/entity/brooklynnode/BrooklynNode.java
+++ b/software/base/src/main/java/brooklyn/entity/brooklynnode/BrooklynNode.java
@@ -40,6 +40,8 @@ import brooklyn.event.basic.Sensors;
 import brooklyn.event.basic.BasicAttributeSensorAndConfigKey.StringAttributeSensorAndConfigKey;
 import brooklyn.event.basic.MapConfigKey;
 import brooklyn.event.basic.PortAttributeSensorAndConfigKey;
+import brooklyn.management.ha.HighAvailabilityMode;
+import brooklyn.management.ha.ManagementNodeState;
 import brooklyn.util.collections.MutableMap;
 import brooklyn.util.flags.SetFromFlag;
 import brooklyn.util.net.Networking;
@@ -215,8 +217,11 @@ public interface BrooklynNode extends SoftwareProcess, UsesJava {
             "brooklynnode.webconsole.portMapper", "Function for mapping private to public ports, for use in inferring the brooklyn URI", Functions.<Integer>identity());
 
     public static final AttributeSensor<URI> WEB_CONSOLE_URI = new BasicAttributeSensor<URI>(
-            URI.class, "brooklynnode.webconsole.url", "URL of the brooklyn web-console");
-    
+        URI.class, "brooklynnode.webconsole.url", "URL of the brooklyn web-console");
+
+    public static final AttributeSensor<ManagementNodeState> MANAGEMENT_NODE_STATE = new BasicAttributeSensor<ManagementNodeState>(
+        ManagementNodeState.class, "brooklynnode.ha.state", "High-availability state of the management node (MASTER, HOT_STANDBY, etc)");
+
     @SetFromFlag("noShutdownOnExit")
     public static final ConfigKey<Boolean> NO_SHUTDOWN_ON_EXIT = ConfigKeys.newBooleanConfigKey("brooklynnode.noshutdownonexit", 
         "Whether to shutdown entities on exit", false);
@@ -275,6 +280,25 @@ public interface BrooklynNode extends SoftwareProcess, UsesJava {
 
     public static final Effector<Void> STOP_NODE_AND_KILL_APPS = StopNodeAndKillAppsEffector.STOP_NODE_AND_KILL_APPS;
 
-    public EntityHttpClient http();
+    public interface SetHAPriorityEffector {
+        ConfigKey<Integer> PRIORITY = ConfigKeys.newIntegerConfigKey("priority", "HA priority");
+        Effector<Integer> SET_HA_PRIORITY = Effectors.effector(Integer.class, "setHAPriotity")
+                .description("Set HA priority on the node, returns the old priority")
+                .parameter(PRIORITY)
+                .buildAbstract();
+    }
 
+    public static final Effector<Integer> SET_HA_PRIORITY = SetHAPriorityEffector.SET_HA_PRIORITY;
+
+    public interface SetHAModeEffector {
+        ConfigKey<HighAvailabilityMode> MODE = ConfigKeys.newConfigKey(HighAvailabilityMode.class, "mode", "HA mode");
+        Effector<ManagementNodeState> SET_HA_MODE = Effectors.effector(ManagementNodeState.class, "setHAMode")
+                .description("Set HA mode on the node, returns the existing state")
+                .parameter(MODE)
+                .buildAbstract();
+    }
+
+    public static final Effector<ManagementNodeState> SET_HA_MODE = SetHAModeEffector.SET_HA_MODE;
+
+    public EntityHttpClient http();
 }

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/6f895aaf/software/base/src/main/java/brooklyn/entity/brooklynnode/BrooklynNodeImpl.java
----------------------------------------------------------------------
diff --git a/software/base/src/main/java/brooklyn/entity/brooklynnode/BrooklynNodeImpl.java b/software/base/src/main/java/brooklyn/entity/brooklynnode/BrooklynNodeImpl.java
index ddf2b96..630563d 100644
--- a/software/base/src/main/java/brooklyn/entity/brooklynnode/BrooklynNodeImpl.java
+++ b/software/base/src/main/java/brooklyn/entity/brooklynnode/BrooklynNodeImpl.java
@@ -38,13 +38,17 @@ import brooklyn.entity.basic.EntityPredicates;
 import brooklyn.entity.basic.Lifecycle;
 import brooklyn.entity.basic.ServiceStateLogic.ServiceNotUpLogic;
 import brooklyn.entity.basic.SoftwareProcessImpl;
+import brooklyn.entity.brooklynnode.effector.SetHAModeEffectorBody;
+import brooklyn.entity.brooklynnode.effector.SetHAPriorityEffectorBody;
 import brooklyn.entity.effector.EffectorBody;
 import brooklyn.entity.effector.Effectors;
 import brooklyn.event.feed.ConfigToAttributes;
 import brooklyn.event.feed.http.HttpFeed;
 import brooklyn.event.feed.http.HttpPollConfig;
 import brooklyn.event.feed.http.HttpValueFunctions;
+import brooklyn.event.feed.http.JsonFunctions;
 import brooklyn.management.TaskAdaptable;
+import brooklyn.management.ha.ManagementNodeState;
 import brooklyn.util.collections.Jsonya;
 import brooklyn.util.collections.MutableMap;
 import brooklyn.util.config.ConfigBag;
@@ -52,6 +56,7 @@ import brooklyn.util.exceptions.Exceptions;
 import brooklyn.util.exceptions.PropagatedRuntimeException;
 import brooklyn.util.guava.Functionals;
 import brooklyn.util.http.HttpToolResponse;
+import brooklyn.util.javalang.Enums;
 import brooklyn.util.javalang.JavaClassNames;
 import brooklyn.util.repeat.Repeater;
 import brooklyn.util.task.DynamicTasks;
@@ -62,6 +67,7 @@ import brooklyn.util.time.Duration;
 
 import com.google.common.annotations.VisibleForTesting;
 import com.google.common.base.Preconditions;
+import com.google.common.collect.ImmutableMap;
 import com.google.gson.Gson;
 
 public class BrooklynNodeImpl extends SoftwareProcessImpl implements BrooklynNode {
@@ -94,6 +100,8 @@ public class BrooklynNodeImpl extends SoftwareProcessImpl implements BrooklynNod
         getMutableEntityType().addEffector(ShutdownEffectorBody.SHUTDOWN);
         getMutableEntityType().addEffector(StopNodeButLeaveAppsEffectorBody.STOP_NODE_BUT_LEAVE_APPS);
         getMutableEntityType().addEffector(StopNodeAndKillAppsEffectorBody.STOP_NODE_AND_KILL_APPS);
+        getMutableEntityType().addEffector(SetHAPriorityEffectorBody.SET_HA_PRIORITY);
+        getMutableEntityType().addEffector(SetHAModeEffectorBody.SET_HA_MODE);
     }
 
     @Override
@@ -191,7 +199,9 @@ public class BrooklynNodeImpl extends SoftwareProcessImpl implements BrooklynNod
                     .addIfNotNull("delayForHttpReturn", toNullableString(parameters.get(DELAY_FOR_HTTP_RETURN)));
             try {
                 HttpToolResponse resp = ((BrooklynNode)entity()).http()
-                    .post("/v1/server/shutdown", MutableMap.<String, String>of(), formParams);
+                    .post("/v1/server/shutdown",
+                        ImmutableMap.of("Brooklyn-Allow-Non-Master-Access", "true"),
+                        formParams);
                 if (resp.getResponseCode() != HttpStatus.SC_NO_CONTENT) {
                     throw new IllegalStateException("Response code "+resp.getResponseCode());
                 }
@@ -341,6 +351,10 @@ public class BrooklynNodeImpl extends SoftwareProcessImpl implements BrooklynNod
                     .poll(new HttpPollConfig<Boolean>(WEB_CONSOLE_ACCESSIBLE)
                             .onSuccess(HttpValueFunctions.responseCodeEquals(200))
                             .setOnFailureOrException(false))
+                    .poll(new HttpPollConfig<ManagementNodeState>(MANAGEMENT_NODE_STATE)
+                            .suburl("/v1/server/ha/state")
+                            .onSuccess(Functionals.chain(Functionals.chain(HttpValueFunctions.jsonContents(), JsonFunctions.cast(String.class)), Enums.fromStringFunction(ManagementNodeState.class)))
+                            .setOnFailureOrException(null))
                     .build();
 
             if (!Lifecycle.RUNNING.equals(getAttribute(SERVICE_STATE_ACTUAL))) {

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/6f895aaf/software/base/src/main/java/brooklyn/entity/brooklynnode/effector/SelectMasterEffectorBody.java
----------------------------------------------------------------------
diff --git a/software/base/src/main/java/brooklyn/entity/brooklynnode/effector/SelectMasterEffectorBody.java b/software/base/src/main/java/brooklyn/entity/brooklynnode/effector/SelectMasterEffectorBody.java
new file mode 100644
index 0000000..255b27c
--- /dev/null
+++ b/software/base/src/main/java/brooklyn/entity/brooklynnode/effector/SelectMasterEffectorBody.java
@@ -0,0 +1,173 @@
+/*
+ * 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 brooklyn.entity.brooklynnode.effector;
+
+import java.util.NoSuchElementException;
+import java.util.concurrent.Callable;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import brooklyn.entity.Effector;
+import brooklyn.entity.Entity;
+import brooklyn.entity.Group;
+import brooklyn.entity.basic.EntityPredicates;
+import brooklyn.entity.brooklynnode.BrooklynCluster;
+import brooklyn.entity.brooklynnode.BrooklynCluster.SelectMasterEffector;
+import brooklyn.entity.brooklynnode.BrooklynNode;
+import brooklyn.entity.brooklynnode.BrooklynNode.SetHAModeEffector;
+import brooklyn.entity.brooklynnode.BrooklynNode.SetHAPriorityEffector;
+import brooklyn.entity.effector.EffectorBody;
+import brooklyn.entity.effector.Effectors;
+import brooklyn.management.ha.HighAvailabilityMode;
+import brooklyn.management.ha.ManagementNodeState;
+import brooklyn.util.collections.MutableMap;
+import brooklyn.util.config.ConfigBag;
+import brooklyn.util.repeat.Repeater;
+import brooklyn.util.task.DynamicTasks;
+import brooklyn.util.time.Duration;
+
+import com.google.api.client.util.Preconditions;
+import com.google.common.collect.Iterables;
+
+public class SelectMasterEffectorBody extends EffectorBody<Void> implements SelectMasterEffector {
+    public static final Effector<Void> SELECT_MASTER = Effectors.effector(SelectMasterEffector.SELECT_MASTER).impl(new SelectMasterEffectorBody()).build();
+    
+    private static final Logger LOG = LoggerFactory.getLogger(SelectMasterEffectorBody.class);
+
+    private static final int HA_STANDBY_PRIORITY = 0;
+    private static final int HA_MASTER_PRIORITY = 1;
+
+    private AtomicBoolean selectMasterInProgress = new AtomicBoolean();
+
+    @Override
+    public Void call(ConfigBag parameters) {
+        if (!selectMasterInProgress.compareAndSet(false, true)) {
+            throw new IllegalStateException("A master change is already in progress.");
+        }
+
+        try {
+            selectMaster(parameters);
+        } finally {
+            selectMasterInProgress.set(false);
+        }
+        return null;
+    }
+
+    private void selectMaster(ConfigBag parameters) {
+        String newMasterId = parameters.get(NEW_MASTER_ID);
+        Preconditions.checkNotNull(newMasterId, NEW_MASTER_ID.getName() + " parameter is required");
+
+        final Entity oldMaster = entity().getAttribute(BrooklynCluster.MASTER_NODE);
+        if (oldMaster != null && oldMaster.getId().equals(newMasterId)) {
+            LOG.info(newMasterId + " is already the current master, no change needed.");
+            return;
+        }
+
+        final Entity newMaster = getMember(newMasterId);
+
+        //1. Increase the priority of the node we wish to become master
+        setNodePriority(newMaster, HA_MASTER_PRIORITY);
+
+        //2. Denote the existing master so a new election takes place
+        try {
+            //If no master was yet selected, at least wait to see
+            //if the new master will be what we expect.
+            if (oldMaster != null) {
+                setNodeState(oldMaster, HighAvailabilityMode.HOT_STANDBY);
+            }
+
+            waitMasterHandover(oldMaster, newMaster);
+        } finally {
+            //3. Revert the priority of the node once it has become master
+            setNodePriority(newMaster, HA_STANDBY_PRIORITY);
+        }
+
+        checkMasterSelected(newMaster);
+    }
+
+    private void waitMasterHandover(final Entity oldMaster, final Entity newMaster) {
+        boolean masterChanged = Repeater.create()
+            .backoff(Duration.millis(500), 1.2, Duration.FIVE_SECONDS)
+            .limitTimeTo(Duration.ONE_MINUTE)
+            .until(new Callable<Boolean>() {
+                @Override
+                public Boolean call() throws Exception {
+                    Entity master = getMasterNode();
+                    return master != oldMaster && master != null;
+                }
+            })
+            .run();
+        if (!masterChanged) {
+            LOG.warn("Timeout waiting for node to become master: " + newMaster + ".");
+        }
+    }
+
+    private void setNodeState(Entity oldMaster, HighAvailabilityMode mode) {
+        ManagementNodeState oldState = DynamicTasks.queue(
+                Effectors.invocation(
+                        oldMaster,
+                        BrooklynNode.SET_HA_MODE,
+                        MutableMap.of(SetHAModeEffector.MODE, mode))
+            ).asTask().getUnchecked();
+
+        if (oldState != ManagementNodeState.MASTER) {
+            LOG.warn("The previous HA state on node " + oldMaster.getId() + " was " + oldState +
+                    ", while the expected value is " + ManagementNodeState.MASTER + ".");
+        }
+    }
+
+    private void setNodePriority(Entity newMaster, int newPriority) {
+        Integer oldPriority = DynamicTasks.queue(
+                Effectors.invocation(
+                    newMaster,
+                    BrooklynNode.SET_HA_PRIORITY,
+                    MutableMap.of(SetHAPriorityEffector.PRIORITY, newPriority))
+            ).asTask().getUnchecked();
+
+        Integer expectedPriority = (newPriority == HA_MASTER_PRIORITY ? HA_STANDBY_PRIORITY : HA_MASTER_PRIORITY);
+        if (oldPriority != expectedPriority) {
+            LOG.warn("The previous HA priority on node " + newMaster.getId() + " was " + oldPriority +
+                    ", while the expected value is " + expectedPriority + " (while setting priority " +
+                    newPriority + ").");
+        }
+    }
+
+    private void checkMasterSelected(Entity newMaster) {
+        Entity actualMaster = getMasterNode();
+        if (actualMaster != newMaster) {
+            throw new IllegalStateException("Expected node " + newMaster + " to be master, but found that " +
+                    "master is " + actualMaster + " instead.");
+        }
+    }
+
+    private Entity getMember(String memberId) {
+        Group cluster = (Group)entity();
+        try {
+            return Iterables.find(cluster.getMembers(), EntityPredicates.idEqualTo(memberId));
+        } catch (NoSuchElementException e) {
+            throw new IllegalStateException(memberId + " is not an ID of brooklyn node in this cluster");
+        }
+    }
+
+    private Entity getMasterNode() {
+        return entity().getAttribute(BrooklynCluster.MASTER_NODE);
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/6f895aaf/software/base/src/main/java/brooklyn/entity/brooklynnode/effector/SetHAModeEffectorBody.java
----------------------------------------------------------------------
diff --git a/software/base/src/main/java/brooklyn/entity/brooklynnode/effector/SetHAModeEffectorBody.java b/software/base/src/main/java/brooklyn/entity/brooklynnode/effector/SetHAModeEffectorBody.java
new file mode 100644
index 0000000..36a51d0
--- /dev/null
+++ b/software/base/src/main/java/brooklyn/entity/brooklynnode/effector/SetHAModeEffectorBody.java
@@ -0,0 +1,64 @@
+/*
+ * 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 brooklyn.entity.brooklynnode.effector;
+
+import org.apache.http.HttpStatus;
+
+import brooklyn.entity.Effector;
+import brooklyn.entity.brooklynnode.BrooklynNode;
+import brooklyn.entity.brooklynnode.BrooklynNode.SetHAModeEffector;
+import brooklyn.entity.brooklynnode.EntityHttpClient;
+import brooklyn.entity.effector.EffectorBody;
+import brooklyn.entity.effector.Effectors;
+import brooklyn.event.feed.http.HttpValueFunctions;
+import brooklyn.event.feed.http.JsonFunctions;
+import brooklyn.management.ha.HighAvailabilityMode;
+import brooklyn.management.ha.ManagementNodeState;
+import brooklyn.util.config.ConfigBag;
+import brooklyn.util.guava.Functionals;
+import brooklyn.util.http.HttpToolResponse;
+import brooklyn.util.javalang.Enums;
+
+import com.google.api.client.util.Preconditions;
+import com.google.common.base.Function;
+import com.google.common.collect.ImmutableMap;
+
+public class SetHAModeEffectorBody extends EffectorBody<ManagementNodeState> implements SetHAModeEffector {
+    public static final Effector<ManagementNodeState> SET_HA_MODE = Effectors.effector(SetHAModeEffector.SET_HA_MODE).impl(new SetHAModeEffectorBody()).build();
+
+    @Override
+    public ManagementNodeState call(ConfigBag parameters) {
+        HighAvailabilityMode mode = parameters.get(MODE);
+        Preconditions.checkNotNull(mode, MODE.getName() + " parameter is required");
+
+        EntityHttpClient httpClient = ((BrooklynNode)entity()).http();
+        HttpToolResponse resp = httpClient.post("/v1/server/ha/state", 
+                ImmutableMap.of("Brooklyn-Allow-Non-Master-Access", "true"),
+                ImmutableMap.of("mode", mode.toString()));
+
+        if (resp.getResponseCode() == HttpStatus.SC_OK) {
+            Function<HttpToolResponse, ManagementNodeState> parseRespone = Functionals.chain(
+                    Functionals.chain(HttpValueFunctions.jsonContents(), JsonFunctions.cast(String.class)),
+                    Enums.fromStringFunction(ManagementNodeState.class));
+            return parseRespone.apply(resp);
+        } else {
+            throw new IllegalStateException("Unexpected response code: " + resp.getResponseCode() + "\n" + resp.getContentAsString());
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/6f895aaf/software/base/src/main/java/brooklyn/entity/brooklynnode/effector/SetHAPriorityEffectorBody.java
----------------------------------------------------------------------
diff --git a/software/base/src/main/java/brooklyn/entity/brooklynnode/effector/SetHAPriorityEffectorBody.java b/software/base/src/main/java/brooklyn/entity/brooklynnode/effector/SetHAPriorityEffectorBody.java
new file mode 100644
index 0000000..94a961c
--- /dev/null
+++ b/software/base/src/main/java/brooklyn/entity/brooklynnode/effector/SetHAPriorityEffectorBody.java
@@ -0,0 +1,55 @@
+/*
+ * 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 brooklyn.entity.brooklynnode.effector;
+
+import org.apache.http.HttpStatus;
+
+import brooklyn.entity.Effector;
+import brooklyn.entity.brooklynnode.BrooklynNode;
+import brooklyn.entity.brooklynnode.BrooklynNode.SetHAPriorityEffector;
+import brooklyn.entity.brooklynnode.EntityHttpClient;
+import brooklyn.entity.effector.EffectorBody;
+import brooklyn.entity.effector.Effectors;
+import brooklyn.util.config.ConfigBag;
+import brooklyn.util.http.HttpToolResponse;
+
+import com.google.api.client.util.Preconditions;
+import com.google.common.collect.ImmutableMap;
+
+public class SetHAPriorityEffectorBody extends EffectorBody<Integer> implements SetHAPriorityEffector {
+    public static final Effector<Integer> SET_HA_PRIORITY = Effectors.effector(SetHAPriorityEffector.SET_HA_PRIORITY).impl(new SetHAPriorityEffectorBody()).build();
+
+    @Override
+    public Integer call(ConfigBag parameters) {
+        Integer priority = parameters.get(PRIORITY);
+        Preconditions.checkNotNull(priority, PRIORITY.getName() + " parameter is required");
+
+        EntityHttpClient httpClient = ((BrooklynNode)entity()).http();
+        HttpToolResponse resp = httpClient.post("/v1/server/ha/priority",
+            ImmutableMap.of("Brooklyn-Allow-Non-Master-Access", "true"),
+            ImmutableMap.of("priority", priority.toString()));
+
+        if (resp.getResponseCode() == HttpStatus.SC_OK) {
+            return Integer.valueOf(resp.getContentAsString());
+        } else {
+            throw new IllegalStateException("Unexpected response code: " + resp.getResponseCode() + "\n" + resp.getContentAsString());
+        }
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/6f895aaf/software/base/src/main/java/brooklyn/entity/brooklynnode/effector/UpgradeClusterEffectorBody.java
----------------------------------------------------------------------
diff --git a/software/base/src/main/java/brooklyn/entity/brooklynnode/effector/UpgradeClusterEffectorBody.java b/software/base/src/main/java/brooklyn/entity/brooklynnode/effector/UpgradeClusterEffectorBody.java
new file mode 100644
index 0000000..70b184a
--- /dev/null
+++ b/software/base/src/main/java/brooklyn/entity/brooklynnode/effector/UpgradeClusterEffectorBody.java
@@ -0,0 +1,199 @@
+/*
+ * 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 brooklyn.entity.brooklynnode.effector;
+
+import java.io.File;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import brooklyn.config.ConfigKey;
+import brooklyn.entity.Effector;
+import brooklyn.entity.Entity;
+import brooklyn.entity.Group;
+import brooklyn.entity.basic.EntityPredicates;
+import brooklyn.entity.basic.Lifecycle;
+import brooklyn.entity.brooklynnode.BrooklynCluster;
+import brooklyn.entity.brooklynnode.BrooklynCluster.SelectMasterEffector;
+import brooklyn.entity.brooklynnode.BrooklynCluster.UpgradeClusterEffector;
+import brooklyn.entity.brooklynnode.BrooklynNode;
+import brooklyn.entity.brooklynnode.BrooklynNode.SetHAModeEffector;
+import brooklyn.entity.effector.EffectorBody;
+import brooklyn.entity.effector.Effectors;
+import brooklyn.entity.group.DynamicCluster;
+import brooklyn.entity.proxying.EntitySpec;
+import brooklyn.event.AttributeSensor;
+import brooklyn.management.ha.HighAvailabilityMode;
+import brooklyn.management.ha.ManagementNodeState;
+import brooklyn.util.collections.MutableMap;
+import brooklyn.util.config.ConfigBag;
+import brooklyn.util.exceptions.Exceptions;
+import brooklyn.util.net.Urls;
+import brooklyn.util.repeat.Repeater;
+import brooklyn.util.task.DynamicTasks;
+import brooklyn.util.task.Tasks;
+import brooklyn.util.time.Duration;
+
+import com.google.api.client.util.Preconditions;
+import com.google.common.base.Predicate;
+import com.google.common.collect.Collections2;
+import com.google.common.collect.Iterables;
+
+public class UpgradeClusterEffectorBody extends EffectorBody<Void> implements UpgradeClusterEffector {
+    public static final Effector<Void> UPGRADE_CLUSTER = Effectors.effector(UpgradeClusterEffector.UPGRADE_CLUSTER).impl(new UpgradeClusterEffectorBody()).build();
+
+    private AtomicBoolean upgradeInProgress = new AtomicBoolean();
+
+    @Override
+    public Void call(ConfigBag parameters) {
+        if (!upgradeInProgress.compareAndSet(false, true)) {
+            throw new IllegalStateException("An upgrade is already in progress.");
+        }
+
+        EntitySpec<?> memberSpec = entity().getConfig(BrooklynCluster.MEMBER_SPEC);
+        Preconditions.checkNotNull(memberSpec, BrooklynCluster.MEMBER_SPEC.getName() + " is required for " + UpgradeClusterEffector.class.getName());
+
+        Map<ConfigKey<?>, Object> specCfg = memberSpec.getConfig();
+        String oldDownloadUrl = (String) specCfg.get(BrooklynNode.DOWNLOAD_URL);
+        String oldUploadUrl = (String) specCfg.get(BrooklynNode.DISTRO_UPLOAD_URL);
+        String newDownloadUrl = parameters.get(BrooklynNode.DOWNLOAD_URL.getConfigKey());
+        String newUploadUrl = inferUploadUrl(newDownloadUrl);
+        try {
+            memberSpec.configure(BrooklynNode.DOWNLOAD_URL, newUploadUrl);
+            memberSpec.configure(BrooklynNode.DISTRO_UPLOAD_URL, newUploadUrl);
+            upgrade(parameters);
+        } catch (Exception e) {
+            memberSpec.configure(BrooklynNode.DOWNLOAD_URL, oldDownloadUrl);
+            memberSpec.configure(BrooklynNode.DISTRO_UPLOAD_URL, oldUploadUrl);
+            throw Exceptions.propagate(e);
+        } finally {
+            upgradeInProgress.set(false);
+        }
+        return null;
+    }
+
+    private String inferUploadUrl(String newDownloadUrl) {
+        boolean isLocal = "file".equals(Urls.getProtocol(newDownloadUrl)) || new File(newDownloadUrl).exists();
+        if (isLocal) {
+            return newDownloadUrl;
+        } else {
+            return null;
+        }
+    }
+
+    private void upgrade(ConfigBag parameters) {
+        //TODO might be worth separating each step in a task for better UI
+
+        Group cluster = (Group)entity();
+        Collection<Entity> initialMembers = cluster.getMembers();
+        int initialClusterSize = initialMembers.size();
+
+        //1. Initially create a single node to check if it will launch successfully
+        Entity initialNode = Iterables.getOnlyElement(createNodes(1));
+
+        //2. If everything is OK with the first node launch the rest as well
+        Collection<Entity> remainingNodes = createNodes(initialClusterSize - 1);
+
+        //3. Once we have all nodes running without errors switch master
+        DynamicTasks.queue(Effectors.invocation(cluster, BrooklynCluster.SELECT_MASTER, MutableMap.of(SelectMasterEffector.NEW_MASTER_ID, initialNode.getId()))).asTask().getUnchecked();
+
+        //4. Stop the nodes which were running at the start of the upgrade call, but keep them around.
+        //   Should we create a quarantine-like zone for old stopped version?
+        //   For members that were created meanwhile - they will be using the new version already. If the new version
+        //   isn't good then they will fail to start as well, forcing the policies to retry (and succeed once the
+        //   URL is reverted).
+        HashSet<Entity> oldMembers = new HashSet<Entity>(initialMembers);
+        oldMembers.removeAll(remainingNodes);
+        oldMembers.remove(initialNode);
+        DynamicTasks.queue(Effectors.invocation(BrooklynNode.STOP_NODE_BUT_LEAVE_APPS, Collections.emptyMap(), oldMembers)).asTask().getUnchecked();
+    }
+
+    private Collection<Entity> createNodes(int nodeCnt) {
+        DynamicCluster cluster = (DynamicCluster)entity();
+
+        //1. Create the nodes
+        Collection<Entity> newNodes = cluster.resizeByDelta(nodeCnt);
+
+        //2. Wait for them to be RUNNING
+        waitAttributeNotEqualTo(
+                newNodes,
+                BrooklynNode.SERVICE_STATE_ACTUAL, Lifecycle.STARTING);
+
+        //3. Set HOT_STANDBY in case it is not enabled on the command line ...
+        DynamicTasks.queue(Effectors.invocation(
+                BrooklynNode.SET_HA_MODE,
+                MutableMap.of(SetHAModeEffector.MODE, HighAvailabilityMode.HOT_STANDBY), 
+                newNodes)).asTask().getUnchecked();
+
+        //4. ... and wait until all of the nodes change state
+        //TODO if the REST call is blocking this is not needed
+        waitAttributeEqualTo(
+                newNodes,
+                BrooklynNode.MANAGEMENT_NODE_STATE,
+                ManagementNodeState.HOT_STANDBY);
+
+        //5. Just in case check if all of the nodes are SERVICE_UP (which would rule out ON_FIRE as well)
+        Collection<Entity> failedNodes = Collections2.filter(newNodes, EntityPredicates.attributeEqualTo(BrooklynNode.SERVICE_UP, Boolean.FALSE));
+        if (!failedNodes.isEmpty()) {
+            throw new IllegalStateException("Nodes " + failedNodes + " are not " + BrooklynNode.SERVICE_UP + " though successfully in " + ManagementNodeState.HOT_STANDBY);
+        }
+        return newNodes;
+    }
+
+    private <T> void waitAttributeEqualTo(Collection<Entity> nodes, AttributeSensor<T> sensor, T value) {
+        waitPredicate(
+                nodes, 
+                EntityPredicates.attributeEqualTo(sensor, value),
+                "Waiting for nodes " + nodes + ", sensor " + sensor + " to be " + value,
+                "Timeout while waiting for nodes " + nodes + ", sensor " + sensor + " to change to " + value);
+    }
+
+    private <T> void waitAttributeNotEqualTo(Collection<Entity> nodes, AttributeSensor<T> sensor, T value) {
+        waitPredicate(
+                nodes, 
+                EntityPredicates.attributeNotEqualTo(sensor, value),
+                "Waiting for nodes " + nodes + ", sensor " + sensor + " to change from " + value,
+                "Timeout while waiting for nodes " + nodes + ", sensor " + sensor + " to change from " + value);
+    }
+
+    private <T extends Entity> void waitPredicate(Collection<T> nodes, Predicate<T> waitPredicate, String blockingMsg, String errorMsg) {
+        Tasks.setBlockingDetails(blockingMsg);
+        boolean pollSuccess = Repeater.create(blockingMsg)
+            .backoff(Duration.ONE_SECOND, 1.2, Duration.TEN_SECONDS)
+            .limitTimeTo(Duration.ONE_HOUR)
+            .until(nodes, allMatch(waitPredicate))
+            .run();
+        Tasks.resetBlockingDetails();
+
+        if (!pollSuccess) {
+            throw new IllegalStateException(errorMsg);
+        }
+    }
+
+    public static <T> Predicate<Collection<T>> allMatch(final Predicate<T> predicate) {
+        return new Predicate<Collection<T>>() {
+            @Override
+            public boolean apply(Collection<T> input) {
+                return Iterables.all(input, predicate);
+            }
+        };
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/6f895aaf/software/base/src/test/java/brooklyn/entity/brooklynnode/effector/CallbackEntityHttpClient.java
----------------------------------------------------------------------
diff --git a/software/base/src/test/java/brooklyn/entity/brooklynnode/effector/CallbackEntityHttpClient.java b/software/base/src/test/java/brooklyn/entity/brooklynnode/effector/CallbackEntityHttpClient.java
new file mode 100644
index 0000000..8c8004b
--- /dev/null
+++ b/software/base/src/test/java/brooklyn/entity/brooklynnode/effector/CallbackEntityHttpClient.java
@@ -0,0 +1,90 @@
+/*
+ * 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 brooklyn.entity.brooklynnode.effector;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.http.HttpStatus;
+import org.apache.http.client.methods.HttpGet;
+import org.apache.http.client.methods.HttpPost;
+
+import brooklyn.entity.Entity;
+import brooklyn.entity.brooklynnode.EntityHttpClient;
+import brooklyn.util.http.HttpTool.HttpClientBuilder;
+import brooklyn.util.http.HttpToolResponse;
+
+import com.google.common.base.Function;
+
+public class CallbackEntityHttpClient implements EntityHttpClient {
+    public static class Request {
+        private Entity entity;
+        private String method;
+        private String path;
+        private Map<String, String> params;
+        public Request(Entity entity, String method, String path, Map<String, String> params) {
+            this.entity = entity;
+            this.method = method;
+            this.path = path;
+            this.params = params;
+        }
+        public Entity getEntity() {
+            return entity;
+        }
+        public String getMethod() {
+            return method;
+        }
+        public String getPath() {
+            return path;
+        }
+        public Map<String, String> getParams() {
+            return params;
+        }
+    }
+    private Function<Request, String> callback;
+    private Entity entity;
+
+    public CallbackEntityHttpClient(Entity entity, Function<Request, String> callback) {
+        this.entity = entity;
+        this.callback = callback;
+    }
+
+    @Override
+    public HttpClientBuilder getHttpClientForBrooklynNode() {
+        throw new IllegalStateException("Method call not expected");
+    }
+
+    @Override
+    public HttpToolResponse get(String path) {
+        String result = callback.apply(new Request(entity, HttpGet.METHOD_NAME, path, Collections.<String, String>emptyMap()));
+        return new HttpToolResponse(HttpStatus.SC_OK, null, result.getBytes(), 0, 0, 0);
+    }
+
+    @Override
+    public HttpToolResponse post(String path, Map<String, String> headers, byte[] body) {
+        throw new IllegalStateException("Method call not expected");
+    }
+
+    @Override
+    public HttpToolResponse post(String path, Map<String, String> headers, Map<String, String> formParams) {
+        String result = callback.apply(new Request(entity, HttpPost.METHOD_NAME, path, formParams));
+        return new HttpToolResponse(HttpStatus.SC_OK, Collections.<String, List<String>>emptyMap(), result.getBytes(), 0, 0, 0);
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/6f895aaf/software/base/src/test/java/brooklyn/entity/brooklynnode/effector/SelectMasterEffectorTest.java
----------------------------------------------------------------------
diff --git a/software/base/src/test/java/brooklyn/entity/brooklynnode/effector/SelectMasterEffectorTest.java b/software/base/src/test/java/brooklyn/entity/brooklynnode/effector/SelectMasterEffectorTest.java
new file mode 100644
index 0000000..6036507
--- /dev/null
+++ b/software/base/src/test/java/brooklyn/entity/brooklynnode/effector/SelectMasterEffectorTest.java
@@ -0,0 +1,267 @@
+/*
+ * 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 brooklyn.entity.brooklynnode.effector;
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertTrue;
+import static org.testng.Assert.fail;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.concurrent.Callable;
+
+import org.apache.http.client.methods.HttpPost;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.testng.annotations.AfterMethod;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import brooklyn.entity.Entity;
+import brooklyn.entity.Group;
+import brooklyn.entity.basic.ApplicationBuilder;
+import brooklyn.entity.basic.BasicApplication;
+import brooklyn.entity.basic.EntityLocal;
+import brooklyn.entity.brooklynnode.BrooklynCluster;
+import brooklyn.entity.brooklynnode.BrooklynCluster.SelectMasterEffector;
+import brooklyn.entity.brooklynnode.BrooklynClusterImpl;
+import brooklyn.entity.brooklynnode.BrooklynNode;
+import brooklyn.entity.brooklynnode.effector.CallbackEntityHttpClient.Request;
+import brooklyn.entity.effector.Effectors;
+import brooklyn.entity.group.DynamicCluster;
+import brooklyn.entity.proxying.EntitySpec;
+import brooklyn.event.feed.AttributePollHandler;
+import brooklyn.event.feed.DelegatingPollHandler;
+import brooklyn.event.feed.Poller;
+import brooklyn.event.feed.function.FunctionFeed;
+import brooklyn.management.ManagementContext;
+import brooklyn.management.ha.ManagementNodeState;
+import brooklyn.test.EntityTestUtils;
+import brooklyn.test.entity.LocalManagementContextForTests;
+import brooklyn.util.task.BasicExecutionContext;
+import brooklyn.util.task.BasicExecutionManager;
+import brooklyn.util.time.Duration;
+
+import com.google.common.base.Function;
+import com.google.common.base.Objects;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Iterables;
+
+public class SelectMasterEffectorTest {
+    private static final Logger LOG = LoggerFactory.getLogger(BrooklynClusterImpl.class);
+
+    protected ManagementContext mgmt;
+    protected BasicApplication app;
+    protected BasicExecutionContext ec;
+    protected BrooklynCluster cluster;
+    protected FunctionFeed scanMaster;
+    protected Poller<Void> poller; 
+
+    @BeforeMethod
+    public void setUp() {
+        mgmt = new LocalManagementContextForTests();
+        EntitySpec<BasicApplication> appSpec = EntitySpec.create(BasicApplication.class)
+                .child(EntitySpec.create(BrooklynCluster.class));
+        app = ApplicationBuilder.newManagedApp(appSpec, mgmt);
+        cluster = (BrooklynCluster)Iterables.getOnlyElement(app.getChildren());
+
+        BasicExecutionManager em = new BasicExecutionManager("mycontext");
+        ec = new BasicExecutionContext(em);
+
+        poller = new Poller<Void>((EntityLocal)app, false);
+        poller.scheduleAtFixedRate(
+            new Callable<Void>() {
+                @Override
+                public Void call() throws Exception {
+                    masterFailoverIfNeeded();
+                    return null;
+                }
+            },
+            new DelegatingPollHandler<Void>(Collections.<AttributePollHandler<? super Void>>emptyList()),
+            Duration.millis(200));
+        poller.start();
+    }
+
+    @AfterMethod
+    public void tearDown() {
+        poller.stop();
+    }
+
+    @Test
+    public void testInvalidNewMasterIdFails() {
+        try {
+            BrooklynCluster cluster = app.addChild(EntitySpec.create(BrooklynCluster.class));
+            selectMaster(cluster, "1234");
+            fail("Non-existend entity ID provided.");
+        } catch (Exception e) {
+            assertTrue(e.toString().contains("1234 is not an ID of brooklyn node in this cluster"));
+        }
+    }
+
+    @Test
+    public void testSelectMaster() {
+        HttpCallback cb = new HttpCallback();
+        BrooklynNode node1 = cluster.addMemberChild(EntitySpec.create(BrooklynNode.class)
+                .impl(TestHttpEntity.class)
+                .configure(TestHttpEntity.HTTP_CLIENT_CALLBACK, cb));
+        BrooklynNode node2 = cluster.addMemberChild(EntitySpec.create(BrooklynNode.class)
+                .impl(TestHttpEntity.class)
+                .configure(TestHttpEntity.HTTP_CLIENT_CALLBACK, cb));
+
+        cluster.addMemberChild(node1);
+        cluster.addMemberChild(node2);
+
+        setManagementState(node1, ManagementNodeState.MASTER);
+        EntityTestUtils.assertAttributeEqualsEventually(cluster, BrooklynCluster.MASTER_NODE, node1);
+
+        selectMaster(cluster, node2.getId());
+        checkMaster(cluster, node2);
+    }
+
+    @Test(groups="WIP")
+    //after throwing an exception in HttpCallback tasks are no longer executed, why?
+    public void testSelectMasterFailsAtChangeState() {
+        HttpCallback cb = new HttpCallback();
+        cb.setFailAtStateChange(true);
+
+        BrooklynNode node1 = cluster.addMemberChild(EntitySpec.create(BrooklynNode.class)
+                .impl(TestHttpEntity.class)
+                .configure(TestHttpEntity.HTTP_CLIENT_CALLBACK, cb));
+        BrooklynNode node2 = cluster.addMemberChild(EntitySpec.create(BrooklynNode.class)
+                .impl(TestHttpEntity.class)
+                .configure(TestHttpEntity.HTTP_CLIENT_CALLBACK, cb));
+
+        cluster.addMemberChild(node1);
+        cluster.addMemberChild(node2);
+
+        setManagementState(node1, ManagementNodeState.MASTER);
+        EntityTestUtils.assertAttributeEqualsEventually(cluster, BrooklynCluster.MASTER_NODE, node1);
+
+        selectMaster(cluster, node2.getId());
+        checkMaster(cluster, node1);
+    }
+
+    private void checkMaster(Group cluster, Entity node) {
+        assertEquals(node.getAttribute(BrooklynNode.MANAGEMENT_NODE_STATE), ManagementNodeState.MASTER);
+        assertEquals(cluster.getAttribute(BrooklynCluster.MASTER_NODE), node);
+        for (Entity member : cluster.getMembers()) {
+            if (member != node) {
+                assertEquals(member.getAttribute(BrooklynNode.MANAGEMENT_NODE_STATE), ManagementNodeState.HOT_STANDBY);
+            }
+            assertEquals((int)member.getAttribute(TestHttpEntity.HA_PRIORITY), 0);
+        }
+    }
+
+    private static class HttpCallback implements Function<CallbackEntityHttpClient.Request, String> {
+        private enum State {
+            INITIAL,
+            PROMOTED
+        }
+        private State state = State.INITIAL;
+        private boolean failAtStateChange;
+
+        @Override
+        public String apply(Request input) {
+            if ("/v1/server/ha/state".equals(input.getPath())) {
+                if (failAtStateChange) {
+                    throw new RuntimeException("Testing failure at chaning node state");
+                }
+
+                checkRequest(input, HttpPost.METHOD_NAME, "/v1/server/ha/state", "mode", "HOT_STANDBY");
+                Entity entity = input.getEntity();
+                EntityTestUtils.assertAttributeEquals(entity, BrooklynNode.MANAGEMENT_NODE_STATE, ManagementNodeState.MASTER);
+                EntityTestUtils.assertAttributeEquals(entity, TestHttpEntity.HA_PRIORITY, 0);
+
+                setManagementState(entity, ManagementNodeState.HOT_STANDBY);
+
+                return "MASTER";
+            } else {
+                switch(state) {
+                case INITIAL:
+                    checkRequest(input, HttpPost.METHOD_NAME, "/v1/server/ha/priority", "priority", "1");
+                    state = State.PROMOTED;
+                    setPriority(input.getEntity(), Integer.parseInt(input.getParams().get("priority")));
+                    return "0";
+                case PROMOTED:
+                    checkRequest(input, HttpPost.METHOD_NAME, "/v1/server/ha/priority", "priority", "0");
+                    state = State.INITIAL;
+                    setPriority(input.getEntity(), Integer.parseInt(input.getParams().get("priority")));
+                    return "1";
+                default: throw new IllegalStateException("Illegal call at state " + state + ". Request = " + input.getMethod() + " " + input.getPath());
+                }
+            }
+        }
+
+        public void checkRequest(Request input, String methodName, String path, String... keyValue) {
+            if (!input.getMethod().equals(methodName) || !input.getPath().equals(path)) {
+                throw new IllegalStateException("Request doesn't match expected state. Expected = " + input.getMethod() + " " + input.getPath() + ". " +
+                        "Actual = " + methodName + " " + path);
+            }
+            for(int i = 0; i < keyValue.length / 2; i++) {
+                String key = keyValue[i];
+                String value = keyValue[i+1];
+                String inputValue = input.getParams().get(key);
+                if(!Objects.equal(value, inputValue)) {
+                    throw new IllegalStateException("Request doesn't match expected parameter " + methodName + " " + path + ". Parameter " + key + 
+                            " expected = " + value + ", actual = " + inputValue);
+                }
+            }
+        }
+
+        public void setFailAtStateChange(boolean failAtStateChange) {
+            this.failAtStateChange = failAtStateChange;
+        }
+
+    }
+
+    private void masterFailoverIfNeeded() {
+        if (cluster.getAttribute(BrooklynCluster.MASTER_NODE) == null) {
+            Collection<Entity> members = cluster.getMembers();
+            if (members.size() > 0) {
+                for (Entity member : members) {
+                    if (member.getAttribute(TestHttpEntity.HA_PRIORITY) == 1) {
+                        masterFailover(member);
+                        return;
+                    }
+                }
+                masterFailover(members.iterator().next());
+            }
+        }
+    }
+
+    private void masterFailover(Entity member) {
+        LOG.debug("Master failover to " + member);
+        setManagementState(member, ManagementNodeState.MASTER);
+        EntityTestUtils.assertAttributeEqualsEventually(cluster, BrooklynCluster.MASTER_NODE, (BrooklynNode)member);
+        return;
+    }
+
+    public static void setManagementState(Entity entity, ManagementNodeState state) {
+        ((EntityLocal)entity).setAttribute(BrooklynNode.MANAGEMENT_NODE_STATE, state);
+    }
+
+    public static void setPriority(Entity entity, int priority) {
+        ((EntityLocal)entity).setAttribute(TestHttpEntity.HA_PRIORITY, priority);
+    }
+
+    private void selectMaster(DynamicCluster cluster, String id) {
+        ec.submit(Effectors.invocation(cluster, BrooklynCluster.SELECT_MASTER, ImmutableMap.of(SelectMasterEffector.NEW_MASTER_ID.getName(), id))).asTask().getUnchecked();
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/6f895aaf/software/base/src/test/java/brooklyn/entity/brooklynnode/effector/TestHttpEntity.java
----------------------------------------------------------------------
diff --git a/software/base/src/test/java/brooklyn/entity/brooklynnode/effector/TestHttpEntity.java b/software/base/src/test/java/brooklyn/entity/brooklynnode/effector/TestHttpEntity.java
new file mode 100644
index 0000000..259a271
--- /dev/null
+++ b/software/base/src/test/java/brooklyn/entity/brooklynnode/effector/TestHttpEntity.java
@@ -0,0 +1,66 @@
+/*
+ * 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 brooklyn.entity.brooklynnode.effector;
+
+import java.util.Collection;
+
+import brooklyn.config.ConfigKey;
+import brooklyn.entity.basic.AbstractEntity;
+import brooklyn.entity.basic.ConfigKeys;
+import brooklyn.entity.brooklynnode.BrooklynNode;
+import brooklyn.entity.brooklynnode.EntityHttpClient;
+import brooklyn.entity.brooklynnode.effector.CallbackEntityHttpClient.Request;
+import brooklyn.event.AttributeSensor;
+import brooklyn.event.basic.BasicAttributeSensor;
+import brooklyn.location.Location;
+
+import com.google.common.base.Function;
+import com.google.common.reflect.TypeToken;
+
+public class TestHttpEntity extends AbstractEntity implements BrooklynNode {
+    @SuppressWarnings("serial")
+    public static final ConfigKey<Function<Request, String>> HTTP_CLIENT_CALLBACK = ConfigKeys.newConfigKey(new TypeToken<Function<Request, String>>(){}, "httpClientCallback");
+    public static final AttributeSensor<Integer> HA_PRIORITY = new BasicAttributeSensor<Integer>(Integer.class, "priority");
+    
+    @Override
+    public void init() {
+        super.init();
+        getMutableEntityType().addEffector(SetHAPriorityEffectorBody.SET_HA_PRIORITY);
+        getMutableEntityType().addEffector(SetHAModeEffectorBody.SET_HA_MODE);
+        setAttribute(HA_PRIORITY, 0);
+    }
+
+    @Override
+    public EntityHttpClient http() {
+        return new CallbackEntityHttpClient(this, getConfig(HTTP_CLIENT_CALLBACK));
+    }
+
+    @Override
+    public void start(Collection<? extends Location> locations) {
+    }
+
+    @Override
+    public void stop() {
+    }
+
+    @Override
+    public void restart() {
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/6f895aaf/usage/rest-api/src/main/java/brooklyn/rest/api/ServerApi.java
----------------------------------------------------------------------
diff --git a/usage/rest-api/src/main/java/brooklyn/rest/api/ServerApi.java b/usage/rest-api/src/main/java/brooklyn/rest/api/ServerApi.java
index e0ba7ea..5d1ca9b 100644
--- a/usage/rest-api/src/main/java/brooklyn/rest/api/ServerApi.java
+++ b/usage/rest-api/src/main/java/brooklyn/rest/api/ServerApi.java
@@ -27,6 +27,7 @@ import javax.ws.rs.Path;
 import javax.ws.rs.Produces;
 import javax.ws.rs.core.MediaType;
 
+import brooklyn.management.ha.HighAvailabilityMode;
 import brooklyn.management.ha.ManagementNodeState;
 import brooklyn.rest.apidoc.Apidoc;
 import brooklyn.rest.domain.HighAvailabilitySummary;
@@ -90,14 +91,34 @@ public interface ServerApi {
     @ApiOperation(value = "Returns the HA state of this management node")
     public ManagementNodeState getHighAvailabilityNodeState();
     
+    @POST
+    @Path("/ha/state")
+    @ApiOperation(value = "Changes the HA state of this management node")
+    public ManagementNodeState setHighAvailabilityNodeState(
+            @ApiParam(name = "state", value = "The state to change to")
+            @FormParam("mode") HighAvailabilityMode mode);
+
     @GET
     @Path("/ha/states")
     @ApiOperation(value = "Returns the HA states and detail for all nodes in this management plane",
         responseClass = "brooklyn.rest.domain.HighAvailabilitySummary")
     public HighAvailabilitySummary getHighAvailabilityPlaneStates();
+
+    @GET
+    @Path("/ha/priority")
+    @ApiOperation(value = "Returns the HA node priority for MASTER failover")
+    public long getHighAvailabitlityPriority();
     
+    @POST
+    @Path("/ha/priority")
+    @ApiOperation(value = "Sets the HA node priority for MASTER failover")
+    public long setHighAvailabilityPriority(
+            @ApiParam(name = "priority", value = "The priority to be set")
+            @FormParam("priority") long priority);
+
     @GET
     @Path("/user")
     @ApiOperation(value = "Return user information for this Brooklyn instance", responseClass = "String", multiValueResponse = false)
-    public String getUser();
+    public String getUser(); 
+
 }

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/6f895aaf/usage/rest-server/src/main/java/brooklyn/rest/resources/ServerResource.java
----------------------------------------------------------------------
diff --git a/usage/rest-server/src/main/java/brooklyn/rest/resources/ServerResource.java b/usage/rest-server/src/main/java/brooklyn/rest/resources/ServerResource.java
index 55fe968..a9cf8a5 100644
--- a/usage/rest-server/src/main/java/brooklyn/rest/resources/ServerResource.java
+++ b/usage/rest-server/src/main/java/brooklyn/rest/resources/ServerResource.java
@@ -38,6 +38,8 @@ import brooklyn.entity.basic.StartableApplication;
 import brooklyn.management.Task;
 import brooklyn.management.entitlement.EntitlementContext;
 import brooklyn.management.entitlement.Entitlements;
+import brooklyn.management.ha.HighAvailabilityManager;
+import brooklyn.management.ha.HighAvailabilityMode;
 import brooklyn.management.ha.ManagementNodeState;
 import brooklyn.management.ha.ManagementPlaneSyncRecord;
 import brooklyn.management.internal.ManagementContextInternal;
@@ -228,7 +230,28 @@ public class ServerResource extends AbstractBrooklynRestResource implements Serv
     public ManagementNodeState getHighAvailabilityNodeState() {
         return mgmt().getHighAvailabilityManager().getNodeState();
     }
-    
+
+    @Override
+    public ManagementNodeState setHighAvailabilityNodeState(HighAvailabilityMode mode) {
+        HighAvailabilityManager haMgr = mgmt().getHighAvailabilityManager();
+        ManagementNodeState existingState = haMgr.getNodeState();
+        haMgr.changeMode(mode);
+        return existingState;
+    }
+
+    @Override
+    public long getHighAvailabitlityPriority() {
+        return mgmt().getHighAvailabilityManager().getPriority();
+    }
+
+    @Override
+    public long setHighAvailabilityPriority(long priority) {
+        HighAvailabilityManager haMgr = mgmt().getHighAvailabilityManager();
+        long oldPrio = haMgr.getPriority();
+        haMgr.setPriority(priority);
+        return oldPrio;
+    }
+
     @Override
     public HighAvailabilitySummary getHighAvailabilityPlaneStates() {
         ManagementPlaneSyncRecord memento = mgmt().getHighAvailabilityManager().getManagementPlaneSyncState();
@@ -244,4 +267,5 @@ public class ServerResource extends AbstractBrooklynRestResource implements Serv
             return null; //User can be null if no authentication was requested
         }
     }
+
 }


[06/29] git commit: fix bug where tasks (eg pollers) are not stopped on unmanagement if the task which started the tasks is the same one which is unmanaging the entity

Posted by al...@apache.org.
fix bug where tasks (eg pollers) are not stopped on unmanagement if the task which started the tasks is the same one which is unmanaging the entity


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

Branch: refs/heads/master
Commit: 6432a1270f1c95131e943ca52e89a3f132c2348c
Parents: 44382b7
Author: Alex Heneveld <al...@cloudsoftcorp.com>
Authored: Fri Oct 24 23:51:36 2014 +0100
Committer: Alex Heneveld <al...@cloudsoftcorp.com>
Committed: Fri Oct 31 09:38:19 2014 -0500

----------------------------------------------------------------------
 .../java/brooklyn/management/internal/LocalEntityManager.java | 7 ++++++-
 1 file changed, 6 insertions(+), 1 deletion(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/6432a127/core/src/main/java/brooklyn/management/internal/LocalEntityManager.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/management/internal/LocalEntityManager.java b/core/src/main/java/brooklyn/management/internal/LocalEntityManager.java
index 4acb9b4..c6a5bcf 100644
--- a/core/src/main/java/brooklyn/management/internal/LocalEntityManager.java
+++ b/core/src/main/java/brooklyn/management/internal/LocalEntityManager.java
@@ -38,6 +38,7 @@ import brooklyn.entity.Application;
 import brooklyn.entity.Entity;
 import brooklyn.entity.Group;
 import brooklyn.entity.basic.AbstractEntity;
+import brooklyn.entity.basic.BrooklynTaskTags;
 import brooklyn.entity.basic.Entities;
 import brooklyn.entity.basic.EntityInternal;
 import brooklyn.entity.basic.EntityPredicates;
@@ -421,8 +422,12 @@ public class LocalEntityManager implements EntityManagerInternal {
         try {
             Set<Task<?>> tasksCancelled = MutableSet.of();
             for (Task<?> t: managementContext.getExecutionContext(entity).getTasks()) {
-                if (hasTaskAsAncestor(t, Tasks.current()))
+                if (entity.equals(BrooklynTaskTags.getContextEntity(Tasks.current())) && hasTaskAsAncestor(t, Tasks.current())) {
+                    // don't cancel if we are running inside a task on the target entity and
+                    // the task being considered is one we have submitted -- e.g. on "stop" don't cancel ourselves!
+                    // but if our current task is from another entity we probably do want to cancel them (we are probably invoking unmanage)
                     continue;
+                }
                 
                 if (!t.isDone()) {
                     try {


[07/29] git commit: main cleanup of brooklyn HA and upgrade

Posted by al...@apache.org.
main cleanup of brooklyn HA and upgrade


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

Branch: refs/heads/master
Commit: 44382b7ca60612711786ea639636484a326aa5ab
Parents: d66c440
Author: Alex Heneveld <al...@cloudsoftcorp.com>
Authored: Sat Oct 18 01:44:32 2014 +0100
Committer: Alex Heneveld <al...@cloudsoftcorp.com>
Committed: Fri Oct 31 09:38:19 2014 -0500

----------------------------------------------------------------------
 .../brooklyn/config/BrooklynProperties.java     |  1 +
 .../location/basic/BasicLocationRegistry.java   |  4 +-
 .../java/brooklyn/util/config/ConfigBag.java    |  2 +-
 .../entity/brooklynnode/BrooklynNode.java       |  6 +-
 .../entity/brooklynnode/BrooklynNodeImpl.java   |  1 +
 .../brooklynnode/BrooklynUpgradeEffector.java   | 83 +++++++++++++++++---
 .../entity/brooklynnode/brooklyn-node.yaml      |  3 +-
 .../java/brooklyn/util/repeat/Repeater.java     |  5 ++
 8 files changed, 89 insertions(+), 16 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/44382b7c/core/src/main/java/brooklyn/config/BrooklynProperties.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/config/BrooklynProperties.java b/core/src/main/java/brooklyn/config/BrooklynProperties.java
index 516784c..63c2289 100644
--- a/core/src/main/java/brooklyn/config/BrooklynProperties.java
+++ b/core/src/main/java/brooklyn/config/BrooklynProperties.java
@@ -433,6 +433,7 @@ public class BrooklynProperties extends LinkedHashMap implements StringConfigMap
 
     @Override
     public <T> T getConfig(ConfigKey<T> key, T defaultValue) {
+        // TODO does not support MapConfigKey etc where entries use subkey notation; for now, access using submap
         if (!containsKey(key.getName())) {
             if (defaultValue!=null) return defaultValue;
             return key.getDefaultValue();

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/44382b7c/core/src/main/java/brooklyn/location/basic/BasicLocationRegistry.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/location/basic/BasicLocationRegistry.java b/core/src/main/java/brooklyn/location/basic/BasicLocationRegistry.java
index 09f69e7..365b347 100644
--- a/core/src/main/java/brooklyn/location/basic/BasicLocationRegistry.java
+++ b/core/src/main/java/brooklyn/location/basic/BasicLocationRegistry.java
@@ -153,9 +153,9 @@ public class BasicLocationRegistry implements LocationRegistry {
     public void updateDefinedLocations() {
         synchronized (definedLocations) {
             // first read all properties starting  brooklyn.location.named.xxx
-            // (would be nice to move to a better way, then deprecate this approach, but first
+            // (would be nice to move to a better way, e.g. yaml, then deprecate this approach, but first
             // we need ability/format for persisting named locations, and better support for adding+saving via REST/GUI)
-            int count = 0;
+            int count = 0; 
             String NAMED_LOCATION_PREFIX = "brooklyn.location.named.";
             ConfigMap namedLocationProps = mgmt.getConfig().submap(ConfigPredicates.startingWith(NAMED_LOCATION_PREFIX));
             for (String k: namedLocationProps.asMapWithStringKeys().keySet()) {

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/44382b7c/core/src/main/java/brooklyn/util/config/ConfigBag.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/util/config/ConfigBag.java b/core/src/main/java/brooklyn/util/config/ConfigBag.java
index e40329a..c57098b 100644
--- a/core/src/main/java/brooklyn/util/config/ConfigBag.java
+++ b/core/src/main/java/brooklyn/util/config/ConfigBag.java
@@ -397,7 +397,7 @@ public class ConfigBag {
     }
 
     protected <T> T get(ConfigKey<T> key, boolean markUsed) {
-        // TODO for now, no evaluation -- closure content / smart (self-extracting) keys are NOT supported
+        // TODO for now, no evaluation -- maps / closure content / other smart (self-extracting) keys are NOT supported
         // (need a clean way to inject that behaviour, as well as desired TypeCoercions)
         if (config.containsKey(key.getName()))
             return coerceFirstNonNullKeyValue(key, getStringKey(key.getName(), markUsed));

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/44382b7c/software/base/src/main/java/brooklyn/entity/brooklynnode/BrooklynNode.java
----------------------------------------------------------------------
diff --git a/software/base/src/main/java/brooklyn/entity/brooklynnode/BrooklynNode.java b/software/base/src/main/java/brooklyn/entity/brooklynnode/BrooklynNode.java
index 16fdca2..3d8683f 100644
--- a/software/base/src/main/java/brooklyn/entity/brooklynnode/BrooklynNode.java
+++ b/software/base/src/main/java/brooklyn/entity/brooklynnode/BrooklynNode.java
@@ -97,7 +97,7 @@ public interface BrooklynNode extends SoftwareProcess, UsesJava {
 
     @SetFromFlag("managementPassword")
     ConfigKey<String> MANAGEMENT_PASSWORD =
-            ConfigKeys.newStringConfigKey("brooklynnode.managementPassword", "Password for MANAGEMENT_USER", "password");
+            ConfigKeys.newStringConfigKey("brooklynnode.managementPassword", "Password for MANAGEMENT_USER", null);
 
     /** useful e.g. with {@link BashCommands#generateKeyInDotSshIdRsaIfNotThere() } */
     @SetFromFlag("extraCustomizationScript")
@@ -159,11 +159,11 @@ public interface BrooklynNode extends SoftwareProcess, UsesJava {
     
     @SetFromFlag("brooklynLocalPropertiesUri")
     public static final ConfigKey<String> BROOKLYN_LOCAL_PROPERTIES_URI = ConfigKeys.newStringConfigKey(
-            "brooklynnode.brooklynproperties.local.uri", "URI for the launch-specific brooklyn properties file (uploaded to ~/.brooklyn/brooklyn.properties)", null);
+            "brooklynnode.brooklynproperties.local.uri", "URI for the launch-specific brooklyn properties file", null);
 
     @SetFromFlag("brooklynLocalPropertiesContents")
     public static final ConfigKey<String> BROOKLYN_LOCAL_PROPERTIES_CONTENTS = ConfigKeys.newStringConfigKey(
-            "brooklynnode.brooklynproperties.local.contents", "Contents for the launch-specific brooklyn properties file (uploaded to ~/.brooklyn/brooklyn.properties)", null);
+            "brooklynnode.brooklynproperties.local.contents", "Contents for the launch-specific brooklyn properties file", null);
     
     // For use in testing primarily
     @SetFromFlag("brooklynCatalogRemotePath")

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/44382b7c/software/base/src/main/java/brooklyn/entity/brooklynnode/BrooklynNodeImpl.java
----------------------------------------------------------------------
diff --git a/software/base/src/main/java/brooklyn/entity/brooklynnode/BrooklynNodeImpl.java b/software/base/src/main/java/brooklyn/entity/brooklynnode/BrooklynNodeImpl.java
index 6416970..6aa2420 100644
--- a/software/base/src/main/java/brooklyn/entity/brooklynnode/BrooklynNodeImpl.java
+++ b/software/base/src/main/java/brooklyn/entity/brooklynnode/BrooklynNodeImpl.java
@@ -109,6 +109,7 @@ public class BrooklynNodeImpl extends SoftwareProcessImpl implements BrooklynNod
     @Override
     protected void preStop() {
         super.preStop();
+
         // Shutdown only if accessible: any of stop_* could have already been called.
         // Don't check serviceUp=true because stop() will already have set serviceUp=false && expectedState=stopping
         if (Boolean.TRUE.equals(getAttribute(BrooklynNode.WEB_CONSOLE_ACCESSIBLE))) {

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/44382b7c/software/base/src/main/java/brooklyn/entity/brooklynnode/BrooklynUpgradeEffector.java
----------------------------------------------------------------------
diff --git a/software/base/src/main/java/brooklyn/entity/brooklynnode/BrooklynUpgradeEffector.java b/software/base/src/main/java/brooklyn/entity/brooklynnode/BrooklynUpgradeEffector.java
index 08da8d4..a55f077 100644
--- a/software/base/src/main/java/brooklyn/entity/brooklynnode/BrooklynUpgradeEffector.java
+++ b/software/base/src/main/java/brooklyn/entity/brooklynnode/BrooklynUpgradeEffector.java
@@ -19,29 +19,48 @@
 package brooklyn.entity.brooklynnode;
 
 import java.util.Map;
+import java.util.concurrent.Callable;
 
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 import brooklyn.config.ConfigKey;
 import brooklyn.entity.Effector;
+import brooklyn.entity.Entity;
 import brooklyn.entity.basic.Entities;
 import brooklyn.entity.basic.EntityInternal;
+import brooklyn.entity.basic.EntityPredicates;
 import brooklyn.entity.basic.SoftwareProcess;
 import brooklyn.entity.effector.EffectorBody;
 import brooklyn.entity.effector.Effectors;
 import brooklyn.entity.proxying.EntitySpec;
 import brooklyn.entity.software.SshEffectorTasks;
+import brooklyn.event.AttributeSensor;
 import brooklyn.event.basic.MapConfigKey;
+import brooklyn.management.TaskAdaptable;
+import brooklyn.management.ha.HighAvailabilityMode;
+import brooklyn.management.ha.ManagementNodeState;
 import brooklyn.util.config.ConfigBag;
+import brooklyn.util.exceptions.Exceptions;
+import brooklyn.util.exceptions.ReferenceWithError;
+import brooklyn.util.guava.Functionals;
 import brooklyn.util.net.Urls;
+import brooklyn.util.repeat.Repeater;
 import brooklyn.util.task.DynamicTasks;
 import brooklyn.util.task.Tasks;
+import brooklyn.util.text.Identifiers;
+import brooklyn.util.time.Duration;
 
+import com.google.common.base.Functions;
 import com.google.common.base.Preconditions;
+import com.google.common.base.Predicate;
+import com.google.common.base.Predicates;
 import com.google.common.reflect.TypeToken;
 
 @SuppressWarnings("serial")
+/** Upgrades a brooklyn node in-place on the box, by creating a child brooklyn node and ensuring it can rebind in HOT_STANDBY;
+ * requires the target node to have persistence enabled 
+ */
 public class BrooklynUpgradeEffector {
 
     private static final Logger log = LoggerFactory.getLogger(BrooklynUpgradeEffector.class);
@@ -70,23 +89,32 @@ public class BrooklynUpgradeEffector {
                 parameters.putAll(extra);
             }
             log.debug(this+" upgrading, using "+parameters);
-            entity().getConfigMap().addToLocalBag(parameters.getAllConfig());
+            
+            // TODO require entity() node state master or hot standby AND require persistence enabled, or a new 'force_attempt_upgrade' parameter to be applied
+            // TODO could have a 'skip_dry_run_upgrade' parameter
+            // TODO could support 'dry_run_only' parameter, with optional resumption tasks (eg new dynamic effector)
 
             // 1 add new brooklyn version entity as child (so uses same machine), with same config apart from things in parameters
             final BrooklynNode dryRunChild = entity().addChild(EntitySpec.create(BrooklynNode.class).configure(parameters.getAllConfig())
                 .displayName("Upgraded Version Dry-Run Node")
-                // TODO enforce hot-standby
+                // force dir and label back to their defaults (do not piggy back on what may have already been installed)
                 .configure(BrooklynNode.INSTALL_DIR, BrooklynNode.INSTALL_DIR.getConfigKey().getDefaultValue())
-                .configure(BrooklynNode.INSTALL_UNIQUE_LABEL, BrooklynNode.INSTALL_UNIQUE_LABEL.getDefaultValue()));
+                .configure(BrooklynNode.INSTALL_UNIQUE_LABEL, "upgrade-tmp-"+Identifiers.makeRandomId(8))
+                .configure(parameters.getAllConfig()));
+            
+            //force this to start as hot-standby
+            String launchCommand = dryRunChild.getConfig(BrooklynNode.LAUNCH_COMMAND);
+            ((EntityInternal)dryRunChild).setConfig(BrooklynNode.LAUNCH_COMMAND, launchCommand + " --highAvailability "+HighAvailabilityMode.HOT_STANDBY);
+            
             Entities.manage(dryRunChild);
-            final String versionUid = dryRunChild.getId();
-            ((EntityInternal)dryRunChild).setDisplayName("Upgraded Version Dry-Run Node ("+versionUid+")");
+            final String dryRunNodeUid = dryRunChild.getId();
+            ((EntityInternal)dryRunChild).setDisplayName("Dry-Run Upgraded Brooklyn Node ("+dryRunNodeUid+")");
 
             DynamicTasks.queue(Effectors.invocation(dryRunChild, BrooklynNode.START, ConfigBag.EMPTY));
             
             // 2 confirm hot standby status
-            // TODO poll, wait for HOT_STANDBY; error if anything else (other than STARTING)
-//            status = dryRun.getAttribute(BrooklynNode.STATUS);
+            DynamicTasks.queue(newWaitForAttributeTask(dryRunChild, BrooklynNode.MANAGEMENT_NODE_STATE, 
+                Predicates.equalTo(ManagementNodeState.HOT_STANDBY), Duration.TWO_MINUTES));
 
             // 3 stop new version
             // 4 stop old version
@@ -100,7 +128,7 @@ public class BrooklynUpgradeEffector {
                 @Override
                 public void run() {
                     String runDir = entity().getAttribute(SoftwareProcess.RUN_DIR);
-                    String bkDir = Urls.mergePaths(runDir, "..", Urls.getBasename(runDir)+"-backups", versionUid);
+                    String bkDir = Urls.mergePaths(runDir, "..", Urls.getBasename(runDir)+"-backups", dryRunNodeUid);
                     String dryRunDir = Preconditions.checkNotNull(dryRunChild.getAttribute(SoftwareProcess.RUN_DIR));
                     log.debug(this+" storing backup of previous version in "+bkDir);
                     DynamicTasks.queue(SshEffectorTasks.ssh(
@@ -113,6 +141,8 @@ public class BrooklynUpgradeEffector {
                 }
             }).build());
 
+            entity().getConfigMap().addToLocalBag(parameters.getAllConfig());
+            
             // 6 start this entity, running the new version
             DynamicTasks.queue(Effectors.invocation(entity(), BrooklynNode.START, ConfigBag.EMPTY));
             
@@ -121,6 +151,41 @@ public class BrooklynUpgradeEffector {
             
             return null;
         }
+
     }
-    
+
+    private static class WaitForRepeaterCallable implements Callable<Boolean> {
+        protected Repeater repeater;
+        protected boolean requireTrue;
+
+        public WaitForRepeaterCallable(Repeater repeater, boolean requireTrue) {
+            this.repeater = repeater;
+            this.requireTrue = requireTrue;
+        }
+
+        @Override
+        public Boolean call() {
+            ReferenceWithError<Boolean> result = repeater.runKeepingError();
+            if (Boolean.TRUE.equals(result.getWithoutError()))
+                return true;
+            if (result.hasError()) 
+                throw Exceptions.propagate(result.getError());
+            if (requireTrue)
+                throw new IllegalStateException("timeout - "+repeater.getDescription());
+            return false;
+        }
+    }
+
+    private static <T> TaskAdaptable<Boolean> newWaitForAttributeTask(Entity node, AttributeSensor<T> sensor, Predicate<T> condition, Duration timeout) {
+        return awaiting( Repeater.create("waiting on "+node+" "+sensor.getName()+" "+condition)
+                    .backoff(Duration.millis(10), 1.5, Duration.millis(200))
+                    .limitTimeTo(timeout==null ? Duration.PRACTICALLY_FOREVER : timeout)
+                    .until(Functionals.callable(Functions.forPredicate(EntityPredicates.attributeSatisfies(sensor, condition)), node)),
+                    true);
+    }
+
+    private static TaskAdaptable<Boolean> awaiting(Repeater repeater, boolean requireTrue) {
+        return Tasks.<Boolean>builder().name(repeater.getDescription()).body(new WaitForRepeaterCallable(repeater, requireTrue)).build();
+    }
+
 }

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/44382b7c/software/base/src/test/resources/brooklyn/entity/brooklynnode/brooklyn-node.yaml
----------------------------------------------------------------------
diff --git a/software/base/src/test/resources/brooklyn/entity/brooklynnode/brooklyn-node.yaml b/software/base/src/test/resources/brooklyn/entity/brooklynnode/brooklyn-node.yaml
index a53e9de..9e6300f 100644
--- a/software/base/src/test/resources/brooklyn/entity/brooklynnode/brooklyn-node.yaml
+++ b/software/base/src/test/resources/brooklyn/entity/brooklynnode/brooklyn-node.yaml
@@ -20,7 +20,8 @@
 services:
 - type: brooklyn.entity.brooklynnode.BrooklynNode
   ## to use a local file, specify something such as the following:
-  # downloadUrl: ~/.m2/repository/io/brooklyn/brooklyn-dist/0.7.0-SNAPSHOT/brooklyn-dist-0.7.0-SNAPSHOT-dist.tar.gz
+  downloadUrl: file:///Users/alex/.m2/repository/org/apache/brooklyn/brooklyn-dist/0.7.0-SNAPSHOT/brooklyn-dist-0.7.0-SNAPSHOT-dist.tar.gz
   # downloadUrl: file:///tmp/brooklyn-dist-0.7.0-SNAPSHOT-dist.tar.gz
 
+## NB if deploying to a remote machine you must also supply management{Username,Password} and a brooklyn properties with those values set 
 location: localhost

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/44382b7c/utils/common/src/main/java/brooklyn/util/repeat/Repeater.java
----------------------------------------------------------------------
diff --git a/utils/common/src/main/java/brooklyn/util/repeat/Repeater.java b/utils/common/src/main/java/brooklyn/util/repeat/Repeater.java
index 1aafc1f..1e3e188 100644
--- a/utils/common/src/main/java/brooklyn/util/repeat/Repeater.java
+++ b/utils/common/src/main/java/brooklyn/util/repeat/Repeater.java
@@ -382,4 +382,9 @@ public class Repeater {
             Time.sleep(delayThisIteration);
         }
     }
+    
+    public String getDescription() {
+        return description;
+    }
+    
 }


[16/29] git commit: fix merge conflicts

Posted by al...@apache.org.
fix merge conflicts


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

Branch: refs/heads/master
Commit: 7f09a80e8b4d54fa75798326b2e9e88f8222a4b0
Parents: ec3701a
Author: Alex Heneveld <al...@cloudsoftcorp.com>
Authored: Mon Oct 27 06:48:32 2014 -0700
Committer: Alex Heneveld <al...@cloudsoftcorp.com>
Committed: Fri Oct 31 09:38:20 2014 -0500

----------------------------------------------------------------------
 core/src/main/java/brooklyn/entity/basic/EntityPredicates.java  | 3 +++
 .../entity/brooklynnode/effector/CallbackEntityHttpClient.java  | 5 +++++
 2 files changed, 8 insertions(+)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/7f09a80e/core/src/main/java/brooklyn/entity/basic/EntityPredicates.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/entity/basic/EntityPredicates.java b/core/src/main/java/brooklyn/entity/basic/EntityPredicates.java
index 0531441..b81b835 100644
--- a/core/src/main/java/brooklyn/entity/basic/EntityPredicates.java
+++ b/core/src/main/java/brooklyn/entity/basic/EntityPredicates.java
@@ -206,6 +206,9 @@ public class EntityPredicates {
         };
     }
     
+    public static <T> Predicate<Entity> attributeNotEqualTo(final AttributeSensor<T> attribute, final T val) {
+        return attributeSatisfies(attribute, Predicates.not(Predicates.equalTo(val)));
+    }
 
     // ---------------------------
 

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/7f09a80e/software/base/src/test/java/brooklyn/entity/brooklynnode/effector/CallbackEntityHttpClient.java
----------------------------------------------------------------------
diff --git a/software/base/src/test/java/brooklyn/entity/brooklynnode/effector/CallbackEntityHttpClient.java b/software/base/src/test/java/brooklyn/entity/brooklynnode/effector/CallbackEntityHttpClient.java
index 8c8004b..78eef1e 100644
--- a/software/base/src/test/java/brooklyn/entity/brooklynnode/effector/CallbackEntityHttpClient.java
+++ b/software/base/src/test/java/brooklyn/entity/brooklynnode/effector/CallbackEntityHttpClient.java
@@ -87,4 +87,9 @@ public class CallbackEntityHttpClient implements EntityHttpClient {
         String result = callback.apply(new Request(entity, HttpPost.METHOD_NAME, path, formParams));
         return new HttpToolResponse(HttpStatus.SC_OK, Collections.<String, List<String>>emptyMap(), result.getBytes(), 0, 0, 0);
     }
+    
+    @Override
+    public HttpToolResponse delete(String path, Map<String, String> headers) {
+        throw new IllegalStateException("Method call not expected");
+    }
 }


[27/29] git commit: more code review for brooklyn upgrade

Posted by al...@apache.org.
    more code review for brooklyn upgrade

    call the class RuntimeTimeoutException, rename methods to Duration.{upper,lower}Bound, and fix reference to onUnmanaged


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

Branch: refs/heads/master
Commit: 724ee5787850f00dd4e5adbaf4c9dbf645de13ac
Parents: d3f6e34
Author: Alex Heneveld <al...@cloudsoftcorp.com>
Authored: Thu Oct 30 09:06:18 2014 -0500
Committer: Alex Heneveld <al...@cloudsoftcorp.com>
Committed: Fri Oct 31 09:39:51 2014 -0500

----------------------------------------------------------------------
 .../rebind/PeriodicDeltaChangeListener.java     |  6 ++--
 .../event/basic/DependentConfiguration.java     | 35 ++++++++++---------
 .../test/java/brooklyn/util/task/TasksTest.java |  8 ++---
 .../exceptions/RuntimeTimeoutException.java     | 36 ++++++++++++++++++++
 .../util/exceptions/TimeoutException.java       | 36 --------------------
 .../main/java/brooklyn/util/time/Duration.java  | 15 ++++++--
 6 files changed, 75 insertions(+), 61 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/724ee578/core/src/main/java/brooklyn/entity/rebind/PeriodicDeltaChangeListener.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/entity/rebind/PeriodicDeltaChangeListener.java b/core/src/main/java/brooklyn/entity/rebind/PeriodicDeltaChangeListener.java
index 9b71633..3ae6916 100644
--- a/core/src/main/java/brooklyn/entity/rebind/PeriodicDeltaChangeListener.java
+++ b/core/src/main/java/brooklyn/entity/rebind/PeriodicDeltaChangeListener.java
@@ -194,13 +194,13 @@ public class PeriodicDeltaChangeListener implements ChangeListener {
             CountdownTimer expiry = timeout.countdownTimer();
             scheduledTask.cancel(false);
             try {
-                waitForPendingComplete(expiry.getDurationRemaining().minimum(Duration.ZERO).add(graceTimeoutForSubsequentOperations));
+                waitForPendingComplete(expiry.getDurationRemaining().lowerBound(Duration.ZERO).add(graceTimeoutForSubsequentOperations));
             } catch (Exception e) {
                 throw Exceptions.propagate(e);
             }
-            scheduledTask.blockUntilEnded(expiry.getDurationRemaining().minimum(Duration.ZERO).add(graceTimeoutForSubsequentOperations));
+            scheduledTask.blockUntilEnded(expiry.getDurationRemaining().lowerBound(Duration.ZERO).add(graceTimeoutForSubsequentOperations));
             scheduledTask.cancel(true);
-            boolean reallyEnded = Tasks.blockUntilInternalTasksEnded(scheduledTask, expiry.getDurationRemaining().minimum(Duration.ZERO).add(graceTimeoutForSubsequentOperations));
+            boolean reallyEnded = Tasks.blockUntilInternalTasksEnded(scheduledTask, expiry.getDurationRemaining().lowerBound(Duration.ZERO).add(graceTimeoutForSubsequentOperations));
             if (!reallyEnded) {
                 LOG.warn("Persistence tasks took too long to complete when stopping persistence (ignoring): "+scheduledTask);
             }

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/724ee578/core/src/main/java/brooklyn/event/basic/DependentConfiguration.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/event/basic/DependentConfiguration.java b/core/src/main/java/brooklyn/event/basic/DependentConfiguration.java
index 51af110..b1d1526 100644
--- a/core/src/main/java/brooklyn/event/basic/DependentConfiguration.java
+++ b/core/src/main/java/brooklyn/event/basic/DependentConfiguration.java
@@ -60,7 +60,7 @@ import brooklyn.util.collections.MutableMap;
 import brooklyn.util.exceptions.CompoundRuntimeException;
 import brooklyn.util.exceptions.Exceptions;
 import brooklyn.util.exceptions.NotManagedException;
-import brooklyn.util.exceptions.TimeoutException;
+import brooklyn.util.exceptions.RuntimeTimeoutException;
 import brooklyn.util.guava.Functionals;
 import brooklyn.util.guava.Maybe;
 import brooklyn.util.task.BasicExecutionContext;
@@ -189,16 +189,16 @@ public class DependentConfiguration {
          * now find a different problem. */
         private final static boolean DEFAULT_IGNORE_UNMANAGED = false;
         
-        final Entity source;
-        final AttributeSensor<T> sensor;
-        final Predicate<? super T> ready;
-        final List<AttributeAndSensorCondition<?>> abortSensorConditions;
-        final String blockingDetails;
-        final Function<? super T,? extends V> postProcess;
-        final Duration timeout;
-        final Maybe<V> onTimeout;
-        final boolean ignoreUnmanaged;
-        final Maybe<V> onUnmanaged;
+        protected final Entity source;
+        protected final AttributeSensor<T> sensor;
+        protected final Predicate<? super T> ready;
+        protected final List<AttributeAndSensorCondition<?>> abortSensorConditions;
+        protected final String blockingDetails;
+        protected final Function<? super T,? extends V> postProcess;
+        protected final Duration timeout;
+        protected final Maybe<V> onTimeout;
+        protected final boolean ignoreUnmanaged;
+        protected final Maybe<V> onUnmanaged;
         // TODO onError Continue / Throw / Return(V)
         
         protected WaitInTaskForAttributeReady(Builder<T, V> builder) {
@@ -311,7 +311,7 @@ public class DependentConfiguration {
                         }
                         if (timer.isExpired()) {
                             if (onTimeout.isPresent()) return onTimeout.get();
-                            throw new TimeoutException("Unsatisfied after "+Duration.sinceUtc(start));
+                            throw new RuntimeTimeoutException("Unsatisfied after "+Duration.sinceUtc(start));
                         }
                     }
 
@@ -328,14 +328,17 @@ public class DependentConfiguration {
                     }
 
                     // check any subscribed values which have come in first
-                    while (!publishedValues.isEmpty()) {
-                        synchronized (publishedValues) { value = publishedValues.pop(); }
+                    while (true) {
+                        synchronized (publishedValues) {
+                            if (publishedValues.isEmpty()) break;
+                            value = publishedValues.pop(); 
+                        }
                         if (ready(value)) break;
                     }
 
                     // if unmanaged then ignore the other abort conditions
                     if (!ignoreUnmanaged && Entities.isNoLongerManaged(entity)) {
-                        if (onTimeout.isPresent()) return onTimeout.get();
+                        if (onUnmanaged.isPresent()) return onUnmanaged.get();
                         throw new NotManagedException(entity);                        
                     }
                     
@@ -343,7 +346,7 @@ public class DependentConfiguration {
                         throw new CompoundRuntimeException("Aborted waiting for ready from "+source+" "+sensor, abortionExceptions);
                     }
 
-                    nextPeriod = nextPeriod.times(2).maximum(maxPeriod);
+                    nextPeriod = nextPeriod.times(2).upperBound(maxPeriod);
                 }
                 if (LOG.isDebugEnabled()) LOG.debug("Attribute-ready for {} in entity {}", sensor, source);
                 return postProcess(value);

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/724ee578/core/src/test/java/brooklyn/util/task/TasksTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/brooklyn/util/task/TasksTest.java b/core/src/test/java/brooklyn/util/task/TasksTest.java
index 68aa2ea..d5e2bb4 100644
--- a/core/src/test/java/brooklyn/util/task/TasksTest.java
+++ b/core/src/test/java/brooklyn/util/task/TasksTest.java
@@ -136,16 +136,16 @@ public class TasksTest extends BrooklynAppUnitTestSupport {
         
         t = Tasks.requiring(Repeater.create().until(Callables.returning(true)).every(Duration.millis(1))).build();
         app.getExecutionContext().submit(t);
-        t.get(Duration.ONE_SECOND);
+        t.get(Duration.TEN_SECONDS);
         
         t = Tasks.testing(Repeater.create().until(Callables.returning(true)).every(Duration.millis(1))).build();
         app.getExecutionContext().submit(t);
-        Assert.assertEquals(t.get(Duration.ONE_SECOND), true);
+        Assert.assertEquals(t.get(Duration.TEN_SECONDS), true);
         
         t = Tasks.requiring(Repeater.create().until(Callables.returning(false)).limitIterationsTo(2).every(Duration.millis(1))).build();
         app.getExecutionContext().submit(t);
         try {
-            t.get(Duration.ONE_SECOND);
+            t.get(Duration.TEN_SECONDS);
             Assert.fail("Should have failed");
         } catch (Exception e) {
             // expected
@@ -153,7 +153,7 @@ public class TasksTest extends BrooklynAppUnitTestSupport {
 
         t = Tasks.testing(Repeater.create().until(Callables.returning(false)).limitIterationsTo(2).every(Duration.millis(1))).build();
         app.getExecutionContext().submit(t);
-        Assert.assertEquals(t.get(Duration.ONE_SECOND), false);
+        Assert.assertEquals(t.get(Duration.TEN_SECONDS), false);
     }
 
 }

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/724ee578/utils/common/src/main/java/brooklyn/util/exceptions/RuntimeTimeoutException.java
----------------------------------------------------------------------
diff --git a/utils/common/src/main/java/brooklyn/util/exceptions/RuntimeTimeoutException.java b/utils/common/src/main/java/brooklyn/util/exceptions/RuntimeTimeoutException.java
new file mode 100644
index 0000000..865ae73
--- /dev/null
+++ b/utils/common/src/main/java/brooklyn/util/exceptions/RuntimeTimeoutException.java
@@ -0,0 +1,36 @@
+/*
+ * 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 brooklyn.util.exceptions;
+
+public class RuntimeTimeoutException extends IllegalStateException {
+
+    private static final long serialVersionUID = -3359163414517503809L;
+
+    public RuntimeTimeoutException() {
+        super("timeout");
+    }
+    
+    public RuntimeTimeoutException(String message) {
+        super(message);
+    }
+    
+    public RuntimeTimeoutException(String message, Throwable cause) {
+        super(message, cause);
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/724ee578/utils/common/src/main/java/brooklyn/util/exceptions/TimeoutException.java
----------------------------------------------------------------------
diff --git a/utils/common/src/main/java/brooklyn/util/exceptions/TimeoutException.java b/utils/common/src/main/java/brooklyn/util/exceptions/TimeoutException.java
deleted file mode 100644
index c31512f..0000000
--- a/utils/common/src/main/java/brooklyn/util/exceptions/TimeoutException.java
+++ /dev/null
@@ -1,36 +0,0 @@
-/*
- * 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 brooklyn.util.exceptions;
-
-public class TimeoutException extends IllegalStateException {
-
-    private static final long serialVersionUID = -3359163414517503809L;
-
-    public TimeoutException() {
-        super("timeout");
-    }
-    
-    public TimeoutException(String message) {
-        super(message);
-    }
-    
-    public TimeoutException(String message, Throwable cause) {
-        super(message, cause);
-    }
-}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/724ee578/utils/common/src/main/java/brooklyn/util/time/Duration.java
----------------------------------------------------------------------
diff --git a/utils/common/src/main/java/brooklyn/util/time/Duration.java b/utils/common/src/main/java/brooklyn/util/time/Duration.java
index c6622ba..3950bc7 100644
--- a/utils/common/src/main/java/brooklyn/util/time/Duration.java
+++ b/utils/common/src/main/java/brooklyn/util/time/Duration.java
@@ -282,15 +282,26 @@ public class Duration implements Comparable<Duration>, Serializable {
     }
 
     /** returns the larger of this value or the argument */
-    public Duration minimum(Duration alternateMinimumValue) {
+    public Duration lowerBound(Duration alternateMinimumValue) {
         if (isShorterThan(alternateMinimumValue)) return alternateMinimumValue;
         return this;
     }
 
     /** returns the smaller of this value or the argument */
-    public Duration maximum(Duration alternateMaximumValue) {
+    public Duration upperBound(Duration alternateMaximumValue) {
         if (isLongerThan(alternateMaximumValue)) return alternateMaximumValue;
         return this;
     }
 
+    /** @deprecated since 0.7.0 use {@link #lowerBound(Duration)} */ @Deprecated
+    public Duration minimum(Duration alternateMinimumValue) {
+        return lowerBound(alternateMinimumValue);
+    }
+
+    /** @deprecated since 0.7.0 use {@link #upperBound(Duration)} */ @Deprecated
+    /** returns the smaller of this value or the argument */
+    public Duration maximum(Duration alternateMaximumValue) {
+        return upperBound(alternateMaximumValue);
+    }
+    
 }


[26/29] git commit: make test which requires being online an integration test, and tidy reporting of errors

Posted by al...@apache.org.
make test which requires being online an integration test, and tidy reporting of errors


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

Branch: refs/heads/master
Commit: 139822a9c3ac5c38ef52ab0ddbfac88a509ad46d
Parents: 4de0dc9
Author: Alex Heneveld <al...@cloudsoftcorp.com>
Authored: Thu Oct 30 22:49:16 2014 -0500
Committer: Alex Heneveld <al...@cloudsoftcorp.com>
Committed: Fri Oct 31 09:39:51 2014 -0500

----------------------------------------------------------------------
 .../main/java/brooklyn/location/basic/HostLocationResolver.java | 2 +-
 .../java/brooklyn/location/basic/HostLocationResolverTest.java  | 5 +++++
 .../common/src/main/java/brooklyn/util/text/KeyValueParser.java | 3 +--
 3 files changed, 7 insertions(+), 3 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/139822a9/core/src/main/java/brooklyn/location/basic/HostLocationResolver.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/location/basic/HostLocationResolver.java b/core/src/main/java/brooklyn/location/basic/HostLocationResolver.java
index 6bd70c1..2bb5b4b 100644
--- a/core/src/main/java/brooklyn/location/basic/HostLocationResolver.java
+++ b/core/src/main/java/brooklyn/location/basic/HostLocationResolver.java
@@ -64,7 +64,7 @@ public class HostLocationResolver extends AbstractLocationResolver {
         Maybe<Location> testResolve = managementContext.getLocationRegistry().resolve(target, false, null);
         if (!testResolve.isPresent()) {
             throw new IllegalArgumentException("Invalid target location '" + target + "' for location '"+HOST+"': "+
-                Exceptions.collapseText( ((Absent<?>)testResolve).getException() ));
+                Exceptions.collapseText( ((Absent<?>)testResolve).getException() ), ((Absent<?>)testResolve).getException());
         }
         
         return managementContext.getLocationManager().createLocation(LocationSpec.create(SingleMachineProvisioningLocation.class)

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/139822a9/core/src/test/java/brooklyn/location/basic/HostLocationResolverTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/brooklyn/location/basic/HostLocationResolverTest.java b/core/src/test/java/brooklyn/location/basic/HostLocationResolverTest.java
index 8347e55..8ea9df0 100644
--- a/core/src/test/java/brooklyn/location/basic/HostLocationResolverTest.java
+++ b/core/src/test/java/brooklyn/location/basic/HostLocationResolverTest.java
@@ -68,6 +68,11 @@ public class HostLocationResolverTest {
     public void resolveHosts() {
         resolve("host:(\"1.1.1.1\")");
         resolve("host:(\"localhost\")");
+    }
+    
+    @Test(groups="Integration")
+    public void resolveRealHosts() {
+        // must be online to resolve this
         resolve("host:(\"www.foo.com\")");
     }
     

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/139822a9/utils/common/src/main/java/brooklyn/util/text/KeyValueParser.java
----------------------------------------------------------------------
diff --git a/utils/common/src/main/java/brooklyn/util/text/KeyValueParser.java b/utils/common/src/main/java/brooklyn/util/text/KeyValueParser.java
index f2f59db..76a4532 100644
--- a/utils/common/src/main/java/brooklyn/util/text/KeyValueParser.java
+++ b/utils/common/src/main/java/brooklyn/util/text/KeyValueParser.java
@@ -58,11 +58,10 @@ public class KeyValueParser {
         
         StringBuilder result = new StringBuilder();
         for (Map.Entry<String, String> entry : parts.entrySet()) {
+            if (result.length()>0) result.append(", ");
             result.append(tokenizer.quoteToken(entry.getKey()));
             if (entry.getValue() != null) result.append("="+tokenizer.quoteToken(entry.getValue()));
-            result.append(", ");
         }
-        if (result.length() > 0) result.deleteCharAt(result.length()-1);
         return result.toString();
     }
 


[13/29] git commit: increase the brooklyn node poll frequency to every 2s, to prevent saturating it with monitoring, and other minor tunings to BrooklynNode

Posted by al...@apache.org.
increase the brooklyn node poll frequency to every 2s, to prevent saturating it with monitoring, and other minor tunings to BrooklynNode


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

Branch: refs/heads/master
Commit: d94ed9a9e555d771ed0868a7fa78c78bd0fdfee4
Parents: 38c79ad
Author: Alex Heneveld <al...@cloudsoftcorp.com>
Authored: Sun Oct 26 17:41:30 2014 +0000
Committer: Alex Heneveld <al...@cloudsoftcorp.com>
Committed: Fri Oct 31 09:38:20 2014 -0500

----------------------------------------------------------------------
 .../brooklyn/entity/brooklynnode/BrooklynNode.java     | 13 ++++++++++---
 .../brooklyn/entity/brooklynnode/BrooklynNodeImpl.java |  3 ++-
 usage/cli/src/main/java/brooklyn/cli/Main.java         |  6 ++----
 3 files changed, 14 insertions(+), 8 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/d94ed9a9/software/base/src/main/java/brooklyn/entity/brooklynnode/BrooklynNode.java
----------------------------------------------------------------------
diff --git a/software/base/src/main/java/brooklyn/entity/brooklynnode/BrooklynNode.java b/software/base/src/main/java/brooklyn/entity/brooklynnode/BrooklynNode.java
index e125971..a0f0032 100644
--- a/software/base/src/main/java/brooklyn/entity/brooklynnode/BrooklynNode.java
+++ b/software/base/src/main/java/brooklyn/entity/brooklynnode/BrooklynNode.java
@@ -221,14 +221,21 @@ public interface BrooklynNode extends SoftwareProcess, UsesJava {
             "brooklynnode.webconsole.portMapper", "Function for mapping private to public ports, for use in inferring the brooklyn URI", Functions.<Integer>identity());
 
     public static final AttributeSensor<URI> WEB_CONSOLE_URI = new BasicAttributeSensor<URI>(
-        URI.class, "brooklynnode.webconsole.url", "URL of the brooklyn web-console");
+            URI.class, "brooklynnode.webconsole.url", "URL of the brooklyn web-console");
 
     public static final AttributeSensor<ManagementNodeState> MANAGEMENT_NODE_STATE = new BasicAttributeSensor<ManagementNodeState>(
-        ManagementNodeState.class, "brooklynnode.ha.state", "High-availability state of the management node (MASTER, HOT_STANDBY, etc)");
+            ManagementNodeState.class, "brooklynnode.ha.state", "High-availability state of the management node (MASTER, HOT_STANDBY, etc)");
+    
+    public static final ConfigKey<Duration> POLL_PERIOD = ConfigKeys.newConfigKey(Duration.class, "brooklynnode.poll_period",
+            "Frequency to poll for client sensors", Duration.seconds(2));
 
+    @Deprecated
+    /** @deprecated since 0.7.0  this flag is being replaced with the stopWhichAppsOnShutdown flag; 
+     * if truly needed that could be represented here, but in general the hope is to remove all such complexity, 
+     * so the better way if you really do need this is to use {@link #EXTRA_LAUNCH_PARAMETERS} */
     @SetFromFlag("noShutdownOnExit")
     public static final ConfigKey<Boolean> NO_SHUTDOWN_ON_EXIT = ConfigKeys.newBooleanConfigKey("brooklynnode.noshutdownonexit", 
-        "Whether to shutdown entities on exit", false);
+        "Whether to pass the (deprecated) noShutdownOnExit flag to the process", false);
 
     public interface DeployBlueprintEffector {
         ConfigKey<Map<String,Object>> BLUEPRINT_CAMP_PLAN = new MapConfigKey<Object>(Object.class, "blueprintPlan",

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/d94ed9a9/software/base/src/main/java/brooklyn/entity/brooklynnode/BrooklynNodeImpl.java
----------------------------------------------------------------------
diff --git a/software/base/src/main/java/brooklyn/entity/brooklynnode/BrooklynNodeImpl.java b/software/base/src/main/java/brooklyn/entity/brooklynnode/BrooklynNodeImpl.java
index 946d3fd..fac22ee 100644
--- a/software/base/src/main/java/brooklyn/entity/brooklynnode/BrooklynNodeImpl.java
+++ b/software/base/src/main/java/brooklyn/entity/brooklynnode/BrooklynNodeImpl.java
@@ -355,7 +355,7 @@ public class BrooklynNodeImpl extends SoftwareProcessImpl implements BrooklynNod
         if (webConsoleUri != null) {
             httpFeed = HttpFeed.builder()
                     .entity(this)
-                    .period(200)
+                    .period(getConfig(POLL_PERIOD))
                     .baseUri(webConsoleUri)
                     .credentialsIfNotNull(getConfig(MANAGEMENT_USER), getConfig(MANAGEMENT_PASSWORD))
                     .poll(new HttpPollConfig<Boolean>(WEB_CONSOLE_ACCESSIBLE)
@@ -365,6 +365,7 @@ public class BrooklynNodeImpl extends SoftwareProcessImpl implements BrooklynNod
                             .suburl("/v1/server/ha/state")
                             .onSuccess(Functionals.chain(Functionals.chain(HttpValueFunctions.jsonContents(), JsonFunctions.cast(String.class)), Enums.fromStringFunction(ManagementNodeState.class)))
                             .setOnFailureOrException(null))
+                    // TODO sensors for load, size, etc
                     .build();
 
             if (!Lifecycle.RUNNING.equals(getAttribute(SERVICE_STATE_ACTUAL))) {

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/d94ed9a9/usage/cli/src/main/java/brooklyn/cli/Main.java
----------------------------------------------------------------------
diff --git a/usage/cli/src/main/java/brooklyn/cli/Main.java b/usage/cli/src/main/java/brooklyn/cli/Main.java
index f3b9de1..00bf407 100644
--- a/usage/cli/src/main/java/brooklyn/cli/Main.java
+++ b/usage/cli/src/main/java/brooklyn/cli/Main.java
@@ -19,8 +19,6 @@
 package brooklyn.cli;
 
 import static com.google.common.base.Preconditions.checkNotNull;
-
-import brooklyn.management.ha.OsgiManager;
 import groovy.lang.GroovyClassLoader;
 import groovy.lang.GroovyShell;
 import io.airlift.command.Cli;
@@ -66,7 +64,7 @@ import brooklyn.launcher.BrooklynServerDetails;
 import brooklyn.launcher.config.StopWhichAppsOnShutdown;
 import brooklyn.management.ManagementContext;
 import brooklyn.management.ha.HighAvailabilityMode;
-import brooklyn.mementos.BrooklynMementoRawData;
+import brooklyn.management.ha.OsgiManager;
 import brooklyn.rest.security.PasswordHasher;
 import brooklyn.util.ResourceUtils;
 import brooklyn.util.exceptions.Exceptions;
@@ -813,9 +811,9 @@ public class Main extends AbstractMain {
 
     /** method intended for overriding when a different {@link Cli} is desired,
      * or when the subclass wishes to change any of the arguments */
+    @SuppressWarnings("unchecked")
     @Override
     protected CliBuilder<BrooklynCommand> cliBuilder() {
-        @SuppressWarnings({ "unchecked" })
         CliBuilder<BrooklynCommand> builder = Cli.<BrooklynCommand>builder(cliScriptName())
                 .withDescription("Brooklyn Management Service")
                 .withDefaultCommand(InfoCommand.class)


[10/29] git commit: bunch of clean ups - mainly addressing @aledsage's comments on @neykov's cluster upgrade PR, plus misc things i've noticed. needs further testing though.

Posted by al...@apache.org.
bunch of clean ups - mainly addressing @aledsage's comments on @neykov's cluster upgrade PR, plus misc things i've noticed.
needs further testing though.


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

Branch: refs/heads/master
Commit: 7bae4e66342048069b079eb035b492b1c0d000fd
Parents: 634b66b
Author: Alex Heneveld <al...@cloudsoftcorp.com>
Authored: Sat Oct 25 00:32:24 2014 +0100
Committer: Alex Heneveld <al...@cloudsoftcorp.com>
Committed: Fri Oct 31 09:38:19 2014 -0500

----------------------------------------------------------------------
 .../entity/brooklynnode/BrooklynCluster.java    |   4 +-
 .../brooklynnode/BrooklynClusterImpl.java       |  59 ++---
 .../entity/brooklynnode/BrooklynNodeImpl.java   |   3 +-
 .../brooklynnode/BrooklynUpgradeEffector.java   | 214 -------------------
 .../brooklynnode/RemoteEffectorBuilder.java     |  23 +-
 .../BrooklynClusterUpgradeEffectorBody.java     | 203 ++++++++++++++++++
 .../BrooklynNodeUpgradeEffectorBody.java        | 212 ++++++++++++++++++
 .../effector/UpgradeClusterEffectorBody.java    | 199 -----------------
 8 files changed, 460 insertions(+), 457 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/7bae4e66/software/base/src/main/java/brooklyn/entity/brooklynnode/BrooklynCluster.java
----------------------------------------------------------------------
diff --git a/software/base/src/main/java/brooklyn/entity/brooklynnode/BrooklynCluster.java b/software/base/src/main/java/brooklyn/entity/brooklynnode/BrooklynCluster.java
index a3c2157..30a46bd 100644
--- a/software/base/src/main/java/brooklyn/entity/brooklynnode/BrooklynCluster.java
+++ b/software/base/src/main/java/brooklyn/entity/brooklynnode/BrooklynCluster.java
@@ -26,11 +26,11 @@ import brooklyn.entity.effector.Effectors;
 import brooklyn.entity.group.DynamicCluster;
 import brooklyn.entity.proxying.ImplementedBy;
 import brooklyn.event.AttributeSensor;
-import brooklyn.event.basic.BasicAttributeSensor;
+import brooklyn.event.basic.Sensors;
 
 @ImplementedBy(BrooklynClusterImpl.class)
 public interface BrooklynCluster extends DynamicCluster {
-    public static final AttributeSensor<BrooklynNode> MASTER_NODE = new BasicAttributeSensor<BrooklynNode>(
+    public static final AttributeSensor<BrooklynNode> MASTER_NODE = Sensors.newSensor(
             BrooklynNode.class, "brooklyncluster.master", "Pointer to the child node with MASTER state in the cluster");
 
     public interface SelectMasterEffector {

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/7bae4e66/software/base/src/main/java/brooklyn/entity/brooklynnode/BrooklynClusterImpl.java
----------------------------------------------------------------------
diff --git a/software/base/src/main/java/brooklyn/entity/brooklynnode/BrooklynClusterImpl.java b/software/base/src/main/java/brooklyn/entity/brooklynnode/BrooklynClusterImpl.java
index bfaf33c..dc3ee17 100644
--- a/software/base/src/main/java/brooklyn/entity/brooklynnode/BrooklynClusterImpl.java
+++ b/software/base/src/main/java/brooklyn/entity/brooklynnode/BrooklynClusterImpl.java
@@ -26,9 +26,10 @@ import org.slf4j.LoggerFactory;
 
 import brooklyn.entity.Entity;
 import brooklyn.entity.basic.EntityPredicates;
-import brooklyn.entity.basic.ServiceStateLogic.ServiceNotUpLogic;
+import brooklyn.entity.basic.ServiceStateLogic;
+import brooklyn.entity.basic.ServiceStateLogic.ServiceProblemsLogic;
 import brooklyn.entity.brooklynnode.effector.SelectMasterEffectorBody;
-import brooklyn.entity.brooklynnode.effector.UpgradeClusterEffectorBody;
+import brooklyn.entity.brooklynnode.effector.BrooklynClusterUpgradeEffectorBody;
 import brooklyn.entity.group.DynamicClusterImpl;
 import brooklyn.event.feed.function.FunctionFeed;
 import brooklyn.event.feed.function.FunctionPollConfig;
@@ -41,69 +42,69 @@ import com.google.common.collect.Iterables;
 public class BrooklynClusterImpl extends DynamicClusterImpl implements BrooklynCluster {
 
     private static final String MSG_NO_MASTER = "No master node in cluster";
+    private static final String MSG_TOO_MANY_MASTERS = "Too many master nodes in cluster";
 
     private static final Logger LOG = LoggerFactory.getLogger(BrooklynClusterImpl.class);
 
     //TODO set MEMBER_SPEC
 
+    @SuppressWarnings("unused")
     private FunctionFeed scanMaster;
 
     @Override
     public void init() {
         super.init();
         getMutableEntityType().addEffector(SelectMasterEffectorBody.SELECT_MASTER);
-        getMutableEntityType().addEffector(UpgradeClusterEffectorBody.UPGRADE_CLUSTER);
+        getMutableEntityType().addEffector(BrooklynClusterUpgradeEffectorBody.UPGRADE_CLUSTER);
 
-        ServiceNotUpLogic.updateNotUpIndicator(this, MASTER_NODE, MSG_NO_MASTER);
+        ServiceProblemsLogic.updateProblemsIndicator(this, MASTER_NODE, MSG_NO_MASTER);
         scanMaster = FunctionFeed.builder()
                 .entity(this)
                 .poll(new FunctionPollConfig<Object, BrooklynNode>(MASTER_NODE)
                         .period(Duration.ONE_SECOND)
-                        .callable(new Callable<BrooklynNode>() {
-                                @Override
-                                public BrooklynNode call() throws Exception {
-                                    return findMasterChild();
-                                }
-                            }))
+                        .callable(new MasterChildFinder()))
                 .build();
     }
 
+    private final class MasterChildFinder implements Callable<BrooklynNode> {
+        @Override
+        public BrooklynNode call() throws Exception {
+            return findMasterChild();
+        }
+    }
+
     private BrooklynNode findMasterChild() {
         Collection<Entity> masters = FluentIterable.from(getMembers())
                 .filter(EntityPredicates.attributeEqualTo(BrooklynNode.MANAGEMENT_NODE_STATE, ManagementNodeState.MASTER))
                 .toList();
 
         if (masters.size() == 0) {
-            ServiceNotUpLogic.updateNotUpIndicator(this, MASTER_NODE, MSG_NO_MASTER);
+            ServiceProblemsLogic.updateProblemsIndicator(this, MASTER_NODE, MSG_NO_MASTER);
             return null;
+            
         } else if (masters.size() == 1) {
-            ServiceNotUpLogic.clearNotUpIndicator(this, MASTER_NODE);
+            ServiceStateLogic.ServiceProblemsLogic.clearProblemsIndicator(this, MASTER_NODE);
             return (BrooklynNode)Iterables.getOnlyElement(masters);
+            
         } else if (masters.size() == 2) {
-            //Probably hit a window where we have a new master
+            LOG.warn("Two masters detected, probably a handover just occured: " + masters);
+
+            //Don't clearProblemsIndicator - if there were no masters previously why have two now.
+            //But also don't set it. Probably hit a window where we have a new master
             //its BrooklynNode picked it up, but the BrooklynNode
             //for the old master hasn't refreshed its state yet.
             //Just pick one of them, should sort itself out in next update.
-            LOG.warn("Two masters detected, probably a handover just occured: " + masters);
-
-            //Don't clearNotUpIndicator - if there were no masters previously
-            //why have two now.
-
-            return (BrooklynNode)Iterables.getOnlyElement(masters);
+            
+            //TODO Do set such indicator if this continues for an extended period of time
+            
+            return (BrooklynNode)masters.iterator().next();
+            
         } else {
-            //Set on fire?
+            ServiceProblemsLogic.updateProblemsIndicator(this, MASTER_NODE, MSG_TOO_MANY_MASTERS);
             String msg = "Multiple (>=3) master nodes in cluster: " + masters;
             LOG.error(msg);
             throw new IllegalStateException(msg);
-        }
-    }
-
-    @Override
-    public void stop() {
-        super.stop();
-
-        if (scanMaster != null && scanMaster.isActivated()) {
-            scanMaster.stop();
+            
         }
     }
 

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/7bae4e66/software/base/src/main/java/brooklyn/entity/brooklynnode/BrooklynNodeImpl.java
----------------------------------------------------------------------
diff --git a/software/base/src/main/java/brooklyn/entity/brooklynnode/BrooklynNodeImpl.java b/software/base/src/main/java/brooklyn/entity/brooklynnode/BrooklynNodeImpl.java
index 6aa2420..5bdbe2d 100644
--- a/software/base/src/main/java/brooklyn/entity/brooklynnode/BrooklynNodeImpl.java
+++ b/software/base/src/main/java/brooklyn/entity/brooklynnode/BrooklynNodeImpl.java
@@ -39,6 +39,7 @@ import brooklyn.entity.basic.Lifecycle;
 import brooklyn.entity.basic.ServiceStateLogic;
 import brooklyn.entity.basic.ServiceStateLogic.ServiceNotUpLogic;
 import brooklyn.entity.basic.SoftwareProcessImpl;
+import brooklyn.entity.brooklynnode.effector.BrooklynNodeUpgradeEffectorBody;
 import brooklyn.entity.brooklynnode.effector.SetHAModeEffectorBody;
 import brooklyn.entity.brooklynnode.effector.SetHAPriorityEffectorBody;
 import brooklyn.entity.effector.EffectorBody;
@@ -103,7 +104,7 @@ public class BrooklynNodeImpl extends SoftwareProcessImpl implements BrooklynNod
         getMutableEntityType().addEffector(StopNodeAndKillAppsEffectorBody.STOP_NODE_AND_KILL_APPS);
         getMutableEntityType().addEffector(SetHAPriorityEffectorBody.SET_HA_PRIORITY);
         getMutableEntityType().addEffector(SetHAModeEffectorBody.SET_HA_MODE);
-        getMutableEntityType().addEffector(BrooklynUpgradeEffector.UPGRADE);
+        getMutableEntityType().addEffector(BrooklynNodeUpgradeEffectorBody.UPGRADE);
     }
 
     @Override

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/7bae4e66/software/base/src/main/java/brooklyn/entity/brooklynnode/BrooklynUpgradeEffector.java
----------------------------------------------------------------------
diff --git a/software/base/src/main/java/brooklyn/entity/brooklynnode/BrooklynUpgradeEffector.java b/software/base/src/main/java/brooklyn/entity/brooklynnode/BrooklynUpgradeEffector.java
deleted file mode 100644
index 3dc2015..0000000
--- a/software/base/src/main/java/brooklyn/entity/brooklynnode/BrooklynUpgradeEffector.java
+++ /dev/null
@@ -1,214 +0,0 @@
-/*
- * 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 brooklyn.entity.brooklynnode;
-
-import java.util.Map;
-import java.util.concurrent.Callable;
-
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import brooklyn.config.ConfigKey;
-import brooklyn.entity.Effector;
-import brooklyn.entity.Entity;
-import brooklyn.entity.basic.Entities;
-import brooklyn.entity.basic.EntityInternal;
-import brooklyn.entity.basic.EntityPredicates;
-import brooklyn.entity.basic.SoftwareProcess;
-import brooklyn.entity.effector.EffectorBody;
-import brooklyn.entity.effector.Effectors;
-import brooklyn.entity.proxying.EntitySpec;
-import brooklyn.entity.software.SshEffectorTasks;
-import brooklyn.event.AttributeSensor;
-import brooklyn.event.basic.MapConfigKey;
-import brooklyn.management.TaskAdaptable;
-import brooklyn.management.ha.HighAvailabilityMode;
-import brooklyn.management.ha.ManagementNodeState;
-import brooklyn.util.config.ConfigBag;
-import brooklyn.util.exceptions.Exceptions;
-import brooklyn.util.exceptions.ReferenceWithError;
-import brooklyn.util.guava.Functionals;
-import brooklyn.util.net.Urls;
-import brooklyn.util.repeat.Repeater;
-import brooklyn.util.task.DynamicTasks;
-import brooklyn.util.task.Tasks;
-import brooklyn.util.text.Identifiers;
-import brooklyn.util.text.Strings;
-import brooklyn.util.time.Duration;
-
-import com.google.common.base.Functions;
-import com.google.common.base.Preconditions;
-import com.google.common.base.Predicate;
-import com.google.common.base.Predicates;
-import com.google.common.reflect.TypeToken;
-
-@SuppressWarnings("serial")
-/** Upgrades a brooklyn node in-place on the box, 
- * by creating a child brooklyn node and ensuring it can rebind in HOT_STANDBY
- * <p>
- * Requires the target node to have persistence enabled. 
- */
-public class BrooklynUpgradeEffector {
-
-    private static final Logger log = LoggerFactory.getLogger(BrooklynUpgradeEffector.class);
-    
-    public static final ConfigKey<String> DOWNLOAD_URL = BrooklynNode.DOWNLOAD_URL.getConfigKey();
-    public static final ConfigKey<Map<String,Object>> EXTRA_CONFIG = MapConfigKey.builder(new TypeToken<Map<String,Object>>() {}).name("extraConfig").description("Additional new config to set on this entity as part of upgrading").build();
-
-    public static final Effector<Void> UPGRADE = Effectors.effector(Void.class, "upgrade")
-        .description("Changes the Brooklyn build used to run this node, by spawning a dry-run node then copying the installed files across. "
-            + "This node must be running for persistence for in-place upgrading to work.")
-        .parameter(BrooklynNode.SUGGESTED_VERSION).parameter(DOWNLOAD_URL).parameter(EXTRA_CONFIG)
-        .impl(new UpgradeImpl()).build();
-    
-    public static class UpgradeImpl extends EffectorBody<Void> {
-        @Override
-        public Void call(ConfigBag parametersO) {
-            if (!isPersistenceModeEnabled(entity())) {
-                // would could try a `forcePersistNow`, but that's sloppy; 
-                // for now, require HA/persistence for upgrading 
-                DynamicTasks.queue( Tasks.warning("Persistence does not appear to be enabled at this node. "
-                    + "In-place upgrade is unlikely to succeed.", null) );
-            }
-            
-            ConfigBag parameters = ConfigBag.newInstanceCopying(parametersO);
-            
-            /*
-             * all parameters are passed to children, apart from EXTRA_CONFIG
-             * whose value (as a map) is so passed; it provides an easy way to set extra config in the gui.
-             * (IOW a key-value mapping can be passed either inside EXTRA_CONFIG or as a sibling to EXTRA_CONFIG)  
-             */
-            if (parameters.containsKey(EXTRA_CONFIG)) {
-                Map<String, Object> extra = parameters.get(EXTRA_CONFIG);
-                parameters.remove(EXTRA_CONFIG);
-                parameters.putAll(extra);
-            }
-            log.debug(this+" upgrading, using "+parameters);
-            
-            // TODO require entity() node state master or hot standby AND require persistence enabled, or a new 'force_attempt_upgrade' parameter to be applied
-            // TODO could have a 'skip_dry_run_upgrade' parameter
-            // TODO could support 'dry_run_only' parameter, with optional resumption tasks (eg new dynamic effector)
-
-            // 1 add new brooklyn version entity as child (so uses same machine), with same config apart from things in parameters
-            final BrooklynNode dryRunChild = entity().addChild(EntitySpec.create(BrooklynNode.class).configure(parameters.getAllConfig())
-                .displayName("Upgraded Version Dry-Run Node")
-                // force dir and label back to their defaults (do not piggy back on what may have already been installed)
-                .configure(BrooklynNode.INSTALL_DIR, BrooklynNode.INSTALL_DIR.getConfigKey().getDefaultValue())
-                .configure(BrooklynNode.INSTALL_UNIQUE_LABEL, "upgrade-tmp-"+Identifiers.makeRandomId(8))
-                .configure(parameters.getAllConfig()));
-            
-            //force this to start as hot-standby
-            String launchParameters = dryRunChild.getConfig(BrooklynNode.EXTRA_LAUNCH_PARAMETERS);
-            if (Strings.isBlank(launchParameters)) launchParameters = "";
-            else launchParameters += " ";
-            launchParameters += "--highAvailability "+HighAvailabilityMode.HOT_STANDBY;
-            ((EntityInternal)dryRunChild).setConfig(BrooklynNode.EXTRA_LAUNCH_PARAMETERS, launchParameters);
-            
-            Entities.manage(dryRunChild);
-            final String dryRunNodeUid = dryRunChild.getId();
-            ((EntityInternal)dryRunChild).setDisplayName("Dry-Run Upgraded Brooklyn Node ("+dryRunNodeUid+")");
-
-            DynamicTasks.queue(Effectors.invocation(dryRunChild, BrooklynNode.START, ConfigBag.EMPTY));
-            
-            // 2 confirm hot standby status
-            DynamicTasks.queue(newWaitForAttributeTask(dryRunChild, BrooklynNode.MANAGEMENT_NODE_STATE, 
-                Predicates.equalTo(ManagementNodeState.HOT_STANDBY), Duration.TWO_MINUTES));
-
-            // 3 stop new version
-            // 4 stop old version
-            DynamicTasks.queue(Tasks.builder().name("shutdown original and transient nodes")
-                .add(Effectors.invocation(dryRunChild, BrooklynNode.SHUTDOWN, ConfigBag.EMPTY))
-                .add(Effectors.invocation(entity(), BrooklynNode.SHUTDOWN, ConfigBag.EMPTY))
-                .build());
-            
-            // 5 move old files, and move new files
-            DynamicTasks.queue(Tasks.builder().name("setup new version").body(new Runnable() {
-                @Override
-                public void run() {
-                    String runDir = entity().getAttribute(SoftwareProcess.RUN_DIR);
-                    String bkDir = Urls.mergePaths(runDir, "..", Urls.getBasename(runDir)+"-backups", dryRunNodeUid);
-                    String dryRunDir = Preconditions.checkNotNull(dryRunChild.getAttribute(SoftwareProcess.RUN_DIR));
-                    log.debug(this+" storing backup of previous version in "+bkDir);
-                    DynamicTasks.queue(SshEffectorTasks.ssh(
-                        "cd "+runDir,
-                        "mkdir -p "+bkDir,
-                        "mv * "+bkDir,
-                        "cd "+dryRunDir,
-                        "mv * "+runDir
-                        ).summary("move files"));
-                }
-            }).build());
-
-            entity().getConfigMap().addToLocalBag(parameters.getAllConfig());
-            
-            // 6 start this entity, running the new version
-            DynamicTasks.queue(Effectors.invocation(entity(), BrooklynNode.START, ConfigBag.EMPTY));
-            
-            DynamicTasks.waitForLast();
-            Entities.unmanage(dryRunChild);
-            
-            return null;
-        }
-
-        private boolean isPersistenceModeEnabled(EntityInternal entity) {
-            // TODO when there are PERSIST* options in BrooklynNode, look at them here!
-            // or, better, have a sensor for persistence
-            String params = entity.getConfig(BrooklynNode.EXTRA_LAUNCH_PARAMETERS);
-            if (params==null) return false;
-            if (params.indexOf("persist")==0) return false;
-            return true;
-        }
-
-    }
-
-    private static class WaitForRepeaterCallable implements Callable<Boolean> {
-        protected Repeater repeater;
-        protected boolean requireTrue;
-
-        public WaitForRepeaterCallable(Repeater repeater, boolean requireTrue) {
-            this.repeater = repeater;
-            this.requireTrue = requireTrue;
-        }
-
-        @Override
-        public Boolean call() {
-            ReferenceWithError<Boolean> result = repeater.runKeepingError();
-            if (Boolean.TRUE.equals(result.getWithoutError()))
-                return true;
-            if (result.hasError()) 
-                throw Exceptions.propagate(result.getError());
-            if (requireTrue)
-                throw new IllegalStateException("timeout - "+repeater.getDescription());
-            return false;
-        }
-    }
-
-    private static <T> TaskAdaptable<Boolean> newWaitForAttributeTask(Entity node, AttributeSensor<T> sensor, Predicate<T> condition, Duration timeout) {
-        return awaiting( Repeater.create("waiting on "+node+" "+sensor.getName()+" "+condition)
-                    .backoff(Duration.millis(10), 1.5, Duration.millis(200))
-                    .limitTimeTo(timeout==null ? Duration.PRACTICALLY_FOREVER : timeout)
-                    .until(Functionals.callable(Functions.forPredicate(EntityPredicates.attributeSatisfies(sensor, condition)), node)),
-                    true);
-    }
-
-    private static TaskAdaptable<Boolean> awaiting(Repeater repeater, boolean requireTrue) {
-        return Tasks.<Boolean>builder().name(repeater.getDescription()).body(new WaitForRepeaterCallable(repeater, requireTrue)).build();
-    }
-
-}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/7bae4e66/software/base/src/main/java/brooklyn/entity/brooklynnode/RemoteEffectorBuilder.java
----------------------------------------------------------------------
diff --git a/software/base/src/main/java/brooklyn/entity/brooklynnode/RemoteEffectorBuilder.java b/software/base/src/main/java/brooklyn/entity/brooklynnode/RemoteEffectorBuilder.java
index eba3c1a..3b1cdc1 100644
--- a/software/base/src/main/java/brooklyn/entity/brooklynnode/RemoteEffectorBuilder.java
+++ b/software/base/src/main/java/brooklyn/entity/brooklynnode/RemoteEffectorBuilder.java
@@ -37,19 +37,28 @@ public class RemoteEffectorBuilder {
             return input.getContentAsString();
         }
     }
+    
 
     public static Collection<Effector<String>> of(Collection<?> cfgEffectors) {
         Collection<Effector<String>> effectors = new ArrayList<Effector<String>>();
         for (Object objEff : cfgEffectors) {
             Map<?, ?> cfgEff = (Map<?, ?>)objEff;
-//            String returnTypeName = (String)cfgEff.get("returnType");
             String effName = (String)cfgEff.get("name");
             String description = (String)cfgEff.get("description");
 
-//            Class<?> returnType = getType(returnTypeName);
             EffectorBuilder<String> eff = Effectors.effector(String.class, effName);
             Collection<?> params = (Collection<?>)cfgEff.get("parameters");
 
+            /* The *return type* should NOT be included in the signature here.
+             * It might be a type known only at the mirrored brooklyn node
+             * (in which case loading it here would fail); or possibly it could
+             * be a different version of the type here, in which case the signature
+             * would look valid here, but deserializing it would fail.
+             * 
+             * Best to just pass the json representation back to the caller.
+             * (They won't be able to tell the difference between that and deserialize-then-serialize!)
+             */
+            
             if (description != null) {
                 eff.description(description);
             }
@@ -65,21 +74,11 @@ public class RemoteEffectorBuilder {
     }
 
     private static void buildParam(EffectorBuilder<String> eff, Map<?, ?> cfgParam) {
-//        String type = (String)cfgParam.get("type");
         String name = (String)cfgParam.get("name");
         String description = (String)cfgParam.get("description");
         String defaultValue = (String)cfgParam.get("defaultValue");
 
-//        Class<?> paramType = getType(type);
         eff.parameter(Object.class, name, description, defaultValue /*TypeCoercions.coerce(defaultValue, paramType)*/);
     }
 
-//    private static Class<?> getType(String type) {
-//        try {
-//            return Class.forName(type);
-//        } catch (ClassNotFoundException e) {
-//            return Object.class;
-//        }
-//    }
-
 }

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/7bae4e66/software/base/src/main/java/brooklyn/entity/brooklynnode/effector/BrooklynClusterUpgradeEffectorBody.java
----------------------------------------------------------------------
diff --git a/software/base/src/main/java/brooklyn/entity/brooklynnode/effector/BrooklynClusterUpgradeEffectorBody.java b/software/base/src/main/java/brooklyn/entity/brooklynnode/effector/BrooklynClusterUpgradeEffectorBody.java
new file mode 100644
index 0000000..8215a0b
--- /dev/null
+++ b/software/base/src/main/java/brooklyn/entity/brooklynnode/effector/BrooklynClusterUpgradeEffectorBody.java
@@ -0,0 +1,203 @@
+/*
+ * 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 brooklyn.entity.brooklynnode.effector;
+
+import java.io.File;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import brooklyn.config.ConfigKey;
+import brooklyn.entity.Effector;
+import brooklyn.entity.Entity;
+import brooklyn.entity.Group;
+import brooklyn.entity.basic.EntityPredicates;
+import brooklyn.entity.basic.Lifecycle;
+import brooklyn.entity.brooklynnode.BrooklynCluster;
+import brooklyn.entity.brooklynnode.BrooklynCluster.SelectMasterEffector;
+import brooklyn.entity.brooklynnode.BrooklynCluster.UpgradeClusterEffector;
+import brooklyn.entity.brooklynnode.BrooklynNode;
+import brooklyn.entity.brooklynnode.BrooklynNode.SetHAModeEffector;
+import brooklyn.entity.effector.EffectorBody;
+import brooklyn.entity.effector.Effectors;
+import brooklyn.entity.group.DynamicCluster;
+import brooklyn.entity.proxying.EntitySpec;
+import brooklyn.event.AttributeSensor;
+import brooklyn.management.ha.HighAvailabilityMode;
+import brooklyn.management.ha.ManagementNodeState;
+import brooklyn.util.collections.MutableMap;
+import brooklyn.util.config.ConfigBag;
+import brooklyn.util.exceptions.Exceptions;
+import brooklyn.util.net.Urls;
+import brooklyn.util.repeat.Repeater;
+import brooklyn.util.task.DynamicTasks;
+import brooklyn.util.task.Tasks;
+import brooklyn.util.time.Duration;
+
+import com.google.api.client.util.Preconditions;
+import com.google.common.base.Predicate;
+import com.google.common.collect.Collections2;
+import com.google.common.collect.Iterables;
+
+public class BrooklynClusterUpgradeEffectorBody extends EffectorBody<Void> implements UpgradeClusterEffector {
+    public static final Effector<Void> UPGRADE_CLUSTER = Effectors.effector(UpgradeClusterEffector.UPGRADE_CLUSTER).impl(new BrooklynClusterUpgradeEffectorBody()).build();
+
+    private AtomicBoolean upgradeInProgress = new AtomicBoolean();
+
+    @Override
+    public Void call(ConfigBag parameters) {
+        if (!upgradeInProgress.compareAndSet(false, true)) {
+            throw new IllegalStateException("An upgrade is already in progress.");
+        }
+
+        EntitySpec<?> memberSpec = entity().getConfig(BrooklynCluster.MEMBER_SPEC);
+        Preconditions.checkNotNull(memberSpec, BrooklynCluster.MEMBER_SPEC.getName() + " is required for " + UpgradeClusterEffector.class.getName());
+
+        Map<ConfigKey<?>, Object> specCfg = memberSpec.getConfig();
+        String oldDownloadUrl = (String) specCfg.get(BrooklynNode.DOWNLOAD_URL);
+        String oldUploadUrl = (String) specCfg.get(BrooklynNode.DISTRO_UPLOAD_URL);
+        String newDownloadUrl = parameters.get(BrooklynNode.DOWNLOAD_URL.getConfigKey());
+        String newUploadUrl = inferUploadUrl(newDownloadUrl);
+        try {
+            memberSpec.configure(BrooklynNode.DOWNLOAD_URL, newUploadUrl);
+            memberSpec.configure(BrooklynNode.DISTRO_UPLOAD_URL, newUploadUrl);
+            upgrade(parameters);
+        } catch (Exception e) {
+            memberSpec.configure(BrooklynNode.DOWNLOAD_URL, oldDownloadUrl);
+            memberSpec.configure(BrooklynNode.DISTRO_UPLOAD_URL, oldUploadUrl);
+            throw Exceptions.propagate(e);
+        } finally {
+            upgradeInProgress.set(false);
+        }
+        return null;
+    }
+
+    private String inferUploadUrl(String newDownloadUrl) {
+        boolean isLocal = "file".equals(Urls.getProtocol(newDownloadUrl)) || new File(newDownloadUrl).exists();
+        if (isLocal) {
+            return newDownloadUrl;
+        } else {
+            return null;
+        }
+    }
+
+    private void upgrade(ConfigBag parameters) {
+        //TODO might be worth separating each step in a task for better UI
+        //TODO currently this will fight with auto-scaler policies; you should turn them off
+
+        Group cluster = (Group)entity();
+        Collection<Entity> initialMembers = cluster.getMembers();
+        int initialClusterSize = initialMembers.size();
+
+        //1. Initially create a single node to check if it will launch successfully
+        Entity initialNode = Iterables.getOnlyElement(createNodes(1));
+
+        //2. If everything is OK with the first node launch the rest as well
+        Collection<Entity> remainingNodes = createNodes(initialClusterSize - 1);
+
+        //3. Once we have all nodes running without errors switch master
+        DynamicTasks.queue(Effectors.invocation(cluster, BrooklynCluster.SELECT_MASTER, MutableMap.of(SelectMasterEffector.NEW_MASTER_ID, initialNode.getId()))).asTask().getUnchecked();
+
+        //4. Stop the nodes which were running at the start of the upgrade call, but keep them around.
+        //   Should we create a quarantine-like zone for old stopped version?
+        //   For members that were created meanwhile - they will be using the new version already. If the new version
+        //   isn't good then they will fail to start as well, forcing the policies to retry (and succeed once the
+        //   URL is reverted).
+        //TODO can get into problem state if more old nodes are created; better might be to set the
+        //version on this cluster before the above select-master call, and then delete any which are running the old
+        //version (would require tracking the version number at the entity)
+        HashSet<Entity> oldMembers = new HashSet<Entity>(initialMembers);
+        oldMembers.removeAll(remainingNodes);
+        oldMembers.remove(initialNode);
+        DynamicTasks.queue(Effectors.invocation(BrooklynNode.STOP_NODE_BUT_LEAVE_APPS, Collections.emptyMap(), oldMembers)).asTask().getUnchecked();
+    }
+
+    private Collection<Entity> createNodes(int nodeCnt) {
+        DynamicCluster cluster = (DynamicCluster)entity();
+
+        //1. Create the nodes
+        Collection<Entity> newNodes = cluster.resizeByDelta(nodeCnt);
+
+        //2. Wait for them to be RUNNING
+        waitAttributeNotEqualTo(
+                newNodes,
+                BrooklynNode.SERVICE_STATE_ACTUAL, Lifecycle.STARTING);
+
+        //3. Set HOT_STANDBY in case it is not enabled on the command line ...
+        DynamicTasks.queue(Effectors.invocation(
+                BrooklynNode.SET_HA_MODE,
+                MutableMap.of(SetHAModeEffector.MODE, HighAvailabilityMode.HOT_STANDBY), 
+                newNodes)).asTask().getUnchecked();
+
+        //4. ... and wait until all of the nodes change state
+        //TODO if the REST call is blocking this is not needed
+        waitAttributeEqualTo(
+                newNodes,
+                BrooklynNode.MANAGEMENT_NODE_STATE,
+                ManagementNodeState.HOT_STANDBY);
+
+        //5. Just in case check if all of the nodes are SERVICE_UP (which would rule out ON_FIRE as well)
+        Collection<Entity> failedNodes = Collections2.filter(newNodes, EntityPredicates.attributeEqualTo(BrooklynNode.SERVICE_UP, Boolean.FALSE));
+        if (!failedNodes.isEmpty()) {
+            throw new IllegalStateException("Nodes " + failedNodes + " are not " + BrooklynNode.SERVICE_UP + " though successfully in " + ManagementNodeState.HOT_STANDBY);
+        }
+        return newNodes;
+    }
+
+    private <T> void waitAttributeEqualTo(Collection<Entity> nodes, AttributeSensor<T> sensor, T value) {
+        waitPredicate(
+                nodes, 
+                EntityPredicates.attributeEqualTo(sensor, value),
+                "Waiting for nodes " + nodes + ", sensor " + sensor + " to be " + value,
+                "Timeout while waiting for nodes " + nodes + ", sensor " + sensor + " to change to " + value);
+    }
+
+    private <T> void waitAttributeNotEqualTo(Collection<Entity> nodes, AttributeSensor<T> sensor, T value) {
+        waitPredicate(
+                nodes, 
+                EntityPredicates.attributeNotEqualTo(sensor, value),
+                "Waiting for nodes " + nodes + ", sensor " + sensor + " to change from " + value,
+                "Timeout while waiting for nodes " + nodes + ", sensor " + sensor + " to change from " + value);
+    }
+
+    private <T extends Entity> void waitPredicate(Collection<T> nodes, Predicate<T> waitPredicate, String blockingMsg, String errorMsg) {
+        Tasks.setBlockingDetails(blockingMsg);
+        boolean pollSuccess = Repeater.create(blockingMsg)
+            .backoff(Duration.ONE_SECOND, 1.2, Duration.TEN_SECONDS)
+            .limitTimeTo(Duration.ONE_HOUR)
+            .until(nodes, allMatch(waitPredicate))
+            .run();
+        Tasks.resetBlockingDetails();
+
+        if (!pollSuccess) {
+            throw new IllegalStateException(errorMsg);
+        }
+    }
+
+    public static <T> Predicate<Collection<T>> allMatch(final Predicate<T> predicate) {
+        return new Predicate<Collection<T>>() {
+            @Override
+            public boolean apply(Collection<T> input) {
+                return Iterables.all(input, predicate);
+            }
+        };
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/7bae4e66/software/base/src/main/java/brooklyn/entity/brooklynnode/effector/BrooklynNodeUpgradeEffectorBody.java
----------------------------------------------------------------------
diff --git a/software/base/src/main/java/brooklyn/entity/brooklynnode/effector/BrooklynNodeUpgradeEffectorBody.java b/software/base/src/main/java/brooklyn/entity/brooklynnode/effector/BrooklynNodeUpgradeEffectorBody.java
new file mode 100644
index 0000000..a6433aa
--- /dev/null
+++ b/software/base/src/main/java/brooklyn/entity/brooklynnode/effector/BrooklynNodeUpgradeEffectorBody.java
@@ -0,0 +1,212 @@
+/*
+ * 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 brooklyn.entity.brooklynnode.effector;
+
+import java.util.Map;
+import java.util.concurrent.Callable;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import brooklyn.config.ConfigKey;
+import brooklyn.entity.Effector;
+import brooklyn.entity.Entity;
+import brooklyn.entity.basic.Entities;
+import brooklyn.entity.basic.EntityInternal;
+import brooklyn.entity.basic.EntityPredicates;
+import brooklyn.entity.basic.SoftwareProcess;
+import brooklyn.entity.brooklynnode.BrooklynNode;
+import brooklyn.entity.effector.EffectorBody;
+import brooklyn.entity.effector.Effectors;
+import brooklyn.entity.proxying.EntitySpec;
+import brooklyn.entity.software.SshEffectorTasks;
+import brooklyn.event.AttributeSensor;
+import brooklyn.event.basic.MapConfigKey;
+import brooklyn.management.TaskAdaptable;
+import brooklyn.management.ha.HighAvailabilityMode;
+import brooklyn.management.ha.ManagementNodeState;
+import brooklyn.util.config.ConfigBag;
+import brooklyn.util.exceptions.Exceptions;
+import brooklyn.util.exceptions.ReferenceWithError;
+import brooklyn.util.guava.Functionals;
+import brooklyn.util.net.Urls;
+import brooklyn.util.repeat.Repeater;
+import brooklyn.util.task.DynamicTasks;
+import brooklyn.util.task.Tasks;
+import brooklyn.util.text.Identifiers;
+import brooklyn.util.text.Strings;
+import brooklyn.util.time.Duration;
+
+import com.google.common.base.Functions;
+import com.google.common.base.Preconditions;
+import com.google.common.base.Predicate;
+import com.google.common.base.Predicates;
+import com.google.common.reflect.TypeToken;
+
+@SuppressWarnings("serial")
+/** Upgrades a brooklyn node in-place on the box, 
+ * by creating a child brooklyn node and ensuring it can rebind in HOT_STANDBY
+ * <p>
+ * Requires the target node to have persistence enabled. 
+ */
+public class BrooklynNodeUpgradeEffectorBody extends EffectorBody<Void> {
+
+    private static final Logger log = LoggerFactory.getLogger(BrooklynNodeUpgradeEffectorBody.class);
+    
+    public static final ConfigKey<String> DOWNLOAD_URL = BrooklynNode.DOWNLOAD_URL.getConfigKey();
+    public static final ConfigKey<Map<String,Object>> EXTRA_CONFIG = MapConfigKey.builder(new TypeToken<Map<String,Object>>() {}).name("extraConfig").description("Additional new config to set on this entity as part of upgrading").build();
+
+    public static final Effector<Void> UPGRADE = Effectors.effector(Void.class, "upgrade")
+        .description("Changes the Brooklyn build used to run this node, by spawning a dry-run node then copying the installed files across. "
+            + "This node must be running for persistence for in-place upgrading to work.")
+        .parameter(BrooklynNode.SUGGESTED_VERSION).parameter(DOWNLOAD_URL).parameter(EXTRA_CONFIG)
+        .impl(new BrooklynNodeUpgradeEffectorBody()).build();
+    
+    @Override
+    public Void call(ConfigBag parametersO) {
+        if (!isPersistenceModeEnabled(entity())) {
+            // would could try a `forcePersistNow`, but that's sloppy; 
+            // for now, require HA/persistence for upgrading 
+            DynamicTasks.queue( Tasks.warning("Persistence does not appear to be enabled at this node. "
+                + "In-place upgrade is unlikely to succeed.", null) );
+        }
+
+        ConfigBag parameters = ConfigBag.newInstanceCopying(parametersO);
+
+        /*
+         * all parameters are passed to children, apart from EXTRA_CONFIG
+         * whose value (as a map) is so passed; it provides an easy way to set extra config in the gui.
+         * (IOW a key-value mapping can be passed either inside EXTRA_CONFIG or as a sibling to EXTRA_CONFIG)  
+         */
+        if (parameters.containsKey(EXTRA_CONFIG)) {
+            Map<String, Object> extra = parameters.get(EXTRA_CONFIG);
+            parameters.remove(EXTRA_CONFIG);
+            parameters.putAll(extra);
+        }
+        log.debug(this+" upgrading, using "+parameters);
+
+        // TODO require entity() node state master or hot standby AND require persistence enabled, or a new 'force_attempt_upgrade' parameter to be applied
+        // TODO could have a 'skip_dry_run_upgrade' parameter
+        // TODO could support 'dry_run_only' parameter, with optional resumption tasks (eg new dynamic effector)
+
+        // 1 add new brooklyn version entity as child (so uses same machine), with same config apart from things in parameters
+        final BrooklynNode dryRunChild = entity().addChild(EntitySpec.create(BrooklynNode.class).configure(parameters.getAllConfig())
+            .displayName("Upgraded Version Dry-Run Node")
+            // force dir and label back to their defaults (do not piggy back on what may have already been installed)
+            .configure(BrooklynNode.INSTALL_DIR, BrooklynNode.INSTALL_DIR.getConfigKey().getDefaultValue())
+            .configure(BrooklynNode.INSTALL_UNIQUE_LABEL, "upgrade-tmp-"+Identifiers.makeRandomId(8))
+            .configure(parameters.getAllConfig()));
+
+        //force this to start as hot-standby
+        String launchParameters = dryRunChild.getConfig(BrooklynNode.EXTRA_LAUNCH_PARAMETERS);
+        if (Strings.isBlank(launchParameters)) launchParameters = "";
+        else launchParameters += " ";
+        launchParameters += "--highAvailability "+HighAvailabilityMode.HOT_STANDBY;
+        ((EntityInternal)dryRunChild).setConfig(BrooklynNode.EXTRA_LAUNCH_PARAMETERS, launchParameters);
+
+        Entities.manage(dryRunChild);
+        final String dryRunNodeUid = dryRunChild.getId();
+        ((EntityInternal)dryRunChild).setDisplayName("Dry-Run Upgraded Brooklyn Node ("+dryRunNodeUid+")");
+
+        DynamicTasks.queue(Effectors.invocation(dryRunChild, BrooklynNode.START, ConfigBag.EMPTY));
+
+        // 2 confirm hot standby status
+        DynamicTasks.queue(newWaitForAttributeTask(dryRunChild, BrooklynNode.MANAGEMENT_NODE_STATE, 
+            Predicates.equalTo(ManagementNodeState.HOT_STANDBY), Duration.TWO_MINUTES));
+
+        // 3 stop new version
+        // 4 stop old version
+        DynamicTasks.queue(Tasks.builder().name("shutdown original and transient nodes")
+            .add(Effectors.invocation(dryRunChild, BrooklynNode.SHUTDOWN, ConfigBag.EMPTY))
+            .add(Effectors.invocation(entity(), BrooklynNode.SHUTDOWN, ConfigBag.EMPTY))
+            .build());
+
+        // 5 move old files, and move new files
+        DynamicTasks.queue(Tasks.builder().name("setup new version").body(new Runnable() {
+            @Override
+            public void run() {
+                String runDir = entity().getAttribute(SoftwareProcess.RUN_DIR);
+                String bkDir = Urls.mergePaths(runDir, "..", Urls.getBasename(runDir)+"-backups", dryRunNodeUid);
+                String dryRunDir = Preconditions.checkNotNull(dryRunChild.getAttribute(SoftwareProcess.RUN_DIR));
+                log.debug(this+" storing backup of previous version in "+bkDir);
+                DynamicTasks.queue(SshEffectorTasks.ssh(
+                    "cd "+runDir,
+                    "mkdir -p "+bkDir,
+                    "mv * "+bkDir,
+                    "cd "+dryRunDir,
+                    "mv * "+runDir
+                    ).summary("move files"));
+            }
+        }).build());
+
+        entity().getConfigMap().addToLocalBag(parameters.getAllConfig());
+
+        // 6 start this entity, running the new version
+        DynamicTasks.queue(Effectors.invocation(entity(), BrooklynNode.START, ConfigBag.EMPTY));
+
+        DynamicTasks.waitForLast();
+        Entities.unmanage(dryRunChild);
+
+        return null;
+    }
+
+    private boolean isPersistenceModeEnabled(EntityInternal entity) {
+        // TODO when there are PERSIST* options in BrooklynNode, look at them here!
+        // or, better, have a sensor for persistence
+        String params = entity.getConfig(BrooklynNode.EXTRA_LAUNCH_PARAMETERS);
+        if (params==null) return false;
+        if (params.indexOf("persist")==0) return false;
+        return true;
+    }
+
+    private static class WaitForRepeaterCallable implements Callable<Boolean> {
+        protected Repeater repeater;
+        protected boolean requireTrue;
+
+        public WaitForRepeaterCallable(Repeater repeater, boolean requireTrue) {
+            this.repeater = repeater;
+            this.requireTrue = requireTrue;
+        }
+
+        @Override
+        public Boolean call() {
+            ReferenceWithError<Boolean> result = repeater.runKeepingError();
+            if (Boolean.TRUE.equals(result.getWithoutError()))
+                return true;
+            if (result.hasError()) 
+                throw Exceptions.propagate(result.getError());
+            if (requireTrue)
+                throw new IllegalStateException("timeout - "+repeater.getDescription());
+            return false;
+        }
+    }
+
+    private static <T> TaskAdaptable<Boolean> newWaitForAttributeTask(Entity node, AttributeSensor<T> sensor, Predicate<T> condition, Duration timeout) {
+        return awaiting( Repeater.create("waiting on "+node+" "+sensor.getName()+" "+condition)
+                    .backoff(Duration.millis(10), 1.5, Duration.millis(200))
+                    .limitTimeTo(timeout==null ? Duration.PRACTICALLY_FOREVER : timeout)
+                    .until(Functionals.callable(Functions.forPredicate(EntityPredicates.attributeSatisfies(sensor, condition)), node)),
+                    true);
+    }
+
+    private static TaskAdaptable<Boolean> awaiting(Repeater repeater, boolean requireTrue) {
+        return Tasks.<Boolean>builder().name(repeater.getDescription()).body(new WaitForRepeaterCallable(repeater, requireTrue)).build();
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/7bae4e66/software/base/src/main/java/brooklyn/entity/brooklynnode/effector/UpgradeClusterEffectorBody.java
----------------------------------------------------------------------
diff --git a/software/base/src/main/java/brooklyn/entity/brooklynnode/effector/UpgradeClusterEffectorBody.java b/software/base/src/main/java/brooklyn/entity/brooklynnode/effector/UpgradeClusterEffectorBody.java
deleted file mode 100644
index 70b184a..0000000
--- a/software/base/src/main/java/brooklyn/entity/brooklynnode/effector/UpgradeClusterEffectorBody.java
+++ /dev/null
@@ -1,199 +0,0 @@
-/*
- * 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 brooklyn.entity.brooklynnode.effector;
-
-import java.io.File;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.HashSet;
-import java.util.Map;
-import java.util.concurrent.atomic.AtomicBoolean;
-
-import brooklyn.config.ConfigKey;
-import brooklyn.entity.Effector;
-import brooklyn.entity.Entity;
-import brooklyn.entity.Group;
-import brooklyn.entity.basic.EntityPredicates;
-import brooklyn.entity.basic.Lifecycle;
-import brooklyn.entity.brooklynnode.BrooklynCluster;
-import brooklyn.entity.brooklynnode.BrooklynCluster.SelectMasterEffector;
-import brooklyn.entity.brooklynnode.BrooklynCluster.UpgradeClusterEffector;
-import brooklyn.entity.brooklynnode.BrooklynNode;
-import brooklyn.entity.brooklynnode.BrooklynNode.SetHAModeEffector;
-import brooklyn.entity.effector.EffectorBody;
-import brooklyn.entity.effector.Effectors;
-import brooklyn.entity.group.DynamicCluster;
-import brooklyn.entity.proxying.EntitySpec;
-import brooklyn.event.AttributeSensor;
-import brooklyn.management.ha.HighAvailabilityMode;
-import brooklyn.management.ha.ManagementNodeState;
-import brooklyn.util.collections.MutableMap;
-import brooklyn.util.config.ConfigBag;
-import brooklyn.util.exceptions.Exceptions;
-import brooklyn.util.net.Urls;
-import brooklyn.util.repeat.Repeater;
-import brooklyn.util.task.DynamicTasks;
-import brooklyn.util.task.Tasks;
-import brooklyn.util.time.Duration;
-
-import com.google.api.client.util.Preconditions;
-import com.google.common.base.Predicate;
-import com.google.common.collect.Collections2;
-import com.google.common.collect.Iterables;
-
-public class UpgradeClusterEffectorBody extends EffectorBody<Void> implements UpgradeClusterEffector {
-    public static final Effector<Void> UPGRADE_CLUSTER = Effectors.effector(UpgradeClusterEffector.UPGRADE_CLUSTER).impl(new UpgradeClusterEffectorBody()).build();
-
-    private AtomicBoolean upgradeInProgress = new AtomicBoolean();
-
-    @Override
-    public Void call(ConfigBag parameters) {
-        if (!upgradeInProgress.compareAndSet(false, true)) {
-            throw new IllegalStateException("An upgrade is already in progress.");
-        }
-
-        EntitySpec<?> memberSpec = entity().getConfig(BrooklynCluster.MEMBER_SPEC);
-        Preconditions.checkNotNull(memberSpec, BrooklynCluster.MEMBER_SPEC.getName() + " is required for " + UpgradeClusterEffector.class.getName());
-
-        Map<ConfigKey<?>, Object> specCfg = memberSpec.getConfig();
-        String oldDownloadUrl = (String) specCfg.get(BrooklynNode.DOWNLOAD_URL);
-        String oldUploadUrl = (String) specCfg.get(BrooklynNode.DISTRO_UPLOAD_URL);
-        String newDownloadUrl = parameters.get(BrooklynNode.DOWNLOAD_URL.getConfigKey());
-        String newUploadUrl = inferUploadUrl(newDownloadUrl);
-        try {
-            memberSpec.configure(BrooklynNode.DOWNLOAD_URL, newUploadUrl);
-            memberSpec.configure(BrooklynNode.DISTRO_UPLOAD_URL, newUploadUrl);
-            upgrade(parameters);
-        } catch (Exception e) {
-            memberSpec.configure(BrooklynNode.DOWNLOAD_URL, oldDownloadUrl);
-            memberSpec.configure(BrooklynNode.DISTRO_UPLOAD_URL, oldUploadUrl);
-            throw Exceptions.propagate(e);
-        } finally {
-            upgradeInProgress.set(false);
-        }
-        return null;
-    }
-
-    private String inferUploadUrl(String newDownloadUrl) {
-        boolean isLocal = "file".equals(Urls.getProtocol(newDownloadUrl)) || new File(newDownloadUrl).exists();
-        if (isLocal) {
-            return newDownloadUrl;
-        } else {
-            return null;
-        }
-    }
-
-    private void upgrade(ConfigBag parameters) {
-        //TODO might be worth separating each step in a task for better UI
-
-        Group cluster = (Group)entity();
-        Collection<Entity> initialMembers = cluster.getMembers();
-        int initialClusterSize = initialMembers.size();
-
-        //1. Initially create a single node to check if it will launch successfully
-        Entity initialNode = Iterables.getOnlyElement(createNodes(1));
-
-        //2. If everything is OK with the first node launch the rest as well
-        Collection<Entity> remainingNodes = createNodes(initialClusterSize - 1);
-
-        //3. Once we have all nodes running without errors switch master
-        DynamicTasks.queue(Effectors.invocation(cluster, BrooklynCluster.SELECT_MASTER, MutableMap.of(SelectMasterEffector.NEW_MASTER_ID, initialNode.getId()))).asTask().getUnchecked();
-
-        //4. Stop the nodes which were running at the start of the upgrade call, but keep them around.
-        //   Should we create a quarantine-like zone for old stopped version?
-        //   For members that were created meanwhile - they will be using the new version already. If the new version
-        //   isn't good then they will fail to start as well, forcing the policies to retry (and succeed once the
-        //   URL is reverted).
-        HashSet<Entity> oldMembers = new HashSet<Entity>(initialMembers);
-        oldMembers.removeAll(remainingNodes);
-        oldMembers.remove(initialNode);
-        DynamicTasks.queue(Effectors.invocation(BrooklynNode.STOP_NODE_BUT_LEAVE_APPS, Collections.emptyMap(), oldMembers)).asTask().getUnchecked();
-    }
-
-    private Collection<Entity> createNodes(int nodeCnt) {
-        DynamicCluster cluster = (DynamicCluster)entity();
-
-        //1. Create the nodes
-        Collection<Entity> newNodes = cluster.resizeByDelta(nodeCnt);
-
-        //2. Wait for them to be RUNNING
-        waitAttributeNotEqualTo(
-                newNodes,
-                BrooklynNode.SERVICE_STATE_ACTUAL, Lifecycle.STARTING);
-
-        //3. Set HOT_STANDBY in case it is not enabled on the command line ...
-        DynamicTasks.queue(Effectors.invocation(
-                BrooklynNode.SET_HA_MODE,
-                MutableMap.of(SetHAModeEffector.MODE, HighAvailabilityMode.HOT_STANDBY), 
-                newNodes)).asTask().getUnchecked();
-
-        //4. ... and wait until all of the nodes change state
-        //TODO if the REST call is blocking this is not needed
-        waitAttributeEqualTo(
-                newNodes,
-                BrooklynNode.MANAGEMENT_NODE_STATE,
-                ManagementNodeState.HOT_STANDBY);
-
-        //5. Just in case check if all of the nodes are SERVICE_UP (which would rule out ON_FIRE as well)
-        Collection<Entity> failedNodes = Collections2.filter(newNodes, EntityPredicates.attributeEqualTo(BrooklynNode.SERVICE_UP, Boolean.FALSE));
-        if (!failedNodes.isEmpty()) {
-            throw new IllegalStateException("Nodes " + failedNodes + " are not " + BrooklynNode.SERVICE_UP + " though successfully in " + ManagementNodeState.HOT_STANDBY);
-        }
-        return newNodes;
-    }
-
-    private <T> void waitAttributeEqualTo(Collection<Entity> nodes, AttributeSensor<T> sensor, T value) {
-        waitPredicate(
-                nodes, 
-                EntityPredicates.attributeEqualTo(sensor, value),
-                "Waiting for nodes " + nodes + ", sensor " + sensor + " to be " + value,
-                "Timeout while waiting for nodes " + nodes + ", sensor " + sensor + " to change to " + value);
-    }
-
-    private <T> void waitAttributeNotEqualTo(Collection<Entity> nodes, AttributeSensor<T> sensor, T value) {
-        waitPredicate(
-                nodes, 
-                EntityPredicates.attributeNotEqualTo(sensor, value),
-                "Waiting for nodes " + nodes + ", sensor " + sensor + " to change from " + value,
-                "Timeout while waiting for nodes " + nodes + ", sensor " + sensor + " to change from " + value);
-    }
-
-    private <T extends Entity> void waitPredicate(Collection<T> nodes, Predicate<T> waitPredicate, String blockingMsg, String errorMsg) {
-        Tasks.setBlockingDetails(blockingMsg);
-        boolean pollSuccess = Repeater.create(blockingMsg)
-            .backoff(Duration.ONE_SECOND, 1.2, Duration.TEN_SECONDS)
-            .limitTimeTo(Duration.ONE_HOUR)
-            .until(nodes, allMatch(waitPredicate))
-            .run();
-        Tasks.resetBlockingDetails();
-
-        if (!pollSuccess) {
-            throw new IllegalStateException(errorMsg);
-        }
-    }
-
-    public static <T> Predicate<Collection<T>> allMatch(final Predicate<T> predicate) {
-        return new Predicate<Collection<T>>() {
-            @Override
-            public boolean apply(Collection<T> input) {
-                return Iterables.all(input, predicate);
-            }
-        };
-    }
-}