You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@brooklyn.apache.org by he...@apache.org on 2014/10/27 13:52:26 UTC

[5/7] git commit: fix failures introduced by dynamic/parametrized RESTART effector; main items are: * factory not calling init (deprecate EntityFactory); * do{Start,Stop,Restart} methods not getting invoked (deprecated, replaced by pre- and post-

fix failures introduced by dynamic/parametrized RESTART effector; main items are:
    * factory not calling init (deprecate EntityFactory);
    * do{Start,Stop,Restart} methods not getting invoked (deprecated, replaced by pre- and post- start overrides)


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

Branch: refs/heads/master
Commit: 4b6f331d705cc890fbc30d8487b537e0a722e4dc
Parents: 1fd9b16
Author: Alex Heneveld <al...@cloudsoftcorp.com>
Authored: Tue Oct 21 14:57:03 2014 +0100
Committer: Alex Heneveld <al...@cloudsoftcorp.com>
Committed: Tue Oct 21 23:13:03 2014 +0100

----------------------------------------------------------------------
 .../AbstractConfigurableEntityFactory.java      | 11 ++-
 .../brooklyn/entity/basic/EntityFactory.java    |  4 +-
 .../brooklyn/entity/effector/Effectors.java     |  6 ++
 .../entity/proxying/EntityProxyImpl.java        | 14 ++-
 .../java/brooklyn/util/task/DynamicTasks.java   |  1 -
 .../brooklyn/entity/basic/SoftwareProcess.java  | 14 ++-
 ...wareProcessDriverLifecycleEffectorTasks.java |  7 ++
 .../entity/basic/SoftwareProcessImpl.java       | 27 +++++-
 .../entity/brooklynnode/BrooklynNode.java       |  1 +
 .../entity/brooklynnode/BrooklynNodeImpl.java   |  6 +-
 .../software/MachineLifecycleEffectorTasks.java | 38 ++++++--
 .../entity/basic/SoftwareProcessEntityTest.java | 38 ++++++++
 .../basic/SoftwareProcessSubclassTest.java      | 22 +++--
 .../entity/nosql/couchdb/CouchDBNodeImpl.java   |  6 +-
 .../entity/proxy/nginx/NginxControllerImpl.java |  4 +-
 .../webapp/JavaWebAppSoftwareProcessImpl.java   |  4 +-
 .../webapp/nodejs/NodeJsWebAppServiceImpl.java  |  4 +-
 ...lledDynamicWebAppClusterIntegrationTest.java |  4 +-
 .../ControlledDynamicWebAppClusterTest.java     | 19 +++-
 .../entity/webapp/DynamicWebAppClusterTest.java | 13 +--
 .../entity/webapp/DynamicWebAppFabricTest.java  | 22 ++---
 .../test/entity/TestJavaWebAppEntity.java       | 97 +++++++++++++-------
 22 files changed, 258 insertions(+), 104 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/4b6f331d/core/src/main/java/brooklyn/entity/basic/AbstractConfigurableEntityFactory.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/entity/basic/AbstractConfigurableEntityFactory.java b/core/src/main/java/brooklyn/entity/basic/AbstractConfigurableEntityFactory.java
index ef24b69..274c1de 100644
--- a/core/src/main/java/brooklyn/entity/basic/AbstractConfigurableEntityFactory.java
+++ b/core/src/main/java/brooklyn/entity/basic/AbstractConfigurableEntityFactory.java
@@ -23,10 +23,16 @@ import java.util.HashMap;
 import java.util.LinkedHashMap;
 import java.util.Map;
 
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
 import brooklyn.config.ConfigKey;
 import brooklyn.entity.Entity;
+import brooklyn.util.exceptions.Exceptions;
 
 public abstract class AbstractConfigurableEntityFactory<T extends Entity> implements ConfigurableEntityFactory<T>, Serializable {
+    private static final Logger log = LoggerFactory.getLogger(AbstractConfigurableEntityFactory.class);
+    
     protected final Map config = new LinkedHashMap();
 
     public AbstractConfigurableEntityFactory(){
@@ -67,7 +73,10 @@ public abstract class AbstractConfigurableEntityFactory<T extends Entity> implem
         Map flags2 = new HashMap();
         flags2.putAll(config);
         flags2.putAll(flags);
-        return newEntity2(flags2, parent);
+        T result = newEntity2(flags2, parent);
+        // we rely increasingly on init, which factory doesn't call; really should remove factories!
+        log.warn("Deprecated legacy compatibility, using factory (init will not be invoked): "+result);
+        return result;
     }
 
     public abstract T newEntity2(Map flags, Entity parent);

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/4b6f331d/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 42fb44a..cf06a1d 100644
--- a/core/src/main/java/brooklyn/entity/basic/EntityFactory.java
+++ b/core/src/main/java/brooklyn/entity/basic/EntityFactory.java
@@ -25,8 +25,8 @@ import java.util.Map;
 /**
  * A Factory for creating entities.
  *
- * @param <T>
- */
+ * @deprecated since 0.7.0; use EntitySpec instead, as the factory does not put the entity through the initialization process */
+@Deprecated
 public interface EntityFactory<T extends Entity> {
     T newEntity(Map flags, Entity parent);
 }

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/4b6f331d/core/src/main/java/brooklyn/entity/effector/Effectors.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/entity/effector/Effectors.java b/core/src/main/java/brooklyn/entity/effector/Effectors.java
index ebb70b3..5d89e56 100644
--- a/core/src/main/java/brooklyn/entity/effector/Effectors.java
+++ b/core/src/main/java/brooklyn/entity/effector/Effectors.java
@@ -50,6 +50,7 @@ import brooklyn.util.text.Strings;
 import com.google.common.base.Objects;
 import com.google.common.base.Preconditions;
 import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
 
 public class Effectors {
 
@@ -125,6 +126,11 @@ public class Effectors {
         return builder;
     }
 
+    /** as {@link #invocation(Entity, Effector, Map)} but convenience for passing a {@link ConfigBag} */
+    public static <T> TaskAdaptable<T> invocation(Entity entity, Effector<T> eff, ConfigBag parameters) {
+        return invocation(entity, eff, parameters==null ? ImmutableMap.of() : parameters.getAllConfig());
+    }
+    
     /** returns an unsubmitted task which invokes the given effector; use {@link Entities#invokeEffector(EntityLocal, Entity, Effector, Map)} for a submitted variant */
     public static <T> TaskAdaptable<T> invocation(Entity entity, Effector<T> eff, @Nullable Map<?,?> parameters) {
         @SuppressWarnings("unchecked")

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/4b6f331d/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 04b5d44..19188ab 100644
--- a/core/src/main/java/brooklyn/entity/proxying/EntityProxyImpl.java
+++ b/core/src/main/java/brooklyn/entity/proxying/EntityProxyImpl.java
@@ -24,6 +24,7 @@ import java.lang.reflect.Method;
 import java.lang.reflect.Proxy;
 import java.util.Arrays;
 import java.util.LinkedHashSet;
+import java.util.Map;
 import java.util.Set;
 import java.util.WeakHashMap;
 
@@ -36,11 +37,16 @@ import brooklyn.entity.basic.AbstractEntity;
 import brooklyn.entity.basic.EntityInternal;
 import brooklyn.entity.basic.EntityLocal;
 import brooklyn.entity.basic.EntityTransientCopyInternal;
+import brooklyn.entity.effector.EffectorWithBody;
 import brooklyn.entity.rebind.RebindManagerImpl.RebindTracker;
 import brooklyn.management.ManagementContext;
+import brooklyn.management.TaskAdaptable;
 import brooklyn.management.internal.EffectorUtils;
 import brooklyn.management.internal.EntityManagerInternal;
 import brooklyn.management.internal.ManagementTransitionInfo.ManagementTransitionMode;
+import brooklyn.util.config.ConfigBag;
+import brooklyn.util.task.DynamicTasks;
+import brooklyn.util.task.TaskTags;
 
 import com.google.common.base.Objects;
 import com.google.common.collect.Sets;
@@ -192,7 +198,13 @@ public class EntityProxyImpl implements java.lang.reflect.InvocationHandler {
                 Object[] nonNullArgs = (args == null) ? new Object[0] : args;
                 Effector<?> eff = findEffector(m, nonNullArgs);
                 if (eff != null) {
-                    result = EffectorUtils.invokeMethodEffector(delegate, eff, nonNullArgs);
+                    @SuppressWarnings("rawtypes")
+                    Map parameters = EffectorUtils.prepareArgsForEffectorAsMapFromArray(eff, nonNullArgs);
+                    @SuppressWarnings({ "unchecked", "rawtypes" })
+                    TaskAdaptable<?> task = ((EffectorWithBody)eff).getBody().newTask(delegate, eff, ConfigBag.newInstance(parameters));
+                    // as per LocalManagementContext.runAtEntity(Entity entity, TaskAdaptable<T> task) 
+                    TaskTags.markInessential(task);
+                    result = DynamicTasks.queueIfPossible(task.asTask()).orSubmitAsync(delegate).andWaitForSuccess();
                 } else {
                     result = m.invoke(delegate, nonNullArgs);
                 }

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/4b6f331d/core/src/main/java/brooklyn/util/task/DynamicTasks.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/util/task/DynamicTasks.java b/core/src/main/java/brooklyn/util/task/DynamicTasks.java
index 3d027c6..bb87676 100644
--- a/core/src/main/java/brooklyn/util/task/DynamicTasks.java
+++ b/core/src/main/java/brooklyn/util/task/DynamicTasks.java
@@ -34,7 +34,6 @@ import brooklyn.management.TaskQueueingContext;
 import brooklyn.management.TaskWrapper;
 import brooklyn.util.exceptions.Exceptions;
 import brooklyn.util.time.Duration;
-import brooklyn.util.time.Time;
 
 import com.google.common.annotations.Beta;
 import com.google.common.base.Preconditions;

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/4b6f331d/software/base/src/main/java/brooklyn/entity/basic/SoftwareProcess.java
----------------------------------------------------------------------
diff --git a/software/base/src/main/java/brooklyn/entity/basic/SoftwareProcess.java b/software/base/src/main/java/brooklyn/entity/basic/SoftwareProcess.java
index e1f4d42..8352cf1 100644
--- a/software/base/src/main/java/brooklyn/entity/basic/SoftwareProcess.java
+++ b/software/base/src/main/java/brooklyn/entity/basic/SoftwareProcess.java
@@ -22,7 +22,6 @@ import java.util.Map;
 
 import brooklyn.config.ConfigKey;
 import brooklyn.entity.Entity;
-import brooklyn.entity.effector.Effectors;
 import brooklyn.entity.trait.Startable;
 import brooklyn.event.AttributeSensor;
 import brooklyn.event.basic.AttributeSensorAndConfigKey;
@@ -241,9 +240,11 @@ public interface SoftwareProcess extends Entity, Startable {
 
         @Beta /** @since 0.7.0 semantics of parameters to restart being explored */
         public static final ConfigKey<Object> RESTART_MACHINE = ConfigKeys.newConfigKey(Object.class, "restartMachine",
-            "Whether to restart the machine where this is running: 'true', 'false', or 'auto' supported, "
-            + "with the default being auto, ie only restarting the machine if there is an apparent problem with the machine"
-            + " (only applies when the machine was provisioned for this entity)", RestartMode.AUTO.toString().toLowerCase());
+            "Whether to restart/replace the machine where this is running: 'true', 'false', or 'auto' supported, "
+            + "with the default being auto, ie only act at the machine level if there is nothing else to restart"
+            + " (e.g. there is no machine);"
+            + " NB: only applies when the machine was provisioned for this entity", 
+            RestartMode.AUTO.toString().toLowerCase());
         
         // we supply a typed variant for retrieval; we want the untyped (above) to use lower case as the default in the GUI
         // (very hard if using enum, since enum takes the name, and RendererHints do not apply to parameters) 
@@ -253,9 +254,6 @@ public interface SoftwareProcess extends Entity, Startable {
         public enum RestartMode { TRUE, FALSE, AUTO }
     }
     
-    brooklyn.entity.Effector<Void> RESTART = Effectors.effector(Startable.RESTART)
-        .parameter(RestartSoftwareParameters.RESTART_CHILDREN)
-        .parameter(RestartSoftwareParameters.RESTART_MACHINE)
-        .build();
+    // NB: the START, STOP, and RESTART effectors themselves are (re)defined by MachineLifecycleEffectorTasks
 
 }

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/4b6f331d/software/base/src/main/java/brooklyn/entity/basic/SoftwareProcessDriverLifecycleEffectorTasks.java
----------------------------------------------------------------------
diff --git a/software/base/src/main/java/brooklyn/entity/basic/SoftwareProcessDriverLifecycleEffectorTasks.java b/software/base/src/main/java/brooklyn/entity/basic/SoftwareProcessDriverLifecycleEffectorTasks.java
index eb6d08e..de66db8 100644
--- a/software/base/src/main/java/brooklyn/entity/basic/SoftwareProcessDriverLifecycleEffectorTasks.java
+++ b/software/base/src/main/java/brooklyn/entity/basic/SoftwareProcessDriverLifecycleEffectorTasks.java
@@ -206,6 +206,13 @@ public class SoftwareProcessDriverLifecycleEffectorTasks extends MachineLifecycl
         
         return result;
     }
+    
+    @Override
+    protected void postStopCustom() {
+        super.postStopCustom();
+        
+        entity().postStop();
+    }
 
 }
 

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/4b6f331d/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 8d62322..5b5c433 100644
--- a/software/base/src/main/java/brooklyn/entity/basic/SoftwareProcessImpl.java
+++ b/software/base/src/main/java/brooklyn/entity/basic/SoftwareProcessImpl.java
@@ -118,6 +118,12 @@ public abstract class SoftwareProcessImpl extends AbstractEntity implements Soft
     }
 
     @Override
+    public void init() {
+        super.init();
+        LIFECYCLE_TASKS.attachLifecycleEffectors(this);
+    }
+    
+    @Override
     protected void initEnrichers() {
         super.initEnrichers();
         ServiceNotUpLogic.updateNotUpIndicator(this, SERVICE_PROCESS_IS_RUNNING, "No information yet about whether this service is running");
@@ -206,6 +212,12 @@ public abstract class SoftwareProcessImpl extends AbstractEntity implements Soft
     }
 
     /**
+     * Called after the rest of stop has completed (after VM deprovisioned, but before state set to STOPPED)
+     */
+    protected void postStop() {
+    }
+
+    /**
      * For disconnecting from the running app. Will be called on stop.
      */
     protected void disconnectSensors() {
@@ -471,24 +483,33 @@ public abstract class SoftwareProcessImpl extends AbstractEntity implements Soft
     /**
      * To be overridden instead of {@link #start(Collection)}; sub-classes should call {@code super.doStart(locations)} and should
      * add do additional work via tasks, executed using {@link DynamicTasks#queue(String, Callable)}.
+     * @deprecated since 0.7.0 override {@link #preStart()} or {@link #postStart()}, or define custom lifecycle tasks if needed
+     * (this method is no longer invoked, hence now marking it final)
      */
-    protected void doStart(Collection<? extends Location> locations) {
+    @Deprecated
+    protected final void doStart(Collection<? extends Location> locations) {
         LIFECYCLE_TASKS.start(locations);
     }
     
     /**
      * To be overridden instead of {@link #stop()}; sub-classes should call {@code super.doStop()} and should
      * add do additional work via tasks, executed using {@link DynamicTasks#queue(String, Callable)}.
+     * @deprecated since 0.7.0 override {@link #preStop()} or {@link #postStop()}, or define custom lifecycle tasks if needed
+     * (this method is no longer invoked, hence now marking it final)
      */
-    protected void doStop() {
+    @Deprecated
+    protected final void doStop() {
         LIFECYCLE_TASKS.stop();
     }
     
     /**
      * To be overridden instead of {@link #restart()}; sub-classes should call {@code super.doRestart(ConfigBag)} and should
      * add do additional work via tasks, executed using {@link DynamicTasks#queue(String, Callable)}.
+     * @deprecated since 0.7.0 define custom lifecycle tasks if needed
+     * (this method is no longer invoked, hence now marking it final)
      */
-    protected void doRestart(ConfigBag parameters) {
+    @Deprecated
+    protected final void doRestart(ConfigBag parameters) {
         LIFECYCLE_TASKS.restart(parameters);
     }
 

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/4b6f331d/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 b6ffb97..16b46c6 100644
--- a/software/base/src/main/java/brooklyn/entity/brooklynnode/BrooklynNode.java
+++ b/software/base/src/main/java/brooklyn/entity/brooklynnode/BrooklynNode.java
@@ -244,6 +244,7 @@ public interface BrooklynNode extends SoftwareProcess, UsesJava {
         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)
             .parameter(SHUTDOWN_TIMEOUT)

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/4b6f331d/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 56582c6..5966cf8 100644
--- a/software/base/src/main/java/brooklyn/entity/brooklynnode/BrooklynNodeImpl.java
+++ b/software/base/src/main/java/brooklyn/entity/brooklynnode/BrooklynNodeImpl.java
@@ -95,13 +95,13 @@ public class BrooklynNodeImpl extends SoftwareProcessImpl implements BrooklynNod
     }
 
     @Override
-    protected void doStop() {
+    protected void preStop() {
+        super.preStop();
         //shutdown only if running, any of stop_* could've been already previously
         if (getAttribute(Attributes.SERVICE_UP)) {
             Preconditions.checkState(getChildren().isEmpty(), "Can't stop instance with running applications.");
             DynamicTasks.queue(Effectors.invocation(this, SHUTDOWN, MutableMap.of(ShutdownEffector.REQUEST_TIMEOUT, Duration.ONE_MINUTE)));
         }
-        super.doStop();
     }
 
     public static class DeployBlueprintEffectorBody extends EffectorBody<String> implements DeployBlueprintEffector {
@@ -229,6 +229,7 @@ public class BrooklynNodeImpl extends SoftwareProcessImpl implements BrooklynNod
                 TaskTags.markInessential(shutdownTask);
             }
             DynamicTasks.queue(shutdownTask).asTask().getUnchecked();
+            DynamicTasks.queue(Effectors.invocation(entity(), STOP, ConfigBag.EMPTY)).asTask().getUnchecked();
             Entities.destroy(entity);
             return null;
         }
@@ -250,6 +251,7 @@ public class BrooklynNodeImpl extends SoftwareProcessImpl implements BrooklynNod
                 TaskTags.markInessential(shutdownTask);
             }
             DynamicTasks.queue(shutdownTask).asTask().getUnchecked();
+            DynamicTasks.queue(Effectors.invocation(entity(), STOP, ConfigBag.EMPTY)).asTask().getUnchecked();
             Entities.destroy(entity);
             return null;
         }

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/4b6f331d/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 381b186..37df887 100644
--- a/software/base/src/main/java/brooklyn/entity/software/MachineLifecycleEffectorTasks.java
+++ b/software/base/src/main/java/brooklyn/entity/software/MachineLifecycleEffectorTasks.java
@@ -44,6 +44,7 @@ import brooklyn.entity.basic.Lifecycle;
 import brooklyn.entity.basic.ServiceStateLogic;
 import brooklyn.entity.basic.SoftwareProcess;
 import brooklyn.entity.basic.SoftwareProcess.RestartSoftwareParameters;
+import brooklyn.entity.basic.SoftwareProcess.RestartSoftwareParameters.RestartMode;
 import brooklyn.entity.effector.EffectorBody;
 import brooklyn.entity.effector.Effectors;
 import brooklyn.entity.trait.Startable;
@@ -130,9 +131,12 @@ public abstract class MachineLifecycleEffectorTasks {
 
     /** @see {@link #newStartEffector()} */
     public Effector<Void> newRestartEffector() {
-        return Effectors.effector(Startable.RESTART).impl(newRestartEffectorTask()).build();
+        return Effectors.effector(Startable.RESTART).
+            parameter(RestartSoftwareParameters.RESTART_CHILDREN).
+            parameter(RestartSoftwareParameters.RESTART_MACHINE).
+            impl(newRestartEffectorTask()).build();
     }
-
+    
     /** @see {@link #newStartEffector()} */
     public Effector<Void> newStopEffector() {
         return Effectors.effector(Startable.STOP).impl(newStopEffectorTask()).build();
@@ -434,12 +438,26 @@ public abstract class MachineLifecycleEffectorTasks {
      */
     public void restart(ConfigBag parameters) {
         ServiceStateLogic.setExpectedState(entity(), Lifecycle.STOPPING);
-        DynamicTasks.queue("stopping (process)", new Callable<String>() { public String call() {
-            DynamicTasks.markInessential();
-            stopProcessesAtMachine();
-            DynamicTasks.waitForLast();
-            return "Stop of process completed with no errors.";
-        }});
+        
+        RestartMode isRestartMachine = parameters.get(RestartSoftwareParameters.RESTART_MACHINE_TYPED);
+        if (isRestartMachine==null) isRestartMachine=RestartMode.AUTO;
+        if (isRestartMachine==RestartMode.AUTO) isRestartMachine = RestartMode.FALSE;
+
+        if (isRestartMachine==RestartMode.FALSE) {
+            DynamicTasks.queue("stopping (process)", new Callable<String>() { public String call() {
+                DynamicTasks.markInessential();
+                stopProcessesAtMachine();
+                DynamicTasks.waitForLast();
+                return "Stop of process completed with no errors.";
+            }});
+        } else {
+            DynamicTasks.queue("stopping (machine)", new Callable<String>() { public String call() {
+                DynamicTasks.markInessential();
+                stop();
+                DynamicTasks.waitForLast();
+                return "Stop of machine completed with no errors.";
+            }});            
+        }
 
         DynamicTasks.queue("starting", new Runnable() { public void run() {
             // startInLocations will look up the location, and provision a machine if necessary
@@ -548,6 +566,10 @@ public abstract class MachineLifecycleEffectorTasks {
     protected void preStopCustom() {
         // nothing needed here
     }
+    
+    protected void postStopCustom() {
+        // nothing needed here
+    }
 
     public static class StopMachineDetails<T> implements Serializable {
         private static final long serialVersionUID = 3256747214315895431L;

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/4b6f331d/software/base/src/test/java/brooklyn/entity/basic/SoftwareProcessEntityTest.java
----------------------------------------------------------------------
diff --git a/software/base/src/test/java/brooklyn/entity/basic/SoftwareProcessEntityTest.java b/software/base/src/test/java/brooklyn/entity/basic/SoftwareProcessEntityTest.java
index e6de298..a59d39a 100644
--- a/software/base/src/test/java/brooklyn/entity/basic/SoftwareProcessEntityTest.java
+++ b/software/base/src/test/java/brooklyn/entity/basic/SoftwareProcessEntityTest.java
@@ -35,13 +35,18 @@ import org.testng.annotations.Test;
 import brooklyn.config.ConfigKey;
 import brooklyn.entity.BrooklynAppUnitTestSupport;
 import brooklyn.entity.Entity;
+import brooklyn.entity.basic.SoftwareProcess.RestartSoftwareParameters;
+import brooklyn.entity.basic.SoftwareProcess.RestartSoftwareParameters.RestartMode;
+import brooklyn.entity.effector.Effectors;
 import brooklyn.entity.proxying.EntitySpec;
 import brooklyn.entity.proxying.ImplementedBy;
 import brooklyn.entity.trait.Startable;
 import brooklyn.location.LocationSpec;
 import brooklyn.location.basic.FixedListMachineProvisioningLocation;
+import brooklyn.location.basic.Locations;
 import brooklyn.location.basic.SshMachineLocation;
 import brooklyn.management.Task;
+import brooklyn.management.TaskAdaptable;
 import brooklyn.util.collections.MutableMap;
 import brooklyn.util.config.ConfigBag;
 import brooklyn.util.net.UserAndHostAndPort;
@@ -49,6 +54,7 @@ import brooklyn.util.os.Os;
 import brooklyn.util.task.DynamicTasks;
 import brooklyn.util.task.Tasks;
 import brooklyn.util.text.Strings;
+import brooklyn.util.time.Duration;
 
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableSet;
@@ -173,6 +179,38 @@ public class SoftwareProcessEntityTest extends BrooklynAppUnitTestSupport {
     }
     
     @Test
+    public void testBasicSoftwareProcessRestarts() throws Exception {
+        MyService entity = app.createAndManageChild(EntitySpec.create(MyService.class));
+        entity.start(ImmutableList.of(loc));
+        SimulatedDriver d = (SimulatedDriver) entity.getDriver();
+        Assert.assertTrue(d.isRunning());
+        
+        // this will cause restart to fail if it attempts to replace the machine
+        loc.removeMachine(Locations.findUniqueSshMachineLocation(entity.getLocations()).get());
+        
+        // with defaults, it won't reboot machine
+        entity.restart();
+        
+        // but here, it will try to reboot, and fail because there is no machine available
+        TaskAdaptable<Void> t1 = Entities.submit(entity, Effectors.invocation(entity, Startable.RESTART, 
+                ConfigBag.newInstance().configure(RestartSoftwareParameters.RESTART_MACHINE_TYPED, RestartMode.TRUE)));
+        t1.asTask().blockUntilEnded(Duration.TEN_SECONDS);
+        if (!t1.asTask().isError()) {
+            Assert.fail("Should have thrown error during "+t1+" because no more machines available at "+loc);
+        }
+
+        // now it has a machine, so reboot should succeed
+        SshMachineLocation machine2 = mgmt.getLocationManager().createLocation(LocationSpec.create(SshMachineLocation.class)
+            .configure("address", "localhost"));
+        loc.addMachine(machine2);
+        TaskAdaptable<Void> t2 = Entities.submit(entity, Effectors.invocation(entity, Startable.RESTART, 
+            ConfigBag.newInstance().configure(RestartSoftwareParameters.RESTART_MACHINE_TYPED, RestartMode.TRUE)));
+        t2.asTask().get();
+        
+        Assert.assertFalse(d.isRunning());
+    }
+    
+    @Test
     public void testShutdownIsIdempotent() throws Exception {
         MyService entity = app.createAndManageChild(EntitySpec.create(MyService.class));
         entity.start(ImmutableList.of(loc));

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/4b6f331d/software/base/src/test/java/brooklyn/entity/basic/SoftwareProcessSubclassTest.java
----------------------------------------------------------------------
diff --git a/software/base/src/test/java/brooklyn/entity/basic/SoftwareProcessSubclassTest.java b/software/base/src/test/java/brooklyn/entity/basic/SoftwareProcessSubclassTest.java
index aa7f874..ccc7ac0 100644
--- a/software/base/src/test/java/brooklyn/entity/basic/SoftwareProcessSubclassTest.java
+++ b/software/base/src/test/java/brooklyn/entity/basic/SoftwareProcessSubclassTest.java
@@ -20,7 +20,6 @@ package brooklyn.entity.basic;
 
 import static org.testng.Assert.assertEquals;
 
-import java.util.Collection;
 import java.util.Collections;
 import java.util.List;
 
@@ -30,10 +29,10 @@ import org.testng.annotations.BeforeMethod;
 import org.testng.annotations.Test;
 
 import brooklyn.entity.BrooklynAppUnitTestSupport;
+import brooklyn.entity.effector.EffectorAndBody;
 import brooklyn.entity.proxying.EntitySpec;
 import brooklyn.entity.proxying.ImplementedBy;
 import brooklyn.location.Location;
-import brooklyn.util.config.ConfigBag;
 
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
@@ -51,32 +50,39 @@ public class SoftwareProcessSubclassTest extends BrooklynAppUnitTestSupport {
     public static interface SubSoftwareProcess extends EmptySoftwareProcess {
         public List<String> getCallHistory();
         public void triggerStopOutsideOfEffector();
+        public void customRestart();
     }
     
     public static class SubSoftwareProcessImpl extends EmptySoftwareProcessImpl implements SubSoftwareProcess {
+        
         protected List<String> callHistory = Collections.synchronizedList(Lists.<String>newArrayList());
 
         @Override
+        public void init() {
+            super.init();
+            getMutableEntityType().addEffector(new EffectorAndBody<Void>(SoftwareProcess.RESTART, new MethodEffector<Void>(SubSoftwareProcess.class, "customRestart").getBody()));
+        }
+        
+        @Override
         public List<String> getCallHistory() {
             return callHistory;
         }
 
         @Override
-        public void doStart(Collection<? extends Location> locs) {
+        public void preStart() {
             callHistory.add("doStart");
-            super.doStart(locs);
+            super.preStart();
         }
         
         @Override
-        public void doStop() {
+        public void preStop() {
             callHistory.add("doStop");
-            super.doStop();
+            super.preStop();
         }
         
         @Override
-        public void doRestart(ConfigBag parameters) {
+        public void customRestart() {
             callHistory.add("doRestart");
-            super.doRestart(parameters);
         }
         
         @Override

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/4b6f331d/software/nosql/src/main/java/brooklyn/entity/nosql/couchdb/CouchDBNodeImpl.java
----------------------------------------------------------------------
diff --git a/software/nosql/src/main/java/brooklyn/entity/nosql/couchdb/CouchDBNodeImpl.java b/software/nosql/src/main/java/brooklyn/entity/nosql/couchdb/CouchDBNodeImpl.java
index cb34385..89573ec 100644
--- a/software/nosql/src/main/java/brooklyn/entity/nosql/couchdb/CouchDBNodeImpl.java
+++ b/software/nosql/src/main/java/brooklyn/entity/nosql/couchdb/CouchDBNodeImpl.java
@@ -95,10 +95,10 @@ public class CouchDBNodeImpl extends SoftwareProcessImpl implements CouchDBNode
         disconnectServiceUpIsRunning();
     }
 
-    /** @see JavaWebAppSoftwareProcessImpl#stop() */
+    /** @see JavaWebAppSoftwareProcessImpl#postStop() */
     @Override
-    protected void doStop() {
-        super.doStop();
+    protected void postStop() {
+        super.postStop();
         // zero our workrate derived workrates.
         setAttribute(REQUESTS_PER_SECOND_LAST, 0D);
         setAttribute(REQUESTS_PER_SECOND_IN_WINDOW, 0D);

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/4b6f331d/software/webapp/src/main/java/brooklyn/entity/proxy/nginx/NginxControllerImpl.java
----------------------------------------------------------------------
diff --git a/software/webapp/src/main/java/brooklyn/entity/proxy/nginx/NginxControllerImpl.java b/software/webapp/src/main/java/brooklyn/entity/proxy/nginx/NginxControllerImpl.java
index 2aa2cc4..74727fb 100644
--- a/software/webapp/src/main/java/brooklyn/entity/proxy/nginx/NginxControllerImpl.java
+++ b/software/webapp/src/main/java/brooklyn/entity/proxy/nginx/NginxControllerImpl.java
@@ -157,9 +157,9 @@ public class NginxControllerImpl extends AbstractControllerImpl implements Nginx
     }
     
     @Override
-    protected void doStop() {
+    protected void postStop() {
         // TODO don't want stop to race with the last poll.
-        super.doStop();
+        super.postStop();
         setAttribute(SERVICE_UP, false);
     }
 

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/4b6f331d/software/webapp/src/main/java/brooklyn/entity/webapp/JavaWebAppSoftwareProcessImpl.java
----------------------------------------------------------------------
diff --git a/software/webapp/src/main/java/brooklyn/entity/webapp/JavaWebAppSoftwareProcessImpl.java b/software/webapp/src/main/java/brooklyn/entity/webapp/JavaWebAppSoftwareProcessImpl.java
index 5c89497..04117ea 100644
--- a/software/webapp/src/main/java/brooklyn/entity/webapp/JavaWebAppSoftwareProcessImpl.java
+++ b/software/webapp/src/main/java/brooklyn/entity/webapp/JavaWebAppSoftwareProcessImpl.java
@@ -167,8 +167,8 @@ public abstract class JavaWebAppSoftwareProcessImpl extends SoftwareProcessImpl
     }
     
     @Override
-    protected void doStop() {
-        super.doStop();
+    protected void postStop() {
+        super.postStop();
         // zero our workrate derived workrates.
         // TODO might not be enough, as policy may still be executing and have a record of historic vals; should remove policies
         // (also not sure we want this; implies more generally a responsibility for sensors to announce things when disconnected,

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/4b6f331d/software/webapp/src/main/java/brooklyn/entity/webapp/nodejs/NodeJsWebAppServiceImpl.java
----------------------------------------------------------------------
diff --git a/software/webapp/src/main/java/brooklyn/entity/webapp/nodejs/NodeJsWebAppServiceImpl.java b/software/webapp/src/main/java/brooklyn/entity/webapp/nodejs/NodeJsWebAppServiceImpl.java
index 7b4e8ce..1a83c57 100644
--- a/software/webapp/src/main/java/brooklyn/entity/webapp/nodejs/NodeJsWebAppServiceImpl.java
+++ b/software/webapp/src/main/java/brooklyn/entity/webapp/nodejs/NodeJsWebAppServiceImpl.java
@@ -79,8 +79,8 @@ public class NodeJsWebAppServiceImpl extends SoftwareProcessImpl implements Node
     }
 
     @Override
-    protected void doStop() {
-        super.doStop();
+    protected void postStop() {
+        super.postStop();
 
         setAttribute(REQUESTS_PER_SECOND_LAST, 0D);
         setAttribute(REQUESTS_PER_SECOND_IN_WINDOW, 0D);

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/4b6f331d/software/webapp/src/test/java/brooklyn/entity/webapp/ControlledDynamicWebAppClusterIntegrationTest.java
----------------------------------------------------------------------
diff --git a/software/webapp/src/test/java/brooklyn/entity/webapp/ControlledDynamicWebAppClusterIntegrationTest.java b/software/webapp/src/test/java/brooklyn/entity/webapp/ControlledDynamicWebAppClusterIntegrationTest.java
index 46a097a..471f485 100644
--- a/software/webapp/src/test/java/brooklyn/entity/webapp/ControlledDynamicWebAppClusterIntegrationTest.java
+++ b/software/webapp/src/test/java/brooklyn/entity/webapp/ControlledDynamicWebAppClusterIntegrationTest.java
@@ -124,9 +124,9 @@ public class ControlledDynamicWebAppClusterIntegrationTest extends BrooklynAppLi
     // Needs to be integration test because still using nginx controller; could pass in mock controller
     @Test(groups="Integration")
     public void testSetsServiceLifecycle() {
-        ControlledDynamicWebAppCluster cluster = app.createAndManageChild(EntitySpec.create(ControlledDynamicWebAppCluster.class)
+        ControlledDynamicWebAppCluster cluster = app.createAndManageChild( EntitySpec.create(ControlledDynamicWebAppCluster.class)
                 .configure("initialSize", 1)
-                .configure("factory", new BasicConfigurableEntityFactory<TestJavaWebAppEntity>(TestJavaWebAppEntity.class)));
+                .configure(ControlledDynamicWebAppCluster.MEMBER_SPEC, EntitySpec.create(TestJavaWebAppEntity.class)) );
         
         EntityTestUtils.assertAttributeEqualsEventually(cluster, Attributes.SERVICE_STATE_ACTUAL, Lifecycle.STOPPED);
         

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/4b6f331d/software/webapp/src/test/java/brooklyn/entity/webapp/ControlledDynamicWebAppClusterTest.java
----------------------------------------------------------------------
diff --git a/software/webapp/src/test/java/brooklyn/entity/webapp/ControlledDynamicWebAppClusterTest.java b/software/webapp/src/test/java/brooklyn/entity/webapp/ControlledDynamicWebAppClusterTest.java
index 041616f..366f29b 100644
--- a/software/webapp/src/test/java/brooklyn/entity/webapp/ControlledDynamicWebAppClusterTest.java
+++ b/software/webapp/src/test/java/brooklyn/entity/webapp/ControlledDynamicWebAppClusterTest.java
@@ -32,6 +32,7 @@ import brooklyn.entity.BrooklynAppUnitTestSupport;
 import brooklyn.entity.basic.BasicConfigurableEntityFactory;
 import brooklyn.entity.basic.Entities;
 import brooklyn.entity.basic.Lifecycle;
+import brooklyn.entity.basic.SoftwareProcess;
 import brooklyn.entity.proxy.AbstractController;
 import brooklyn.entity.proxy.LoadBalancer;
 import brooklyn.entity.proxy.TrackingAbstractController;
@@ -43,6 +44,7 @@ import brooklyn.location.basic.LocalhostMachineProvisioningLocation;
 import brooklyn.test.Asserts;
 import brooklyn.test.EntityTestUtils;
 import brooklyn.test.entity.TestJavaWebAppEntity;
+import brooklyn.test.entity.TestJavaWebAppEntity.TestJavaWebAppEntityImpl;
 
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.Iterables;
@@ -102,11 +104,22 @@ public class ControlledDynamicWebAppClusterTest extends BrooklynAppUnitTestSuppo
     }
     
     @Test
+    public void testTheTestJavaWebApp() {
+        SoftwareProcess n = app.createAndManageChild(EntitySpec.create(TestJavaWebAppEntity.class));
+        app.start(locs);
+
+        EntityTestUtils.assertAttributeEqualsEventually(n, AbstractController.SERVICE_UP, true);
+        
+        app.stop();
+        EntityTestUtils.assertAttributeEqualsEventually(n, AbstractController.SERVICE_UP, false);
+    }
+    
+    @Test
     public void testSetsInitialSize() {
         ControlledDynamicWebAppCluster cluster = app.createAndManageChild(EntitySpec.create(ControlledDynamicWebAppCluster.class)
                 .configure("initialSize", 2)
                 .configure(ControlledDynamicWebAppCluster.CONTROLLER_SPEC, EntitySpec.create(TrackingAbstractController.class))
-                .configure("factory", new BasicConfigurableEntityFactory<TestJavaWebAppEntity>(TestJavaWebAppEntity.class)));
+                .configure(ControlledDynamicWebAppCluster.MEMBER_SPEC, EntitySpec.create(TestJavaWebAppEntity.class)) );
         app.start(locs);
 
         Iterable<TestJavaWebAppEntity> webservers = Iterables.filter(cluster.getCluster().getMembers(), TestJavaWebAppEntity.class);
@@ -162,7 +175,7 @@ public class ControlledDynamicWebAppClusterTest extends BrooklynAppUnitTestSuppo
         final ControlledDynamicWebAppCluster cluster = app.createAndManageChild(EntitySpec.create(ControlledDynamicWebAppCluster.class)
                 .configure("initialSize", 1)
                 .configure(ControlledDynamicWebAppCluster.CONTROLLER_SPEC, EntitySpec.create(TrackingAbstractController.class))
-                .configure("factory", new BasicConfigurableEntityFactory<TestJavaWebAppEntity>(TestJavaWebAppEntity.class)));
+                .configure(ControlledDynamicWebAppCluster.MEMBER_SPEC, EntitySpec.create(TestJavaWebAppEntity.class)) );
         app.start(locs);
         final DynamicWebAppCluster childCluster = cluster.getCluster();
         
@@ -195,7 +208,7 @@ public class ControlledDynamicWebAppClusterTest extends BrooklynAppUnitTestSuppo
         final ControlledDynamicWebAppCluster cluster = app.createAndManageChild(EntitySpec.create(ControlledDynamicWebAppCluster.class)
                 .configure("initialSize", 1)
                 .configure(ControlledDynamicWebAppCluster.CONTROLLER_SPEC, EntitySpec.create(TrackingAbstractController.class))
-                .configure("factory", new BasicConfigurableEntityFactory<TestJavaWebAppEntity>(TestJavaWebAppEntity.class)));
+                .configure(ControlledDynamicWebAppCluster.MEMBER_SPEC, EntitySpec.create(TestJavaWebAppEntity.class)) );
         app.start(locs);
         final DynamicWebAppCluster childCluster = cluster.getCluster();
         LoadBalancer controller = cluster.getController();

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/4b6f331d/software/webapp/src/test/java/brooklyn/entity/webapp/DynamicWebAppClusterTest.java
----------------------------------------------------------------------
diff --git a/software/webapp/src/test/java/brooklyn/entity/webapp/DynamicWebAppClusterTest.java b/software/webapp/src/test/java/brooklyn/entity/webapp/DynamicWebAppClusterTest.java
index 8def438..446aa46 100644
--- a/software/webapp/src/test/java/brooklyn/entity/webapp/DynamicWebAppClusterTest.java
+++ b/software/webapp/src/test/java/brooklyn/entity/webapp/DynamicWebAppClusterTest.java
@@ -67,7 +67,7 @@ public class DynamicWebAppClusterTest {
 
     @Test
     public void testTestJavaWebAppEntity() throws Exception {
-        Entity test = app.createAndManageChild(EntitySpec.create(Entity.class, TestJavaWebAppEntity.class));
+        Entity test = app.createAndManageChild(EntitySpec.create(TestJavaWebAppEntity.class));
         test.invoke(Startable.START, ImmutableMap.of("locations", ImmutableList.of())).get();
     }
     
@@ -75,7 +75,7 @@ public class DynamicWebAppClusterTest {
     public void testRequestCountAggregation() throws Exception {
         final DynamicWebAppCluster cluster = app.createAndManageChild(EntitySpec.create(DynamicWebAppCluster.class)
                 .configure("initialSize", 2)
-                .configure("factory", new BasicConfigurableEntityFactory<TestJavaWebAppEntity>(TestJavaWebAppEntity.class)));
+                .configure(ControlledDynamicWebAppCluster.MEMBER_SPEC, EntitySpec.create(TestJavaWebAppEntity.class)) );
         
         app.start(ImmutableList.of(new SimulatedLocation()));
         
@@ -105,7 +105,7 @@ public class DynamicWebAppClusterTest {
     public void testSetsServiceUpIfMemberIsUp() throws Exception {
         DynamicWebAppCluster cluster = app.createAndManageChild(EntitySpec.create(DynamicWebAppCluster.class)
                 .configure("initialSize", 1)
-                .configure("factory", new BasicConfigurableEntityFactory<TestJavaWebAppEntity>(TestJavaWebAppEntity.class)));
+                .configure(ControlledDynamicWebAppCluster.MEMBER_SPEC, EntitySpec.create(TestJavaWebAppEntity.class)) );
     
         app.start(ImmutableList.of(new SimulatedLocation()));
         
@@ -132,13 +132,14 @@ public class DynamicWebAppClusterTest {
     @Test
     public void testPropertiesToChildren() throws Exception {
         DynamicWebAppCluster cluster = app.createAndManageChild(EntitySpec.create(DynamicWebAppCluster.class)
-            .configure("factory", new BasicConfigurableEntityFactory<TestJavaWebAppEntity>(MutableMap.of("a", 1), TestJavaWebAppEntity.class))
+            .configure(ControlledDynamicWebAppCluster.MEMBER_SPEC, EntitySpec.create(TestJavaWebAppEntity.class)
+                .configure("a", 1))
             .configure(DynamicWebAppCluster.CUSTOM_CHILD_FLAGS, ImmutableMap.of("b", 2)));
 
         app.start(ImmutableList.of(new SimulatedLocation()));
         
         TestJavaWebAppEntity we = (TestJavaWebAppEntity) Iterables.getOnlyElement(cluster.getMembers());
-        assertEquals(we.a, 1);
-        assertEquals(we.b, 2);
+        assertEquals(we.getA(), 1);
+        assertEquals(we.getB(), 2);
     }
 }

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/4b6f331d/software/webapp/src/test/java/brooklyn/entity/webapp/DynamicWebAppFabricTest.java
----------------------------------------------------------------------
diff --git a/software/webapp/src/test/java/brooklyn/entity/webapp/DynamicWebAppFabricTest.java b/software/webapp/src/test/java/brooklyn/entity/webapp/DynamicWebAppFabricTest.java
index ff3babb..d898bf6 100644
--- a/software/webapp/src/test/java/brooklyn/entity/webapp/DynamicWebAppFabricTest.java
+++ b/software/webapp/src/test/java/brooklyn/entity/webapp/DynamicWebAppFabricTest.java
@@ -19,7 +19,6 @@
 package brooklyn.entity.webapp;
 
 import java.util.List;
-import java.util.Map;
 
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -28,12 +27,11 @@ import org.testng.annotations.BeforeMethod;
 import org.testng.annotations.Test;
 
 import brooklyn.entity.Entity;
-import brooklyn.entity.basic.AbstractConfigurableEntityFactory;
 import brooklyn.entity.basic.ApplicationBuilder;
-import brooklyn.entity.basic.BasicConfigurableEntityFactory;
+import brooklyn.entity.basic.DynamicGroup;
 import brooklyn.entity.basic.Entities;
+import brooklyn.entity.basic.EntityInternal;
 import brooklyn.entity.proxying.EntitySpec;
-import brooklyn.entity.trait.Changeable;
 import brooklyn.location.basic.SimulatedLocation;
 import brooklyn.test.EntityTestUtils;
 import brooklyn.test.entity.TestApplication;
@@ -70,20 +68,13 @@ public class DynamicWebAppFabricTest {
 
     @Test
     public void testRequestCountAggregation() {
-        // TODO Want to use EntitySpec, but TestJavaWebAppEntity not converted (so can't call spoofRequest on an interface).
-        //      .configure(DynamicWebAppFabric.MEMBER_SPEC, EntitySpec.create(Entity.class).impl(TestJavaWebAppEntity.class)));
-        
         DynamicWebAppFabric fabric = app.createAndManageChild(EntitySpec.create(DynamicWebAppFabric.class)
-                .configure(DynamicWebAppFabric.FACTORY, new AbstractConfigurableEntityFactory<TestJavaWebAppEntity>() {
-                    @Override public TestJavaWebAppEntity newEntity2(Map flags, Entity parent) {
-                        TestJavaWebAppEntity result = new TestJavaWebAppEntity(flags, parent);
-                        result.setAttribute(Changeable.GROUP_SIZE, 1);
-                        return result;
-                    }}));
+                .configure(DynamicWebAppFabric.MEMBER_SPEC, EntitySpec.create(TestJavaWebAppEntity.class)) );
         
         app.start(locs);
         
         for (Entity member : fabric.getChildren()) {
+            ((EntityInternal)member).setAttribute(DynamicGroup.GROUP_SIZE, 1);
             ((TestJavaWebAppEntity)member).spoofRequest();
         }
         EntityTestUtils.assertAttributeEqualsEventually(MutableMap.of("timeout", TIMEOUT_MS), fabric, DynamicWebAppFabric.REQUEST_COUNT, 2);
@@ -101,9 +92,10 @@ public class DynamicWebAppFabricTest {
     @Test
     public void testRequestCountAggregationOverClusters() {
         DynamicWebAppFabric fabric = app.createAndManageChild(EntitySpec.create(DynamicWebAppFabric.class)
-                .configure(DynamicWebAppFabric.MEMBER_SPEC, EntitySpec.create(DynamicWebAppCluster.class)
+                .configure(DynamicWebAppFabric.MEMBER_SPEC, 
+                    EntitySpec.create(DynamicWebAppCluster.class)
                         .configure("initialSize", 2)
-                        .configure(DynamicWebAppCluster.FACTORY, new BasicConfigurableEntityFactory<TestJavaWebAppEntity>(TestJavaWebAppEntity.class))));
+                        .configure(ControlledDynamicWebAppCluster.MEMBER_SPEC, EntitySpec.create(TestJavaWebAppEntity.class)) ));
 
         app.start(locs);
         

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/4b6f331d/software/webapp/src/test/java/brooklyn/test/entity/TestJavaWebAppEntity.java
----------------------------------------------------------------------
diff --git a/software/webapp/src/test/java/brooklyn/test/entity/TestJavaWebAppEntity.java b/software/webapp/src/test/java/brooklyn/test/entity/TestJavaWebAppEntity.java
index a33d707..48d5376 100644
--- a/software/webapp/src/test/java/brooklyn/test/entity/TestJavaWebAppEntity.java
+++ b/software/webapp/src/test/java/brooklyn/test/entity/TestJavaWebAppEntity.java
@@ -18,63 +18,90 @@
  */
 package brooklyn.test.entity;
 
-import java.util.Collection;
 import java.util.Map;
 
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-import brooklyn.entity.Effector;
 import brooklyn.entity.Entity;
 import brooklyn.entity.basic.Attributes;
 import brooklyn.entity.basic.Lifecycle;
-import brooklyn.entity.basic.MethodEffector;
 import brooklyn.entity.basic.ServiceStateLogic;
-import brooklyn.entity.basic.SoftwareProcessImpl;
-import brooklyn.entity.effector.EffectorAndBody;
+import brooklyn.entity.basic.SoftwareProcessDriverLifecycleEffectorTasks;
+import brooklyn.entity.java.VanillaJavaApp;
 import brooklyn.entity.java.VanillaJavaAppImpl;
+import brooklyn.entity.proxying.ImplementedBy;
+import brooklyn.entity.webapp.WebAppService;
 import brooklyn.entity.webapp.WebAppServiceConstants;
 import brooklyn.location.Location;
-import brooklyn.util.config.ConfigBag;
+import brooklyn.test.entity.TestJavaWebAppEntity.TestJavaWebAppEntityImpl;
 import brooklyn.util.flags.SetFromFlag;
 
 /**
  * Mock web application server entity for testing.
  */
-public class TestJavaWebAppEntity extends VanillaJavaAppImpl {
-    private static final Logger LOG = LoggerFactory.getLogger(TestJavaWebAppEntity.class);
-    public static final Effector<Void> START = new EffectorAndBody<Void>(SoftwareProcessImpl.START, new MethodEffector<Void>(TestJavaWebAppEntity.class, "customStart").getBody());
+@ImplementedBy(TestJavaWebAppEntityImpl.class)
+public interface TestJavaWebAppEntity extends VanillaJavaApp, WebAppService {
 
-    @SetFromFlag public int a;
-    @SetFromFlag public int b;
-    @SetFromFlag public int c;
-
-    public TestJavaWebAppEntity() {}
-    public TestJavaWebAppEntity(@SuppressWarnings("rawtypes") Map flags, Entity parent) { super(flags, parent); }
+    void spoofRequest();
+    int getA();
+    int getB();
+    int getC();
     
-    public void waitForHttpPort() { }
+    public static class TestJavaWebAppEntityImpl extends VanillaJavaAppImpl implements TestJavaWebAppEntity {
 
-    
-    public void customStart(Collection<? extends Location> loc) {
-        ServiceStateLogic.setExpectedState(this, Lifecycle.STARTING);
-        LOG.trace("Starting {}", this);
-        ServiceStateLogic.setExpectedState(this, Lifecycle.RUNNING);
-        setAttribute(Attributes.SERVICE_UP, true);
-    }
+        private static final Logger LOG = LoggerFactory.getLogger(TestJavaWebAppEntity.class);
 
-    @Override
-    protected void doStop() {
-        LOG.trace("Stopping {}", this);
-    }
+        @SetFromFlag public int a;
+        @SetFromFlag public int b;
+        @SetFromFlag public int c;
 
-    @Override
-    public void doRestart(ConfigBag parameters) {
-        throw new UnsupportedOperationException();
-    }
+        public TestJavaWebAppEntityImpl() {}
+        public TestJavaWebAppEntityImpl(@SuppressWarnings("rawtypes") Map flags, Entity parent) { super(flags, parent); }
 
-    public synchronized void spoofRequest() {
-        Integer rc = getAttribute(WebAppServiceConstants.REQUEST_COUNT);
-        if (rc==null) rc = 0;
-        setAttribute(WebAppServiceConstants.REQUEST_COUNT, rc+1);
+        private static final SoftwareProcessDriverLifecycleEffectorTasks LIFECYCLE_TASKS =
+            new SoftwareProcessDriverLifecycleEffectorTasks() {
+            public void start(java.util.Collection<? extends Location> locations) {
+                ServiceStateLogic.setExpectedState(entity(), Lifecycle.STARTING);
+                LOG.trace("Starting {}", this);
+                entity().setAttribute(Attributes.SERVICE_UP, true);
+                ServiceStateLogic.setExpectedState(entity(), Lifecycle.RUNNING);
+            }
+            public void stop() {
+                ServiceStateLogic.setExpectedState(entity(), Lifecycle.STOPPING);
+                LOG.trace("Stopping {}", this);
+                entity().setAttribute(Attributes.SERVICE_UP, false);
+                ServiceStateLogic.setExpectedState(entity(), Lifecycle.STOPPED);
+            }
+        };
+
+        @Override
+        public void init() {
+            super.init();
+            LIFECYCLE_TASKS.attachLifecycleEffectors(this);
+        }
+
+        @Override
+        public synchronized void spoofRequest() {
+            Integer rc = getAttribute(WebAppServiceConstants.REQUEST_COUNT);
+            if (rc==null) rc = 0;
+            setAttribute(WebAppServiceConstants.REQUEST_COUNT, rc+1);
+        }
+
+        @Override
+        public int getA() {
+            return a;
+        }
+        
+        @Override
+        public int getB() {
+            return b;
+        }
+        
+        @Override
+        public int getC() {
+            return c;
+        }
     }
+
 }