Add finer-grained STOP effector parameters.

Separate flags for process and machine stop, better control with regard to the 
machine 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/6ac2cd95
Tree: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/tree/6ac2cd95
Diff: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/diff/6ac2cd95

Branch: refs/heads/master
Commit: 6ac2cd95d1fa6385a6dce28cf4f0b7c0b129cb25
Parents: dc53885
Author: Svetoslav Neykov <[email protected]>
Authored: Tue Dec 23 14:35:04 2014 +0200
Committer: Svetoslav Neykov <[email protected]>
Committed: Sat Jan 17 00:29:41 2015 +0200

----------------------------------------------------------------------
 .../brooklyn/entity/basic/SoftwareProcess.java  | 14 ++++
 .../software/MachineLifecycleEffectorTasks.java | 79 +++++++++++++------
 .../entity/basic/SoftwareProcessEntityTest.java | 81 ++++++++++++++++++--
 .../MachineLifecycleEffectorTasksTest.java      | 51 ++++++++++++
 4 files changed, 197 insertions(+), 28 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/6ac2cd95/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 6e0f9fa..2b27a47 100644
--- a/software/base/src/main/java/brooklyn/entity/basic/SoftwareProcess.java
+++ b/software/base/src/main/java/brooklyn/entity/basic/SoftwareProcess.java
@@ -264,6 +264,20 @@ public interface SoftwareProcess extends Entity, Startable 
{
         public static final ConfigKey<Boolean> STOP_MACHINE = 
ConfigKeys.newBooleanConfigKey("stopMachine",
                 "Whether to stop the machine provisioned for this entity:  
'true', or 'false' are supported, "
                         + "with the default being 'true'", true);
+
+        //IF_NOT_STOPPED includes STARTING, STOPPING, RUNNING
+        public enum StopMode { ALWAYS, IF_NOT_STOPPED, NEVER };
+
+        @Beta /** @since 0.7.0 semantics of parameters to restart being 
explored */
+        public static final ConfigKey<StopMode> STOP_PROCESS_MODE = 
ConfigKeys.newConfigKey(StopMode.class, "stopProcessMode",
+                "When to stop the process with regard to the entity state", 
StopMode.IF_NOT_STOPPED);
+
+        @Beta /** @since 0.7.0 semantics of parameters to restart being 
explored */
+        public static final ConfigKey<StopMode> STOP_MACHINE_MODE = 
ConfigKeys.newConfigKey(StopMode.class, "stopMachineMode",
+                "When to stop the machine with regard to the entity state. " +
+                "ALWAYS will try to stop the machine even if the entity is 
already stopped, " +
+                "IF_NOT_STOPPED stops the machine only if the entity is not 
already stopped, " +
+                "NEVER doesn't stop the machine.", StopMode.IF_NOT_STOPPED);
     }
     
     // NB: the START, STOP, and RESTART effectors themselves are (re)defined 
by MachineLifecycleEffectorTasks

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/6ac2cd95/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 ab0cae4..ecba2b3 100644
--- 
a/software/base/src/main/java/brooklyn/entity/software/MachineLifecycleEffectorTasks.java
+++ 
b/software/base/src/main/java/brooklyn/entity/software/MachineLifecycleEffectorTasks.java
@@ -47,6 +47,7 @@ import brooklyn.entity.basic.SoftwareProcess;
 import brooklyn.entity.basic.SoftwareProcess.RestartSoftwareParameters;
 import brooklyn.entity.basic.SoftwareProcess.StopSoftwareParameters;
 import 
brooklyn.entity.basic.SoftwareProcess.RestartSoftwareParameters.RestartMachineMode;
+import brooklyn.entity.basic.SoftwareProcess.StopSoftwareParameters.StopMode;
 import brooklyn.entity.effector.EffectorBody;
 import brooklyn.entity.effector.Effectors;
 import brooklyn.entity.trait.Startable;
@@ -77,6 +78,7 @@ import brooklyn.util.text.Strings;
 import brooklyn.util.time.Duration;
 
 import com.google.common.annotations.Beta;
