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); + } + +}