+import com.google.common.annotations.VisibleForTesting;
 import com.google.common.base.Preconditions;
 import com.google.common.base.Supplier;
 import com.google.common.base.Suppliers;
@@ -143,7 +145,8 @@ public abstract class MachineLifecycleEffectorTasks {
     /** @see {@link #newStartEffector()} */
     public Effector<Void> newStopEffector() {
         return Effectors.effector(Startable.STOP)
-                .parameter(StopSoftwareParameters.STOP_MACHINE)
+                .parameter(StopSoftwareParameters.STOP_PROCESS_MODE)
+                .parameter(StopSoftwareParameters.STOP_MACHINE_MODE)
                 .impl(newStopEffectorTask())
                 .build();
     }
@@ -535,10 +538,24 @@ public abstract class MachineLifecycleEffectorTasks {
     public void stop(ConfigBag parameters) {
         log.info("Stopping {} in {}", entity(), entity().getLocations());
 
-        Boolean isStopMachine = 
parameters.get(StopSoftwareParameters.STOP_MACHINE);
+        final boolean hasStopMachine = 
parameters.containsKey(StopSoftwareParameters.STOP_MACHINE);
+        final Boolean isStopMachine = 
parameters.get(StopSoftwareParameters.STOP_MACHINE);
 
-        if (isStopMachine==null)
-            isStopMachine = Boolean.TRUE;
+        final StopMode stopProcessMode = 
parameters.get(StopSoftwareParameters.STOP_PROCESS_MODE);
+
+        final boolean hasStopMachineMode = 
parameters.containsKey(StopSoftwareParameters.STOP_MACHINE_MODE);
+        StopMode stopMachineMode = 
parameters.get(StopSoftwareParameters.STOP_MACHINE_MODE);
+
+        if (hasStopMachine && isStopMachine != null) {
+            checkCompatibleMachineModes(isStopMachine, hasStopMachineMode, 
stopMachineMode);
+            if (isStopMachine) {
+                stopMachineMode = StopMode.IF_NOT_STOPPED;
+            } else {
+                stopMachineMode = StopMode.NEVER;
+            }
+        }
+
+        boolean isEntityStopped = 
entity().getAttribute(SoftwareProcess.SERVICE_STATE_ACTUAL)==Lifecycle.STOPPED;
 
         DynamicTasks.queue("pre-stop", new Callable<String>() { public String 
call() {
             if 
(entity().getAttribute(SoftwareProcess.SERVICE_STATE_ACTUAL)==Lifecycle.STOPPED)
 {
@@ -551,28 +568,22 @@ public abstract class MachineLifecycleEffectorTasks {
             return null;
         }});
 
-        if 
(entity().getAttribute(SoftwareProcess.SERVICE_STATE_ACTUAL)==Lifecycle.STOPPED)
 {
-            return;
-        }
-
         Maybe<SshMachineLocation> sshMachine = 
Machines.findUniqueSshMachineLocation(entity().getLocations());
-        Task<String> stoppingProcess = DynamicTasks.queue("stopping 
(process)", new Callable<String>() { public String call() {
-            DynamicTasks.markInessential();
-            stopProcessesAtMachine();
-            DynamicTasks.waitForLast();
-            return "Stop at machine completed with no errors.";
-        }});
-
+        Task<String> stoppingProcess = null;
+        if (canStop(stopProcessMode, isEntityStopped)) {
+            stoppingProcess = DynamicTasks.queue("stopping (process)", new 
Callable<String>() { public String call() {
+                DynamicTasks.markInessential();
+                stopProcessesAtMachine();
+                DynamicTasks.waitForLast();
+                return "Stop at machine completed with no errors.";
+            }});
+        }
 
         Task<StopMachineDetails<Integer>> stoppingMachine = null;
-        if (isStopMachine) {
+        if (canStop(stopMachineMode, isEntityStopped)) {
             // Release this machine (even if error trying to stop process - we 
rethrow that after)
             stoppingMachine = DynamicTasks.queue("stopping (machine)", new 
Callable<StopMachineDetails<Integer>>() {
                 public StopMachineDetails<Integer> call() {
-                    if 
(entity().getAttribute(SoftwareProcess.SERVICE_STATE_ACTUAL) == 
Lifecycle.STOPPED) {
-                        log.debug("Skipping stop of entity " + entity() + " 
when already stopped");
-                        return new StopMachineDetails<Integer>("Already 
stopped", 0);
-                    }
                     return stopAnyProvisionedMachines();
                 }
             });
@@ -584,8 +595,14 @@ public abstract class MachineLifecycleEffectorTasks {
                 // task also used as mutex by DST when it submits it; ensure 
it only submits once!
                 if (!stoppingMachine.isSubmitted()) {
                     // force the stoppingMachine task to run by submitting it 
here
-                    log.warn("Submitting machine stop early in background for 
"+entity()+" because process stop has "+
-                            (stoppingProcess.isDone() ? "finished abnormally" 
: "not finished"));
+                    StringBuilder msg = new StringBuilder("Submitting machine 
stop early in background for ").append(entity());
+                    if (stoppingProcess == null) {
+                        msg.append(". Process stop skipped, pre-stop not 
finished?");
+                    } else {
+                        msg.append(" because process stop has "+
+                                (stoppingProcess.isDone() ? "finished 
abnormally" : "not finished"));
+                    }
+                    log.warn(msg.toString());
                     Entities.submit(entity(), stoppingMachine);
                 }
             }
@@ -594,7 +611,7 @@ public abstract class MachineLifecycleEffectorTasks {
         try {
             // This maintains previous behaviour of silently squashing any 
errors on the stoppingProcess task if the
             // stoppingMachine exits with a nonzero value
-            boolean checkStopProcesses = (stoppingMachine == null || 
stoppingMachine.get().value == 0);
+            boolean checkStopProcesses = (stoppingProcess != null && 
(stoppingMachine == null || stoppingMachine.get().value == 0));
 
             if (checkStopProcesses) {
                 // TODO we should test for destruction above, not merely 
successful "stop", as things like localhost and ssh won't be destroyed
@@ -614,6 +631,22 @@ public abstract class MachineLifecycleEffectorTasks {
         if (log.isDebugEnabled()) log.debug("Stopped software process entity 
"+entity());
     }
 
+    protected static boolean canStop(StopMode stopMode, boolean 
isEntityStopped) {
+        return stopMode == StopMode.ALWAYS ||
+                stopMode == StopMode.IF_NOT_STOPPED && !isEntityStopped;
+    }
+
+    private void checkCompatibleMachineModes(Boolean isStopMachine, boolean 
hasStopMachineMode, StopMode stopMachineMode) {
+        if (hasStopMachineMode &&
+                (isStopMachine && stopMachineMode != StopMode.IF_NOT_STOPPED ||
+                 !isStopMachine && stopMachineMode != StopMode.NEVER)) {
+            throw new IllegalStateException("Incompatible values for " +
+                    StopSoftwareParameters.STOP_MACHINE.getName() + " (" + 
isStopMachine + ") and " +
+                    StopSoftwareParameters.STOP_MACHINE_MODE.getName() + " (" 
+ stopMachineMode + "). " +
+                    "Use only one of the parameters.");
+        }
+    }
+
     protected void preStopCustom() {
         // nothing needed here
     }

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/6ac2cd95/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 b2a7993..81dd571 100644
--- 
a/software/base/src/test/java/brooklyn/entity/basic/SoftwareProcessEntityTest.java
+++ 
b/software/base/src/test/java/brooklyn/entity/basic/SoftwareProcessEntityTest.java
@@ -24,9 +24,12 @@ import brooklyn.entity.Entity;
 import brooklyn.entity.basic.SoftwareProcess.RestartSoftwareParameters;
 import 
brooklyn.entity.basic.SoftwareProcess.RestartSoftwareParameters.RestartMachineMode;
 import brooklyn.entity.basic.SoftwareProcess.StopSoftwareParameters;
+import brooklyn.entity.basic.SoftwareProcess.StopSoftwareParameters.StopMode;
 import brooklyn.entity.effector.Effectors;
 import brooklyn.entity.proxying.EntitySpec;
 import brooklyn.entity.proxying.ImplementedBy;
+import brooklyn.entity.software.MachineLifecycleEffectorTasks;
+import brooklyn.entity.software.MachineLifecycleEffectorTasksTest;
 import brooklyn.entity.trait.Startable;
 import brooklyn.location.Location;
 import brooklyn.location.LocationSpec;
@@ -37,15 +40,18 @@ import brooklyn.management.Task;
 import brooklyn.management.TaskAdaptable;
 import brooklyn.util.collections.MutableMap;
 import brooklyn.util.config.ConfigBag;
+import brooklyn.util.exceptions.PropagatedRuntimeException;
 import brooklyn.util.net.UserAndHostAndPort;
 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;
 import com.google.common.collect.Iterables;
+
 import org.jclouds.util.Throwables2;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -74,14 +80,19 @@ public class SoftwareProcessEntityTest extends 
BrooklynAppUnitTestSupport {
     private FixedListMachineProvisioningLocation<SshMachineLocation> loc;
     
     @BeforeMethod(alwaysRun=true)
-    @SuppressWarnings("unchecked")
     @Override
     public void setUp() throws Exception {
         super.setUp();
-        loc = 
mgmt.getLocationManager().createLocation(LocationSpec.create(FixedListMachineProvisioningLocation.class));
+        loc = getLocation();
+    }
+
+    @SuppressWarnings("unchecked")
+    private FixedListMachineProvisioningLocation<SshMachineLocation> 
getLocation() {
+        FixedListMachineProvisioningLocation<SshMachineLocation> loc = 
mgmt.getLocationManager().createLocation(LocationSpec.create(FixedListMachineProvisioningLocation.class));
         machine = 
mgmt.getLocationManager().createLocation(LocationSpec.create(SshMachineLocation.class)
                 .configure("address", "localhost"));
         loc.addMachine(machine);
+        return loc;
     }
 
     @Test
@@ -256,9 +267,7 @@ public class SoftwareProcessEntityTest extends 
BrooklynAppUnitTestSupport {
         d.events.clear();
 
         TaskAdaptable<Void> t1 = Entities.submit(entity, 
Effectors.invocation(entity, Startable.STOP,
-                
ConfigBag.newInstance().configure(StopSoftwareParameters.STOP_MACHINE, false
-
-                )));
+                
ConfigBag.newInstance().configure(StopSoftwareParameters.STOP_MACHINE, false)));
         t1.asTask().get(10, TimeUnit.SECONDS);
 
         assertEquals(d.events, ImmutableList.of("stop"));
@@ -266,6 +275,68 @@ public class SoftwareProcessEntityTest extends 
BrooklynAppUnitTestSupport {
         assertFalse(loc.getAvailable().contains(machine));
     }
     
+    @Test(groups = "Integration")
+    public void testBasicSoftwareProcessStopAllModes() throws Exception {
+        for (boolean isEntityStopped : new boolean[] {true, false}) {
+            for (StopMode stopProcessMode : StopMode.values()) {
+                for (StopMode stopMachineMode : StopMode.values()) {
+                    try {
+                        testBasicSoftwareProcessStopModes(stopProcessMode, 
stopMachineMode, isEntityStopped);
+                    } catch (Exception e) {
+                        String msg = "stopProcessMode: " + stopProcessMode + 
", stopMachineMode: " + stopMachineMode + ", isEntityStopped: " + 
isEntityStopped;
+                        throw new PropagatedRuntimeException(msg, e);
+                    }
+                }
+            }
+        }
+    }
+    
+    @Test
+    public void testBasicSoftwareProcessStopSomeModes() throws Exception {
+        for (boolean isEntityStopped : new boolean[] {true, false}) {
+            StopMode stopProcessMode = StopMode.IF_NOT_STOPPED;
+            StopMode stopMachineMode = StopMode.IF_NOT_STOPPED;
+            try {
+                testBasicSoftwareProcessStopModes(stopProcessMode, 
stopMachineMode, isEntityStopped);
+            } catch (Exception e) {
+                String msg = "stopProcessMode: " + stopProcessMode + ", 
stopMachineMode: " + stopMachineMode + ", isEntityStopped: " + isEntityStopped;
+                throw new PropagatedRuntimeException(msg, e);
+            }
+        }
+    }
+    
+    private void testBasicSoftwareProcessStopModes(StopMode stopProcessMode, 
StopMode stopMachineMode, boolean isEntityStopped) throws Exception {
+        FixedListMachineProvisioningLocation<SshMachineLocation> l = 
getLocation();
+        MyService entity = 
app.createAndManageChild(EntitySpec.create(MyService.class));
+        entity.start(ImmutableList.of(l));
+        SimulatedDriver d = (SimulatedDriver) entity.getDriver();
+        Location machine = Iterables.getOnlyElement(entity.getLocations());
+        d.events.clear();
+
+        if (isEntityStopped) {
+            
((EntityInternal)entity).setAttribute(ServiceStateLogic.SERVICE_STATE_ACTUAL, 
Lifecycle.STOPPED);
+        }
+
+        TaskAdaptable<Void> t1 = Entities.submit(entity, 
Effectors.invocation(entity, Startable.STOP,
+                ConfigBag.newInstance()
+                    .configure(StopSoftwareParameters.STOP_PROCESS_MODE, 
stopProcessMode)
+                    .configure(StopSoftwareParameters.STOP_MACHINE_MODE, 
stopMachineMode)));
+        t1.asTask().get(10, TimeUnit.SECONDS);
+
+        if (MachineLifecycleEffectorTasksTest.canStop(stopProcessMode, 
isEntityStopped)) {
+            assertEquals(d.events, ImmutableList.of("stop"));
+        } else {
+            assertTrue(d.events.isEmpty());
+        }
+        if (MachineLifecycleEffectorTasksTest.canStop(stopMachineMode, 
isEntityStopped)) {
+            assertTrue(entity.getLocations().isEmpty());
+            assertTrue(l.getAvailable().contains(machine));
+        } else {
+            assertEquals(ImmutableList.copyOf(entity.getLocations()), 
ImmutableList.of(machine));
+            assertFalse(l.getAvailable().contains(machine));
+        }
+    }
+
     @Test
     public void testShutdownIsIdempotent() throws Exception {
         MyService entity = 
app.createAndManageChild(EntitySpec.create(MyService.class));

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/6ac2cd95/software/base/src/test/java/brooklyn/entity/software/MachineLifecycleEffectorTasksTest.java
----------------------------------------------------------------------
diff --git 
a/software/base/src/test/java/brooklyn/entity/software/MachineLifecycleEffectorTasksTest.java
 
b/software/base/src/test/java/brooklyn/entity/software/MachineLifecycleEffectorTasksTest.java
new file mode 100644
index 0000000..223d00b
--- /dev/null
+++ 
b/software/base/src/test/java/brooklyn/entity/software/MachineLifecycleEffectorTasksTest.java
@@ -0,0 +1,51 @@
+/*
+ * 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.software;
+
+import static org.testng.Assert.assertEquals;
+
+import org.testng.annotations.DataProvider;
+import org.testng.annotations.Test;
+
+import brooklyn.entity.basic.SoftwareProcess.StopSoftwareParameters.StopMode;
+
+public class MachineLifecycleEffectorTasksTest {
+    public static boolean canStop(StopMode stopMode, boolean isEntityStopped) {
+        return MachineLifecycleEffectorTasks.canStop(stopMode, 
isEntityStopped);
+    }
+    
+    @DataProvider(name = "canStopStates")
+    public Object[][] canStopStates() {
+        return new Object[][] {
+            { StopMode.ALWAYS, true, true },
+            { StopMode.ALWAYS, false, true },
+            { StopMode.IF_NOT_STOPPED, true, false },
+            { StopMode.IF_NOT_STOPPED, false, true },
+            { StopMode.NEVER, true, false },
+            { StopMode.NEVER, false, false },
+        };
+    }
+
+    @Test(dataProvider = "canStopStates")
+    public void testBasicSonftwareProcessCanStop(StopMode mode, boolean 
isEntityStopped, boolean expected) {
+        boolean canStop = canStop(mode, isEntityStopped);
+        assertEquals(canStop, expected);
+    }
+
+}

Reply via email to