From 7b53143bcd4c3abf465cf9dbd5c4fc012025ed66 Mon Sep 17 00:00:00 2001 From: Andrew Donald Kennedy Date: Wed, 22 Mar 2017 13:54:18 +0000 Subject: [PATCH 01/43] Add series of meta effectors for composite operations --- .../brooklyn/core/effector/AddEffector.java | 20 +- .../composite/AbstractCompositeEffector.java | 184 ++++++++++++++++++ .../effector/composite/ComposeEffector.java | 109 +++++++++++ .../core/effector/composite/LoopEffector.java | 108 ++++++++++ .../effector/composite/SequenceEffector.java | 100 ++++++++++ .../effector/composite/TransformEffector.java | 90 +++++++++ 6 files changed, 601 insertions(+), 10 deletions(-) create mode 100644 core/src/main/java/org/apache/brooklyn/core/effector/composite/AbstractCompositeEffector.java create mode 100644 core/src/main/java/org/apache/brooklyn/core/effector/composite/ComposeEffector.java create mode 100644 core/src/main/java/org/apache/brooklyn/core/effector/composite/LoopEffector.java create mode 100644 core/src/main/java/org/apache/brooklyn/core/effector/composite/SequenceEffector.java create mode 100644 core/src/main/java/org/apache/brooklyn/core/effector/composite/TransformEffector.java diff --git a/core/src/main/java/org/apache/brooklyn/core/effector/AddEffector.java b/core/src/main/java/org/apache/brooklyn/core/effector/AddEffector.java index 9590bcff57..795fa954f0 100644 --- a/core/src/main/java/org/apache/brooklyn/core/effector/AddEffector.java +++ b/core/src/main/java/org/apache/brooklyn/core/effector/AddEffector.java @@ -54,31 +54,31 @@ * are only those supplied by a user at runtime; in order to merge with default * values, use {@link #getMergedParams(Effector, ConfigBag)}. * - * @since 0.7.0 */ -@Beta + * @since 0.7.0 + */ public class AddEffector implements EntityInitializer { - + public static final ConfigKey EFFECTOR_NAME = ConfigKeys.newStringConfigKey("name"); public static final ConfigKey EFFECTOR_DESCRIPTION = ConfigKeys.newStringConfigKey("description"); - + public static final ConfigKey> EFFECTOR_PARAMETER_DEFS = new MapConfigKey(Object.class, "parameters"); - final Effector effector; - + protected final Effector effector; + public AddEffector(Effector effector) { this.effector = Preconditions.checkNotNull(effector, "effector"); } - + @Override public void apply(EntityLocal entity) { ((EntityInternal)entity).getMutableEntityType().addEffector(effector); } - + public static EffectorBuilder newEffectorBuilder(Class type, ConfigBag params) { String name = Preconditions.checkNotNull(params.get(EFFECTOR_NAME), "name must be supplied when defining an effector: %s", params); EffectorBuilder eff = Effectors.effector(type, name); eff.description(params.get(EFFECTOR_DESCRIPTION)); - + Map paramDefs = params.get(EFFECTOR_PARAMETER_DEFS); if (paramDefs!=null) { for (Map.Entry paramDef: paramDefs.entrySet()){ @@ -97,7 +97,7 @@ public static EffectorBuilder newEffectorBuilder(Class type, ConfigBag } } } - + return eff; } diff --git a/core/src/main/java/org/apache/brooklyn/core/effector/composite/AbstractCompositeEffector.java b/core/src/main/java/org/apache/brooklyn/core/effector/composite/AbstractCompositeEffector.java new file mode 100644 index 0000000000..1bc28239d2 --- /dev/null +++ b/core/src/main/java/org/apache/brooklyn/core/effector/composite/AbstractCompositeEffector.java @@ -0,0 +1,184 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.brooklyn.core.effector.composite; + +import java.util.Map; +import java.util.Set; + +import org.apache.brooklyn.api.effector.Effector; +import org.apache.brooklyn.api.entity.Entity; +import org.apache.brooklyn.api.entity.EntityLocal; +import org.apache.brooklyn.core.effector.AddEffector; +import org.apache.brooklyn.core.effector.EffectorBody; +import org.apache.brooklyn.util.collections.CollectionFunctionals; +import org.apache.brooklyn.util.core.config.ConfigBag; +import org.apache.brooklyn.util.guava.Maybe; +import org.apache.brooklyn.util.text.Strings; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.google.common.annotations.Beta; +import com.google.common.collect.Iterables; + +@Beta +public abstract class AbstractCompositeEffector extends AddEffector { + + private static final Logger LOG = LoggerFactory.getLogger(AbstractCompositeEffector.class); + + public AbstractCompositeEffector(Effector effector) { + super(effector); + } + + @Override + public void apply(EntityLocal entity) { + Maybe> effectorMaybe = entity.getEntityType().getEffectorByName(effector.getName()); + if (!effectorMaybe.isAbsentOrNull()) { +// Effector original = Effectors.effector(effectorMaybe.get()).name(ORIGINAL_PREFIX + effector.getName()).build(); +// ((EntityInternal) entity).getMutableEntityType().addEffector(original); + } + super.apply(entity); + } + + protected static abstract class Body extends EffectorBody { + protected final Effector effector; + protected final ConfigBag params; + + public Body(Effector eff, ConfigBag params) { + this.effector = eff; + this.params = params; + } + + @Override + public abstract T call(final ConfigBag params); + + protected String getEffectorName(Object effectorDetails) { + String effectorName = null; + if (effectorDetails instanceof String) { + effectorName = (String) effectorDetails; + } else if (effectorDetails instanceof Map) { + Map effectorMap = (Map) effectorDetails; + Set keys = effectorMap.keySet(); + if (keys.size() != 1) { + throw new IllegalArgumentException("Effector parameter cannot be parsed: " + effectorDetails); + } + effectorName = Iterables.getOnlyElement(keys); + } else { + effectorName = Strings.toString(effectorDetails); + } + return effectorName; + } + + protected Entity getTargetEntity(Object effectorDetails) { + Entity targetEntity = entity(); + if (effectorDetails instanceof Map) { + Map effectorMap = (Map) effectorDetails; + Set keys = effectorMap.keySet(); + if (keys.size() != 1) { + throw new IllegalArgumentException("Effector parameter cannot be parsed: " + effectorDetails); + } + String effectorName = Iterables.getOnlyElement(keys); + Object effectorParams = effectorMap.get(effectorName); + if (effectorParams instanceof Map) { + Map effectorParamsMap = (Map) effectorParams; + if (effectorParamsMap.containsKey("target")) { + Object targetObject = effectorParamsMap.get("target"); + if (targetObject instanceof Entity) { + targetEntity = (Entity) targetObject; + } else { + throw new IllegalArgumentException("Effector target is not an Entity: " + targetObject); + } + } + } + } + return targetEntity; + } + + protected String getInputArgument(Object effectorDetails) { + String inputArgument = null; + if (effectorDetails instanceof Map) { + Map effectorMap = (Map) effectorDetails; + Set keys = effectorMap.keySet(); + if (keys.size() != 1) { + throw new IllegalArgumentException("Effector parameter cannot be parsed: " + effectorDetails); + } + String effectorName = Iterables.getOnlyElement(keys); + Object effectorParams = effectorMap.get(effectorName); + if (effectorParams instanceof Map) { + Map effectorParamsMap = (Map) effectorParams; + if (effectorParamsMap.containsKey("input")) { + Object inputDetails = effectorParamsMap.get("input"); + if (inputDetails instanceof String) { + inputArgument = (String) inputDetails; + } else if (inputDetails instanceof Map) { + Map inputMap = (Map) inputDetails; + Set inputKeys = inputMap.keySet(); + if (inputKeys.size() != 1) { + throw new IllegalArgumentException("Effector input cannot be parsed: " + inputDetails); + } + inputArgument = Iterables.getOnlyElement(inputKeys); + } else { + inputArgument = Strings.toString(inputDetails); + } + } + } + } + return inputArgument; + } + + protected String getInputParameter(Object effectorDetails) { + String inputArgument = getInputArgument(effectorDetails); + String inputParameter = null; + if (inputArgument != null && effectorDetails instanceof Map) { + Map effectorMap = (Map) effectorDetails; + Set keys = effectorMap.keySet(); + if (keys.size() != 1) { + throw new IllegalArgumentException("Effector parameter cannot be parsed: " + effectorDetails); + } + String effectorName = Iterables.getOnlyElement(keys); + Object effectorParams = effectorMap.get(effectorName); + if (effectorParams instanceof Map) { + Map effectorParamsMap = (Map) effectorParams; + if (effectorParamsMap.containsKey("input")) { + Object inputDetails = effectorParamsMap.get("input"); + if (inputDetails instanceof Map) { + Map inputMap = (Map) inputDetails; + if (inputMap.size() != 1) { + throw new IllegalArgumentException("Effector input cannot be parsed: " + inputDetails); + } + inputParameter = Strings.toString(inputMap.get(inputArgument)); + } + } + } + } + return inputParameter; + } + + protected Object invokeEffectorNamed(Entity target, String effectorName, ConfigBag params) { + LOG.info("{} invoking effector on {}, effector={}, parameters={}", + new Object[]{this, target, effectorName, params}); + Maybe> effector = target.getEntityType().getEffectorByName(effectorName); + if (effector.isAbsent()) { + throw new IllegalStateException("Cannot find effector " + effectorName); + } + return target.invoke(effector.get(), params.getAllConfig()).getUnchecked(); + } + + } + +} diff --git a/core/src/main/java/org/apache/brooklyn/core/effector/composite/ComposeEffector.java b/core/src/main/java/org/apache/brooklyn/core/effector/composite/ComposeEffector.java new file mode 100644 index 0000000000..90988d3740 --- /dev/null +++ b/core/src/main/java/org/apache/brooklyn/core/effector/composite/ComposeEffector.java @@ -0,0 +1,109 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.brooklyn.core.effector.composite; + +import java.util.List; +import java.util.Map; + +import org.apache.brooklyn.api.effector.Effector; +import org.apache.brooklyn.api.entity.Entity; +import org.apache.brooklyn.config.ConfigKey; +import org.apache.brooklyn.core.config.ConfigKeys; +import org.apache.brooklyn.core.effector.AddEffector; +import org.apache.brooklyn.core.effector.EffectorBody; +import org.apache.brooklyn.core.effector.Effectors.EffectorBuilder; +import org.apache.brooklyn.core.entity.EntityInitializers; +import org.apache.brooklyn.util.core.config.ConfigBag; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.google.common.annotations.Beta; +import com.google.common.base.Preconditions; +import com.google.common.collect.ImmutableList; +import com.google.common.reflect.TypeToken; + +@Beta +public class ComposeEffector extends AbstractCompositeEffector { + + private static final Logger LOG = LoggerFactory.getLogger(ComposeEffector.class); + + public static final ConfigKey> COMPOSE = ConfigKeys.newConfigKey( + new TypeToken>() { }, + "compose", + "Effector details list for the compose effector", + ImmutableList.of()); + + public ComposeEffector(ConfigBag params) { + super(newEffectorBuilder(params).build()); + } + + public ComposeEffector(Map params) { + this(ConfigBag.newInstance(params)); + } + + public static EffectorBuilder newEffectorBuilder(ConfigBag params) { + EffectorBuilder eff = AddEffector.newEffectorBuilder(Object.class, params); + EffectorBody body = new Body(eff.buildAbstract(), params); + eff.impl(body); + return eff; + } + + protected static class Body extends AbstractCompositeEffector.Body { + + public Body(Effector eff, ConfigBag params) { + super(eff, params); + Preconditions.checkNotNull(params.getAllConfigRaw().get(COMPOSE.getName()), "Effector names must be supplied when defining this effector"); + } + + @Override + public Object call(final ConfigBag params) { + ConfigBag config = ConfigBag.newInstanceCopying(this.params).putAll(params); + List effectors = EntityInitializers.resolve(config, COMPOSE); + + Object result = null; + + for (Object effectorDetails : effectors) { + String effectorName = getEffectorName(effectorDetails); + String inputArgument = getInputArgument(effectorDetails); + String inputParameter = getInputParameter(effectorDetails); + Entity targetEntity = getTargetEntity(effectorDetails); + + if (inputArgument == null) { + throw new IllegalArgumentException("Input is not set for this effector: " + effectorDetails); + } + if (inputParameter == null) { + if (result == null) { + Object input = config.getStringKey(inputArgument); + config.putStringKey(inputArgument, input); + } else { + config.putStringKey(inputArgument, result); + } + } else { + Object input = config.getStringKey(inputParameter); + config.putStringKey(inputArgument, input); + } + + result = invokeEffectorNamed(targetEntity, effectorName, config); + } + + return result; + } + } + +} diff --git a/core/src/main/java/org/apache/brooklyn/core/effector/composite/LoopEffector.java b/core/src/main/java/org/apache/brooklyn/core/effector/composite/LoopEffector.java new file mode 100644 index 0000000000..c88cd23c9f --- /dev/null +++ b/core/src/main/java/org/apache/brooklyn/core/effector/composite/LoopEffector.java @@ -0,0 +1,108 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.brooklyn.core.effector.composite; + +import java.util.Collection; +import java.util.List; +import java.util.Map; + +import org.apache.brooklyn.api.effector.Effector; +import org.apache.brooklyn.api.entity.Entity; +import org.apache.brooklyn.config.ConfigKey; +import org.apache.brooklyn.core.config.ConfigKeys; +import org.apache.brooklyn.core.effector.AddEffector; +import org.apache.brooklyn.core.effector.EffectorBody; +import org.apache.brooklyn.core.effector.Effectors.EffectorBuilder; +import org.apache.brooklyn.core.entity.EntityInitializers; +import org.apache.brooklyn.util.core.config.ConfigBag; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.google.common.annotations.Beta; +import com.google.common.base.Preconditions; +import com.google.common.collect.Lists; + +@Beta +public class LoopEffector extends AbstractCompositeEffector { + + private static final Logger LOG = LoggerFactory.getLogger(LoopEffector.class); + + public static final ConfigKey INPUT = ConfigKeys.newStringConfigKey( + "input", + "Loop input parameter"); + + public static final ConfigKey LOOP = ConfigKeys.newConfigKey( + Object.class, + "loop", + "Effector details for the loop effector"); + + public LoopEffector(ConfigBag params) { + super(newEffectorBuilder(params).build()); + } + + public LoopEffector(Map params) { + this(ConfigBag.newInstance(params)); + } + + public static EffectorBuilder newEffectorBuilder(ConfigBag params) { + EffectorBuilder eff = AddEffector.newEffectorBuilder(List.class, params); + EffectorBody body = new Body(eff.buildAbstract(), params); + eff.impl(body); + return eff; + } + + protected static class Body extends AbstractCompositeEffector.Body { + + public Body(Effector eff, ConfigBag params) { + super(eff, params); + Preconditions.checkNotNull(params.getAllConfigRaw().get(LOOP.getName()), "Effector names must be supplied when defining this effector"); + } + + @Override + public List call(final ConfigBag params) { + ConfigBag config = ConfigBag.newInstanceCopying(this.params).putAll(params); + Object effectorDetails = EntityInitializers.resolve(config, LOOP); + String input = config.get(INPUT); + Object inputObject = config.getStringKey(input); + if (!(inputObject instanceof Collection)) { + throw new IllegalArgumentException("Input to loop is not a collection: " + inputObject); + } + Collection inputCollection = (Collection) inputObject; + + List result = Lists.newArrayList(); + + String effectorName = getEffectorName(effectorDetails); + String inputArgument = getInputArgument(effectorDetails); + Entity targetEntity = getTargetEntity(effectorDetails); + + if (inputArgument == null) { + throw new IllegalArgumentException("Input is not set for this effector: " + effectorDetails); + } + + for (Object inputEach : inputCollection) { + config.putStringKey(inputArgument, inputEach); + + result.add(invokeEffectorNamed(targetEntity, effectorName, config)); + } + + return result; + } + } + +} diff --git a/core/src/main/java/org/apache/brooklyn/core/effector/composite/SequenceEffector.java b/core/src/main/java/org/apache/brooklyn/core/effector/composite/SequenceEffector.java new file mode 100644 index 0000000000..d8aa30f451 --- /dev/null +++ b/core/src/main/java/org/apache/brooklyn/core/effector/composite/SequenceEffector.java @@ -0,0 +1,100 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.brooklyn.core.effector.composite; + +import java.util.List; +import java.util.Map; + +import org.apache.brooklyn.api.effector.Effector; +import org.apache.brooklyn.api.entity.Entity; +import org.apache.brooklyn.config.ConfigKey; +import org.apache.brooklyn.core.config.ConfigKeys; +import org.apache.brooklyn.core.effector.AddEffector; +import org.apache.brooklyn.core.effector.EffectorBody; +import org.apache.brooklyn.core.effector.Effectors.EffectorBuilder; +import org.apache.brooklyn.core.entity.EntityInitializers; +import org.apache.brooklyn.util.core.config.ConfigBag; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.google.common.annotations.Beta; +import com.google.common.base.Preconditions; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.Lists; +import com.google.common.reflect.TypeToken; + +@Beta +public class SequenceEffector extends AbstractCompositeEffector { + + private static final Logger LOG = LoggerFactory.getLogger(SequenceEffector.class); + + public static final ConfigKey> SEQUENCE = ConfigKeys.newConfigKey( + new TypeToken>() { }, + "sequence", + "Effector details list for the sequence effector", + ImmutableList.of()); + + public SequenceEffector(ConfigBag params) { + super(newEffectorBuilder(params).build()); + } + + public SequenceEffector(Map params) { + this(ConfigBag.newInstance(params)); + } + + public static EffectorBuilder newEffectorBuilder(ConfigBag params) { + EffectorBuilder eff = AddEffector.newEffectorBuilder(Object.class, params); + EffectorBody body = new Body(eff.buildAbstract(), params); + eff.impl(body); + return eff; + } + + protected static class Body extends AbstractCompositeEffector.Body { + + public Body(Effector eff, ConfigBag params) { + super(eff, params); + Preconditions.checkNotNull(params.getAllConfigRaw().get(SEQUENCE.getName()), "Effector names must be supplied when defining this effector"); + } + + @Override + public Object call(final ConfigBag params) { + ConfigBag config = ConfigBag.newInstanceCopying(this.params).putAll(params); + List effectors = EntityInitializers.resolve(config, SEQUENCE); + + Object result = null; + + for (Object effectorDetails : effectors) { + String effectorName = getEffectorName(effectorDetails); + String inputArgument = getInputArgument(effectorDetails); + Entity targetEntity = getTargetEntity(effectorDetails); + + if (inputArgument == null) { + throw new IllegalArgumentException("Input is not set for this effector: " + effectorDetails); + } + Object input = config.getStringKey(inputArgument); + config.putStringKey(inputArgument, input); + + result = invokeEffectorNamed(targetEntity, effectorName, config); + } + + return result; + } + } + +} diff --git a/core/src/main/java/org/apache/brooklyn/core/effector/composite/TransformEffector.java b/core/src/main/java/org/apache/brooklyn/core/effector/composite/TransformEffector.java new file mode 100644 index 0000000000..5863e8d5a5 --- /dev/null +++ b/core/src/main/java/org/apache/brooklyn/core/effector/composite/TransformEffector.java @@ -0,0 +1,90 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.brooklyn.core.effector.composite; + +import java.util.Map; + +import org.apache.brooklyn.api.effector.Effector; +import org.apache.brooklyn.config.ConfigKey; +import org.apache.brooklyn.core.config.ConfigKeys; +import org.apache.brooklyn.core.effector.AddEffector; +import org.apache.brooklyn.core.effector.EffectorBody; +import org.apache.brooklyn.core.effector.Effectors.EffectorBuilder; +import org.apache.brooklyn.core.entity.EntityInitializers; +import org.apache.brooklyn.util.core.config.ConfigBag; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.google.common.annotations.Beta; +import com.google.common.base.Function; +import com.google.common.base.Functions; +import com.google.common.base.Preconditions; +import com.google.common.reflect.TypeToken; + +@Beta +public class TransformEffector extends AbstractCompositeEffector { + + private static final Logger LOG = LoggerFactory.getLogger(TransformEffector.class); + + public static final ConfigKey INPUT = ConfigKeys.newStringConfigKey( + "input", + "Transformer input parameter"); + + public static final ConfigKey> FUNCTION = ConfigKeys.newConfigKey( + new TypeToken>() { }, + "function", + "Transformer function to apply", + Functions.identity()); + + public TransformEffector(ConfigBag params) { + super(newEffectorBuilder(params).build()); + } + + public TransformEffector(Map params) { + this(ConfigBag.newInstance(params)); + } + + public static EffectorBuilder newEffectorBuilder(ConfigBag params) { + EffectorBuilder eff = AddEffector.newEffectorBuilder(Object.class, params); + EffectorBody body = new Body(eff.buildAbstract(), params); + eff.impl(body); + return eff; + } + + protected static class Body extends AbstractCompositeEffector.Body { + + public Body(Effector eff, ConfigBag params) { + super(eff, params); + Preconditions.checkNotNull(params.getAllConfigRaw().get(FUNCTION.getName()), "Function must be supplied when defining this effector"); + } + + @Override + public Object call(final ConfigBag params) { + ConfigBag config = ConfigBag.newInstanceCopying(this.params).putAll(params); + Function function = EntityInitializers.resolve(config, FUNCTION); + + String input = config.get(INPUT); + Object value = config.getStringKey(input); + Object result = function.apply(value); + + return result; + } + } + +} From a318784b7caa83a3d12568d4039511a14406683b Mon Sep 17 00:00:00 2001 From: Andrew Donald Kennedy Date: Fri, 24 Mar 2017 12:26:57 +0000 Subject: [PATCH 02/43] Add ReplaceEffector and deprecate CompositeEffector --- .../core/effector/CompositeEffector.java | 6 + .../effector/composite/ReplaceEffector.java | 131 ++++++++++++++++++ 2 files changed, 137 insertions(+) create mode 100644 core/src/main/java/org/apache/brooklyn/core/effector/composite/ReplaceEffector.java diff --git a/core/src/main/java/org/apache/brooklyn/core/effector/CompositeEffector.java b/core/src/main/java/org/apache/brooklyn/core/effector/CompositeEffector.java index 62a598454f..5031203313 100644 --- a/core/src/main/java/org/apache/brooklyn/core/effector/CompositeEffector.java +++ b/core/src/main/java/org/apache/brooklyn/core/effector/CompositeEffector.java @@ -29,6 +29,8 @@ import org.apache.brooklyn.config.ConfigKey; import org.apache.brooklyn.core.config.ConfigKeys; import org.apache.brooklyn.core.effector.Effectors.EffectorBuilder; +import org.apache.brooklyn.core.effector.composite.ReplaceEffector; +import org.apache.brooklyn.core.effector.composite.SequenceEffector; import org.apache.brooklyn.core.entity.EntityInitializers; import org.apache.brooklyn.core.entity.EntityInternal; import org.apache.brooklyn.util.core.config.ConfigBag; @@ -42,7 +44,11 @@ import com.google.common.collect.Lists; import com.google.common.reflect.TypeToken; +/** + * @deprecated use a combintaion of {@link ReplaceEffector} and {@link SequenceEffector} instead + */ @Beta +@Deprecated public class CompositeEffector extends AddEffector { private static final Logger LOG = LoggerFactory.getLogger(CompositeEffector.class); diff --git a/core/src/main/java/org/apache/brooklyn/core/effector/composite/ReplaceEffector.java b/core/src/main/java/org/apache/brooklyn/core/effector/composite/ReplaceEffector.java new file mode 100644 index 0000000000..613262bbd8 --- /dev/null +++ b/core/src/main/java/org/apache/brooklyn/core/effector/composite/ReplaceEffector.java @@ -0,0 +1,131 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.brooklyn.core.effector.composite; + +import java.util.Map; + +import org.apache.brooklyn.api.effector.Effector; +import org.apache.brooklyn.api.entity.Entity; +import org.apache.brooklyn.api.entity.EntityLocal; +import org.apache.brooklyn.config.ConfigKey; +import org.apache.brooklyn.core.config.ConfigKeys; +import org.apache.brooklyn.core.effector.AddEffector; +import org.apache.brooklyn.core.effector.EffectorBody; +import org.apache.brooklyn.core.effector.Effectors; +import org.apache.brooklyn.core.effector.Effectors.EffectorBuilder; +import org.apache.brooklyn.core.entity.EntityInitializers; +import org.apache.brooklyn.core.entity.EntityInternal; +import org.apache.brooklyn.util.core.config.ConfigBag; +import org.apache.brooklyn.util.guava.Maybe; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.google.common.annotations.Beta; +import com.google.common.base.Preconditions; + +@Beta +public final class ReplaceEffector extends AbstractCompositeEffector { + + private static final Logger LOG = LoggerFactory.getLogger(TransformEffector.class); + + public enum ReplaceAction { + PRE, + POST, + OVERRIDE + } + + public static final String ORIGINAL = "original-"; + + public static final ConfigKey PREFIX = ConfigKeys.newStringConfigKey( + "prefix", + "Prefix for replaced original effector", + ORIGINAL); + + public static final ConfigKey ACTION = ConfigKeys.newConfigKey( + ReplaceAction.class, + "action", + "Action to take with the replaced effector", + ReplaceAction.OVERRIDE); + + public static final ConfigKey REPLACE = ConfigKeys.newConfigKey( + Object.class, + "replace", + "Effector details for the replace effector"); + + public ReplaceEffector(ConfigBag params) { + super(newEffectorBuilder(params).build()); + } + + public ReplaceEffector(Map params) { + this(ConfigBag.newInstance(params)); + } + + public static EffectorBuilder newEffectorBuilder(ConfigBag params) { + EffectorBuilder eff = AddEffector.newEffectorBuilder(Object.class, params); + EffectorBody body = new Body(eff.buildAbstract(), params); + eff.impl(body); + return eff; + } + + @Override + public void apply(EntityLocal entity) { + Maybe> effectorMaybe = entity.getEntityType().getEffectorByName(effector.getName()); + if (effectorMaybe.isPresentAndNonNull()) { + // String prefix = config.get(PREFIX); + String prefix = ORIGINAL; + Effector original = Effectors.effector(effectorMaybe.get()).name(prefix + effector.getName()).build(); + ((EntityInternal) entity).getMutableEntityType().addEffector(original); + } + super.apply(entity); + } + + protected static class Body extends AbstractCompositeEffector.Body { + + public Body(Effector eff, ConfigBag params) { + super(eff, params); + Preconditions.checkNotNull(params.getAllConfigRaw().get(REPLACE.getName()), "Effector details must be supplied when defining this effector"); + } + + @Override + public Object call(final ConfigBag params) { + ConfigBag config = ConfigBag.newInstanceCopying(this.params).putAll(params); + ReplaceAction action = config.get(ACTION); + Object effectorDetails = EntityInitializers.resolve(config, REPLACE); + + String effectorName = getEffectorName(effectorDetails); + String inputArgument = getInputArgument(effectorDetails); + Entity targetEntity = getTargetEntity(effectorDetails); + + if (inputArgument != null) { + Object input = config.getStringKey(inputArgument); + config.putStringKey(inputArgument, input); + } + + if (action == ReplaceAction.POST) { + invokeEffectorNamed(targetEntity, ORIGINAL + effectorName, params); + } + Object result = invokeEffectorNamed(targetEntity, effectorName, params); + if (action == ReplaceAction.PRE) { + invokeEffectorNamed(targetEntity, ORIGINAL + effectorName, params); + } + + return result; + } + } +} From 78da36fe0182949d408d1d4c9eb1772777111c5e Mon Sep 17 00:00:00 2001 From: Andrew Donald Kennedy Date: Fri, 24 Mar 2017 12:28:04 +0000 Subject: [PATCH 03/43] Add IterableTransformerFunction to CollectionFunctionals --- .../collections/CollectionFunctionals.java | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/collections/CollectionFunctionals.java b/utils/common/src/main/java/org/apache/brooklyn/util/collections/CollectionFunctionals.java index a264b7e8e8..24982b340b 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/collections/CollectionFunctionals.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/collections/CollectionFunctionals.java @@ -194,6 +194,26 @@ public List apply(I input) { } } + public static final class IterableTransformerFunction implements Function, Iterable> { + private final Function function; + + private IterableTransformerFunction(Function function) { + this.function = function; + } + + @Override + public Iterable apply(Iterable input) { + if (input==null) return null; + return Iterables.transform(input, function); + } + + @Override public String toString() { return "iterableTransformer"; } + } + + public static Function, Iterable> iterableTransformer(Function function) { + return new IterableTransformerFunction(function); + } + // --------- public static > Predicate contains(I item) { return new CollectionContains(item); From 90bebf0e9a3d37e6de89d5bc0f604ce41c80823b Mon Sep 17 00:00:00 2001 From: Andrew Donald Kennedy Date: Fri, 24 Mar 2017 12:31:44 +0000 Subject: [PATCH 04/43] Remove commented out logic --- .../composite/AbstractCompositeEffector.java | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/core/src/main/java/org/apache/brooklyn/core/effector/composite/AbstractCompositeEffector.java b/core/src/main/java/org/apache/brooklyn/core/effector/composite/AbstractCompositeEffector.java index 1bc28239d2..c25527662f 100644 --- a/core/src/main/java/org/apache/brooklyn/core/effector/composite/AbstractCompositeEffector.java +++ b/core/src/main/java/org/apache/brooklyn/core/effector/composite/AbstractCompositeEffector.java @@ -23,10 +23,8 @@ import org.apache.brooklyn.api.effector.Effector; import org.apache.brooklyn.api.entity.Entity; -import org.apache.brooklyn.api.entity.EntityLocal; import org.apache.brooklyn.core.effector.AddEffector; import org.apache.brooklyn.core.effector.EffectorBody; -import org.apache.brooklyn.util.collections.CollectionFunctionals; import org.apache.brooklyn.util.core.config.ConfigBag; import org.apache.brooklyn.util.guava.Maybe; import org.apache.brooklyn.util.text.Strings; @@ -45,16 +43,6 @@ public AbstractCompositeEffector(Effector effector) { super(effector); } - @Override - public void apply(EntityLocal entity) { - Maybe> effectorMaybe = entity.getEntityType().getEffectorByName(effector.getName()); - if (!effectorMaybe.isAbsentOrNull()) { -// Effector original = Effectors.effector(effectorMaybe.get()).name(ORIGINAL_PREFIX + effector.getName()).build(); -// ((EntityInternal) entity).getMutableEntityType().addEffector(original); - } - super.apply(entity); - } - protected static abstract class Body extends EffectorBody { protected final Effector effector; protected final ConfigBag params; From 49f9a0ea53f7c2158d0079504d571800db67c8bd Mon Sep 17 00:00:00 2001 From: Andrew Donald Kennedy Date: Fri, 24 Mar 2017 15:55:28 +0000 Subject: [PATCH 05/43] Fix mixup between config and params when calling composite sub effectors --- .../composite/AbstractCompositeEffector.java | 6 +++--- .../core/effector/composite/ComposeEffector.java | 15 +++++++-------- .../core/effector/composite/LoopEffector.java | 11 +++++------ .../core/effector/composite/ReplaceEffector.java | 9 ++++----- .../core/effector/composite/SequenceEffector.java | 11 +++++------ .../effector/composite/TransformEffector.java | 7 +++---- 6 files changed, 27 insertions(+), 32 deletions(-) diff --git a/core/src/main/java/org/apache/brooklyn/core/effector/composite/AbstractCompositeEffector.java b/core/src/main/java/org/apache/brooklyn/core/effector/composite/AbstractCompositeEffector.java index c25527662f..9fa31f523f 100644 --- a/core/src/main/java/org/apache/brooklyn/core/effector/composite/AbstractCompositeEffector.java +++ b/core/src/main/java/org/apache/brooklyn/core/effector/composite/AbstractCompositeEffector.java @@ -45,11 +45,11 @@ public AbstractCompositeEffector(Effector effector) { protected static abstract class Body extends EffectorBody { protected final Effector effector; - protected final ConfigBag params; + protected final ConfigBag config; - public Body(Effector eff, ConfigBag params) { + public Body(Effector eff, ConfigBag config) { this.effector = eff; - this.params = params; + this.config = config; } @Override diff --git a/core/src/main/java/org/apache/brooklyn/core/effector/composite/ComposeEffector.java b/core/src/main/java/org/apache/brooklyn/core/effector/composite/ComposeEffector.java index 90988d3740..7884536216 100644 --- a/core/src/main/java/org/apache/brooklyn/core/effector/composite/ComposeEffector.java +++ b/core/src/main/java/org/apache/brooklyn/core/effector/composite/ComposeEffector.java @@ -66,14 +66,13 @@ public static EffectorBuilder newEffectorBuilder(ConfigBag params) { protected static class Body extends AbstractCompositeEffector.Body { - public Body(Effector eff, ConfigBag params) { - super(eff, params); - Preconditions.checkNotNull(params.getAllConfigRaw().get(COMPOSE.getName()), "Effector names must be supplied when defining this effector"); + public Body(Effector eff, ConfigBag config) { + super(eff, config); + Preconditions.checkNotNull(config.getAllConfigRaw().get(COMPOSE.getName()), "Effector names must be supplied when defining this effector"); } @Override public Object call(final ConfigBag params) { - ConfigBag config = ConfigBag.newInstanceCopying(this.params).putAll(params); List effectors = EntityInitializers.resolve(config, COMPOSE); Object result = null; @@ -90,16 +89,16 @@ public Object call(final ConfigBag params) { if (inputParameter == null) { if (result == null) { Object input = config.getStringKey(inputArgument); - config.putStringKey(inputArgument, input); + params.putStringKey(inputArgument, input); } else { - config.putStringKey(inputArgument, result); + params.putStringKey(inputArgument, result); } } else { Object input = config.getStringKey(inputParameter); - config.putStringKey(inputArgument, input); + params.putStringKey(inputArgument, input); } - result = invokeEffectorNamed(targetEntity, effectorName, config); + result = invokeEffectorNamed(targetEntity, effectorName, params); } return result; diff --git a/core/src/main/java/org/apache/brooklyn/core/effector/composite/LoopEffector.java b/core/src/main/java/org/apache/brooklyn/core/effector/composite/LoopEffector.java index c88cd23c9f..9e694f6ee3 100644 --- a/core/src/main/java/org/apache/brooklyn/core/effector/composite/LoopEffector.java +++ b/core/src/main/java/org/apache/brooklyn/core/effector/composite/LoopEffector.java @@ -69,14 +69,13 @@ public static EffectorBuilder newEffectorBuilder(ConfigBag params) { protected static class Body extends AbstractCompositeEffector.Body { - public Body(Effector eff, ConfigBag params) { - super(eff, params); - Preconditions.checkNotNull(params.getAllConfigRaw().get(LOOP.getName()), "Effector names must be supplied when defining this effector"); + public Body(Effector eff, ConfigBag config) { + super(eff, config); + Preconditions.checkNotNull(config.getAllConfigRaw().get(LOOP.getName()), "Effector names must be supplied when defining this effector"); } @Override public List call(final ConfigBag params) { - ConfigBag config = ConfigBag.newInstanceCopying(this.params).putAll(params); Object effectorDetails = EntityInitializers.resolve(config, LOOP); String input = config.get(INPUT); Object inputObject = config.getStringKey(input); @@ -96,9 +95,9 @@ public List call(final ConfigBag params) { } for (Object inputEach : inputCollection) { - config.putStringKey(inputArgument, inputEach); + params.putStringKey(inputArgument, inputEach); - result.add(invokeEffectorNamed(targetEntity, effectorName, config)); + result.add(invokeEffectorNamed(targetEntity, effectorName, params)); } return result; diff --git a/core/src/main/java/org/apache/brooklyn/core/effector/composite/ReplaceEffector.java b/core/src/main/java/org/apache/brooklyn/core/effector/composite/ReplaceEffector.java index 613262bbd8..c580cc17e9 100644 --- a/core/src/main/java/org/apache/brooklyn/core/effector/composite/ReplaceEffector.java +++ b/core/src/main/java/org/apache/brooklyn/core/effector/composite/ReplaceEffector.java @@ -97,14 +97,13 @@ public void apply(EntityLocal entity) { protected static class Body extends AbstractCompositeEffector.Body { - public Body(Effector eff, ConfigBag params) { - super(eff, params); - Preconditions.checkNotNull(params.getAllConfigRaw().get(REPLACE.getName()), "Effector details must be supplied when defining this effector"); + public Body(Effector eff, ConfigBag config) { + super(eff, config); + Preconditions.checkNotNull(config.getAllConfigRaw().get(REPLACE.getName()), "Effector details must be supplied when defining this effector"); } @Override public Object call(final ConfigBag params) { - ConfigBag config = ConfigBag.newInstanceCopying(this.params).putAll(params); ReplaceAction action = config.get(ACTION); Object effectorDetails = EntityInitializers.resolve(config, REPLACE); @@ -114,7 +113,7 @@ public Object call(final ConfigBag params) { if (inputArgument != null) { Object input = config.getStringKey(inputArgument); - config.putStringKey(inputArgument, input); + params.putStringKey(inputArgument, input); } if (action == ReplaceAction.POST) { diff --git a/core/src/main/java/org/apache/brooklyn/core/effector/composite/SequenceEffector.java b/core/src/main/java/org/apache/brooklyn/core/effector/composite/SequenceEffector.java index d8aa30f451..8f40365e21 100644 --- a/core/src/main/java/org/apache/brooklyn/core/effector/composite/SequenceEffector.java +++ b/core/src/main/java/org/apache/brooklyn/core/effector/composite/SequenceEffector.java @@ -67,14 +67,13 @@ public static EffectorBuilder newEffectorBuilder(ConfigBag params) { protected static class Body extends AbstractCompositeEffector.Body { - public Body(Effector eff, ConfigBag params) { - super(eff, params); - Preconditions.checkNotNull(params.getAllConfigRaw().get(SEQUENCE.getName()), "Effector names must be supplied when defining this effector"); + public Body(Effector eff, ConfigBag config) { + super(eff, config); + Preconditions.checkNotNull(config.getAllConfigRaw().get(SEQUENCE.getName()), "Effector names must be supplied when defining this effector"); } @Override public Object call(final ConfigBag params) { - ConfigBag config = ConfigBag.newInstanceCopying(this.params).putAll(params); List effectors = EntityInitializers.resolve(config, SEQUENCE); Object result = null; @@ -88,9 +87,9 @@ public Object call(final ConfigBag params) { throw new IllegalArgumentException("Input is not set for this effector: " + effectorDetails); } Object input = config.getStringKey(inputArgument); - config.putStringKey(inputArgument, input); + params.putStringKey(inputArgument, input); - result = invokeEffectorNamed(targetEntity, effectorName, config); + result = invokeEffectorNamed(targetEntity, effectorName, params); } return result; diff --git a/core/src/main/java/org/apache/brooklyn/core/effector/composite/TransformEffector.java b/core/src/main/java/org/apache/brooklyn/core/effector/composite/TransformEffector.java index 5863e8d5a5..e68e44bf83 100644 --- a/core/src/main/java/org/apache/brooklyn/core/effector/composite/TransformEffector.java +++ b/core/src/main/java/org/apache/brooklyn/core/effector/composite/TransformEffector.java @@ -69,14 +69,13 @@ public static EffectorBuilder newEffectorBuilder(ConfigBag params) { protected static class Body extends AbstractCompositeEffector.Body { - public Body(Effector eff, ConfigBag params) { - super(eff, params); - Preconditions.checkNotNull(params.getAllConfigRaw().get(FUNCTION.getName()), "Function must be supplied when defining this effector"); + public Body(Effector eff, ConfigBag config) { + super(eff, config); + Preconditions.checkNotNull(config.getAllConfigRaw().get(FUNCTION.getName()), "Function must be supplied when defining this effector"); } @Override public Object call(final ConfigBag params) { - ConfigBag config = ConfigBag.newInstanceCopying(this.params).putAll(params); Function function = EntityInitializers.resolve(config, FUNCTION); String input = config.get(INPUT); From 09eb10f6bceab75c10171f5b0fa730d6a72d6de7 Mon Sep 17 00:00:00 2001 From: Andrew Donald Kennedy Date: Fri, 24 Mar 2017 21:10:24 +0000 Subject: [PATCH 06/43] Added new scheduled effector policy implementation --- .../action/ScheduledEffectorPolicy.java | 144 ++++++++++++++++++ 1 file changed, 144 insertions(+) create mode 100644 policy/src/main/java/org/apache/brooklyn/policy/action/ScheduledEffectorPolicy.java diff --git a/policy/src/main/java/org/apache/brooklyn/policy/action/ScheduledEffectorPolicy.java b/policy/src/main/java/org/apache/brooklyn/policy/action/ScheduledEffectorPolicy.java new file mode 100644 index 0000000000..ed75772505 --- /dev/null +++ b/policy/src/main/java/org/apache/brooklyn/policy/action/ScheduledEffectorPolicy.java @@ -0,0 +1,144 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.brooklyn.policy.action; + +import java.util.Map; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; + +import org.apache.brooklyn.api.effector.Effector; +import org.apache.brooklyn.api.entity.EntityLocal; +import org.apache.brooklyn.api.sensor.AttributeSensor; +import org.apache.brooklyn.api.sensor.SensorEvent; +import org.apache.brooklyn.api.sensor.SensorEventListener; +import org.apache.brooklyn.config.ConfigKey; +import org.apache.brooklyn.core.config.ConfigKeys; +import org.apache.brooklyn.core.entity.EntityInitializers; +import org.apache.brooklyn.core.policy.AbstractPolicy; +import org.apache.brooklyn.core.sensor.Sensors; +import org.apache.brooklyn.util.collections.MutableMap; +import org.apache.brooklyn.util.guava.Maybe; +import org.apache.brooklyn.util.time.Duration; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.google.common.annotations.Beta; +import com.google.common.base.Predicates; +import com.google.common.collect.ImmutableMap; +import com.google.common.reflect.TypeToken; + +/** + *
{@code
+ * brooklyn.policies:
+ *   - type: org.apache.brooklyn.policy.action.ScheduledEffectorPolicy
+ *     brooklyn.config:
+ *       effector: repaveCluster
+ *       args:
+ *         k: $brooklyn:config("repave.size")
+ *       period: 1 day
+ * }
+ */ +@Beta +public class ScheduledEffectorPolicy extends AbstractPolicy implements Runnable { + + private static final Logger LOG = LoggerFactory.getLogger(ScheduledEffectorPolicy.class); + + public static final ConfigKey EFFECTOR = ConfigKeys.builder(String.class) + .name("effector") + .description("The effector to be executed by this policy") + .constraint(Predicates.notNull()) + .build(); + + public static final ConfigKey> EFFECTOR_ARGUMENTS = ConfigKeys.builder(new TypeToken>() { }) + .name("args") + .description("The effector arguments and their values") + .constraint(Predicates.notNull()) + .defaultValue(ImmutableMap.of()) + .build(); + + public static final ConfigKey PERIOD = ConfigKeys.builder(Duration.class) + .name("period") + .description("The duration between executions of this policy") + .constraint(Predicates.notNull()) + .defaultValue(Duration.hours(1)) + .build(); + + public static final AttributeSensor INVOKE_IMMEDIATELY = Sensors.newSensor(Void.TYPE, "scheduler.invoke"); + public static final AttributeSensor START_SCHEDULER = Sensors.newBooleanSensor("scheduler.start"); + + protected long delay; + + private final ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor(); + + public ScheduledEffectorPolicy() { + this(MutableMap.of()); + Duration period = config().get(PERIOD); + delay = period.toMilliseconds(); + } + + public ScheduledEffectorPolicy(Map props) { + super(props); + } + + @Override + public void destroy(){ + super.destroy(); + executor.shutdownNow(); + } + + @Override + public void setEntity(final EntityLocal entity) { + super.setEntity(entity); + subscriptions().subscribe(entity, INVOKE_IMMEDIATELY, handler); + subscriptions().subscribe(entity, START_SCHEDULER, handler); + } + + + private final SensorEventListener handler = new SensorEventListener() { + @Override + public void onEvent(SensorEvent event) { + if (event.getSensor().equals(START_SCHEDULER)) { + if (Boolean.TRUE.equals(event.getValue())) { + executor.scheduleWithFixedDelay(ScheduledEffectorPolicy.this, delay, delay, TimeUnit.MILLISECONDS); + } else { + executor.shutdown(); + } + } else if (event.getSensor().equals(INVOKE_IMMEDIATELY)) { + executor.submit(ScheduledEffectorPolicy.this); + } + } + }; + + @Override + public void run() { + String effectorName = config().get(EFFECTOR); + Map args = EntityInitializers.resolve(config().getBag(), EFFECTOR_ARGUMENTS); + + Maybe> effector = entity.getEntityType().getEffectorByName(effectorName); + if (effector.isAbsent()) { + throw new IllegalStateException("Cannot find effector " + effectorName); + } + + LOG.info("{} invoking effector on {}, effector={}, args={}", new Object[] { this, entity, effectorName, args }); + entity.invoke(effector.get(), args).getUnchecked(); + } + +} From d0aadb1d68e49f8c20147c22bc0a4cf0b5c12b2d Mon Sep 17 00:00:00 2001 From: Andrew Donald Kennedy Date: Sun, 26 Mar 2017 17:40:05 +0100 Subject: [PATCH 07/43] Add one-shot shecduled execution policy and abstract parent class --- .../AbstractScheduledEffectorPolicy.java | 88 +++++++++++++++ .../policy/action/PeriodicEffectorPolicy.java | 104 ++++++++++++++++++ .../action/ScheduledEffectorPolicy.java | 98 ++++------------- 3 files changed, 216 insertions(+), 74 deletions(-) create mode 100644 policy/src/main/java/org/apache/brooklyn/policy/action/AbstractScheduledEffectorPolicy.java create mode 100644 policy/src/main/java/org/apache/brooklyn/policy/action/PeriodicEffectorPolicy.java diff --git a/policy/src/main/java/org/apache/brooklyn/policy/action/AbstractScheduledEffectorPolicy.java b/policy/src/main/java/org/apache/brooklyn/policy/action/AbstractScheduledEffectorPolicy.java new file mode 100644 index 0000000000..d158495c1b --- /dev/null +++ b/policy/src/main/java/org/apache/brooklyn/policy/action/AbstractScheduledEffectorPolicy.java @@ -0,0 +1,88 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.brooklyn.policy.action; + +import java.util.Map; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; + +import org.apache.brooklyn.api.effector.Effector; +import org.apache.brooklyn.config.ConfigKey; +import org.apache.brooklyn.core.config.ConfigKeys; +import org.apache.brooklyn.core.entity.EntityInitializers; +import org.apache.brooklyn.core.policy.AbstractPolicy; +import org.apache.brooklyn.util.collections.MutableMap; +import org.apache.brooklyn.util.guava.Maybe; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.google.common.annotations.Beta; +import com.google.common.base.Predicates; +import com.google.common.collect.ImmutableMap; +import com.google.common.reflect.TypeToken; + +@Beta +public abstract class AbstractScheduledEffectorPolicy extends AbstractPolicy implements Runnable { + + private static final Logger LOG = LoggerFactory.getLogger(AbstractScheduledEffectorPolicy.class); + + public static final ConfigKey EFFECTOR = ConfigKeys.builder(String.class) + .name("effector") + .description("The effector to be executed by this policy") + .constraint(Predicates.notNull()) + .build(); + + public static final ConfigKey> EFFECTOR_ARGUMENTS = ConfigKeys.builder(new TypeToken>() { }) + .name("args") + .description("The effector arguments and their values") + .constraint(Predicates.notNull()) + .defaultValue(ImmutableMap.of()) + .build(); + + private final ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor(); + + public AbstractScheduledEffectorPolicy() { + this(MutableMap.of()); + } + + public AbstractScheduledEffectorPolicy(Map props) { + super(props); + } + + @Override + public void destroy(){ + super.destroy(); + executor.shutdownNow(); + } + + @Override + public void run() { + String effectorName = config().get(EFFECTOR); + Map args = EntityInitializers.resolve(config().getBag(), EFFECTOR_ARGUMENTS); + + Maybe> effector = entity.getEntityType().getEffectorByName(effectorName); + if (effector.isAbsent()) { + throw new IllegalStateException("Cannot find effector " + effectorName); + } + + LOG.info("{} invoking effector on {}, effector={}, args={}", new Object[] { this, entity, effectorName, args }); + entity.invoke(effector.get(), args).getUnchecked(); + } +} diff --git a/policy/src/main/java/org/apache/brooklyn/policy/action/PeriodicEffectorPolicy.java b/policy/src/main/java/org/apache/brooklyn/policy/action/PeriodicEffectorPolicy.java new file mode 100644 index 0000000000..44ec00fb5f --- /dev/null +++ b/policy/src/main/java/org/apache/brooklyn/policy/action/PeriodicEffectorPolicy.java @@ -0,0 +1,104 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.brooklyn.policy.action; + +import java.util.Map; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; + +import org.apache.brooklyn.api.entity.EntityLocal; +import org.apache.brooklyn.api.sensor.AttributeSensor; +import org.apache.brooklyn.api.sensor.SensorEvent; +import org.apache.brooklyn.api.sensor.SensorEventListener; +import org.apache.brooklyn.config.ConfigKey; +import org.apache.brooklyn.core.config.ConfigKeys; +import org.apache.brooklyn.core.sensor.Sensors; +import org.apache.brooklyn.util.collections.MutableMap; +import org.apache.brooklyn.util.time.Duration; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.google.common.annotations.Beta; +import com.google.common.base.Predicates; + +/** + *
{@code
+ * brooklyn.policies:
+ *   - type: org.apache.brooklyn.policy.action.ScheduledEffectorPolicy
+ *     brooklyn.config:
+ *       effector: repaveCluster
+ *       args:
+ *         k: $brooklyn:config("repave.size")
+ *       period: 1 day
+ * }
+ */ +@Beta +public class PeriodicEffectorPolicy extends AbstractScheduledEffectorPolicy { + + private static final Logger LOG = LoggerFactory.getLogger(PeriodicEffectorPolicy.class); + + public static final ConfigKey PERIOD = ConfigKeys.builder(Duration.class) + .name("period") + .description("The duration between executions of this policy") + .constraint(Predicates.notNull()) + .defaultValue(Duration.hours(1)) + .build(); + + public static final AttributeSensor INVOKE_IMMEDIATELY = Sensors.newSensor(Void.TYPE, "scheduler.invoke"); + public static final AttributeSensor START_SCHEDULER = Sensors.newBooleanSensor("scheduler.start"); + + protected long delay; + + private final ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor(); + + public PeriodicEffectorPolicy() { + this(MutableMap.of()); + } + + public PeriodicEffectorPolicy(Map props) { + super(props); + Duration period = config().get(PERIOD); + delay = period.toMilliseconds(); + } + + @Override + public void setEntity(final EntityLocal entity) { + super.setEntity(entity); + subscriptions().subscribe(entity, INVOKE_IMMEDIATELY, handler); + subscriptions().subscribe(entity, START_SCHEDULER, handler); + } + + private final SensorEventListener handler = new SensorEventListener() { + @Override + public void onEvent(SensorEvent event) { + if (event.getSensor().equals(START_SCHEDULER)) { + if (Boolean.TRUE.equals(event.getValue())) { + executor.scheduleWithFixedDelay(PeriodicEffectorPolicy.this, delay, delay, TimeUnit.MILLISECONDS); + } else { + executor.shutdown(); + } + } else if (event.getSensor().equals(INVOKE_IMMEDIATELY)) { + executor.submit(PeriodicEffectorPolicy.this); + } + } + }; + +} diff --git a/policy/src/main/java/org/apache/brooklyn/policy/action/ScheduledEffectorPolicy.java b/policy/src/main/java/org/apache/brooklyn/policy/action/ScheduledEffectorPolicy.java index ed75772505..00334507c0 100644 --- a/policy/src/main/java/org/apache/brooklyn/policy/action/ScheduledEffectorPolicy.java +++ b/policy/src/main/java/org/apache/brooklyn/policy/action/ScheduledEffectorPolicy.java @@ -19,31 +19,24 @@ package org.apache.brooklyn.policy.action; +import java.text.DateFormat; +import java.text.ParseException; +import java.util.Date; import java.util.Map; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; -import org.apache.brooklyn.api.effector.Effector; import org.apache.brooklyn.api.entity.EntityLocal; -import org.apache.brooklyn.api.sensor.AttributeSensor; -import org.apache.brooklyn.api.sensor.SensorEvent; -import org.apache.brooklyn.api.sensor.SensorEventListener; import org.apache.brooklyn.config.ConfigKey; import org.apache.brooklyn.core.config.ConfigKeys; -import org.apache.brooklyn.core.entity.EntityInitializers; -import org.apache.brooklyn.core.policy.AbstractPolicy; -import org.apache.brooklyn.core.sensor.Sensors; import org.apache.brooklyn.util.collections.MutableMap; -import org.apache.brooklyn.util.guava.Maybe; -import org.apache.brooklyn.util.time.Duration; +import org.apache.brooklyn.util.exceptions.Exceptions; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.google.common.annotations.Beta; import com.google.common.base.Predicates; -import com.google.common.collect.ImmutableMap; -import com.google.common.reflect.TypeToken; /** *
{@code
@@ -53,92 +46,49 @@
  *       effector: repaveCluster
  *       args:
  *         k: $brooklyn:config("repave.size")
- *       period: 1 day
+ *       time: 12:00 01 January 2018
  * }
*/ @Beta -public class ScheduledEffectorPolicy extends AbstractPolicy implements Runnable { +public class ScheduledEffectorPolicy extends AbstractScheduledEffectorPolicy { private static final Logger LOG = LoggerFactory.getLogger(ScheduledEffectorPolicy.class); - public static final ConfigKey EFFECTOR = ConfigKeys.builder(String.class) - .name("effector") - .description("The effector to be executed by this policy") + public static final ConfigKey TIME = ConfigKeys.builder(String.class) + .name("time") + .description("The time when this policy should be executed") .constraint(Predicates.notNull()) .build(); - public static final ConfigKey> EFFECTOR_ARGUMENTS = ConfigKeys.builder(new TypeToken>() { }) - .name("args") - .description("The effector arguments and their values") - .constraint(Predicates.notNull()) - .defaultValue(ImmutableMap.of()) - .build(); - - public static final ConfigKey PERIOD = ConfigKeys.builder(Duration.class) - .name("period") - .description("The duration between executions of this policy") - .constraint(Predicates.notNull()) - .defaultValue(Duration.hours(1)) - .build(); - - public static final AttributeSensor INVOKE_IMMEDIATELY = Sensors.newSensor(Void.TYPE, "scheduler.invoke"); - public static final AttributeSensor START_SCHEDULER = Sensors.newBooleanSensor("scheduler.start"); - - protected long delay; + protected Date when; private final ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor(); public ScheduledEffectorPolicy() { this(MutableMap.of()); - Duration period = config().get(PERIOD); - delay = period.toMilliseconds(); } public ScheduledEffectorPolicy(Map props) { super(props); - } - - @Override - public void destroy(){ - super.destroy(); - executor.shutdownNow(); + String time = config().get(TIME); + DateFormat format = DateFormat.getDateTimeInstance(); + try { + when = format.parse(time); + } catch (ParseException e) { + Exceptions.propagate(e); + } + Date now = new Date(); + if (when.before(now)) { + throw new IllegalStateException("The time provided must be in the future"); + } } @Override public void setEntity(final EntityLocal entity) { super.setEntity(entity); - subscriptions().subscribe(entity, INVOKE_IMMEDIATELY, handler); - subscriptions().subscribe(entity, START_SCHEDULER, handler); - } - - - private final SensorEventListener handler = new SensorEventListener() { - @Override - public void onEvent(SensorEvent event) { - if (event.getSensor().equals(START_SCHEDULER)) { - if (Boolean.TRUE.equals(event.getValue())) { - executor.scheduleWithFixedDelay(ScheduledEffectorPolicy.this, delay, delay, TimeUnit.MILLISECONDS); - } else { - executor.shutdown(); - } - } else if (event.getSensor().equals(INVOKE_IMMEDIATELY)) { - executor.submit(ScheduledEffectorPolicy.this); - } - } - }; - - @Override - public void run() { - String effectorName = config().get(EFFECTOR); - Map args = EntityInitializers.resolve(config().getBag(), EFFECTOR_ARGUMENTS); - - Maybe> effector = entity.getEntityType().getEffectorByName(effectorName); - if (effector.isAbsent()) { - throw new IllegalStateException("Cannot find effector " + effectorName); - } - - LOG.info("{} invoking effector on {}, effector={}, args={}", new Object[] { this, entity, effectorName, args }); - entity.invoke(effector.get(), args).getUnchecked(); + Date now = new Date(); + long difference = Math.max(0, when.getTime() - now.getTime()); + executor.schedule(this, difference, TimeUnit.MILLISECONDS); } } From cee90f8dcffc8f0e57315fdb6d6bbee44017881a Mon Sep 17 00:00:00 2001 From: Andrew Donald Kennedy Date: Mon, 27 Mar 2017 11:42:23 +0100 Subject: [PATCH 08/43] Add more logging for composite effectors --- .../core/effector/composite/AbstractCompositeEffector.java | 3 +-- .../brooklyn/core/effector/composite/ComposeEffector.java | 2 ++ .../apache/brooklyn/core/effector/composite/LoopEffector.java | 2 ++ .../brooklyn/core/effector/composite/ReplaceEffector.java | 2 ++ .../brooklyn/core/effector/composite/SequenceEffector.java | 2 ++ .../brooklyn/core/effector/composite/TransformEffector.java | 1 + .../apache/brooklyn/policy/action/PeriodicEffectorPolicy.java | 3 ++- .../apache/brooklyn/policy/action/ScheduledEffectorPolicy.java | 3 ++- 8 files changed, 14 insertions(+), 4 deletions(-) diff --git a/core/src/main/java/org/apache/brooklyn/core/effector/composite/AbstractCompositeEffector.java b/core/src/main/java/org/apache/brooklyn/core/effector/composite/AbstractCompositeEffector.java index 9fa31f523f..057f6af32e 100644 --- a/core/src/main/java/org/apache/brooklyn/core/effector/composite/AbstractCompositeEffector.java +++ b/core/src/main/java/org/apache/brooklyn/core/effector/composite/AbstractCompositeEffector.java @@ -158,8 +158,7 @@ protected String getInputParameter(Object effectorDetails) { } protected Object invokeEffectorNamed(Entity target, String effectorName, ConfigBag params) { - LOG.info("{} invoking effector on {}, effector={}, parameters={}", - new Object[]{this, target, effectorName, params}); + LOG.info("{} invoking {} with params {}", new Object[] { this, effectorName, params }); Maybe> effector = target.getEntityType().getEffectorByName(effectorName); if (effector.isAbsent()) { throw new IllegalStateException("Cannot find effector " + effectorName); diff --git a/core/src/main/java/org/apache/brooklyn/core/effector/composite/ComposeEffector.java b/core/src/main/java/org/apache/brooklyn/core/effector/composite/ComposeEffector.java index 7884536216..96548a326a 100644 --- a/core/src/main/java/org/apache/brooklyn/core/effector/composite/ComposeEffector.java +++ b/core/src/main/java/org/apache/brooklyn/core/effector/composite/ComposeEffector.java @@ -73,6 +73,7 @@ public Body(Effector eff, ConfigBag config) { @Override public Object call(final ConfigBag params) { + LOG.info("{} called with config {}", new Object[] { this, config }); List effectors = EntityInitializers.resolve(config, COMPOSE); Object result = null; @@ -82,6 +83,7 @@ public Object call(final ConfigBag params) { String inputArgument = getInputArgument(effectorDetails); String inputParameter = getInputParameter(effectorDetails); Entity targetEntity = getTargetEntity(effectorDetails); + LOG.info("{} executing {}({}:{}) on {}", new Object[] { this, effectorName, inputArgument, inputParameter, targetEntity }); if (inputArgument == null) { throw new IllegalArgumentException("Input is not set for this effector: " + effectorDetails); diff --git a/core/src/main/java/org/apache/brooklyn/core/effector/composite/LoopEffector.java b/core/src/main/java/org/apache/brooklyn/core/effector/composite/LoopEffector.java index 9e694f6ee3..8767579801 100644 --- a/core/src/main/java/org/apache/brooklyn/core/effector/composite/LoopEffector.java +++ b/core/src/main/java/org/apache/brooklyn/core/effector/composite/LoopEffector.java @@ -76,6 +76,7 @@ public Body(Effector eff, ConfigBag config) { @Override public List call(final ConfigBag params) { + LOG.info("{} called with config {}", new Object[] { this, config }); Object effectorDetails = EntityInitializers.resolve(config, LOOP); String input = config.get(INPUT); Object inputObject = config.getStringKey(input); @@ -89,6 +90,7 @@ public List call(final ConfigBag params) { String effectorName = getEffectorName(effectorDetails); String inputArgument = getInputArgument(effectorDetails); Entity targetEntity = getTargetEntity(effectorDetails); + LOG.info("{} executing {}({}) on {}", new Object[] { this, effectorName, inputArgument, targetEntity }); if (inputArgument == null) { throw new IllegalArgumentException("Input is not set for this effector: " + effectorDetails); diff --git a/core/src/main/java/org/apache/brooklyn/core/effector/composite/ReplaceEffector.java b/core/src/main/java/org/apache/brooklyn/core/effector/composite/ReplaceEffector.java index c580cc17e9..bd1adb2ca4 100644 --- a/core/src/main/java/org/apache/brooklyn/core/effector/composite/ReplaceEffector.java +++ b/core/src/main/java/org/apache/brooklyn/core/effector/composite/ReplaceEffector.java @@ -104,12 +104,14 @@ public Body(Effector eff, ConfigBag config) { @Override public Object call(final ConfigBag params) { + LOG.info("{} called with config {}", new Object[] { this, config }); ReplaceAction action = config.get(ACTION); Object effectorDetails = EntityInitializers.resolve(config, REPLACE); String effectorName = getEffectorName(effectorDetails); String inputArgument = getInputArgument(effectorDetails); Entity targetEntity = getTargetEntity(effectorDetails); + LOG.info("{} executing {}({}) on {}", new Object[] { this, effectorName, inputArgument, targetEntity }); if (inputArgument != null) { Object input = config.getStringKey(inputArgument); diff --git a/core/src/main/java/org/apache/brooklyn/core/effector/composite/SequenceEffector.java b/core/src/main/java/org/apache/brooklyn/core/effector/composite/SequenceEffector.java index 8f40365e21..2bde97a7c1 100644 --- a/core/src/main/java/org/apache/brooklyn/core/effector/composite/SequenceEffector.java +++ b/core/src/main/java/org/apache/brooklyn/core/effector/composite/SequenceEffector.java @@ -74,6 +74,7 @@ public Body(Effector eff, ConfigBag config) { @Override public Object call(final ConfigBag params) { + LOG.info("{} called with config {}", new Object[] { this, config }); List effectors = EntityInitializers.resolve(config, SEQUENCE); Object result = null; @@ -82,6 +83,7 @@ public Object call(final ConfigBag params) { String effectorName = getEffectorName(effectorDetails); String inputArgument = getInputArgument(effectorDetails); Entity targetEntity = getTargetEntity(effectorDetails); + LOG.info("{} executing {}({}) on {}", new Object[] { this, effectorName, inputArgument, targetEntity }); if (inputArgument == null) { throw new IllegalArgumentException("Input is not set for this effector: " + effectorDetails); diff --git a/core/src/main/java/org/apache/brooklyn/core/effector/composite/TransformEffector.java b/core/src/main/java/org/apache/brooklyn/core/effector/composite/TransformEffector.java index e68e44bf83..2dbadc0392 100644 --- a/core/src/main/java/org/apache/brooklyn/core/effector/composite/TransformEffector.java +++ b/core/src/main/java/org/apache/brooklyn/core/effector/composite/TransformEffector.java @@ -76,6 +76,7 @@ public Body(Effector eff, ConfigBag config) { @Override public Object call(final ConfigBag params) { + LOG.info("{} called with config {}", new Object[] { this, config }); Function function = EntityInitializers.resolve(config, FUNCTION); String input = config.get(INPUT); diff --git a/policy/src/main/java/org/apache/brooklyn/policy/action/PeriodicEffectorPolicy.java b/policy/src/main/java/org/apache/brooklyn/policy/action/PeriodicEffectorPolicy.java index 44ec00fb5f..3dac156d27 100644 --- a/policy/src/main/java/org/apache/brooklyn/policy/action/PeriodicEffectorPolicy.java +++ b/policy/src/main/java/org/apache/brooklyn/policy/action/PeriodicEffectorPolicy.java @@ -37,6 +37,7 @@ import org.slf4j.LoggerFactory; import com.google.common.annotations.Beta; +import com.google.common.base.Preconditions; import com.google.common.base.Predicates; /** @@ -75,7 +76,7 @@ public PeriodicEffectorPolicy() { public PeriodicEffectorPolicy(Map props) { super(props); - Duration period = config().get(PERIOD); + Duration period = Preconditions.checkNotNull(config().get(PERIOD), "The period must be copnfigured for this policy"); delay = period.toMilliseconds(); } diff --git a/policy/src/main/java/org/apache/brooklyn/policy/action/ScheduledEffectorPolicy.java b/policy/src/main/java/org/apache/brooklyn/policy/action/ScheduledEffectorPolicy.java index 00334507c0..1018ab01df 100644 --- a/policy/src/main/java/org/apache/brooklyn/policy/action/ScheduledEffectorPolicy.java +++ b/policy/src/main/java/org/apache/brooklyn/policy/action/ScheduledEffectorPolicy.java @@ -36,6 +36,7 @@ import org.slf4j.LoggerFactory; import com.google.common.annotations.Beta; +import com.google.common.base.Preconditions; import com.google.common.base.Predicates; /** @@ -70,7 +71,7 @@ public ScheduledEffectorPolicy() { public ScheduledEffectorPolicy(Map props) { super(props); - String time = config().get(TIME); + String time = Preconditions.checkNotNull(config().get(TIME), "The time must be configured for this policy"); DateFormat format = DateFormat.getDateTimeInstance(); try { when = format.parse(time); From 05086e10e44c6c10a793807fe6eaf8f327496a8a Mon Sep 17 00:00:00 2001 From: Andrew Donald Kennedy Date: Mon, 27 Mar 2017 14:43:42 +0100 Subject: [PATCH 09/43] Further fix for setting pareams on composite effectors --- .../brooklyn/core/effector/composite/ComposeEffector.java | 6 +++--- .../brooklyn/core/effector/composite/LoopEffector.java | 4 ++-- .../brooklyn/core/effector/composite/ReplaceEffector.java | 4 ++-- .../brooklyn/core/effector/composite/SequenceEffector.java | 4 ++-- .../brooklyn/core/effector/composite/TransformEffector.java | 4 ++-- 5 files changed, 11 insertions(+), 11 deletions(-) diff --git a/core/src/main/java/org/apache/brooklyn/core/effector/composite/ComposeEffector.java b/core/src/main/java/org/apache/brooklyn/core/effector/composite/ComposeEffector.java index 96548a326a..1ccad48f5d 100644 --- a/core/src/main/java/org/apache/brooklyn/core/effector/composite/ComposeEffector.java +++ b/core/src/main/java/org/apache/brooklyn/core/effector/composite/ComposeEffector.java @@ -73,7 +73,7 @@ public Body(Effector eff, ConfigBag config) { @Override public Object call(final ConfigBag params) { - LOG.info("{} called with config {}", new Object[] { this, config }); + LOG.info("{} called with config {}, params {}", new Object[] { this, config, params }); List effectors = EntityInitializers.resolve(config, COMPOSE); Object result = null; @@ -90,13 +90,13 @@ public Object call(final ConfigBag params) { } if (inputParameter == null) { if (result == null) { - Object input = config.getStringKey(inputArgument); + Object input = params.getStringKey(inputArgument); params.putStringKey(inputArgument, input); } else { params.putStringKey(inputArgument, result); } } else { - Object input = config.getStringKey(inputParameter); + Object input = params.getStringKey(inputParameter); params.putStringKey(inputArgument, input); } diff --git a/core/src/main/java/org/apache/brooklyn/core/effector/composite/LoopEffector.java b/core/src/main/java/org/apache/brooklyn/core/effector/composite/LoopEffector.java index 8767579801..cc3be06c29 100644 --- a/core/src/main/java/org/apache/brooklyn/core/effector/composite/LoopEffector.java +++ b/core/src/main/java/org/apache/brooklyn/core/effector/composite/LoopEffector.java @@ -76,10 +76,10 @@ public Body(Effector eff, ConfigBag config) { @Override public List call(final ConfigBag params) { - LOG.info("{} called with config {}", new Object[] { this, config }); + LOG.info("{} called with config {}, params {}", new Object[] { this, config, params }); Object effectorDetails = EntityInitializers.resolve(config, LOOP); String input = config.get(INPUT); - Object inputObject = config.getStringKey(input); + Object inputObject = params.getStringKey(input); if (!(inputObject instanceof Collection)) { throw new IllegalArgumentException("Input to loop is not a collection: " + inputObject); } diff --git a/core/src/main/java/org/apache/brooklyn/core/effector/composite/ReplaceEffector.java b/core/src/main/java/org/apache/brooklyn/core/effector/composite/ReplaceEffector.java index bd1adb2ca4..5d2c04f863 100644 --- a/core/src/main/java/org/apache/brooklyn/core/effector/composite/ReplaceEffector.java +++ b/core/src/main/java/org/apache/brooklyn/core/effector/composite/ReplaceEffector.java @@ -104,7 +104,7 @@ public Body(Effector eff, ConfigBag config) { @Override public Object call(final ConfigBag params) { - LOG.info("{} called with config {}", new Object[] { this, config }); + LOG.info("{} called with config {}, params {}", new Object[] { this, config, params }); ReplaceAction action = config.get(ACTION); Object effectorDetails = EntityInitializers.resolve(config, REPLACE); @@ -114,7 +114,7 @@ public Object call(final ConfigBag params) { LOG.info("{} executing {}({}) on {}", new Object[] { this, effectorName, inputArgument, targetEntity }); if (inputArgument != null) { - Object input = config.getStringKey(inputArgument); + Object input = params.getStringKey(inputArgument); params.putStringKey(inputArgument, input); } diff --git a/core/src/main/java/org/apache/brooklyn/core/effector/composite/SequenceEffector.java b/core/src/main/java/org/apache/brooklyn/core/effector/composite/SequenceEffector.java index 2bde97a7c1..be49c8d8c4 100644 --- a/core/src/main/java/org/apache/brooklyn/core/effector/composite/SequenceEffector.java +++ b/core/src/main/java/org/apache/brooklyn/core/effector/composite/SequenceEffector.java @@ -74,7 +74,7 @@ public Body(Effector eff, ConfigBag config) { @Override public Object call(final ConfigBag params) { - LOG.info("{} called with config {}", new Object[] { this, config }); + LOG.info("{} called with config {}, params {}", new Object[] { this, config, params }); List effectors = EntityInitializers.resolve(config, SEQUENCE); Object result = null; @@ -88,7 +88,7 @@ public Object call(final ConfigBag params) { if (inputArgument == null) { throw new IllegalArgumentException("Input is not set for this effector: " + effectorDetails); } - Object input = config.getStringKey(inputArgument); + Object input = params.getStringKey(inputArgument); params.putStringKey(inputArgument, input); result = invokeEffectorNamed(targetEntity, effectorName, params); diff --git a/core/src/main/java/org/apache/brooklyn/core/effector/composite/TransformEffector.java b/core/src/main/java/org/apache/brooklyn/core/effector/composite/TransformEffector.java index 2dbadc0392..cfb472e34b 100644 --- a/core/src/main/java/org/apache/brooklyn/core/effector/composite/TransformEffector.java +++ b/core/src/main/java/org/apache/brooklyn/core/effector/composite/TransformEffector.java @@ -76,11 +76,11 @@ public Body(Effector eff, ConfigBag config) { @Override public Object call(final ConfigBag params) { - LOG.info("{} called with config {}", new Object[] { this, config }); + LOG.info("{} called with config {}, params {}", new Object[] { this, config, params }); Function function = EntityInitializers.resolve(config, FUNCTION); String input = config.get(INPUT); - Object value = config.getStringKey(input); + Object value = params.getStringKey(input); Object result = function.apply(value); return result; From 338336e1ae2076fd8a71031aa68192b88993b9a4 Mon Sep 17 00:00:00 2001 From: Andrew Donald Kennedy Date: Mon, 27 Mar 2017 17:04:23 +0100 Subject: [PATCH 10/43] Log at DEBUG level in composite effectors --- .../core/effector/composite/AbstractCompositeEffector.java | 2 +- .../brooklyn/core/effector/composite/ComposeEffector.java | 4 ++-- .../apache/brooklyn/core/effector/composite/LoopEffector.java | 4 ++-- .../brooklyn/core/effector/composite/ReplaceEffector.java | 4 ++-- .../brooklyn/core/effector/composite/SequenceEffector.java | 4 ++-- .../brooklyn/core/effector/composite/TransformEffector.java | 2 +- 6 files changed, 10 insertions(+), 10 deletions(-) diff --git a/core/src/main/java/org/apache/brooklyn/core/effector/composite/AbstractCompositeEffector.java b/core/src/main/java/org/apache/brooklyn/core/effector/composite/AbstractCompositeEffector.java index 057f6af32e..5b5357503c 100644 --- a/core/src/main/java/org/apache/brooklyn/core/effector/composite/AbstractCompositeEffector.java +++ b/core/src/main/java/org/apache/brooklyn/core/effector/composite/AbstractCompositeEffector.java @@ -158,7 +158,7 @@ protected String getInputParameter(Object effectorDetails) { } protected Object invokeEffectorNamed(Entity target, String effectorName, ConfigBag params) { - LOG.info("{} invoking {} with params {}", new Object[] { this, effectorName, params }); + LOG.debug("{} invoking {} with params {}", new Object[] { this, effectorName, params }); Maybe> effector = target.getEntityType().getEffectorByName(effectorName); if (effector.isAbsent()) { throw new IllegalStateException("Cannot find effector " + effectorName); diff --git a/core/src/main/java/org/apache/brooklyn/core/effector/composite/ComposeEffector.java b/core/src/main/java/org/apache/brooklyn/core/effector/composite/ComposeEffector.java index 1ccad48f5d..4ffd89265c 100644 --- a/core/src/main/java/org/apache/brooklyn/core/effector/composite/ComposeEffector.java +++ b/core/src/main/java/org/apache/brooklyn/core/effector/composite/ComposeEffector.java @@ -73,7 +73,7 @@ public Body(Effector eff, ConfigBag config) { @Override public Object call(final ConfigBag params) { - LOG.info("{} called with config {}, params {}", new Object[] { this, config, params }); + LOG.debug("{} called with config {}, params {}", new Object[] { this, config, params }); List effectors = EntityInitializers.resolve(config, COMPOSE); Object result = null; @@ -83,7 +83,7 @@ public Object call(final ConfigBag params) { String inputArgument = getInputArgument(effectorDetails); String inputParameter = getInputParameter(effectorDetails); Entity targetEntity = getTargetEntity(effectorDetails); - LOG.info("{} executing {}({}:{}) on {}", new Object[] { this, effectorName, inputArgument, inputParameter, targetEntity }); + LOG.debug("{} executing {}({}:{}) on {}", new Object[] { this, effectorName, inputArgument, inputParameter, targetEntity }); if (inputArgument == null) { throw new IllegalArgumentException("Input is not set for this effector: " + effectorDetails); diff --git a/core/src/main/java/org/apache/brooklyn/core/effector/composite/LoopEffector.java b/core/src/main/java/org/apache/brooklyn/core/effector/composite/LoopEffector.java index cc3be06c29..f011daba21 100644 --- a/core/src/main/java/org/apache/brooklyn/core/effector/composite/LoopEffector.java +++ b/core/src/main/java/org/apache/brooklyn/core/effector/composite/LoopEffector.java @@ -76,7 +76,7 @@ public Body(Effector eff, ConfigBag config) { @Override public List call(final ConfigBag params) { - LOG.info("{} called with config {}, params {}", new Object[] { this, config, params }); + LOG.debug("{} called with config {}, params {}", new Object[] { this, config, params }); Object effectorDetails = EntityInitializers.resolve(config, LOOP); String input = config.get(INPUT); Object inputObject = params.getStringKey(input); @@ -90,7 +90,7 @@ public List call(final ConfigBag params) { String effectorName = getEffectorName(effectorDetails); String inputArgument = getInputArgument(effectorDetails); Entity targetEntity = getTargetEntity(effectorDetails); - LOG.info("{} executing {}({}) on {}", new Object[] { this, effectorName, inputArgument, targetEntity }); + LOG.debug("{} executing {}({}) on {}", new Object[] { this, effectorName, inputArgument, targetEntity }); if (inputArgument == null) { throw new IllegalArgumentException("Input is not set for this effector: " + effectorDetails); diff --git a/core/src/main/java/org/apache/brooklyn/core/effector/composite/ReplaceEffector.java b/core/src/main/java/org/apache/brooklyn/core/effector/composite/ReplaceEffector.java index 5d2c04f863..d6c23bba9f 100644 --- a/core/src/main/java/org/apache/brooklyn/core/effector/composite/ReplaceEffector.java +++ b/core/src/main/java/org/apache/brooklyn/core/effector/composite/ReplaceEffector.java @@ -104,14 +104,14 @@ public Body(Effector eff, ConfigBag config) { @Override public Object call(final ConfigBag params) { - LOG.info("{} called with config {}, params {}", new Object[] { this, config, params }); + LOG.debug("{} called with config {}, params {}", new Object[] { this, config, params }); ReplaceAction action = config.get(ACTION); Object effectorDetails = EntityInitializers.resolve(config, REPLACE); String effectorName = getEffectorName(effectorDetails); String inputArgument = getInputArgument(effectorDetails); Entity targetEntity = getTargetEntity(effectorDetails); - LOG.info("{} executing {}({}) on {}", new Object[] { this, effectorName, inputArgument, targetEntity }); + LOG.debug("{} executing {}({}) on {}", new Object[] { this, effectorName, inputArgument, targetEntity }); if (inputArgument != null) { Object input = params.getStringKey(inputArgument); diff --git a/core/src/main/java/org/apache/brooklyn/core/effector/composite/SequenceEffector.java b/core/src/main/java/org/apache/brooklyn/core/effector/composite/SequenceEffector.java index be49c8d8c4..1dc4d1ea4c 100644 --- a/core/src/main/java/org/apache/brooklyn/core/effector/composite/SequenceEffector.java +++ b/core/src/main/java/org/apache/brooklyn/core/effector/composite/SequenceEffector.java @@ -74,7 +74,7 @@ public Body(Effector eff, ConfigBag config) { @Override public Object call(final ConfigBag params) { - LOG.info("{} called with config {}, params {}", new Object[] { this, config, params }); + LOG.debug("{} called with config {}, params {}", new Object[] { this, config, params }); List effectors = EntityInitializers.resolve(config, SEQUENCE); Object result = null; @@ -83,7 +83,7 @@ public Object call(final ConfigBag params) { String effectorName = getEffectorName(effectorDetails); String inputArgument = getInputArgument(effectorDetails); Entity targetEntity = getTargetEntity(effectorDetails); - LOG.info("{} executing {}({}) on {}", new Object[] { this, effectorName, inputArgument, targetEntity }); + LOG.debug("{} executing {}({}) on {}", new Object[] { this, effectorName, inputArgument, targetEntity }); if (inputArgument == null) { throw new IllegalArgumentException("Input is not set for this effector: " + effectorDetails); diff --git a/core/src/main/java/org/apache/brooklyn/core/effector/composite/TransformEffector.java b/core/src/main/java/org/apache/brooklyn/core/effector/composite/TransformEffector.java index cfb472e34b..5ce814d3f0 100644 --- a/core/src/main/java/org/apache/brooklyn/core/effector/composite/TransformEffector.java +++ b/core/src/main/java/org/apache/brooklyn/core/effector/composite/TransformEffector.java @@ -76,7 +76,7 @@ public Body(Effector eff, ConfigBag config) { @Override public Object call(final ConfigBag params) { - LOG.info("{} called with config {}, params {}", new Object[] { this, config, params }); + LOG.debug("{} called with config {}, params {}", new Object[] { this, config, params }); Function function = EntityInitializers.resolve(config, FUNCTION); String input = config.get(INPUT); From 7866fa60477fa6b6658364623418d866034c8559 Mon Sep 17 00:00:00 2001 From: Andrew Donald Kennedy Date: Mon, 27 Mar 2017 17:04:58 +0100 Subject: [PATCH 11/43] Add new splitter function to utils --- .../brooklyn/util/text/StringFunctions.java | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/text/StringFunctions.java b/utils/common/src/main/java/org/apache/brooklyn/util/text/StringFunctions.java index ddf1914d05..5ce718bbf6 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/text/StringFunctions.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/text/StringFunctions.java @@ -28,6 +28,8 @@ import com.google.common.base.Functions; import com.google.common.base.Objects; import com.google.common.base.Preconditions; +import com.google.common.base.Splitter; +import com.google.common.collect.ImmutableList; import com.google.common.collect.Iterables; public class StringFunctions { @@ -279,6 +281,24 @@ public String apply(@Nullable Iterable input) { } } + /** splits a string into a collection of elements */ + public static Function> splitter(final String separator) { + return new SplitterFunction(separator); + } + + private static class SplitterFunction implements Function> { + private final String separator; + + public SplitterFunction(String separator) { + this.separator = separator; + } + @Override + public Iterable apply(@Nullable String input) { + if (input == null) return ImmutableList.of(); + return ImmutableList.copyOf(Splitter.on(separator).split(input)); + } + } + /** joins the given objects in a collection as a toString with the given separator */ public static Function, String> joiner(final String separator) { return new JoinerFunction(separator); From 1e7794edebd5d59563a63b3cb58e63d83f3a5978 Mon Sep 17 00:00:00 2001 From: Andrew Donald Kennedy Date: Tue, 28 Mar 2017 15:30:08 +0100 Subject: [PATCH 12/43] Tidy up policy code for scheduled effectors --- .../action/AbstractScheduledEffectorPolicy.java | 4 ++-- .../policy/action/PeriodicEffectorPolicy.java | 15 ++++++--------- .../policy/action/ScheduledEffectorPolicy.java | 12 ++++++------ 3 files changed, 14 insertions(+), 17 deletions(-) diff --git a/policy/src/main/java/org/apache/brooklyn/policy/action/AbstractScheduledEffectorPolicy.java b/policy/src/main/java/org/apache/brooklyn/policy/action/AbstractScheduledEffectorPolicy.java index d158495c1b..005b09b1cb 100644 --- a/policy/src/main/java/org/apache/brooklyn/policy/action/AbstractScheduledEffectorPolicy.java +++ b/policy/src/main/java/org/apache/brooklyn/policy/action/AbstractScheduledEffectorPolicy.java @@ -56,7 +56,7 @@ public abstract class AbstractScheduledEffectorPolicy extends AbstractPolicy imp .defaultValue(ImmutableMap.of()) .build(); - private final ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor(); + protected final ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor(); public AbstractScheduledEffectorPolicy() { this(MutableMap.of()); @@ -82,7 +82,7 @@ public void run() { throw new IllegalStateException("Cannot find effector " + effectorName); } - LOG.info("{} invoking effector on {}, effector={}, args={}", new Object[] { this, entity, effectorName, args }); + LOG.debug("{} invoking effector on {}, effector={}, args={}", new Object[] { this, entity, effectorName, args }); entity.invoke(effector.get(), args).getUnchecked(); } } diff --git a/policy/src/main/java/org/apache/brooklyn/policy/action/PeriodicEffectorPolicy.java b/policy/src/main/java/org/apache/brooklyn/policy/action/PeriodicEffectorPolicy.java index 3dac156d27..231dbae6b6 100644 --- a/policy/src/main/java/org/apache/brooklyn/policy/action/PeriodicEffectorPolicy.java +++ b/policy/src/main/java/org/apache/brooklyn/policy/action/PeriodicEffectorPolicy.java @@ -20,8 +20,6 @@ package org.apache.brooklyn.policy.action; import java.util.Map; -import java.util.concurrent.Executors; -import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; import org.apache.brooklyn.api.entity.EntityLocal; @@ -63,13 +61,11 @@ public class PeriodicEffectorPolicy extends AbstractScheduledEffectorPolicy { .defaultValue(Duration.hours(1)) .build(); - public static final AttributeSensor INVOKE_IMMEDIATELY = Sensors.newSensor(Void.TYPE, "scheduler.invoke"); - public static final AttributeSensor START_SCHEDULER = Sensors.newBooleanSensor("scheduler.start"); + public static final AttributeSensor INVOKE_IMMEDIATELY = Sensors.newBooleanSensor("scheduler.invoke", "Invoke the configured effector immediately when this becomes true"); + public static final AttributeSensor START_SCHEDULER = Sensors.newBooleanSensor("scheduler.start", "Start the periodic effector execution after this becomes true"); protected long delay; - private final ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor(); - public PeriodicEffectorPolicy() { this(MutableMap.of()); } @@ -90,14 +86,15 @@ public void setEntity(final EntityLocal entity) { private final SensorEventListener handler = new SensorEventListener() { @Override public void onEvent(SensorEvent event) { + LOG.debug("{} got event {}", PeriodicEffectorPolicy.this, event); if (event.getSensor().equals(START_SCHEDULER)) { if (Boolean.TRUE.equals(event.getValue())) { executor.scheduleWithFixedDelay(PeriodicEffectorPolicy.this, delay, delay, TimeUnit.MILLISECONDS); - } else { - executor.shutdown(); } } else if (event.getSensor().equals(INVOKE_IMMEDIATELY)) { - executor.submit(PeriodicEffectorPolicy.this); + if (Boolean.TRUE.equals(event.getValue())) { + executor.submit(PeriodicEffectorPolicy.this); + } } } }; diff --git a/policy/src/main/java/org/apache/brooklyn/policy/action/ScheduledEffectorPolicy.java b/policy/src/main/java/org/apache/brooklyn/policy/action/ScheduledEffectorPolicy.java index 1018ab01df..f0d2ef7b69 100644 --- a/policy/src/main/java/org/apache/brooklyn/policy/action/ScheduledEffectorPolicy.java +++ b/policy/src/main/java/org/apache/brooklyn/policy/action/ScheduledEffectorPolicy.java @@ -21,10 +21,9 @@ import java.text.DateFormat; import java.text.ParseException; +import java.text.SimpleDateFormat; import java.util.Date; import java.util.Map; -import java.util.concurrent.Executors; -import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; import org.apache.brooklyn.api.entity.EntityLocal; @@ -47,7 +46,7 @@ * effector: repaveCluster * args: * k: $brooklyn:config("repave.size") - * time: 12:00 01 January 2018 + * time: 2017-12-11 12:00:00 * } */ @Beta @@ -55,6 +54,8 @@ public class ScheduledEffectorPolicy extends AbstractScheduledEffectorPolicy { private static final Logger LOG = LoggerFactory.getLogger(ScheduledEffectorPolicy.class); + public static final String TIME_FORMAT = "yyyy-MM-dd HH:mm:ss"; + public static final ConfigKey TIME = ConfigKeys.builder(String.class) .name("time") .description("The time when this policy should be executed") @@ -63,8 +64,6 @@ public class ScheduledEffectorPolicy extends AbstractScheduledEffectorPolicy { protected Date when; - private final ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor(); - public ScheduledEffectorPolicy() { this(MutableMap.of()); } @@ -72,10 +71,11 @@ public ScheduledEffectorPolicy() { public ScheduledEffectorPolicy(Map props) { super(props); String time = Preconditions.checkNotNull(config().get(TIME), "The time must be configured for this policy"); - DateFormat format = DateFormat.getDateTimeInstance(); + DateFormat format = new SimpleDateFormat(TIME_FORMAT); try { when = format.parse(time); } catch (ParseException e) { + LOG.warn("The time must be formatted as " + TIME_FORMAT + " for this policy", e); Exceptions.propagate(e); } Date now = new Date(); From e41dd1cb9f34e4fc199bdc6f00aa45f05edbb2bc Mon Sep 17 00:00:00 2001 From: Andrew Donald Kennedy Date: Tue, 28 Mar 2017 17:10:48 +0100 Subject: [PATCH 13/43] Update scheduled effector policy to accept sensor with target time to invoke --- .../AbstractScheduledEffectorPolicy.java | 1 - .../policy/action/PeriodicEffectorPolicy.java | 8 +-- .../action/ScheduledEffectorPolicy.java | 68 +++++++++++++------ 3 files changed, 49 insertions(+), 28 deletions(-) diff --git a/policy/src/main/java/org/apache/brooklyn/policy/action/AbstractScheduledEffectorPolicy.java b/policy/src/main/java/org/apache/brooklyn/policy/action/AbstractScheduledEffectorPolicy.java index 005b09b1cb..ba808d0d50 100644 --- a/policy/src/main/java/org/apache/brooklyn/policy/action/AbstractScheduledEffectorPolicy.java +++ b/policy/src/main/java/org/apache/brooklyn/policy/action/AbstractScheduledEffectorPolicy.java @@ -16,7 +16,6 @@ * specific language governing permissions and limitations * under the License. */ - package org.apache.brooklyn.policy.action; import java.util.Map; diff --git a/policy/src/main/java/org/apache/brooklyn/policy/action/PeriodicEffectorPolicy.java b/policy/src/main/java/org/apache/brooklyn/policy/action/PeriodicEffectorPolicy.java index 231dbae6b6..b238ea29b7 100644 --- a/policy/src/main/java/org/apache/brooklyn/policy/action/PeriodicEffectorPolicy.java +++ b/policy/src/main/java/org/apache/brooklyn/policy/action/PeriodicEffectorPolicy.java @@ -16,7 +16,6 @@ * specific language governing permissions and limitations * under the License. */ - package org.apache.brooklyn.policy.action; import java.util.Map; @@ -61,7 +60,6 @@ public class PeriodicEffectorPolicy extends AbstractScheduledEffectorPolicy { .defaultValue(Duration.hours(1)) .build(); - public static final AttributeSensor INVOKE_IMMEDIATELY = Sensors.newBooleanSensor("scheduler.invoke", "Invoke the configured effector immediately when this becomes true"); public static final AttributeSensor START_SCHEDULER = Sensors.newBooleanSensor("scheduler.start", "Start the periodic effector execution after this becomes true"); protected long delay; @@ -79,7 +77,7 @@ public PeriodicEffectorPolicy(Map props) { @Override public void setEntity(final EntityLocal entity) { super.setEntity(entity); - subscriptions().subscribe(entity, INVOKE_IMMEDIATELY, handler); + subscriptions().subscribe(entity, START_SCHEDULER, handler); } @@ -91,10 +89,6 @@ public void onEvent(SensorEvent event) { if (Boolean.TRUE.equals(event.getValue())) { executor.scheduleWithFixedDelay(PeriodicEffectorPolicy.this, delay, delay, TimeUnit.MILLISECONDS); } - } else if (event.getSensor().equals(INVOKE_IMMEDIATELY)) { - if (Boolean.TRUE.equals(event.getValue())) { - executor.submit(PeriodicEffectorPolicy.this); - } } } }; diff --git a/policy/src/main/java/org/apache/brooklyn/policy/action/ScheduledEffectorPolicy.java b/policy/src/main/java/org/apache/brooklyn/policy/action/ScheduledEffectorPolicy.java index f0d2ef7b69..a4bd26505a 100644 --- a/policy/src/main/java/org/apache/brooklyn/policy/action/ScheduledEffectorPolicy.java +++ b/policy/src/main/java/org/apache/brooklyn/policy/action/ScheduledEffectorPolicy.java @@ -16,7 +16,6 @@ * specific language governing permissions and limitations * under the License. */ - package org.apache.brooklyn.policy.action; import java.text.DateFormat; @@ -27,16 +26,19 @@ import java.util.concurrent.TimeUnit; import org.apache.brooklyn.api.entity.EntityLocal; +import org.apache.brooklyn.api.sensor.AttributeSensor; +import org.apache.brooklyn.api.sensor.SensorEvent; +import org.apache.brooklyn.api.sensor.SensorEventListener; import org.apache.brooklyn.config.ConfigKey; import org.apache.brooklyn.core.config.ConfigKeys; +import org.apache.brooklyn.core.sensor.Sensors; import org.apache.brooklyn.util.collections.MutableMap; import org.apache.brooklyn.util.exceptions.Exceptions; +import org.apache.brooklyn.util.text.Strings; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.google.common.annotations.Beta; -import com.google.common.base.Preconditions; -import com.google.common.base.Predicates; /** *
{@code
@@ -56,13 +58,15 @@ public class ScheduledEffectorPolicy extends AbstractScheduledEffectorPolicy {
 
     public static final String TIME_FORMAT = "yyyy-MM-dd HH:mm:ss";
 
+    protected static final DateFormat FORMATTER = new SimpleDateFormat(TIME_FORMAT);
+
     public static final ConfigKey TIME = ConfigKeys.builder(String.class)
             .name("time")
-            .description("The time when this policy should be executed")
-            .constraint(Predicates.notNull())
+            .description("An optional time when this policy should be first executed")
             .build();
 
-    protected Date when;
+    public static final AttributeSensor INVOKE_IMMEDIATELY = Sensors.newBooleanSensor("scheduler.invoke.now", "Invoke the configured effector immediately when this becomes true");
+    public static final AttributeSensor INVOKE_AT = Sensors.newStringSensor("scheduler.invoke.at", "Invoke the configured effector at this time");
 
     public ScheduledEffectorPolicy() {
         this(MutableMap.of());
@@ -70,26 +74,50 @@ public ScheduledEffectorPolicy() {
 
     public ScheduledEffectorPolicy(Map props) {
         super(props);
-        String time = Preconditions.checkNotNull(config().get(TIME), "The time must be configured for this policy");
-        DateFormat format = new SimpleDateFormat(TIME_FORMAT);
-        try {
-            when = format.parse(time);
-        } catch (ParseException e) {
-            LOG.warn("The time must be formatted as " + TIME_FORMAT + " for this policy", e);
-            Exceptions.propagate(e);
-        }
-        Date now = new Date();
-        if (when.before(now)) {
-            throw new IllegalStateException("The time provided must be in the future");
+        String time = config().get(TIME);
+        if (Strings.isNonBlank(time)) {
+            scheduleAt(time);
         }
     }
 
     @Override
     public void setEntity(final EntityLocal entity) {
         super.setEntity(entity);
-        Date now = new Date();
-        long difference = Math.max(0, when.getTime() - now.getTime());
-        executor.schedule(this, difference, TimeUnit.MILLISECONDS);
+
+        subscriptions().subscribe(entity, INVOKE_IMMEDIATELY, handler);
+        subscriptions().subscribe(entity, INVOKE_AT, handler);
     }
 
+    protected void scheduleAt(String time) {
+        try {
+            Date when = FORMATTER.parse(time);
+            Date now = new Date();
+            if (when.before(now)) {
+                throw new IllegalStateException("The time provided must be in the future: " + FORMATTER.format(time));
+            }
+            long difference = Math.max(0, when.getTime() - now.getTime());
+            executor.schedule(this, difference, TimeUnit.MILLISECONDS);
+        } catch (ParseException e) {
+            LOG.warn("The time must be formatted as " + TIME_FORMAT + " for this policy", e);
+            Exceptions.propagate(e);
+        }
+    }
+
+    private final SensorEventListener handler = new SensorEventListener() {
+        @Override
+        public void onEvent(SensorEvent event) {
+            LOG.debug("{} got event {}", ScheduledEffectorPolicy.this, event);
+            if (event.getSensor().equals(INVOKE_AT)) {
+                String time = (String) event.getValue();
+                if (Strings.isNonBlank(time)) {
+                    scheduleAt(time);
+                }
+            } else if (event.getSensor().equals(INVOKE_IMMEDIATELY)) {
+                if (Boolean.TRUE.equals(event.getValue())) {
+                    executor.submit(ScheduledEffectorPolicy.this);
+                }
+            }
+        }
+    };
+
 }

From 4d1203bb2a740f2692fbf98ecab23a6752588e19 Mon Sep 17 00:00:00 2001
From: Andrew Donald Kennedy 
Date: Tue, 28 Mar 2017 22:02:21 +0100
Subject: [PATCH 14/43] Added extra choice effector for if-then-else composites

---
 .../effector/composite/ChoiceEffector.java    | 142 ++++++++++++++++++
 1 file changed, 142 insertions(+)
 create mode 100644 core/src/main/java/org/apache/brooklyn/core/effector/composite/ChoiceEffector.java

diff --git a/core/src/main/java/org/apache/brooklyn/core/effector/composite/ChoiceEffector.java b/core/src/main/java/org/apache/brooklyn/core/effector/composite/ChoiceEffector.java
new file mode 100644
index 0000000000..c23ba1c1b9
--- /dev/null
+++ b/core/src/main/java/org/apache/brooklyn/core/effector/composite/ChoiceEffector.java
@@ -0,0 +1,142 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.brooklyn.core.effector.composite;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.brooklyn.api.effector.Effector;
+import org.apache.brooklyn.api.entity.Entity;
+import org.apache.brooklyn.config.ConfigKey;
+import org.apache.brooklyn.core.config.ConfigKeys;
+import org.apache.brooklyn.core.effector.AddEffector;
+import org.apache.brooklyn.core.effector.EffectorBody;
+import org.apache.brooklyn.core.effector.Effectors.EffectorBuilder;
+import org.apache.brooklyn.core.entity.EntityInitializers;
+import org.apache.brooklyn.util.core.config.ConfigBag;
+import org.apache.brooklyn.util.text.Strings;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.common.annotations.Beta;
+import com.google.common.base.Preconditions;
+import com.google.common.collect.Lists;
+
+@Beta
+public class ChoiceEffector extends AbstractCompositeEffector {
+
+    private static final Logger LOG = LoggerFactory.getLogger(ChoiceEffector.class);
+
+    public static final ConfigKey INPUT = ConfigKeys.newStringConfigKey(
+            "input",
+            "Choice input parameter");
+
+    public static final ConfigKey CHOICE = ConfigKeys.newConfigKey(
+            Object.class,
+            "choice",
+            "Effector details for the choice effector");
+
+    public static final ConfigKey SUCCESS = ConfigKeys.newConfigKey(
+            Object.class,
+            "success",
+            "Effector details for the success effector");
+
+    public static final ConfigKey FAILURE = ConfigKeys.newConfigKey(
+            Object.class,
+            "failure",
+            "Effector details for the failure effector");
+
+    public ChoiceEffector(ConfigBag params) {
+        super(newEffectorBuilder(params).build());
+    }
+
+    public ChoiceEffector(Map params) {
+        this(ConfigBag.newInstance(params));
+    }
+
+    public static EffectorBuilder newEffectorBuilder(ConfigBag params) {
+        EffectorBuilder eff = AddEffector.newEffectorBuilder(Object.class, params);
+        EffectorBody body = new Body(eff.buildAbstract(), params);
+        eff.impl(body);
+        return eff;
+    }
+
+    protected static class Body extends AbstractCompositeEffector.Body {
+
+        public Body(Effector eff, ConfigBag config) {
+            super(eff, config);
+            Preconditions.checkNotNull(config.getAllConfigRaw().get(CHOICE.getName()), "Choice effector details must be supplied when defining this effector");
+            Preconditions.checkNotNull(config.getAllConfigRaw().get(SUCCESS.getName()), "Success effector details must be supplied when defining this effector");
+        }
+
+        @Override
+        public Object call(final ConfigBag params) {
+            LOG.debug("{} called with config {}, params {}", new Object[] { this, config, params });
+            String input = config.get(INPUT);
+            Object inputObject = params.getStringKey(input);
+
+            Object choiceDetails = EntityInitializers.resolve(config, CHOICE);
+            String choiceEffectorName = getEffectorName(choiceDetails);
+            String choiceInputArgument = getInputArgument(choiceDetails);
+            Entity choiceTargetEntity = getTargetEntity(choiceDetails);
+            LOG.debug("{} executing {}({}) on {}", new Object[] { this, choiceEffectorName, choiceInputArgument, choiceTargetEntity });
+
+            if (choiceInputArgument == null) {
+                throw new IllegalArgumentException("Input is not set for choice effector: " + choiceDetails);
+            }
+            params.putStringKey(choiceInputArgument, inputObject);
+
+            Object output = invokeEffectorNamed(choiceTargetEntity, choiceEffectorName, params);
+            Boolean result = Boolean.parseBoolean(Strings.toString(output));
+
+            if (result) {
+                Object successDetails = EntityInitializers.resolve(config, SUCCESS);
+                String successEffectorName = getEffectorName(successDetails);
+                String successInputArgument = getInputArgument(successDetails);
+                Entity successTargetEntity = getTargetEntity(successDetails);
+                LOG.debug("{} executing {}({}) on {}", new Object[] { this, successEffectorName, successInputArgument, successTargetEntity });
+
+                if (successInputArgument == null) {
+                    throw new IllegalArgumentException("Input is not set for success effector: " + successDetails);
+                }
+                params.putStringKey(successInputArgument, inputObject);
+
+                return invokeEffectorNamed(successTargetEntity, successEffectorName, params);
+            } else {
+                Object failureDetails = EntityInitializers.resolve(config, FAILURE);
+                if (failureDetails == null) {
+                    return null;
+                }
+                String failureEffectorName = getEffectorName(failureDetails);
+                String failureInputArgument = getInputArgument(failureDetails);
+                Entity failureTargetEntity = getTargetEntity(failureDetails);
+                LOG.debug("{} executing {}({}) on {}", new Object[] { this, failureEffectorName, failureInputArgument, failureTargetEntity });
+
+                if (failureInputArgument == null) {
+                    throw new IllegalArgumentException("Input is not set for success effector: " + failureDetails);
+                }
+                params.putStringKey(failureInputArgument, inputObject);
+
+                return invokeEffectorNamed(failureTargetEntity, failureEffectorName, params);
+            }
+        }
+    }
+
+}

From f7c7c848c5d4a27e3a046b15709bf1ab3e2c00a6 Mon Sep 17 00:00:00 2001
From: Andrew Donald Kennedy 
Date: Wed, 29 Mar 2017 14:17:13 +0100
Subject: [PATCH 15/43] Add annotations to TaskSummary for JSON marshalling

---
 .../org/apache/brooklyn/rest/domain/TaskSummary.java | 12 ++++++++----
 1 file changed, 8 insertions(+), 4 deletions(-)

diff --git a/rest/rest-api/src/main/java/org/apache/brooklyn/rest/domain/TaskSummary.java b/rest/rest-api/src/main/java/org/apache/brooklyn/rest/domain/TaskSummary.java
index eef57e6516..08d8cec215 100644
--- a/rest/rest-api/src/main/java/org/apache/brooklyn/rest/domain/TaskSummary.java
+++ b/rest/rest-api/src/main/java/org/apache/brooklyn/rest/domain/TaskSummary.java
@@ -29,12 +29,16 @@
 
 import org.apache.brooklyn.util.collections.Jsonya;
 
+import com.fasterxml.jackson.annotation.JsonIdentityInfo;
 import com.fasterxml.jackson.annotation.JsonIgnore;
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.annotation.JsonInclude.Include;
 import com.fasterxml.jackson.annotation.JsonProperty;
-import com.fasterxml.jackson.databind.annotation.JsonSerialize;
+import com.fasterxml.jackson.annotation.ObjectIdGenerators;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
 
+@JsonIdentityInfo(generator=ObjectIdGenerators.IntSequenceGenerator.class, property="@id")
 public class TaskSummary implements HasId, Serializable {
 
     private static final long serialVersionUID = 4637850742127078158L;
@@ -58,14 +62,14 @@ public class TaskSummary implements HasId, Serializable {
     private final List children;
     private final LinkWithMetadata submittedByTask;
 
-    @JsonSerialize(include = JsonSerialize.Inclusion.NON_NULL)
+    @JsonInclude(Include.NON_NULL)
     private final LinkWithMetadata blockingTask;
-    @JsonSerialize(include = JsonSerialize.Inclusion.NON_NULL)
+    @JsonInclude(Include.NON_NULL)
     private final String blockingDetails;
 
     private final String detailedStatus;
 
-    @JsonSerialize(include = JsonSerialize.Inclusion.NON_NULL)
+    @JsonInclude(Include.NON_NULL)
     private final Map streams;
 
     private final Map links;

From c228e4966fc04bdd94825aace3879c28ef798557 Mon Sep 17 00:00:00 2001
From: Andrew Donald Kennedy 
Date: Wed, 29 Mar 2017 14:44:42 +0100
Subject: [PATCH 16/43] Minor tidy up of composite effectors

---
 .../brooklyn/core/effector/composite/ChoiceEffector.java | 3 ---
 .../core/effector/composite/ReplaceEffector.java         | 9 +--------
 .../core/effector/composite/SequenceEffector.java        | 1 -
 3 files changed, 1 insertion(+), 12 deletions(-)

diff --git a/core/src/main/java/org/apache/brooklyn/core/effector/composite/ChoiceEffector.java b/core/src/main/java/org/apache/brooklyn/core/effector/composite/ChoiceEffector.java
index c23ba1c1b9..2053fdfa86 100644
--- a/core/src/main/java/org/apache/brooklyn/core/effector/composite/ChoiceEffector.java
+++ b/core/src/main/java/org/apache/brooklyn/core/effector/composite/ChoiceEffector.java
@@ -18,8 +18,6 @@
  */
 package org.apache.brooklyn.core.effector.composite;
 
-import java.util.Collection;
-import java.util.List;
 import java.util.Map;
 
 import org.apache.brooklyn.api.effector.Effector;
@@ -37,7 +35,6 @@
 
 import com.google.common.annotations.Beta;
 import com.google.common.base.Preconditions;
-import com.google.common.collect.Lists;
 
 @Beta
 public class ChoiceEffector extends AbstractCompositeEffector {
diff --git a/core/src/main/java/org/apache/brooklyn/core/effector/composite/ReplaceEffector.java b/core/src/main/java/org/apache/brooklyn/core/effector/composite/ReplaceEffector.java
index d6c23bba9f..e28abb585e 100644
--- a/core/src/main/java/org/apache/brooklyn/core/effector/composite/ReplaceEffector.java
+++ b/core/src/main/java/org/apache/brooklyn/core/effector/composite/ReplaceEffector.java
@@ -52,11 +52,6 @@ public enum ReplaceAction {
 
     public static final String ORIGINAL = "original-";
 
-    public static final ConfigKey PREFIX = ConfigKeys.newStringConfigKey(
-            "prefix",
-            "Prefix for replaced original effector",
-            ORIGINAL);
-
     public static final ConfigKey ACTION = ConfigKeys.newConfigKey(
             ReplaceAction.class,
             "action",
@@ -87,9 +82,7 @@ public static EffectorBuilder newEffectorBuilder(ConfigBag params) {
     public void apply(EntityLocal entity) {
         Maybe> effectorMaybe = entity.getEntityType().getEffectorByName(effector.getName());
         if (effectorMaybe.isPresentAndNonNull()) {
-            // String prefix = config.get(PREFIX);
-            String prefix = ORIGINAL;
-            Effector original = Effectors.effector(effectorMaybe.get()).name(prefix + effector.getName()).build();
+            Effector original = Effectors.effector(effectorMaybe.get()).name(ORIGINAL + effector.getName()).build();
             ((EntityInternal) entity).getMutableEntityType().addEffector(original);
         }
         super.apply(entity);
diff --git a/core/src/main/java/org/apache/brooklyn/core/effector/composite/SequenceEffector.java b/core/src/main/java/org/apache/brooklyn/core/effector/composite/SequenceEffector.java
index 1dc4d1ea4c..a7bcd16bef 100644
--- a/core/src/main/java/org/apache/brooklyn/core/effector/composite/SequenceEffector.java
+++ b/core/src/main/java/org/apache/brooklyn/core/effector/composite/SequenceEffector.java
@@ -36,7 +36,6 @@
 import com.google.common.annotations.Beta;
 import com.google.common.base.Preconditions;
 import com.google.common.collect.ImmutableList;
-import com.google.common.collect.Lists;
 import com.google.common.reflect.TypeToken;
 
 @Beta

From 131d24f6922a9452a375d277553d8555a6f841bd Mon Sep 17 00:00:00 2001
From: Andrew Donald Kennedy 
Date: Wed, 29 Mar 2017 15:41:53 +0100
Subject: [PATCH 17/43] Better check of boolean values in policies

---
 .../brooklyn/policy/action/PeriodicEffectorPolicy.java      | 6 ++++--
 .../brooklyn/policy/action/ScheduledEffectorPolicy.java     | 3 ++-
 2 files changed, 6 insertions(+), 3 deletions(-)

diff --git a/policy/src/main/java/org/apache/brooklyn/policy/action/PeriodicEffectorPolicy.java b/policy/src/main/java/org/apache/brooklyn/policy/action/PeriodicEffectorPolicy.java
index b238ea29b7..044079fa08 100644
--- a/policy/src/main/java/org/apache/brooklyn/policy/action/PeriodicEffectorPolicy.java
+++ b/policy/src/main/java/org/apache/brooklyn/policy/action/PeriodicEffectorPolicy.java
@@ -29,6 +29,7 @@
 import org.apache.brooklyn.core.config.ConfigKeys;
 import org.apache.brooklyn.core.sensor.Sensors;
 import org.apache.brooklyn.util.collections.MutableMap;
+import org.apache.brooklyn.util.text.Strings;
 import org.apache.brooklyn.util.time.Duration;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -70,7 +71,7 @@ public PeriodicEffectorPolicy() {
 
     public PeriodicEffectorPolicy(Map props) {
         super(props);
-        Duration period = Preconditions.checkNotNull(config().get(PERIOD), "The period must be copnfigured for this policy");
+        Duration period = Preconditions.checkNotNull(config().get(PERIOD), "The period must be configured for this policy");
         delay = period.toMilliseconds();
     }
 
@@ -86,7 +87,8 @@ public void setEntity(final EntityLocal entity) {
         public void onEvent(SensorEvent event) {
             LOG.debug("{} got event {}", PeriodicEffectorPolicy.this, event);
             if (event.getSensor().equals(START_SCHEDULER)) {
-                if (Boolean.TRUE.equals(event.getValue())) {
+                Boolean start = Boolean.parseBoolean(Strings.toString(event.getValue()));
+                if (start) {
                     executor.scheduleWithFixedDelay(PeriodicEffectorPolicy.this, delay, delay, TimeUnit.MILLISECONDS);
                 }
             }
diff --git a/policy/src/main/java/org/apache/brooklyn/policy/action/ScheduledEffectorPolicy.java b/policy/src/main/java/org/apache/brooklyn/policy/action/ScheduledEffectorPolicy.java
index a4bd26505a..7dbd904180 100644
--- a/policy/src/main/java/org/apache/brooklyn/policy/action/ScheduledEffectorPolicy.java
+++ b/policy/src/main/java/org/apache/brooklyn/policy/action/ScheduledEffectorPolicy.java
@@ -113,7 +113,8 @@ public void onEvent(SensorEvent event) {
                     scheduleAt(time);
                 }
             } else if (event.getSensor().equals(INVOKE_IMMEDIATELY)) {
-                if (Boolean.TRUE.equals(event.getValue())) {
+                Boolean invoke = Boolean.parseBoolean(Strings.toString(event.getValue()));
+                if (invoke) {
                     executor.submit(ScheduledEffectorPolicy.this);
                 }
             }

From 35463eb4a4df383d36e3723c8b972af16ce1fc16 Mon Sep 17 00:00:00 2001
From: Andrew Donald Kennedy 
Date: Wed, 29 Mar 2017 15:49:09 +0100
Subject: [PATCH 18/43] Fix bug where wrong original effrector is called by
 replace

---
 .../brooklyn/core/effector/composite/ReplaceEffector.java     | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/core/src/main/java/org/apache/brooklyn/core/effector/composite/ReplaceEffector.java b/core/src/main/java/org/apache/brooklyn/core/effector/composite/ReplaceEffector.java
index e28abb585e..d956e17a18 100644
--- a/core/src/main/java/org/apache/brooklyn/core/effector/composite/ReplaceEffector.java
+++ b/core/src/main/java/org/apache/brooklyn/core/effector/composite/ReplaceEffector.java
@@ -112,11 +112,11 @@ public Object call(final ConfigBag params) {
             }
 
             if (action == ReplaceAction.POST) {
-                invokeEffectorNamed(targetEntity, ORIGINAL + effectorName, params);
+                invokeEffectorNamed(targetEntity, ORIGINAL + effector.getName(), params);
             }
             Object result = invokeEffectorNamed(targetEntity, effectorName, params);
             if (action == ReplaceAction.PRE) {
-                invokeEffectorNamed(targetEntity, ORIGINAL + effectorName, params);
+                invokeEffectorNamed(targetEntity, ORIGINAL + effector.getName(), params);
             }
 
             return result;

From 14a714da44fbb2afa895da4c94a4dd44433a85ac Mon Sep 17 00:00:00 2001
From: Andrew Donald Kennedy 
Date: Thu, 30 Mar 2017 11:52:10 +0100
Subject: [PATCH 19/43] Use JsonIdentityInfo annotation on all HasId domain
 objects

---
 .../src/main/java/org/apache/brooklyn/rest/domain/HasId.java  | 4 ++++
 .../java/org/apache/brooklyn/rest/domain/TaskSummary.java     | 3 ---
 2 files changed, 4 insertions(+), 3 deletions(-)

diff --git a/rest/rest-api/src/main/java/org/apache/brooklyn/rest/domain/HasId.java b/rest/rest-api/src/main/java/org/apache/brooklyn/rest/domain/HasId.java
index 69b4a9c34c..a67f6c53db 100644
--- a/rest/rest-api/src/main/java/org/apache/brooklyn/rest/domain/HasId.java
+++ b/rest/rest-api/src/main/java/org/apache/brooklyn/rest/domain/HasId.java
@@ -18,7 +18,11 @@
  */
 package org.apache.brooklyn.rest.domain;
 
+import com.fasterxml.jackson.annotation.JsonIdentityInfo;
+import com.fasterxml.jackson.annotation.ObjectIdGenerators;
+
 /** Marker interface for summary objects with an id field */
+@JsonIdentityInfo(generator=ObjectIdGenerators.IntSequenceGenerator.class, property="@id")
 public interface HasId {
 
     public String getId();
diff --git a/rest/rest-api/src/main/java/org/apache/brooklyn/rest/domain/TaskSummary.java b/rest/rest-api/src/main/java/org/apache/brooklyn/rest/domain/TaskSummary.java
index 08d8cec215..e3b0e9509a 100644
--- a/rest/rest-api/src/main/java/org/apache/brooklyn/rest/domain/TaskSummary.java
+++ b/rest/rest-api/src/main/java/org/apache/brooklyn/rest/domain/TaskSummary.java
@@ -29,16 +29,13 @@
 
 import org.apache.brooklyn.util.collections.Jsonya;
 
-import com.fasterxml.jackson.annotation.JsonIdentityInfo;
 import com.fasterxml.jackson.annotation.JsonIgnore;
 import com.fasterxml.jackson.annotation.JsonInclude;
 import com.fasterxml.jackson.annotation.JsonInclude.Include;
 import com.fasterxml.jackson.annotation.JsonProperty;
-import com.fasterxml.jackson.annotation.ObjectIdGenerators;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
 
-@JsonIdentityInfo(generator=ObjectIdGenerators.IntSequenceGenerator.class, property="@id")
 public class TaskSummary implements HasId, Serializable {
 
     private static final long serialVersionUID = 4637850742127078158L;

From 1fba768eebca235be6523062a713880579e3a086 Mon Sep 17 00:00:00 2001
From: Andrew Donald Kennedy 
Date: Thu, 30 Mar 2017 11:54:41 +0100
Subject: [PATCH 20/43] Add debug logging and an atomic state check to
 scheduler policies

---
 .../AbstractScheduledEffectorPolicy.java       | 18 ++++++++++++------
 .../policy/action/PeriodicEffectorPolicy.java  | 10 +++++++---
 .../policy/action/ScheduledEffectorPolicy.java |  2 ++
 3 files changed, 21 insertions(+), 9 deletions(-)

diff --git a/policy/src/main/java/org/apache/brooklyn/policy/action/AbstractScheduledEffectorPolicy.java b/policy/src/main/java/org/apache/brooklyn/policy/action/AbstractScheduledEffectorPolicy.java
index ba808d0d50..813831bb9d 100644
--- a/policy/src/main/java/org/apache/brooklyn/policy/action/AbstractScheduledEffectorPolicy.java
+++ b/policy/src/main/java/org/apache/brooklyn/policy/action/AbstractScheduledEffectorPolicy.java
@@ -55,6 +55,7 @@ public abstract class AbstractScheduledEffectorPolicy extends AbstractPolicy imp
             .defaultValue(ImmutableMap.of())
             .build();
 
+    protected final Effector effector;
     protected final ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor();
 
     public AbstractScheduledEffectorPolicy() {
@@ -63,6 +64,8 @@ public AbstractScheduledEffectorPolicy() {
 
     public AbstractScheduledEffectorPolicy(Map props) {
         super(props);
+
+        effector = getEffector();
     }
 
     @Override
@@ -71,17 +74,20 @@ public void destroy(){
         executor.shutdownNow();
     }
 
-    @Override
-    public void run() {
+    protected Effector getEffector() {
         String effectorName = config().get(EFFECTOR);
-        Map args = EntityInitializers.resolve(config().getBag(), EFFECTOR_ARGUMENTS);
-
         Maybe> effector = entity.getEntityType().getEffectorByName(effectorName);
         if (effector.isAbsent()) {
             throw new IllegalStateException("Cannot find effector " + effectorName);
         }
+        return effector.get();
+    }
+
+    @Override
+    public void run() {
+        Map args = EntityInitializers.resolve(config().getBag(), EFFECTOR_ARGUMENTS);
 
-        LOG.debug("{} invoking effector on {}, effector={}, args={}", new Object[] { this, entity, effectorName, args });
-        entity.invoke(effector.get(), args).getUnchecked();
+        LOG.debug("{} invoking effector on {}, effector={}, args={}", new Object[] { this, entity, effector.getName(), args });
+        entity.invoke(effector, args).getUnchecked();
     }
 }
diff --git a/policy/src/main/java/org/apache/brooklyn/policy/action/PeriodicEffectorPolicy.java b/policy/src/main/java/org/apache/brooklyn/policy/action/PeriodicEffectorPolicy.java
index 044079fa08..8f4adb90f1 100644
--- a/policy/src/main/java/org/apache/brooklyn/policy/action/PeriodicEffectorPolicy.java
+++ b/policy/src/main/java/org/apache/brooklyn/policy/action/PeriodicEffectorPolicy.java
@@ -20,6 +20,7 @@
 
 import java.util.Map;
 import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
 
 import org.apache.brooklyn.api.entity.EntityLocal;
 import org.apache.brooklyn.api.sensor.AttributeSensor;
@@ -31,12 +32,12 @@
 import org.apache.brooklyn.util.collections.MutableMap;
 import org.apache.brooklyn.util.text.Strings;
 import org.apache.brooklyn.util.time.Duration;
+import org.apache.brooklyn.util.time.DurationPredicates;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 import com.google.common.annotations.Beta;
 import com.google.common.base.Preconditions;
-import com.google.common.base.Predicates;
 
 /**
  * 
{@code
@@ -57,13 +58,14 @@ public class PeriodicEffectorPolicy extends AbstractScheduledEffectorPolicy {
     public static final ConfigKey PERIOD = ConfigKeys.builder(Duration.class)
             .name("period")
             .description("The duration between executions of this policy")
-            .constraint(Predicates.notNull())
+            .constraint(DurationPredicates.positive())
             .defaultValue(Duration.hours(1))
             .build();
 
     public static final AttributeSensor START_SCHEDULER = Sensors.newBooleanSensor("scheduler.start", "Start the periodic effector execution after this becomes true");
 
     protected long delay;
+    protected AtomicBoolean running = new AtomicBoolean(false);
 
     public PeriodicEffectorPolicy() {
         this(MutableMap.of());
@@ -71,6 +73,7 @@ public PeriodicEffectorPolicy() {
 
     public PeriodicEffectorPolicy(Map props) {
         super(props);
+
         Duration period = Preconditions.checkNotNull(config().get(PERIOD), "The period must be configured for this policy");
         delay = period.toMilliseconds();
     }
@@ -88,7 +91,8 @@ public void onEvent(SensorEvent event) {
             LOG.debug("{} got event {}", PeriodicEffectorPolicy.this, event);
             if (event.getSensor().equals(START_SCHEDULER)) {
                 Boolean start = Boolean.parseBoolean(Strings.toString(event.getValue()));
-                if (start) {
+                if (start && running.compareAndSet(false, true)) {
+                    LOG.debug("{} scheduling {} in {}ms", new Object[] { PeriodicEffectorPolicy.this, effector.getName(), delay });
                     executor.scheduleWithFixedDelay(PeriodicEffectorPolicy.this, delay, delay, TimeUnit.MILLISECONDS);
                 }
             }
diff --git a/policy/src/main/java/org/apache/brooklyn/policy/action/ScheduledEffectorPolicy.java b/policy/src/main/java/org/apache/brooklyn/policy/action/ScheduledEffectorPolicy.java
index 7dbd904180..5a77ac7381 100644
--- a/policy/src/main/java/org/apache/brooklyn/policy/action/ScheduledEffectorPolicy.java
+++ b/policy/src/main/java/org/apache/brooklyn/policy/action/ScheduledEffectorPolicy.java
@@ -74,6 +74,7 @@ public ScheduledEffectorPolicy() {
 
     public ScheduledEffectorPolicy(Map props) {
         super(props);
+
         String time = config().get(TIME);
         if (Strings.isNonBlank(time)) {
             scheduleAt(time);
@@ -96,6 +97,7 @@ protected void scheduleAt(String time) {
                 throw new IllegalStateException("The time provided must be in the future: " + FORMATTER.format(time));
             }
             long difference = Math.max(0, when.getTime() - now.getTime());
+            LOG.debug("{} scheduling {} at {} (in {}ms)", new Object[] { this, effector.getName(), time, difference });
             executor.schedule(this, difference, TimeUnit.MILLISECONDS);
         } catch (ParseException e) {
             LOG.warn("The time must be formatted as " + TIME_FORMAT + " for this policy", e);

From 3033b4ab8d1abd2ec1d7aed2b3de0c6289efafff Mon Sep 17 00:00:00 2001
From: Andrew Donald Kennedy 
Date: Thu, 30 Mar 2017 13:56:53 +0100
Subject: [PATCH 21/43] Add start time config for periodic policy

---
 .../AbstractScheduledEffectorPolicy.java      | 20 +++++++++++++++++--
 .../policy/action/PeriodicEffectorPolicy.java | 20 ++++++++++++++++++-
 .../action/ScheduledEffectorPolicy.java       |  9 ---------
 3 files changed, 37 insertions(+), 12 deletions(-)

diff --git a/policy/src/main/java/org/apache/brooklyn/policy/action/AbstractScheduledEffectorPolicy.java b/policy/src/main/java/org/apache/brooklyn/policy/action/AbstractScheduledEffectorPolicy.java
index 813831bb9d..6e3e8ec25a 100644
--- a/policy/src/main/java/org/apache/brooklyn/policy/action/AbstractScheduledEffectorPolicy.java
+++ b/policy/src/main/java/org/apache/brooklyn/policy/action/AbstractScheduledEffectorPolicy.java
@@ -18,14 +18,18 @@
  */
 package org.apache.brooklyn.policy.action;
 
+import java.text.DateFormat;
+import java.text.SimpleDateFormat;
 import java.util.Map;
 import java.util.concurrent.Executors;
 import java.util.concurrent.ScheduledExecutorService;
 
 import org.apache.brooklyn.api.effector.Effector;
+import org.apache.brooklyn.api.entity.EntityLocal;
 import org.apache.brooklyn.config.ConfigKey;
 import org.apache.brooklyn.core.config.ConfigKeys;
 import org.apache.brooklyn.core.entity.EntityInitializers;
+import org.apache.brooklyn.core.entity.EntityInternal;
 import org.apache.brooklyn.core.policy.AbstractPolicy;
 import org.apache.brooklyn.util.collections.MutableMap;
 import org.apache.brooklyn.util.guava.Maybe;
@@ -42,6 +46,10 @@ public abstract class AbstractScheduledEffectorPolicy extends AbstractPolicy imp
 
     private static final Logger LOG = LoggerFactory.getLogger(AbstractScheduledEffectorPolicy.class);
 
+    public static final String TIME_FORMAT = "yyyy-MM-dd HH:mm:ss";
+
+    protected static DateFormat FORMATTER = new SimpleDateFormat(TIME_FORMAT);
+
     public static final ConfigKey EFFECTOR = ConfigKeys.builder(String.class)
             .name("effector")
             .description("The effector to be executed by this policy")
@@ -55,8 +63,13 @@ public abstract class AbstractScheduledEffectorPolicy extends AbstractPolicy imp
             .defaultValue(ImmutableMap.of())
             .build();
 
-    protected final Effector effector;
-    protected final ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor();
+    public static final ConfigKey TIME = ConfigKeys.builder(String.class)
+            .name("time")
+            .description("An optional time when this policy should be first executed")
+            .build();
+
+    protected Effector effector;
+    protected ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor();
 
     public AbstractScheduledEffectorPolicy() {
         this(MutableMap.of());
@@ -64,7 +77,10 @@ public AbstractScheduledEffectorPolicy() {
 
     public AbstractScheduledEffectorPolicy(Map props) {
         super(props);
+    }
 
+    public void setEntity(EntityLocal entity) {
+        super.setEntity(entity);
         effector = getEffector();
     }
 
diff --git a/policy/src/main/java/org/apache/brooklyn/policy/action/PeriodicEffectorPolicy.java b/policy/src/main/java/org/apache/brooklyn/policy/action/PeriodicEffectorPolicy.java
index 8f4adb90f1..c57296ab03 100644
--- a/policy/src/main/java/org/apache/brooklyn/policy/action/PeriodicEffectorPolicy.java
+++ b/policy/src/main/java/org/apache/brooklyn/policy/action/PeriodicEffectorPolicy.java
@@ -18,6 +18,8 @@
  */
 package org.apache.brooklyn.policy.action;
 
+import java.text.ParseException;
+import java.util.Date;
 import java.util.Map;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicBoolean;
@@ -30,6 +32,7 @@
 import org.apache.brooklyn.core.config.ConfigKeys;
 import org.apache.brooklyn.core.sensor.Sensors;
 import org.apache.brooklyn.util.collections.MutableMap;
+import org.apache.brooklyn.util.exceptions.Exceptions;
 import org.apache.brooklyn.util.text.Strings;
 import org.apache.brooklyn.util.time.Duration;
 import org.apache.brooklyn.util.time.DurationPredicates;
@@ -92,8 +95,23 @@ public void onEvent(SensorEvent event) {
             if (event.getSensor().equals(START_SCHEDULER)) {
                 Boolean start = Boolean.parseBoolean(Strings.toString(event.getValue()));
                 if (start && running.compareAndSet(false, true)) {
+                    long wait = delay;
+                    String time = config().get(TIME);
+                    if (Strings.isNonBlank(time)) {
+                        try {
+                            Date when = FORMATTER.parse(time);
+                            Date now = new Date();
+                            if (when.before(now)) {
+                                throw new IllegalStateException("The time provided must be in the future: " + FORMATTER.format(time));
+                            }
+                            wait = Math.max(0, when.getTime() - now.getTime());
+                        } catch (ParseException e) {
+                            LOG.warn("The time must be formatted as " + TIME_FORMAT + " for this policy", e);
+                            Exceptions.propagate(e);
+                        }
+                    }
                     LOG.debug("{} scheduling {} in {}ms", new Object[] { PeriodicEffectorPolicy.this, effector.getName(), delay });
-                    executor.scheduleWithFixedDelay(PeriodicEffectorPolicy.this, delay, delay, TimeUnit.MILLISECONDS);
+                    executor.scheduleWithFixedDelay(PeriodicEffectorPolicy.this, wait, delay, TimeUnit.MILLISECONDS);
                 }
             }
         }
diff --git a/policy/src/main/java/org/apache/brooklyn/policy/action/ScheduledEffectorPolicy.java b/policy/src/main/java/org/apache/brooklyn/policy/action/ScheduledEffectorPolicy.java
index 5a77ac7381..721d968dd6 100644
--- a/policy/src/main/java/org/apache/brooklyn/policy/action/ScheduledEffectorPolicy.java
+++ b/policy/src/main/java/org/apache/brooklyn/policy/action/ScheduledEffectorPolicy.java
@@ -56,15 +56,6 @@ public class ScheduledEffectorPolicy extends AbstractScheduledEffectorPolicy {
 
     private static final Logger LOG = LoggerFactory.getLogger(ScheduledEffectorPolicy.class);
 
-    public static final String TIME_FORMAT = "yyyy-MM-dd HH:mm:ss";
-
-    protected static final DateFormat FORMATTER = new SimpleDateFormat(TIME_FORMAT);
-
-    public static final ConfigKey TIME = ConfigKeys.builder(String.class)
-            .name("time")
-            .description("An optional time when this policy should be first executed")
-            .build();
-
     public static final AttributeSensor INVOKE_IMMEDIATELY = Sensors.newBooleanSensor("scheduler.invoke.now", "Invoke the configured effector immediately when this becomes true");
     public static final AttributeSensor INVOKE_AT = Sensors.newStringSensor("scheduler.invoke.at", "Invoke the configured effector at this time");
 

From 0c735aed88f492f16a1156c4cf6d07832396973a Mon Sep 17 00:00:00 2001
From: Andrew Donald Kennedy 
Date: Thu, 30 Mar 2017 15:44:11 +0100
Subject: [PATCH 22/43] Add policy event handler mutex and fix sensor check

---
 .../AbstractScheduledEffectorPolicy.java      |  1 +
 .../policy/action/PeriodicEffectorPolicy.java | 38 ++++++++++---------
 .../action/ScheduledEffectorPolicy.java       | 21 +++++-----
 3 files changed, 33 insertions(+), 27 deletions(-)

diff --git a/policy/src/main/java/org/apache/brooklyn/policy/action/AbstractScheduledEffectorPolicy.java b/policy/src/main/java/org/apache/brooklyn/policy/action/AbstractScheduledEffectorPolicy.java
index 6e3e8ec25a..bc1386770e 100644
--- a/policy/src/main/java/org/apache/brooklyn/policy/action/AbstractScheduledEffectorPolicy.java
+++ b/policy/src/main/java/org/apache/brooklyn/policy/action/AbstractScheduledEffectorPolicy.java
@@ -70,6 +70,7 @@ public abstract class AbstractScheduledEffectorPolicy extends AbstractPolicy imp
 
     protected Effector effector;
     protected ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor();
+    protected Object mutex = new Object[0];
 
     public AbstractScheduledEffectorPolicy() {
         this(MutableMap.of());
diff --git a/policy/src/main/java/org/apache/brooklyn/policy/action/PeriodicEffectorPolicy.java b/policy/src/main/java/org/apache/brooklyn/policy/action/PeriodicEffectorPolicy.java
index c57296ab03..25a7b01e65 100644
--- a/policy/src/main/java/org/apache/brooklyn/policy/action/PeriodicEffectorPolicy.java
+++ b/policy/src/main/java/org/apache/brooklyn/policy/action/PeriodicEffectorPolicy.java
@@ -91,27 +91,29 @@ public void setEntity(final EntityLocal entity) {
     private final SensorEventListener handler = new SensorEventListener() {
         @Override
         public void onEvent(SensorEvent event) {
-            LOG.debug("{} got event {}", PeriodicEffectorPolicy.this, event);
-            if (event.getSensor().equals(START_SCHEDULER)) {
-                Boolean start = Boolean.parseBoolean(Strings.toString(event.getValue()));
-                if (start && running.compareAndSet(false, true)) {
-                    long wait = delay;
-                    String time = config().get(TIME);
-                    if (Strings.isNonBlank(time)) {
-                        try {
-                            Date when = FORMATTER.parse(time);
-                            Date now = new Date();
-                            if (when.before(now)) {
-                                throw new IllegalStateException("The time provided must be in the future: " + FORMATTER.format(time));
+            synchronized (mutex) {
+                LOG.debug("{} got event {}", PeriodicEffectorPolicy.this, event);
+                if (event.getSensor().getName().equals(START_SCHEDULER.getName())) {
+                    Boolean start = Boolean.parseBoolean(Strings.toString(event.getValue()));
+                    if (start && running.compareAndSet(false, true)) {
+                        long wait = delay;
+                        String time = config().get(TIME);
+                        if (Strings.isNonBlank(time)) {
+                            try {
+                                Date when = FORMATTER.parse(time);
+                                Date now = new Date();
+                                if (when.before(now)) {
+                                    throw new IllegalStateException("The time provided must be in the future: " + FORMATTER.format(time));
+                                }
+                                wait = Math.max(0, when.getTime() - now.getTime());
+                            } catch (ParseException e) {
+                                LOG.warn("The time must be formatted as " + TIME_FORMAT + " for this policy", e);
+                                Exceptions.propagate(e);
                             }
-                            wait = Math.max(0, when.getTime() - now.getTime());
-                        } catch (ParseException e) {
-                            LOG.warn("The time must be formatted as " + TIME_FORMAT + " for this policy", e);
-                            Exceptions.propagate(e);
                         }
+                        LOG.debug("{} scheduling {} in {}ms", new Object[] { PeriodicEffectorPolicy.this, effector.getName(), delay });
+                        executor.scheduleWithFixedDelay(PeriodicEffectorPolicy.this, wait, delay, TimeUnit.MILLISECONDS);
                     }
-                    LOG.debug("{} scheduling {} in {}ms", new Object[] { PeriodicEffectorPolicy.this, effector.getName(), delay });
-                    executor.scheduleWithFixedDelay(PeriodicEffectorPolicy.this, wait, delay, TimeUnit.MILLISECONDS);
                 }
             }
         }
diff --git a/policy/src/main/java/org/apache/brooklyn/policy/action/ScheduledEffectorPolicy.java b/policy/src/main/java/org/apache/brooklyn/policy/action/ScheduledEffectorPolicy.java
index 721d968dd6..e87a759064 100644
--- a/policy/src/main/java/org/apache/brooklyn/policy/action/ScheduledEffectorPolicy.java
+++ b/policy/src/main/java/org/apache/brooklyn/policy/action/ScheduledEffectorPolicy.java
@@ -99,16 +99,19 @@ protected void scheduleAt(String time) {
     private final SensorEventListener handler = new SensorEventListener() {
         @Override
         public void onEvent(SensorEvent event) {
-            LOG.debug("{} got event {}", ScheduledEffectorPolicy.this, event);
-            if (event.getSensor().equals(INVOKE_AT)) {
-                String time = (String) event.getValue();
-                if (Strings.isNonBlank(time)) {
-                    scheduleAt(time);
+            synchronized (mutex) {
+                LOG.debug("{} got event {}", ScheduledEffectorPolicy.this, event);
+                if (event.getSensor().getName().equals(INVOKE_AT.getName())) {
+                    String time = (String) event.getValue();
+                    if (Strings.isNonBlank(time)) {
+                        scheduleAt(time);
+                    }
                 }
-            } else if (event.getSensor().equals(INVOKE_IMMEDIATELY)) {
-                Boolean invoke = Boolean.parseBoolean(Strings.toString(event.getValue()));
-                if (invoke) {
-                    executor.submit(ScheduledEffectorPolicy.this);
+                if (event.getSensor().getName().equals(INVOKE_IMMEDIATELY.getName())) {
+                    Boolean invoke = Boolean.parseBoolean(Strings.toString(event.getValue()));
+                    if (invoke) {
+                        executor.submit(ScheduledEffectorPolicy.this);
+                    }
                 }
             }
         }

From ec1add2ef40ef9020ca451bd086b91b2eba855c3 Mon Sep 17 00:00:00 2001
From: Andrew Donald Kennedy 
Date: Thu, 30 Mar 2017 16:44:20 +0100
Subject: [PATCH 23/43] Display delay as time string in periodic policy

---
 .../brooklyn/policy/action/PeriodicEffectorPolicy.java       | 5 ++++-
 1 file changed, 4 insertions(+), 1 deletion(-)

diff --git a/policy/src/main/java/org/apache/brooklyn/policy/action/PeriodicEffectorPolicy.java b/policy/src/main/java/org/apache/brooklyn/policy/action/PeriodicEffectorPolicy.java
index 25a7b01e65..ba989ab491 100644
--- a/policy/src/main/java/org/apache/brooklyn/policy/action/PeriodicEffectorPolicy.java
+++ b/policy/src/main/java/org/apache/brooklyn/policy/action/PeriodicEffectorPolicy.java
@@ -36,6 +36,7 @@
 import org.apache.brooklyn.util.text.Strings;
 import org.apache.brooklyn.util.time.Duration;
 import org.apache.brooklyn.util.time.DurationPredicates;
+import org.apache.brooklyn.util.time.Time;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -111,7 +112,9 @@ public void onEvent(SensorEvent event) {
                                 Exceptions.propagate(e);
                             }
                         }
-                        LOG.debug("{} scheduling {} in {}ms", new Object[] { PeriodicEffectorPolicy.this, effector.getName(), delay });
+
+                        LOG.debug("{} scheduling {} every {} in {}", new Object[] { PeriodicEffectorPolicy.this, effector.getName(),
+                                Time.fromLongToTimeStringExact().apply(delay), Time.fromLongToTimeStringExact().apply(wait) });
                         executor.scheduleWithFixedDelay(PeriodicEffectorPolicy.this, wait, delay, TimeUnit.MILLISECONDS);
                     }
                 }

From e373b597748379eeb423216a1f2d343d3b2952a0 Mon Sep 17 00:00:00 2001
From: Andrew Donald Kennedy 
Date: Thu, 30 Mar 2017 18:04:33 +0100
Subject: [PATCH 24/43] Log return values from composite effectors

---
 .../effector/composite/ChoiceEffector.java    | 51 ++++++++-----------
 .../effector/composite/ComposeEffector.java   |  1 +
 .../core/effector/composite/LoopEffector.java |  1 +
 .../effector/composite/ReplaceEffector.java   |  1 +
 .../effector/composite/SequenceEffector.java  |  1 +
 .../effector/composite/TransformEffector.java |  1 +
 6 files changed, 25 insertions(+), 31 deletions(-)

diff --git a/core/src/main/java/org/apache/brooklyn/core/effector/composite/ChoiceEffector.java b/core/src/main/java/org/apache/brooklyn/core/effector/composite/ChoiceEffector.java
index 2053fdfa86..2d61872a16 100644
--- a/core/src/main/java/org/apache/brooklyn/core/effector/composite/ChoiceEffector.java
+++ b/core/src/main/java/org/apache/brooklyn/core/effector/composite/ChoiceEffector.java
@@ -101,38 +101,27 @@ public Object call(final ConfigBag params) {
             params.putStringKey(choiceInputArgument, inputObject);
 
             Object output = invokeEffectorNamed(choiceTargetEntity, choiceEffectorName, params);
-            Boolean result = Boolean.parseBoolean(Strings.toString(output));
-
-            if (result) {
-                Object successDetails = EntityInitializers.resolve(config, SUCCESS);
-                String successEffectorName = getEffectorName(successDetails);
-                String successInputArgument = getInputArgument(successDetails);
-                Entity successTargetEntity = getTargetEntity(successDetails);
-                LOG.debug("{} executing {}({}) on {}", new Object[] { this, successEffectorName, successInputArgument, successTargetEntity });
-
-                if (successInputArgument == null) {
-                    throw new IllegalArgumentException("Input is not set for success effector: " + successDetails);
-                }
-                params.putStringKey(successInputArgument, inputObject);
-
-                return invokeEffectorNamed(successTargetEntity, successEffectorName, params);
-            } else {
-                Object failureDetails = EntityInitializers.resolve(config, FAILURE);
-                if (failureDetails == null) {
-                    return null;
-                }
-                String failureEffectorName = getEffectorName(failureDetails);
-                String failureInputArgument = getInputArgument(failureDetails);
-                Entity failureTargetEntity = getTargetEntity(failureDetails);
-                LOG.debug("{} executing {}({}) on {}", new Object[] { this, failureEffectorName, failureInputArgument, failureTargetEntity });
-
-                if (failureInputArgument == null) {
-                    throw new IllegalArgumentException("Input is not set for success effector: " + failureDetails);
-                }
-                params.putStringKey(failureInputArgument, inputObject);
-
-                return invokeEffectorNamed(failureTargetEntity, failureEffectorName, params);
+            Boolean success = Boolean.parseBoolean(Strings.toString(output));
+
+            Object effectorDetails = EntityInitializers.resolve(config, success ? SUCCESS : FAILURE);
+
+            if (!success && effectorDetails == null) {
+                return null;
+            }
+
+            String effectorName = getEffectorName(effectorDetails);
+            String inputArgument = getInputArgument(effectorDetails);
+            Entity targetEntity = getTargetEntity(effectorDetails);
+            LOG.debug("{} executing {}({}) on {}", new Object[] { this, effectorName, inputArgument, targetEntity });
+
+            if (inputArgument == null) {
+                throw new IllegalArgumentException("Input is not set for effector: " + effectorDetails);
             }
+            params.putStringKey(inputArgument, inputObject);
+            Object result = invokeEffectorNamed(targetEntity, effectorName, params);
+
+            LOG.debug("{} effector {} returned {}", new Object[] { this, effector.getName(), result });
+            return result;
         }
     }
 
diff --git a/core/src/main/java/org/apache/brooklyn/core/effector/composite/ComposeEffector.java b/core/src/main/java/org/apache/brooklyn/core/effector/composite/ComposeEffector.java
index 4ffd89265c..8d02c933c1 100644
--- a/core/src/main/java/org/apache/brooklyn/core/effector/composite/ComposeEffector.java
+++ b/core/src/main/java/org/apache/brooklyn/core/effector/composite/ComposeEffector.java
@@ -103,6 +103,7 @@ public Object call(final ConfigBag params) {
                 result = invokeEffectorNamed(targetEntity, effectorName, params);
             }
 
+            LOG.debug("{} effector {} returned {}", new Object[] { this, effector.getName(), result });
             return result;
         }
     }
diff --git a/core/src/main/java/org/apache/brooklyn/core/effector/composite/LoopEffector.java b/core/src/main/java/org/apache/brooklyn/core/effector/composite/LoopEffector.java
index f011daba21..77f8213a48 100644
--- a/core/src/main/java/org/apache/brooklyn/core/effector/composite/LoopEffector.java
+++ b/core/src/main/java/org/apache/brooklyn/core/effector/composite/LoopEffector.java
@@ -102,6 +102,7 @@ public List call(final ConfigBag params) {
                 result.add(invokeEffectorNamed(targetEntity, effectorName, params));
             }
 
+            LOG.debug("{} effector {} returned {}", new Object[] { this, effector.getName(), result });
             return result;
         }
     }
diff --git a/core/src/main/java/org/apache/brooklyn/core/effector/composite/ReplaceEffector.java b/core/src/main/java/org/apache/brooklyn/core/effector/composite/ReplaceEffector.java
index d956e17a18..10522ada6b 100644
--- a/core/src/main/java/org/apache/brooklyn/core/effector/composite/ReplaceEffector.java
+++ b/core/src/main/java/org/apache/brooklyn/core/effector/composite/ReplaceEffector.java
@@ -119,6 +119,7 @@ public Object call(final ConfigBag params) {
                 invokeEffectorNamed(targetEntity, ORIGINAL + effector.getName(), params);
             }
 
+            LOG.debug("{} effector {} returned {}", new Object[] { this, effector.getName(), result });
             return result;
         }
     }
diff --git a/core/src/main/java/org/apache/brooklyn/core/effector/composite/SequenceEffector.java b/core/src/main/java/org/apache/brooklyn/core/effector/composite/SequenceEffector.java
index a7bcd16bef..9c43f82053 100644
--- a/core/src/main/java/org/apache/brooklyn/core/effector/composite/SequenceEffector.java
+++ b/core/src/main/java/org/apache/brooklyn/core/effector/composite/SequenceEffector.java
@@ -93,6 +93,7 @@ public Object call(final ConfigBag params) {
                 result = invokeEffectorNamed(targetEntity, effectorName, params);
             }
 
+            LOG.debug("{} effector {} returned {}", new Object[] { this, effector.getName(), result });
             return result;
         }
     }
diff --git a/core/src/main/java/org/apache/brooklyn/core/effector/composite/TransformEffector.java b/core/src/main/java/org/apache/brooklyn/core/effector/composite/TransformEffector.java
index 5ce814d3f0..630118053d 100644
--- a/core/src/main/java/org/apache/brooklyn/core/effector/composite/TransformEffector.java
+++ b/core/src/main/java/org/apache/brooklyn/core/effector/composite/TransformEffector.java
@@ -83,6 +83,7 @@ public Object call(final ConfigBag params) {
             Object value = params.getStringKey(input);
             Object result = function.apply(value);
 
+            LOG.debug("{} effector {} returned {}", new Object[] { this, effector.getName(), result });
             return result;
         }
     }

From fc37f152d617666bcb69814f8e521fcbdb5d96b9 Mon Sep 17 00:00:00 2001
From: Andrew Donald Kennedy 
Date: Fri, 31 Mar 2017 21:20:21 +0100
Subject: [PATCH 25/43] Update JSON serialisation annotations

---
 .../util/core/json/BrooklynObjectsJsonMapper.java  |  2 +-
 .../brooklyn/rest/domain/EffectorSummary.java      |  4 +++-
 .../apache/brooklyn/rest/domain/EntityDetail.java  | 14 ++++++++------
 .../apache/brooklyn/rest/domain/EntitySpec.java    |  6 +++---
 .../apache/brooklyn/rest/domain/EntitySummary.java | 11 ++++++-----
 .../org/apache/brooklyn/rest/domain/HasId.java     |  4 ----
 .../brooklyn/rest/domain/PolicyConfigSummary.java  |  3 ++-
 .../rest/domain/ScriptExecutionSummary.java        | 11 ++++++-----
 8 files changed, 29 insertions(+), 26 deletions(-)

diff --git a/core/src/main/java/org/apache/brooklyn/util/core/json/BrooklynObjectsJsonMapper.java b/core/src/main/java/org/apache/brooklyn/util/core/json/BrooklynObjectsJsonMapper.java
index 7e96457769..8b289425d2 100644
--- a/core/src/main/java/org/apache/brooklyn/util/core/json/BrooklynObjectsJsonMapper.java
+++ b/core/src/main/java/org/apache/brooklyn/util/core/json/BrooklynObjectsJsonMapper.java
@@ -28,7 +28,7 @@ public static ObjectMapper newMapper(ManagementContext mgmt) {
 
         ObjectMapper mapper = new ObjectMapper();
         mapper.setSerializerProvider(sp);
-        mapper.setVisibilityChecker(new PossiblyStrictPreferringFieldsVisibilityChecker());
+        mapper.setVisibility(new PossiblyStrictPreferringFieldsVisibilityChecker());
 
         SimpleModule mapperModule = new SimpleModule("Brooklyn", new Version(0, 0, 0, "ignored", null, null));
 
diff --git a/rest/rest-api/src/main/java/org/apache/brooklyn/rest/domain/EffectorSummary.java b/rest/rest-api/src/main/java/org/apache/brooklyn/rest/domain/EffectorSummary.java
index 3e09c73f3f..9227144604 100644
--- a/rest/rest-api/src/main/java/org/apache/brooklyn/rest/domain/EffectorSummary.java
+++ b/rest/rest-api/src/main/java/org/apache/brooklyn/rest/domain/EffectorSummary.java
@@ -23,6 +23,8 @@
 import java.util.Map;
 import java.util.Set;
 
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.annotation.JsonInclude.Include;
 import com.fasterxml.jackson.annotation.JsonProperty;
 import com.fasterxml.jackson.databind.annotation.JsonSerialize;
 import com.google.common.base.MoreObjects;
@@ -38,7 +40,7 @@ public static class ParameterSummary implements HasName, Serializable {
 
         private final String name;
         private final String type;
-        @JsonSerialize(include = JsonSerialize.Inclusion.NON_NULL)
+        @JsonInclude(Include.NON_NULL)
         private final String description;
         private final T defaultValue;
         private final boolean shouldSanitize;
diff --git a/rest/rest-api/src/main/java/org/apache/brooklyn/rest/domain/EntityDetail.java b/rest/rest-api/src/main/java/org/apache/brooklyn/rest/domain/EntityDetail.java
index 567c40bda4..b2a22f7f52 100644
--- a/rest/rest-api/src/main/java/org/apache/brooklyn/rest/domain/EntityDetail.java
+++ b/rest/rest-api/src/main/java/org/apache/brooklyn/rest/domain/EntityDetail.java
@@ -18,23 +18,25 @@
  */
 package org.apache.brooklyn.rest.domain;
 
-import com.fasterxml.jackson.annotation.JsonProperty;
-import com.fasterxml.jackson.databind.annotation.JsonSerialize;
-import com.google.common.collect.ImmutableList;
-import org.apache.brooklyn.core.entity.lifecycle.Lifecycle;
-
 import java.net.URI;
 import java.util.Collections;
 import java.util.List;
 import java.util.Map;
 
+import org.apache.brooklyn.core.entity.lifecycle.Lifecycle;
+
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.annotation.JsonInclude.Include;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.google.common.collect.ImmutableList;
+
 public class EntityDetail extends EntitySummary {
 
     private static final long serialVersionUID = 100490507982229165L;
 
     private final String applicationId;
     private final String parentId;
-    @JsonSerialize(include = JsonSerialize.Inclusion.NON_EMPTY)
+    @JsonInclude(Include.NON_NULL)
     private final List children;
     private final List groupIds;
     private final List> members;
diff --git a/rest/rest-api/src/main/java/org/apache/brooklyn/rest/domain/EntitySpec.java b/rest/rest-api/src/main/java/org/apache/brooklyn/rest/domain/EntitySpec.java
index b4d9c04110..494fbd8ca7 100644
--- a/rest/rest-api/src/main/java/org/apache/brooklyn/rest/domain/EntitySpec.java
+++ b/rest/rest-api/src/main/java/org/apache/brooklyn/rest/domain/EntitySpec.java
@@ -20,14 +20,14 @@
 
 import static com.google.common.base.Preconditions.checkNotNull;
 
-import com.fasterxml.jackson.annotation.JsonProperty;
-import com.google.common.collect.ImmutableMap;
-
 import java.io.Serializable;
 import java.util.Collections;
 import java.util.Map;
 import java.util.Objects;
 
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.google.common.collect.ImmutableMap;
+
 public class EntitySpec implements HasName, Serializable {
 
     private static final long serialVersionUID = -3882575609132757188L;
diff --git a/rest/rest-api/src/main/java/org/apache/brooklyn/rest/domain/EntitySummary.java b/rest/rest-api/src/main/java/org/apache/brooklyn/rest/domain/EntitySummary.java
index e0a585a116..ae123ef421 100644
--- a/rest/rest-api/src/main/java/org/apache/brooklyn/rest/domain/EntitySummary.java
+++ b/rest/rest-api/src/main/java/org/apache/brooklyn/rest/domain/EntitySummary.java
@@ -18,15 +18,16 @@
  */
 package org.apache.brooklyn.rest.domain;
 
-import com.fasterxml.jackson.annotation.JsonProperty;
-import com.fasterxml.jackson.databind.annotation.JsonSerialize;
-import com.google.common.collect.ImmutableMap;
-
 import java.io.Serializable;
 import java.net.URI;
 import java.util.Map;
 import java.util.Objects;
 
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.annotation.JsonInclude.Include;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.google.common.collect.ImmutableMap;
+
 public class EntitySummary implements HasId, HasName, Serializable {
 
     private static final long serialVersionUID = 100490507982229165L;
@@ -34,7 +35,7 @@ public class EntitySummary implements HasId, HasName, Serializable {
     private final String id;
     private final String name;
     private final String type;
-    @JsonSerialize(include = JsonSerialize.Inclusion.NON_NULL)
+    @JsonInclude(Include.NON_NULL)
     private final String catalogItemId;
     private final Map links;
 
diff --git a/rest/rest-api/src/main/java/org/apache/brooklyn/rest/domain/HasId.java b/rest/rest-api/src/main/java/org/apache/brooklyn/rest/domain/HasId.java
index a67f6c53db..69b4a9c34c 100644
--- a/rest/rest-api/src/main/java/org/apache/brooklyn/rest/domain/HasId.java
+++ b/rest/rest-api/src/main/java/org/apache/brooklyn/rest/domain/HasId.java
@@ -18,11 +18,7 @@
  */
 package org.apache.brooklyn.rest.domain;
 
-import com.fasterxml.jackson.annotation.JsonIdentityInfo;
-import com.fasterxml.jackson.annotation.ObjectIdGenerators;
-
 /** Marker interface for summary objects with an id field */
-@JsonIdentityInfo(generator=ObjectIdGenerators.IntSequenceGenerator.class, property="@id")
 public interface HasId {
 
     public String getId();
diff --git a/rest/rest-api/src/main/java/org/apache/brooklyn/rest/domain/PolicyConfigSummary.java b/rest/rest-api/src/main/java/org/apache/brooklyn/rest/domain/PolicyConfigSummary.java
index 8115ab97a5..8ffaf6ef93 100644
--- a/rest/rest-api/src/main/java/org/apache/brooklyn/rest/domain/PolicyConfigSummary.java
+++ b/rest/rest-api/src/main/java/org/apache/brooklyn/rest/domain/PolicyConfigSummary.java
@@ -22,10 +22,11 @@
 import java.util.Map;
 import java.util.Objects;
 
+import org.apache.brooklyn.config.ConfigKey;
+
 import com.fasterxml.jackson.annotation.JsonProperty;
 import com.fasterxml.jackson.databind.annotation.JsonSerialize;
 import com.google.common.collect.ImmutableMap;
-import org.apache.brooklyn.config.ConfigKey;
 
 public class PolicyConfigSummary extends ConfigSummary {
 
diff --git a/rest/rest-api/src/main/java/org/apache/brooklyn/rest/domain/ScriptExecutionSummary.java b/rest/rest-api/src/main/java/org/apache/brooklyn/rest/domain/ScriptExecutionSummary.java
index c84573c3d9..27ccdad341 100644
--- a/rest/rest-api/src/main/java/org/apache/brooklyn/rest/domain/ScriptExecutionSummary.java
+++ b/rest/rest-api/src/main/java/org/apache/brooklyn/rest/domain/ScriptExecutionSummary.java
@@ -21,20 +21,21 @@
 import java.io.Serializable;
 import java.util.Objects;
 
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.annotation.JsonInclude.Include;
 import com.fasterxml.jackson.annotation.JsonProperty;
-import com.fasterxml.jackson.databind.annotation.JsonSerialize;
 
 public class ScriptExecutionSummary implements Serializable {
 
     private static final long serialVersionUID = -7707936602991185960L;
 
-    @JsonSerialize(include = JsonSerialize.Inclusion.NON_NULL)
+    @JsonInclude(Include.NON_NULL)
     private final Object result;
-    @JsonSerialize(include = JsonSerialize.Inclusion.NON_EMPTY)
+    @JsonInclude(Include.NON_NULL)
     private final String problem;
-    @JsonSerialize(include = JsonSerialize.Inclusion.NON_EMPTY)
+    @JsonInclude(Include.NON_EMPTY)
     private final String stdout;
-    @JsonSerialize(include = JsonSerialize.Inclusion.NON_EMPTY)
+    @JsonInclude(Include.NON_EMPTY)
     private final String stderr;
 
     public ScriptExecutionSummary(

From 5c7cba396ad8601b737b3586f2c3ea1c60ce99be Mon Sep 17 00:00:00 2001
From: Andrew Donald Kennedy 
Date: Fri, 31 Mar 2017 21:20:43 +0100
Subject: [PATCH 26/43] Make policy config resolve properly

---
 .../action/AbstractScheduledEffectorPolicy.java     | 13 +++++++++++--
 .../policy/action/PeriodicEffectorPolicy.java       |  6 ++----
 .../policy/action/ScheduledEffectorPolicy.java      | 10 +++++-----
 3 files changed, 18 insertions(+), 11 deletions(-)

diff --git a/policy/src/main/java/org/apache/brooklyn/policy/action/AbstractScheduledEffectorPolicy.java b/policy/src/main/java/org/apache/brooklyn/policy/action/AbstractScheduledEffectorPolicy.java
index bc1386770e..6002c68d1c 100644
--- a/policy/src/main/java/org/apache/brooklyn/policy/action/AbstractScheduledEffectorPolicy.java
+++ b/policy/src/main/java/org/apache/brooklyn/policy/action/AbstractScheduledEffectorPolicy.java
@@ -32,6 +32,8 @@
 import org.apache.brooklyn.core.entity.EntityInternal;
 import org.apache.brooklyn.core.policy.AbstractPolicy;
 import org.apache.brooklyn.util.collections.MutableMap;
+import org.apache.brooklyn.util.core.config.ConfigBag;
+import org.apache.brooklyn.util.core.config.ResolvingConfigBag;
 import org.apache.brooklyn.util.guava.Maybe;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -39,6 +41,7 @@
 import com.google.common.annotations.Beta;
 import com.google.common.base.Predicates;
 import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Maps;
 import com.google.common.reflect.TypeToken;
 
 @Beta
@@ -102,9 +105,15 @@ protected Effector getEffector() {
 
     @Override
     public void run() {
-        Map args = EntityInitializers.resolve(config().getBag(), EFFECTOR_ARGUMENTS);
+        ConfigBag bag = ResolvingConfigBag.newInstanceExtending(((EntityInternal) entity).getManagementContext(), config().getBag());
+        Map args = EntityInitializers.resolve(bag, EFFECTOR_ARGUMENTS);
+        bag.putAll(args);
+        Map resolved = Maps.newLinkedHashMap();
+        for (String key : args.keySet()) {
+            resolved.put(key, bag.getStringKey(key));
+        }
 
-        LOG.debug("{} invoking effector on {}, effector={}, args={}", new Object[] { this, entity, effector.getName(), args });
+        LOG.debug("{} invoking effector on {}, effector={}, args={}", new Object[] { this, entity, effector.getName(), resolved });
         entity.invoke(effector, args).getUnchecked();
     }
 }
diff --git a/policy/src/main/java/org/apache/brooklyn/policy/action/PeriodicEffectorPolicy.java b/policy/src/main/java/org/apache/brooklyn/policy/action/PeriodicEffectorPolicy.java
index ba989ab491..b93d65ac8b 100644
--- a/policy/src/main/java/org/apache/brooklyn/policy/action/PeriodicEffectorPolicy.java
+++ b/policy/src/main/java/org/apache/brooklyn/policy/action/PeriodicEffectorPolicy.java
@@ -68,7 +68,6 @@ public class PeriodicEffectorPolicy extends AbstractScheduledEffectorPolicy {
 
     public static final AttributeSensor START_SCHEDULER = Sensors.newBooleanSensor("scheduler.start", "Start the periodic effector execution after this becomes true");
 
-    protected long delay;
     protected AtomicBoolean running = new AtomicBoolean(false);
 
     public PeriodicEffectorPolicy() {
@@ -77,9 +76,6 @@ public PeriodicEffectorPolicy() {
 
     public PeriodicEffectorPolicy(Map props) {
         super(props);
-
-        Duration period = Preconditions.checkNotNull(config().get(PERIOD), "The period must be configured for this policy");
-        delay = period.toMilliseconds();
     }
 
     @Override
@@ -97,6 +93,8 @@ public void onEvent(SensorEvent event) {
                 if (event.getSensor().getName().equals(START_SCHEDULER.getName())) {
                     Boolean start = Boolean.parseBoolean(Strings.toString(event.getValue()));
                     if (start && running.compareAndSet(false, true)) {
+                        Duration period = Preconditions.checkNotNull(config().get(PERIOD), "The period must be configured for this policy");
+                        long delay = period.toMilliseconds();
                         long wait = delay;
                         String time = config().get(TIME);
                         if (Strings.isNonBlank(time)) {
diff --git a/policy/src/main/java/org/apache/brooklyn/policy/action/ScheduledEffectorPolicy.java b/policy/src/main/java/org/apache/brooklyn/policy/action/ScheduledEffectorPolicy.java
index e87a759064..55e9fbcc95 100644
--- a/policy/src/main/java/org/apache/brooklyn/policy/action/ScheduledEffectorPolicy.java
+++ b/policy/src/main/java/org/apache/brooklyn/policy/action/ScheduledEffectorPolicy.java
@@ -65,11 +65,6 @@ public ScheduledEffectorPolicy() {
 
     public ScheduledEffectorPolicy(Map props) {
         super(props);
-
-        String time = config().get(TIME);
-        if (Strings.isNonBlank(time)) {
-            scheduleAt(time);
-        }
     }
 
     @Override
@@ -78,6 +73,11 @@ public void setEntity(final EntityLocal entity) {
 
         subscriptions().subscribe(entity, INVOKE_IMMEDIATELY, handler);
         subscriptions().subscribe(entity, INVOKE_AT, handler);
+
+        String time = config().get(TIME);
+        if (Strings.isNonBlank(time)) {
+            scheduleAt(time);
+        }
     }
 
     protected void scheduleAt(String time) {

From 78d90d258e2b679f7fa769423ff027ea7b1b4e15 Mon Sep 17 00:00:00 2001
From: Andrew Donald Kennedy 
Date: Fri, 31 Mar 2017 22:18:12 +0100
Subject: [PATCH 27/43] Simplify collection functions

---
 .../util/collections/CollectionFunctionals.java  | 16 +++++-----------
 1 file changed, 5 insertions(+), 11 deletions(-)

diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/collections/CollectionFunctionals.java b/utils/common/src/main/java/org/apache/brooklyn/util/collections/CollectionFunctionals.java
index 24982b340b..61426995ee 100644
--- a/utils/common/src/main/java/org/apache/brooklyn/util/collections/CollectionFunctionals.java
+++ b/utils/common/src/main/java/org/apache/brooklyn/util/collections/CollectionFunctionals.java
@@ -184,17 +184,11 @@ private LimitFunction(int max) {
         @Override
         public List apply(I input) {
             if (input==null) return null;
-            MutableList result = MutableList.of();
-            for (T i: input) {
-                result.add(i);
-                if (result.size()>=max)
-                    return result;
-            }
-            return result;
+            return MutableList.copyOf(Iterables.limit(input, max));
         }
     }
 
-    public static final class IterableTransformerFunction implements Function, Iterable> {
+    public static final class IterableTransformerFunction> implements Function> {
         private final Function function;
 
         private IterableTransformerFunction(Function function) {
@@ -202,15 +196,15 @@ private IterableTransformerFunction(Function function) {
         }
 
         @Override
-        public Iterable apply(Iterable input) {
+        public List apply(I input) {
             if (input==null) return null;
-            return Iterables.transform(input, function);
+            return MutableList.copyOf(Iterables.transform(input, function));
         }
 
         @Override public String toString() { return "iterableTransformer"; }
     }
 
-    public static  Function, Iterable> iterableTransformer(Function function) {
+    public static > Function> iterableTransformer(Function function) {
         return new IterableTransformerFunction(function);
     }
 

From 572075e5ab543362e5a00f174a7ee4168037a00e Mon Sep 17 00:00:00 2001
From: Andrew Donald Kennedy 
Date: Sat, 1 Apr 2017 01:19:35 +0100
Subject: [PATCH 28/43] Update ServiceStateLogic to handle STARTING differently

---
 .../core/entity/lifecycle/ServiceStateLogic.java  | 15 +++++++++++++--
 1 file changed, 13 insertions(+), 2 deletions(-)

diff --git a/core/src/main/java/org/apache/brooklyn/core/entity/lifecycle/ServiceStateLogic.java b/core/src/main/java/org/apache/brooklyn/core/entity/lifecycle/ServiceStateLogic.java
index ba08a443ed..ca266f314a 100644
--- a/core/src/main/java/org/apache/brooklyn/core/entity/lifecycle/ServiceStateLogic.java
+++ b/core/src/main/java/org/apache/brooklyn/core/entity/lifecycle/ServiceStateLogic.java
@@ -469,6 +469,11 @@ public static class ComputeServiceIndicatorsFromChildrenAndMembers extends Abstr
             "enricher.service_state.children_and_members.ignore_entities.service_state_values",
             "Service states (including null) which indicate an entity should be ignored when looking at children service states; anything apart from RUNNING not in this list will be treated as not healthy (by default just ON_FIRE will mean not healthy)",
             MutableSet.builder().addAll(Lifecycle.values()).add(null).remove(Lifecycle.RUNNING).remove(Lifecycle.ON_FIRE).build().asUnmodifiable());
+        @SuppressWarnings("serial")
+        public static final ConfigKey> IGNORE_ENTITIES_WITH_THESE_SERVICE_STATES_WHEN_STARTING = ConfigKeys.newConfigKey(new TypeToken>() {},
+            "enricher.service_state.children_and_members.ignore_entities.starting_service_state_values",
+            "Service states (including null) which indicate an entity should be ignored when looking at children service states; anything apart from RUNNING not in this list will be treated as not healthy (by default just ON_FIRE will mean not healthy)",
+            MutableSet.builder().addAll(Lifecycle.values()).add(null).remove(Lifecycle.RUNNING).remove(Lifecycle.ON_FIRE).remove(Lifecycle.STARTING).remove(Lifecycle.CREATED).build().asUnmodifiable());
 
         protected String getKeyForMapSensor() {
             return Preconditions.checkNotNull(super.getUniqueTag());
@@ -552,7 +557,10 @@ protected Object computeServiceNotUp() {
             Map values = getValues(SERVICE_UP);
             List violators = MutableList.of();
             boolean ignoreNull = getConfig(IGNORE_ENTITIES_WITH_SERVICE_UP_NULL);
-            Set ignoreStates = getConfig(IGNORE_ENTITIES_WITH_THESE_SERVICE_STATES);
+            Lifecycle current = entity.sensors().get(Attributes.SERVICE_STATE_ACTUAL);
+            Set ignoreStates = (current == Lifecycle.STARTING)
+                    ? config().get(IGNORE_ENTITIES_WITH_THESE_SERVICE_STATES_WHEN_STARTING)
+                    : config().get(IGNORE_ENTITIES_WITH_THESE_SERVICE_STATES);
             int entries=0;
             int numUp=0;
             for (Map.Entry state: values.entrySet()) {
@@ -590,7 +598,10 @@ protected Object computeServiceProblems() {
             Map values = getValues(SERVICE_STATE_ACTUAL);
             int numRunning=0;
             List onesNotHealthy=MutableList.of();
-            Set ignoreStates = getConfig(IGNORE_ENTITIES_WITH_THESE_SERVICE_STATES);
+            Lifecycle current = entity.sensors().get(Attributes.SERVICE_STATE_ACTUAL);
+            Set ignoreStates = (current == Lifecycle.STARTING)
+                    ? config().get(IGNORE_ENTITIES_WITH_THESE_SERVICE_STATES_WHEN_STARTING)
+                    : config().get(IGNORE_ENTITIES_WITH_THESE_SERVICE_STATES);
             for (Map.Entry state: values.entrySet()) {
                 if (state.getValue()==Lifecycle.RUNNING) numRunning++;
                 else if (!ignoreStates.contains(state.getValue()))

From 99f006f7d3d01304742c43101834ea009712e673 Mon Sep 17 00:00:00 2001
From: Andrew Donald Kennedy 
Date: Sat, 1 Apr 2017 04:37:13 +0100
Subject: [PATCH 29/43] Add mutex to meta effectors and fix resolution of
 effector arguments in policy

---
 .../composite/AbstractCompositeEffector.java  |  2 +
 .../effector/composite/ChoiceEffector.java    | 77 ++++++++++---------
 .../effector/composite/ComposeEffector.java   | 51 ++++++------
 .../core/effector/composite/LoopEffector.java | 57 +++++++-------
 .../effector/composite/ReplaceEffector.java   | 48 ++++++------
 .../effector/composite/SequenceEffector.java  | 35 +++++----
 .../effector/composite/TransformEffector.java | 19 ++---
 .../AbstractScheduledEffectorPolicy.java      |  9 ++-
 8 files changed, 156 insertions(+), 142 deletions(-)

diff --git a/core/src/main/java/org/apache/brooklyn/core/effector/composite/AbstractCompositeEffector.java b/core/src/main/java/org/apache/brooklyn/core/effector/composite/AbstractCompositeEffector.java
index 5b5357503c..cf7c3650dd 100644
--- a/core/src/main/java/org/apache/brooklyn/core/effector/composite/AbstractCompositeEffector.java
+++ b/core/src/main/java/org/apache/brooklyn/core/effector/composite/AbstractCompositeEffector.java
@@ -47,6 +47,8 @@ protected static abstract class Body extends EffectorBody {
         protected final Effector effector;
         protected final ConfigBag config;
 
+        protected Object mutex = new Object[0];
+
         public Body(Effector eff, ConfigBag config) {
             this.effector = eff;
             this.config = config;
diff --git a/core/src/main/java/org/apache/brooklyn/core/effector/composite/ChoiceEffector.java b/core/src/main/java/org/apache/brooklyn/core/effector/composite/ChoiceEffector.java
index 2d61872a16..51f4bfc912 100644
--- a/core/src/main/java/org/apache/brooklyn/core/effector/composite/ChoiceEffector.java
+++ b/core/src/main/java/org/apache/brooklyn/core/effector/composite/ChoiceEffector.java
@@ -85,44 +85,45 @@ public Body(Effector eff, ConfigBag config) {
 
         @Override
         public Object call(final ConfigBag params) {
-            LOG.debug("{} called with config {}, params {}", new Object[] { this, config, params });
-            String input = config.get(INPUT);
-            Object inputObject = params.getStringKey(input);
-
-            Object choiceDetails = EntityInitializers.resolve(config, CHOICE);
-            String choiceEffectorName = getEffectorName(choiceDetails);
-            String choiceInputArgument = getInputArgument(choiceDetails);
-            Entity choiceTargetEntity = getTargetEntity(choiceDetails);
-            LOG.debug("{} executing {}({}) on {}", new Object[] { this, choiceEffectorName, choiceInputArgument, choiceTargetEntity });
-
-            if (choiceInputArgument == null) {
-                throw new IllegalArgumentException("Input is not set for choice effector: " + choiceDetails);
+            synchronized (mutex) {
+                LOG.debug("{} called with config {}, params {}", new Object[] { this, config, params });
+                String input = config.get(INPUT);
+                Object inputObject = params.getStringKey(input);
+
+                Object choiceDetails = EntityInitializers.resolve(config, CHOICE);
+                String choiceEffectorName = getEffectorName(choiceDetails);
+                String choiceInputArgument = getInputArgument(choiceDetails);
+                Entity choiceTargetEntity = getTargetEntity(choiceDetails);
+                LOG.debug("{} executing {}({}) on {}", new Object[] { this, choiceEffectorName, choiceInputArgument, choiceTargetEntity });
+
+                if (choiceInputArgument == null) {
+                    throw new IllegalArgumentException("Input is not set for choice effector: " + choiceDetails);
+                }
+                params.putStringKey(choiceInputArgument, inputObject);
+
+                Object output = invokeEffectorNamed(choiceTargetEntity, choiceEffectorName, params);
+                Boolean success = Boolean.parseBoolean(Strings.toString(output));
+
+                Object effectorDetails = EntityInitializers.resolve(config, success ? SUCCESS : FAILURE);
+
+                if (!success && effectorDetails == null) {
+                    return null;
+                }
+
+                String effectorName = getEffectorName(effectorDetails);
+                String inputArgument = getInputArgument(effectorDetails);
+                Entity targetEntity = getTargetEntity(effectorDetails);
+                LOG.debug("{} executing {}({}) on {}", new Object[] { this, effectorName, inputArgument, targetEntity });
+
+                if (inputArgument == null) {
+                    throw new IllegalArgumentException("Input is not set for effector: " + effectorDetails);
+                }
+                params.putStringKey(inputArgument, inputObject);
+                Object result = invokeEffectorNamed(targetEntity, effectorName, params);
+
+                LOG.debug("{} effector {} returned {}", new Object[] { this, effector.getName(), result });
+                return result;
             }
-            params.putStringKey(choiceInputArgument, inputObject);
-
-            Object output = invokeEffectorNamed(choiceTargetEntity, choiceEffectorName, params);
-            Boolean success = Boolean.parseBoolean(Strings.toString(output));
-
-            Object effectorDetails = EntityInitializers.resolve(config, success ? SUCCESS : FAILURE);
-
-            if (!success && effectorDetails == null) {
-                return null;
-            }
-
-            String effectorName = getEffectorName(effectorDetails);
-            String inputArgument = getInputArgument(effectorDetails);
-            Entity targetEntity = getTargetEntity(effectorDetails);
-            LOG.debug("{} executing {}({}) on {}", new Object[] { this, effectorName, inputArgument, targetEntity });
-
-            if (inputArgument == null) {
-                throw new IllegalArgumentException("Input is not set for effector: " + effectorDetails);
-            }
-            params.putStringKey(inputArgument, inputObject);
-            Object result = invokeEffectorNamed(targetEntity, effectorName, params);
-
-            LOG.debug("{} effector {} returned {}", new Object[] { this, effector.getName(), result });
-            return result;
         }
     }
-
-}
+}
\ No newline at end of file
diff --git a/core/src/main/java/org/apache/brooklyn/core/effector/composite/ComposeEffector.java b/core/src/main/java/org/apache/brooklyn/core/effector/composite/ComposeEffector.java
index 8d02c933c1..bda5276c5e 100644
--- a/core/src/main/java/org/apache/brooklyn/core/effector/composite/ComposeEffector.java
+++ b/core/src/main/java/org/apache/brooklyn/core/effector/composite/ComposeEffector.java
@@ -73,39 +73,40 @@ public Body(Effector eff, ConfigBag config) {
 
         @Override
         public Object call(final ConfigBag params) {
-            LOG.debug("{} called with config {}, params {}", new Object[] { this, config, params });
-            List effectors = EntityInitializers.resolve(config, COMPOSE);
+            synchronized (mutex) {
+                LOG.debug("{} called with config {}, params {}", new Object[] { this, config, params });
+                List effectors = EntityInitializers.resolve(config, COMPOSE);
 
-            Object result = null;
+                Object result = null;
 
-            for (Object effectorDetails : effectors) {
-                String effectorName = getEffectorName(effectorDetails);
-                String inputArgument = getInputArgument(effectorDetails);
-                String inputParameter = getInputParameter(effectorDetails);
-                Entity targetEntity = getTargetEntity(effectorDetails);
-                LOG.debug("{} executing {}({}:{}) on {}", new Object[] { this, effectorName, inputArgument, inputParameter, targetEntity });
+                for (Object effectorDetails : effectors) {
+                    String effectorName = getEffectorName(effectorDetails);
+                    String inputArgument = getInputArgument(effectorDetails);
+                    String inputParameter = getInputParameter(effectorDetails);
+                    Entity targetEntity = getTargetEntity(effectorDetails);
+                    LOG.debug("{} executing {}({}:{}) on {}", new Object[] { this, effectorName, inputArgument, inputParameter, targetEntity });
 
-                if (inputArgument == null) {
-                    throw new IllegalArgumentException("Input is not set for this effector: " + effectorDetails);
-                }
-                if (inputParameter == null) {
-                    if (result == null) {
-                        Object input = params.getStringKey(inputArgument);
-                        params.putStringKey(inputArgument, input);
+                    if (inputArgument == null) {
+                        throw new IllegalArgumentException("Input is not set for this effector: " + effectorDetails);
+                    }
+                    if (inputParameter == null) {
+                        if (result == null) {
+                            Object input = params.getStringKey(inputArgument);
+                            params.putStringKey(inputArgument, input);
+                        } else {
+                            params.putStringKey(inputArgument, result);
+                        }
                     } else {
-                        params.putStringKey(inputArgument, result);
+                        Object input = params.getStringKey(inputParameter);
+                        params.putStringKey(inputArgument, input);
                     }
-                } else {
-                    Object input = params.getStringKey(inputParameter);
-                    params.putStringKey(inputArgument, input);
+
+                    result = invokeEffectorNamed(targetEntity, effectorName, params);
                 }
 
-                result = invokeEffectorNamed(targetEntity, effectorName, params);
+                LOG.debug("{} effector {} returned {}", new Object[] { this, effector.getName(), result });
+                return result;
             }
-
-            LOG.debug("{} effector {} returned {}", new Object[] { this, effector.getName(), result });
-            return result;
         }
     }
-
 }
diff --git a/core/src/main/java/org/apache/brooklyn/core/effector/composite/LoopEffector.java b/core/src/main/java/org/apache/brooklyn/core/effector/composite/LoopEffector.java
index 77f8213a48..63e8b9f1e2 100644
--- a/core/src/main/java/org/apache/brooklyn/core/effector/composite/LoopEffector.java
+++ b/core/src/main/java/org/apache/brooklyn/core/effector/composite/LoopEffector.java
@@ -76,35 +76,36 @@ public Body(Effector eff, ConfigBag config) {
 
         @Override
         public List call(final ConfigBag params) {
-            LOG.debug("{} called with config {}, params {}", new Object[] { this, config, params });
-            Object effectorDetails = EntityInitializers.resolve(config, LOOP);
-            String input = config.get(INPUT);
-            Object inputObject = params.getStringKey(input);
-            if (!(inputObject instanceof Collection)) {
-                throw new IllegalArgumentException("Input to loop is not a collection: " + inputObject);
+            synchronized (mutex) {
+                LOG.debug("{} called with config {}, params {}", new Object[] { this, config, params });
+                Object effectorDetails = EntityInitializers.resolve(config, LOOP);
+                String input = config.get(INPUT);
+                Object inputObject = params.getStringKey(input);
+                if (!(inputObject instanceof Collection)) {
+                    throw new IllegalArgumentException("Input to loop is not a collection: " + inputObject);
+                }
+                Collection inputCollection = (Collection) inputObject;
+
+                List result = Lists.newArrayList();
+
+                String effectorName = getEffectorName(effectorDetails);
+                String inputArgument = getInputArgument(effectorDetails);
+                Entity targetEntity = getTargetEntity(effectorDetails);
+                LOG.debug("{} executing {}({}) on {}", new Object[] { this, effectorName, inputArgument, targetEntity });
+
+                if (inputArgument == null) {
+                    throw new IllegalArgumentException("Input is not set for this effector: " + effectorDetails);
+                }
+
+                for (Object inputEach : inputCollection) {
+                    params.putStringKey(inputArgument, inputEach);
+
+                    result.add(invokeEffectorNamed(targetEntity, effectorName, params));
+                }
+
+                LOG.debug("{} effector {} returned {}", new Object[] { this, effector.getName(), result });
+                return result;
             }
-            Collection inputCollection = (Collection) inputObject;
-
-            List result = Lists.newArrayList();
-
-            String effectorName = getEffectorName(effectorDetails);
-            String inputArgument = getInputArgument(effectorDetails);
-            Entity targetEntity = getTargetEntity(effectorDetails);
-            LOG.debug("{} executing {}({}) on {}", new Object[] { this, effectorName, inputArgument, targetEntity });
-
-            if (inputArgument == null) {
-                throw new IllegalArgumentException("Input is not set for this effector: " + effectorDetails);
-            }
-
-            for (Object inputEach : inputCollection) {
-                params.putStringKey(inputArgument, inputEach);
-
-                result.add(invokeEffectorNamed(targetEntity, effectorName, params));
-            }
-
-            LOG.debug("{} effector {} returned {}", new Object[] { this, effector.getName(), result });
-            return result;
         }
     }
-
 }
diff --git a/core/src/main/java/org/apache/brooklyn/core/effector/composite/ReplaceEffector.java b/core/src/main/java/org/apache/brooklyn/core/effector/composite/ReplaceEffector.java
index 10522ada6b..a9a27929d4 100644
--- a/core/src/main/java/org/apache/brooklyn/core/effector/composite/ReplaceEffector.java
+++ b/core/src/main/java/org/apache/brooklyn/core/effector/composite/ReplaceEffector.java
@@ -97,30 +97,32 @@ public Body(Effector eff, ConfigBag config) {
 
         @Override
         public Object call(final ConfigBag params) {
-            LOG.debug("{} called with config {}, params {}", new Object[] { this, config, params });
-            ReplaceAction action = config.get(ACTION);
-            Object effectorDetails = EntityInitializers.resolve(config, REPLACE);
-
-            String effectorName = getEffectorName(effectorDetails);
-            String inputArgument = getInputArgument(effectorDetails);
-            Entity targetEntity = getTargetEntity(effectorDetails);
-            LOG.debug("{} executing {}({}) on {}", new Object[] { this, effectorName, inputArgument, targetEntity });
-
-            if (inputArgument != null) {
-                Object input = params.getStringKey(inputArgument);
-                params.putStringKey(inputArgument, input);
+            synchronized (mutex) {
+                LOG.debug("{} called with config {}, params {}", new Object[] { this, config, params });
+                ReplaceAction action = config.get(ACTION);
+                Object effectorDetails = EntityInitializers.resolve(config, REPLACE);
+
+                String effectorName = getEffectorName(effectorDetails);
+                String inputArgument = getInputArgument(effectorDetails);
+                Entity targetEntity = getTargetEntity(effectorDetails);
+                LOG.debug("{} executing {}({}) on {}", new Object[] { this, effectorName, inputArgument, targetEntity });
+
+                if (inputArgument != null) {
+                    Object input = params.getStringKey(inputArgument);
+                    params.putStringKey(inputArgument, input);
+                }
+
+                if (action == ReplaceAction.POST) {
+                    invokeEffectorNamed(targetEntity, ORIGINAL + effector.getName(), params);
+                }
+                Object result = invokeEffectorNamed(targetEntity, effectorName, params);
+                if (action == ReplaceAction.PRE) {
+                    invokeEffectorNamed(targetEntity, ORIGINAL + effector.getName(), params);
+                }
+
+                LOG.debug("{} effector {} returned {}", new Object[] { this, effector.getName(), result });
+                return result;
             }
-
-            if (action == ReplaceAction.POST) {
-                invokeEffectorNamed(targetEntity, ORIGINAL + effector.getName(), params);
-            }
-            Object result = invokeEffectorNamed(targetEntity, effectorName, params);
-            if (action == ReplaceAction.PRE) {
-                invokeEffectorNamed(targetEntity, ORIGINAL + effector.getName(), params);
-            }
-
-            LOG.debug("{} effector {} returned {}", new Object[] { this, effector.getName(), result });
-            return result;
         }
     }
 }
diff --git a/core/src/main/java/org/apache/brooklyn/core/effector/composite/SequenceEffector.java b/core/src/main/java/org/apache/brooklyn/core/effector/composite/SequenceEffector.java
index 9c43f82053..7174dc214b 100644
--- a/core/src/main/java/org/apache/brooklyn/core/effector/composite/SequenceEffector.java
+++ b/core/src/main/java/org/apache/brooklyn/core/effector/composite/SequenceEffector.java
@@ -73,29 +73,30 @@ public Body(Effector eff, ConfigBag config) {
 
         @Override
         public Object call(final ConfigBag params) {
-            LOG.debug("{} called with config {}, params {}", new Object[] { this, config, params });
-            List effectors = EntityInitializers.resolve(config, SEQUENCE);
+            synchronized (mutex) {
+                LOG.debug("{} called with config {}, params {}", new Object[] { this, config, params });
+                List effectors = EntityInitializers.resolve(config, SEQUENCE);
 
-            Object result = null;
+                Object result = null;
 
-            for (Object effectorDetails : effectors) {
-                String effectorName = getEffectorName(effectorDetails);
-                String inputArgument = getInputArgument(effectorDetails);
-                Entity targetEntity = getTargetEntity(effectorDetails);
-                LOG.debug("{} executing {}({}) on {}", new Object[] { this, effectorName, inputArgument, targetEntity });
+                for (Object effectorDetails : effectors) {
+                    String effectorName = getEffectorName(effectorDetails);
+                    String inputArgument = getInputArgument(effectorDetails);
+                    Entity targetEntity = getTargetEntity(effectorDetails);
+                    LOG.debug("{} executing {}({}) on {}", new Object[] { this, effectorName, inputArgument, targetEntity });
 
-                if (inputArgument == null) {
-                    throw new IllegalArgumentException("Input is not set for this effector: " + effectorDetails);
+                    if (inputArgument == null) {
+                        throw new IllegalArgumentException("Input is not set for this effector: " + effectorDetails);
+                    }
+                    Object input = params.getStringKey(inputArgument);
+                    params.putStringKey(inputArgument, input);
+
+                    result = invokeEffectorNamed(targetEntity, effectorName, params);
                 }
-                Object input = params.getStringKey(inputArgument);
-                params.putStringKey(inputArgument, input);
 
-                result = invokeEffectorNamed(targetEntity, effectorName, params);
+                LOG.debug("{} effector {} returned {}", new Object[] { this, effector.getName(), result });
+                return result;
             }
-
-            LOG.debug("{} effector {} returned {}", new Object[] { this, effector.getName(), result });
-            return result;
         }
     }
-
 }
diff --git a/core/src/main/java/org/apache/brooklyn/core/effector/composite/TransformEffector.java b/core/src/main/java/org/apache/brooklyn/core/effector/composite/TransformEffector.java
index 630118053d..d7fc9729e7 100644
--- a/core/src/main/java/org/apache/brooklyn/core/effector/composite/TransformEffector.java
+++ b/core/src/main/java/org/apache/brooklyn/core/effector/composite/TransformEffector.java
@@ -76,16 +76,17 @@ public Body(Effector eff, ConfigBag config) {
 
         @Override
         public Object call(final ConfigBag params) {
-            LOG.debug("{} called with config {}, params {}", new Object[] { this, config, params });
-            Function function = EntityInitializers.resolve(config, FUNCTION);
+            synchronized (mutex) {
+                LOG.debug("{} called with config {}, params {}", new Object[] { this, config, params });
+                Function function = EntityInitializers.resolve(config, FUNCTION);
 
-            String input = config.get(INPUT);
-            Object value = params.getStringKey(input);
-            Object result = function.apply(value);
+                String input = config.get(INPUT);
+                Object value = params.getStringKey(input);
+                Object result = function.apply(value);
 
-            LOG.debug("{} effector {} returned {}", new Object[] { this, effector.getName(), result });
-            return result;
+                LOG.debug("{} effector {} returned {}", new Object[] { this, effector.getName(), result });
+                return result;
+            }
         }
     }
-
-}
+}
\ No newline at end of file
diff --git a/policy/src/main/java/org/apache/brooklyn/policy/action/AbstractScheduledEffectorPolicy.java b/policy/src/main/java/org/apache/brooklyn/policy/action/AbstractScheduledEffectorPolicy.java
index 6002c68d1c..ea2a666c47 100644
--- a/policy/src/main/java/org/apache/brooklyn/policy/action/AbstractScheduledEffectorPolicy.java
+++ b/policy/src/main/java/org/apache/brooklyn/policy/action/AbstractScheduledEffectorPolicy.java
@@ -29,11 +29,13 @@
 import org.apache.brooklyn.config.ConfigKey;
 import org.apache.brooklyn.core.config.ConfigKeys;
 import org.apache.brooklyn.core.entity.EntityInitializers;
-import org.apache.brooklyn.core.entity.EntityInternal;
+import org.apache.brooklyn.core.mgmt.BrooklynTaskTags;
 import org.apache.brooklyn.core.policy.AbstractPolicy;
 import org.apache.brooklyn.util.collections.MutableMap;
 import org.apache.brooklyn.util.core.config.ConfigBag;
 import org.apache.brooklyn.util.core.config.ResolvingConfigBag;
+import org.apache.brooklyn.util.core.task.TaskTags;
+import org.apache.brooklyn.util.core.task.Tasks;
 import org.apache.brooklyn.util.guava.Maybe;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -41,6 +43,7 @@
 import com.google.common.annotations.Beta;
 import com.google.common.base.Predicates;
 import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Iterables;
 import com.google.common.collect.Maps;
 import com.google.common.reflect.TypeToken;
 
@@ -105,8 +108,10 @@ protected Effector getEffector() {
 
     @Override
     public void run() {
-        ConfigBag bag = ResolvingConfigBag.newInstanceExtending(((EntityInternal) entity).getManagementContext(), config().getBag());
+        LOG.warn("{}: run task tags: {}", this, Iterables.toString(Tasks.current().getTags()));
+        ConfigBag bag = ResolvingConfigBag.newInstanceExtending(getManagementContext(), config().getBag());
         Map args = EntityInitializers.resolve(bag, EFFECTOR_ARGUMENTS);
+        TaskTags.addTagDynamically(Tasks.current(), BrooklynTaskTags.tagForContextEntity(entity)); // WHY?
         bag.putAll(args);
         Map resolved = Maps.newLinkedHashMap();
         for (String key : args.keySet()) {

From f8c4004e8504a0bc31792361a09f236914e5e765 Mon Sep 17 00:00:00 2001
From: Andrew Donald Kennedy 
Date: Tue, 4 Apr 2017 22:44:14 +0100
Subject: [PATCH 30/43] Adds wait config to scheduled and periodic policies

---
 .../action/AbstractScheduledEffectorPolicy.java     | 11 ++++++++++-
 .../policy/action/PeriodicEffectorPolicy.java       | 13 ++++++++-----
 .../policy/action/ScheduledEffectorPolicy.java      |  8 ++++----
 3 files changed, 22 insertions(+), 10 deletions(-)

diff --git a/policy/src/main/java/org/apache/brooklyn/policy/action/AbstractScheduledEffectorPolicy.java b/policy/src/main/java/org/apache/brooklyn/policy/action/AbstractScheduledEffectorPolicy.java
index ea2a666c47..306afe97f0 100644
--- a/policy/src/main/java/org/apache/brooklyn/policy/action/AbstractScheduledEffectorPolicy.java
+++ b/policy/src/main/java/org/apache/brooklyn/policy/action/AbstractScheduledEffectorPolicy.java
@@ -37,6 +37,8 @@
 import org.apache.brooklyn.util.core.task.TaskTags;
 import org.apache.brooklyn.util.core.task.Tasks;
 import org.apache.brooklyn.util.guava.Maybe;
+import org.apache.brooklyn.util.time.Duration;
+import org.apache.brooklyn.util.time.DurationPredicates;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -74,6 +76,12 @@ public abstract class AbstractScheduledEffectorPolicy extends AbstractPolicy imp
             .description("An optional time when this policy should be first executed")
             .build();
 
+    public static final ConfigKey WAIT = ConfigKeys.builder(Duration.class)
+            .name("wait")
+            .description("An optional duration after which this policy should be first executed. The time config takes precedence if prese")
+            .constraint(Predicates.or(Predicates.isNull(), DurationPredicates.positive()))
+            .build();
+
     protected Effector effector;
     protected ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor();
     protected Object mutex = new Object[0];
@@ -108,10 +116,11 @@ protected Effector getEffector() {
 
     @Override
     public void run() {
-        LOG.warn("{}: run task tags: {}", this, Iterables.toString(Tasks.current().getTags()));
+        LOG.debug("{}: task tags before: {}", this, Iterables.toString(Tasks.current().getTags()));
         ConfigBag bag = ResolvingConfigBag.newInstanceExtending(getManagementContext(), config().getBag());
         Map args = EntityInitializers.resolve(bag, EFFECTOR_ARGUMENTS);
         TaskTags.addTagDynamically(Tasks.current(), BrooklynTaskTags.tagForContextEntity(entity)); // WHY?
+        LOG.debug("{}: task tags after: {}", this, Iterables.toString(Tasks.current().getTags()));
         bag.putAll(args);
         Map resolved = Maps.newLinkedHashMap();
         for (String key : args.keySet()) {
diff --git a/policy/src/main/java/org/apache/brooklyn/policy/action/PeriodicEffectorPolicy.java b/policy/src/main/java/org/apache/brooklyn/policy/action/PeriodicEffectorPolicy.java
index b93d65ac8b..13b5648184 100644
--- a/policy/src/main/java/org/apache/brooklyn/policy/action/PeriodicEffectorPolicy.java
+++ b/policy/src/main/java/org/apache/brooklyn/policy/action/PeriodicEffectorPolicy.java
@@ -94,9 +94,8 @@ public void onEvent(SensorEvent event) {
                     Boolean start = Boolean.parseBoolean(Strings.toString(event.getValue()));
                     if (start && running.compareAndSet(false, true)) {
                         Duration period = Preconditions.checkNotNull(config().get(PERIOD), "The period must be configured for this policy");
-                        long delay = period.toMilliseconds();
-                        long wait = delay;
                         String time = config().get(TIME);
+                        Duration wait = config().get(WAIT);
                         if (Strings.isNonBlank(time)) {
                             try {
                                 Date when = FORMATTER.parse(time);
@@ -104,16 +103,20 @@ public void onEvent(SensorEvent event) {
                                 if (when.before(now)) {
                                     throw new IllegalStateException("The time provided must be in the future: " + FORMATTER.format(time));
                                 }
-                                wait = Math.max(0, when.getTime() - now.getTime());
+                                wait = Duration.millis(Math.max(0, when.getTime() - now.getTime()));
                             } catch (ParseException e) {
                                 LOG.warn("The time must be formatted as " + TIME_FORMAT + " for this policy", e);
                                 Exceptions.propagate(e);
                             }
                         }
+                        if (wait == null) {
+                            wait = period;
+                        }
 
                         LOG.debug("{} scheduling {} every {} in {}", new Object[] { PeriodicEffectorPolicy.this, effector.getName(),
-                                Time.fromLongToTimeStringExact().apply(delay), Time.fromLongToTimeStringExact().apply(wait) });
-                        executor.scheduleWithFixedDelay(PeriodicEffectorPolicy.this, wait, delay, TimeUnit.MILLISECONDS);
+                                Time.fromDurationToTimeStringRounded().apply(period), Time.fromDurationToTimeStringRounded().apply(wait) });
+                        executor.scheduleWithFixedDelay(PeriodicEffectorPolicy.this, wait.toMilliseconds(), period.toMilliseconds(), TimeUnit.MILLISECONDS);
+                        LOG.debug("{} scheduled", PeriodicEffectorPolicy.this);
                     }
                 }
             }
diff --git a/policy/src/main/java/org/apache/brooklyn/policy/action/ScheduledEffectorPolicy.java b/policy/src/main/java/org/apache/brooklyn/policy/action/ScheduledEffectorPolicy.java
index 55e9fbcc95..dbefae1d66 100644
--- a/policy/src/main/java/org/apache/brooklyn/policy/action/ScheduledEffectorPolicy.java
+++ b/policy/src/main/java/org/apache/brooklyn/policy/action/ScheduledEffectorPolicy.java
@@ -18,9 +18,7 @@
  */
 package org.apache.brooklyn.policy.action;
 
-import java.text.DateFormat;
 import java.text.ParseException;
-import java.text.SimpleDateFormat;
 import java.util.Date;
 import java.util.Map;
 import java.util.concurrent.TimeUnit;
@@ -29,12 +27,11 @@
 import org.apache.brooklyn.api.sensor.AttributeSensor;
 import org.apache.brooklyn.api.sensor.SensorEvent;
 import org.apache.brooklyn.api.sensor.SensorEventListener;
-import org.apache.brooklyn.config.ConfigKey;
-import org.apache.brooklyn.core.config.ConfigKeys;
 import org.apache.brooklyn.core.sensor.Sensors;
 import org.apache.brooklyn.util.collections.MutableMap;
 import org.apache.brooklyn.util.exceptions.Exceptions;
 import org.apache.brooklyn.util.text.Strings;
+import org.apache.brooklyn.util.time.Duration;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -75,8 +72,11 @@ public void setEntity(final EntityLocal entity) {
         subscriptions().subscribe(entity, INVOKE_AT, handler);
 
         String time = config().get(TIME);
+        Duration wait = config().get(WAIT);
         if (Strings.isNonBlank(time)) {
             scheduleAt(time);
+        } else if (wait != null) {
+            executor.schedule(this, wait.toMilliseconds(), TimeUnit.MILLISECONDS);
         }
     }
 

From 8d1ad1eec4809ebb835ad6b55013643a986824d7 Mon Sep 17 00:00:00 2001
From: Andrew Donald Kennedy 
Date: Wed, 5 Apr 2017 02:06:58 +0100
Subject: [PATCH 31/43] Updating task submission in policy executor context

---
 .../AbstractScheduledEffectorPolicy.java      | 33 ++++++++++---------
 1 file changed, 18 insertions(+), 15 deletions(-)

diff --git a/policy/src/main/java/org/apache/brooklyn/policy/action/AbstractScheduledEffectorPolicy.java b/policy/src/main/java/org/apache/brooklyn/policy/action/AbstractScheduledEffectorPolicy.java
index 306afe97f0..cbda819f30 100644
--- a/policy/src/main/java/org/apache/brooklyn/policy/action/AbstractScheduledEffectorPolicy.java
+++ b/policy/src/main/java/org/apache/brooklyn/policy/action/AbstractScheduledEffectorPolicy.java
@@ -21,20 +21,20 @@
 import java.text.DateFormat;
 import java.text.SimpleDateFormat;
 import java.util.Map;
+import java.util.concurrent.Callable;
 import java.util.concurrent.Executors;
 import java.util.concurrent.ScheduledExecutorService;
 
 import org.apache.brooklyn.api.effector.Effector;
 import org.apache.brooklyn.api.entity.EntityLocal;
+import org.apache.brooklyn.api.mgmt.Task;
 import org.apache.brooklyn.config.ConfigKey;
 import org.apache.brooklyn.core.config.ConfigKeys;
 import org.apache.brooklyn.core.entity.EntityInitializers;
-import org.apache.brooklyn.core.mgmt.BrooklynTaskTags;
 import org.apache.brooklyn.core.policy.AbstractPolicy;
 import org.apache.brooklyn.util.collections.MutableMap;
 import org.apache.brooklyn.util.core.config.ConfigBag;
 import org.apache.brooklyn.util.core.config.ResolvingConfigBag;
-import org.apache.brooklyn.util.core.task.TaskTags;
 import org.apache.brooklyn.util.core.task.Tasks;
 import org.apache.brooklyn.util.guava.Maybe;
 import org.apache.brooklyn.util.time.Duration;
@@ -45,7 +45,6 @@
 import com.google.common.annotations.Beta;
 import com.google.common.base.Predicates;
 import com.google.common.collect.ImmutableMap;
-import com.google.common.collect.Iterables;
 import com.google.common.collect.Maps;
 import com.google.common.reflect.TypeToken;
 
@@ -116,18 +115,22 @@ protected Effector getEffector() {
 
     @Override
     public void run() {
-        LOG.debug("{}: task tags before: {}", this, Iterables.toString(Tasks.current().getTags()));
-        ConfigBag bag = ResolvingConfigBag.newInstanceExtending(getManagementContext(), config().getBag());
-        Map args = EntityInitializers.resolve(bag, EFFECTOR_ARGUMENTS);
-        TaskTags.addTagDynamically(Tasks.current(), BrooklynTaskTags.tagForContextEntity(entity)); // WHY?
-        LOG.debug("{}: task tags after: {}", this, Iterables.toString(Tasks.current().getTags()));
+        final ConfigBag bag = ResolvingConfigBag.newInstanceExtending(getManagementContext(), config().getBag());
+        final Map args = EntityInitializers.resolve(bag, EFFECTOR_ARGUMENTS);
         bag.putAll(args);
-        Map resolved = Maps.newLinkedHashMap();
-        for (String key : args.keySet()) {
-            resolved.put(key, bag.getStringKey(key));
-        }
-
-        LOG.debug("{} invoking effector on {}, effector={}, args={}", new Object[] { this, entity, effector.getName(), resolved });
-        entity.invoke(effector, args).getUnchecked();
+        Task> resolve = Tasks.create("resolveArguments", new Callable>() {
+            @Override
+            public Map call() {
+                Map resolved = Maps.newLinkedHashMap();
+                for (String key : args.keySet()) {
+                    resolved.put(key, bag.getStringKey(key));
+                }
+                return resolved;
+            }
+        });
+        getManagementContext().getExecutionContext(entity).submit(resolve);
+        Map resolved = resolve.getUnchecked();
+        LOG.debug("{}: invoking effector on {}, {}({})", new Object[] { this, entity, effector.getName(), resolved });
+        entity.invoke(effector, resolved);
     }
 }

From 9aeeee2f845150169dc54b1fb504260043eb9df8 Mon Sep 17 00:00:00 2001
From: Andrew Donald Kennedy 
Date: Wed, 5 Apr 2017 12:52:52 +0100
Subject: [PATCH 32/43] Better handling of wait time and synchronisation in
 scheduler policies

---
 .../AbstractScheduledEffectorPolicy.java      | 69 ++++++++++++++-----
 .../policy/action/PeriodicEffectorPolicy.java | 27 ++------
 .../action/ScheduledEffectorPolicy.java       | 24 ++-----
 3 files changed, 63 insertions(+), 57 deletions(-)

diff --git a/policy/src/main/java/org/apache/brooklyn/policy/action/AbstractScheduledEffectorPolicy.java b/policy/src/main/java/org/apache/brooklyn/policy/action/AbstractScheduledEffectorPolicy.java
index cbda819f30..d26eede675 100644
--- a/policy/src/main/java/org/apache/brooklyn/policy/action/AbstractScheduledEffectorPolicy.java
+++ b/policy/src/main/java/org/apache/brooklyn/policy/action/AbstractScheduledEffectorPolicy.java
@@ -19,7 +19,10 @@
 package org.apache.brooklyn.policy.action;
 
 import java.text.DateFormat;
+import java.text.ParseException;
 import java.text.SimpleDateFormat;
+import java.util.Calendar;
+import java.util.Date;
 import java.util.Map;
 import java.util.concurrent.Callable;
 import java.util.concurrent.Executors;
@@ -36,6 +39,7 @@
 import org.apache.brooklyn.util.core.config.ConfigBag;
 import org.apache.brooklyn.util.core.config.ResolvingConfigBag;
 import org.apache.brooklyn.util.core.task.Tasks;
+import org.apache.brooklyn.util.exceptions.Exceptions;
 import org.apache.brooklyn.util.guava.Maybe;
 import org.apache.brooklyn.util.time.Duration;
 import org.apache.brooklyn.util.time.DurationPredicates;
@@ -45,6 +49,7 @@
 import com.google.common.annotations.Beta;
 import com.google.common.base.Predicates;
 import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Iterables;
 import com.google.common.collect.Maps;
 import com.google.common.reflect.TypeToken;
 
@@ -53,8 +58,7 @@ public abstract class AbstractScheduledEffectorPolicy extends AbstractPolicy imp
 
     private static final Logger LOG = LoggerFactory.getLogger(AbstractScheduledEffectorPolicy.class);
 
-    public static final String TIME_FORMAT = "yyyy-MM-dd HH:mm:ss";
-
+    protected static final String TIME_FORMAT = "HH:mm:ss";
     protected static DateFormat FORMATTER = new SimpleDateFormat(TIME_FORMAT);
 
     public static final ConfigKey EFFECTOR = ConfigKeys.builder(String.class)
@@ -77,7 +81,7 @@ public abstract class AbstractScheduledEffectorPolicy extends AbstractPolicy imp
 
     public static final ConfigKey WAIT = ConfigKeys.builder(Duration.class)
             .name("wait")
-            .description("An optional duration after which this policy should be first executed. The time config takes precedence if prese")
+            .description("An optional duration after which this policy should be first executed. The time config takes precedence if present")
             .constraint(Predicates.or(Predicates.isNull(), DurationPredicates.positive()))
             .build();
 
@@ -113,24 +117,51 @@ protected Effector getEffector() {
         return effector.get();
     }
 
+    // TODO move to java.time classes in JDK 8
+    protected Duration getWaitUntil(String time) {
+        try {
+            Calendar now = Calendar.getInstance();
+            Calendar when = Calendar.getInstance();
+            Date parsed = FORMATTER.parse(time);
+            when.setTime(parsed);
+            when.set(now.get(Calendar.YEAR), now.get(Calendar.MONTH), now.get(Calendar.DATE));
+            if (when.before(now)) {
+                when.add(Calendar.DATE, 1);
+            }
+            return Duration.millis(Math.max(0, when.getTimeInMillis() - now.getTimeInMillis()));
+        } catch (ParseException e) {
+            LOG.warn("The time must be formatted as " + TIME_FORMAT + " for this policy", e);
+            throw Exceptions.propagate(e);
+        }
+    }
+
     @Override
     public void run() {
-        final ConfigBag bag = ResolvingConfigBag.newInstanceExtending(getManagementContext(), config().getBag());
-        final Map args = EntityInitializers.resolve(bag, EFFECTOR_ARGUMENTS);
-        bag.putAll(args);
-        Task> resolve = Tasks.create("resolveArguments", new Callable>() {
-            @Override
-            public Map call() {
-                Map resolved = Maps.newLinkedHashMap();
-                for (String key : args.keySet()) {
-                    resolved.put(key, bag.getStringKey(key));
-                }
-                return resolved;
+        synchronized (mutex) {
+            try {
+                final ConfigBag bag = ResolvingConfigBag.newInstanceExtending(getManagementContext(), config().getBag());
+                final Map args = EntityInitializers.resolve(bag, EFFECTOR_ARGUMENTS);
+                LOG.debug("{}: Resolving arguments for {}: {}", new Object[] { this, effector.getName(), Iterables.toString(args.keySet()) });
+                bag.putAll(args);
+                Task> resolve = Tasks.create("resolveArguments", new Callable>() {
+                    @Override
+                    public Map call() {
+                        Map resolved = Maps.newLinkedHashMap();
+                        for (String key : args.keySet()) {
+                            resolved.put(key, bag.getStringKey(key));
+                        }
+                        return resolved;
+                    }
+                });
+                getManagementContext().getExecutionContext(entity).submit(resolve);
+                Map resolved = resolve.getUnchecked();
+                LOG.debug("{}: Invoking effector on {}, {}({})", new Object[] { this, entity, effector.getName(), resolved });
+                Object result = entity.invoke(effector, resolved).getUnchecked();
+                LOG.debug("{}: Effector {} returned {}", new Object[] { this, effector.getName(), result });
+            } catch (Throwable t) {
+                LOG.warn("{}: Exception running {}: {}", new Object[] { this, effector.getName(), t.getMessage() });
+                Exceptions.propagate(t);
             }
-        });
-        getManagementContext().getExecutionContext(entity).submit(resolve);
-        Map resolved = resolve.getUnchecked();
-        LOG.debug("{}: invoking effector on {}, {}({})", new Object[] { this, entity, effector.getName(), resolved });
-        entity.invoke(effector, resolved);
+        }
     }
 }
diff --git a/policy/src/main/java/org/apache/brooklyn/policy/action/PeriodicEffectorPolicy.java b/policy/src/main/java/org/apache/brooklyn/policy/action/PeriodicEffectorPolicy.java
index 13b5648184..418f0a1ac4 100644
--- a/policy/src/main/java/org/apache/brooklyn/policy/action/PeriodicEffectorPolicy.java
+++ b/policy/src/main/java/org/apache/brooklyn/policy/action/PeriodicEffectorPolicy.java
@@ -18,8 +18,6 @@
  */
 package org.apache.brooklyn.policy.action;
 
-import java.text.ParseException;
-import java.util.Date;
 import java.util.Map;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicBoolean;
@@ -32,7 +30,6 @@
 import org.apache.brooklyn.core.config.ConfigKeys;
 import org.apache.brooklyn.core.sensor.Sensors;
 import org.apache.brooklyn.util.collections.MutableMap;
-import org.apache.brooklyn.util.exceptions.Exceptions;
 import org.apache.brooklyn.util.text.Strings;
 import org.apache.brooklyn.util.time.Duration;
 import org.apache.brooklyn.util.time.DurationPredicates;
@@ -52,6 +49,7 @@
  *       args:
  *         k: $brooklyn:config("repave.size")
  *       period: 1 day
+ *       time: 17:00:00
  * }
  */
 @Beta
@@ -89,7 +87,7 @@ public void setEntity(final EntityLocal entity) {
         @Override
         public void onEvent(SensorEvent event) {
             synchronized (mutex) {
-                LOG.debug("{} got event {}", PeriodicEffectorPolicy.this, event);
+                LOG.debug("{}: Got event {}", PeriodicEffectorPolicy.this, event);
                 if (event.getSensor().getName().equals(START_SCHEDULER.getName())) {
                     Boolean start = Boolean.parseBoolean(Strings.toString(event.getValue()));
                     if (start && running.compareAndSet(false, true)) {
@@ -97,26 +95,15 @@ public void onEvent(SensorEvent event) {
                         String time = config().get(TIME);
                         Duration wait = config().get(WAIT);
                         if (Strings.isNonBlank(time)) {
-                            try {
-                                Date when = FORMATTER.parse(time);
-                                Date now = new Date();
-                                if (when.before(now)) {
-                                    throw new IllegalStateException("The time provided must be in the future: " + FORMATTER.format(time));
-                                }
-                                wait = Duration.millis(Math.max(0, when.getTime() - now.getTime()));
-                            } catch (ParseException e) {
-                                LOG.warn("The time must be formatted as " + TIME_FORMAT + " for this policy", e);
-                                Exceptions.propagate(e);
-                            }
-                        }
-                        if (wait == null) {
+                            wait = getWaitUntil(time);
+                        } else if (wait == null) {
                             wait = period;
                         }
 
-                        LOG.debug("{} scheduling {} every {} in {}", new Object[] { PeriodicEffectorPolicy.this, effector.getName(),
+                        LOG.debug("{}: Scheduling {} every {} in {}", new Object[] { PeriodicEffectorPolicy.this, effector.getName(),
                                 Time.fromDurationToTimeStringRounded().apply(period), Time.fromDurationToTimeStringRounded().apply(wait) });
-                        executor.scheduleWithFixedDelay(PeriodicEffectorPolicy.this, wait.toMilliseconds(), period.toMilliseconds(), TimeUnit.MILLISECONDS);
-                        LOG.debug("{} scheduled", PeriodicEffectorPolicy.this);
+                        executor.scheduleAtFixedRate(PeriodicEffectorPolicy.this, wait.toMilliseconds(), period.toMilliseconds(), TimeUnit.MILLISECONDS);
+                        LOG.debug("{}: Scheduled", PeriodicEffectorPolicy.this);
                     }
                 }
             }
diff --git a/policy/src/main/java/org/apache/brooklyn/policy/action/ScheduledEffectorPolicy.java b/policy/src/main/java/org/apache/brooklyn/policy/action/ScheduledEffectorPolicy.java
index dbefae1d66..528552d080 100644
--- a/policy/src/main/java/org/apache/brooklyn/policy/action/ScheduledEffectorPolicy.java
+++ b/policy/src/main/java/org/apache/brooklyn/policy/action/ScheduledEffectorPolicy.java
@@ -18,8 +18,6 @@
  */
 package org.apache.brooklyn.policy.action;
 
-import java.text.ParseException;
-import java.util.Date;
 import java.util.Map;
 import java.util.concurrent.TimeUnit;
 
@@ -29,9 +27,9 @@
 import org.apache.brooklyn.api.sensor.SensorEventListener;
 import org.apache.brooklyn.core.sensor.Sensors;
 import org.apache.brooklyn.util.collections.MutableMap;
-import org.apache.brooklyn.util.exceptions.Exceptions;
 import org.apache.brooklyn.util.text.Strings;
 import org.apache.brooklyn.util.time.Duration;
+import org.apache.brooklyn.util.time.Time;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -45,7 +43,7 @@
  *       effector: repaveCluster
  *       args:
  *         k: $brooklyn:config("repave.size")
- *       time: 2017-12-11 12:00:00
+ *       time: 12:00:00
  * }
  */
 @Beta
@@ -81,26 +79,16 @@ public void setEntity(final EntityLocal entity) {
     }
 
     protected void scheduleAt(String time) {
-        try {
-            Date when = FORMATTER.parse(time);
-            Date now = new Date();
-            if (when.before(now)) {
-                throw new IllegalStateException("The time provided must be in the future: " + FORMATTER.format(time));
-            }
-            long difference = Math.max(0, when.getTime() - now.getTime());
-            LOG.debug("{} scheduling {} at {} (in {}ms)", new Object[] { this, effector.getName(), time, difference });
-            executor.schedule(this, difference, TimeUnit.MILLISECONDS);
-        } catch (ParseException e) {
-            LOG.warn("The time must be formatted as " + TIME_FORMAT + " for this policy", e);
-            Exceptions.propagate(e);
-        }
+        Duration wait = getWaitUntil(time);
+        LOG.debug("{}: Scheduling {} at {} (in {})", new Object[] { this, effector.getName(), time, Time.fromDurationToTimeStringRounded().apply(wait) });
+        executor.schedule(this, wait.toMilliseconds(), TimeUnit.MILLISECONDS);
     }
 
     private final SensorEventListener handler = new SensorEventListener() {
         @Override
         public void onEvent(SensorEvent event) {
             synchronized (mutex) {
-                LOG.debug("{} got event {}", ScheduledEffectorPolicy.this, event);
+                LOG.debug("{}: Got event {}", ScheduledEffectorPolicy.this, event);
                 if (event.getSensor().getName().equals(INVOKE_AT.getName())) {
                     String time = (String) event.getValue();
                     if (Strings.isNonBlank(time)) {

From 41dc2cc6fed59a25170f105bfcbf167e47a1c952 Mon Sep 17 00:00:00 2001
From: Andrew Donald Kennedy 
Date: Wed, 5 Apr 2017 23:29:34 +0100
Subject: [PATCH 33/43] Use Date object in scheduling policies for time config

---
 .../AbstractScheduledEffectorPolicy.java      | 37 +++++++------------
 .../policy/action/PeriodicEffectorPolicy.java |  8 ++--
 .../action/ScheduledEffectorPolicy.java       | 16 ++++----
 3 files changed, 25 insertions(+), 36 deletions(-)

diff --git a/policy/src/main/java/org/apache/brooklyn/policy/action/AbstractScheduledEffectorPolicy.java b/policy/src/main/java/org/apache/brooklyn/policy/action/AbstractScheduledEffectorPolicy.java
index d26eede675..2efa56ff56 100644
--- a/policy/src/main/java/org/apache/brooklyn/policy/action/AbstractScheduledEffectorPolicy.java
+++ b/policy/src/main/java/org/apache/brooklyn/policy/action/AbstractScheduledEffectorPolicy.java
@@ -18,9 +18,7 @@
  */
 package org.apache.brooklyn.policy.action;
 
-import java.text.DateFormat;
-import java.text.ParseException;
-import java.text.SimpleDateFormat;
+import java.time.LocalTime;
 import java.util.Calendar;
 import java.util.Date;
 import java.util.Map;
@@ -58,9 +56,6 @@ public abstract class AbstractScheduledEffectorPolicy extends AbstractPolicy imp
 
     private static final Logger LOG = LoggerFactory.getLogger(AbstractScheduledEffectorPolicy.class);
 
-    protected static final String TIME_FORMAT = "HH:mm:ss";
-    protected static DateFormat FORMATTER = new SimpleDateFormat(TIME_FORMAT);
-
     public static final ConfigKey EFFECTOR = ConfigKeys.builder(String.class)
             .name("effector")
             .description("The effector to be executed by this policy")
@@ -74,7 +69,7 @@ public abstract class AbstractScheduledEffectorPolicy extends AbstractPolicy imp
             .defaultValue(ImmutableMap.of())
             .build();
 
-    public static final ConfigKey TIME = ConfigKeys.builder(String.class)
+    public static final ConfigKey TIME = ConfigKeys.builder(Date.class)
             .name("time")
             .description("An optional time when this policy should be first executed")
             .build();
@@ -85,9 +80,10 @@ public abstract class AbstractScheduledEffectorPolicy extends AbstractPolicy imp
             .constraint(Predicates.or(Predicates.isNull(), DurationPredicates.positive()))
             .build();
 
+    protected final ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor();
+    protected final Object mutex = new Object[0];
+
     protected Effector effector;
-    protected ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor();
-    protected Object mutex = new Object[0];
 
     public AbstractScheduledEffectorPolicy() {
         this(MutableMap.of());
@@ -117,22 +113,15 @@ protected Effector getEffector() {
         return effector.get();
     }
 
-    // TODO move to java.time classes in JDK 8
-    protected Duration getWaitUntil(String time) {
-        try {
-            Calendar now = Calendar.getInstance();
-            Calendar when = Calendar.getInstance();
-            Date parsed = FORMATTER.parse(time);
-            when.setTime(parsed);
-            when.set(now.get(Calendar.YEAR), now.get(Calendar.MONTH), now.get(Calendar.DATE));
-            if (when.before(now)) {
-                when.add(Calendar.DATE, 1);
-            }
-            return Duration.millis(Math.max(0, when.getTimeInMillis() - now.getTimeInMillis()));
-        } catch (ParseException e) {
-            LOG.warn("The time must be formatted as " + TIME_FORMAT + " for this policy", e);
-            throw Exceptions.propagate(e);
+    protected Duration getWaitUntil(Date time) {
+        Calendar now = Calendar.getInstance();
+        Calendar when = Calendar.getInstance();
+        when.setTime(time);
+        when.set(now.get(Calendar.YEAR), now.get(Calendar.MONTH), now.get(Calendar.DATE));
+        if (when.before(now)) {
+            when.add(Calendar.DATE, 1);
         }
+        return Duration.millis(Math.max(0, when.getTimeInMillis() - now.getTimeInMillis()));
     }
 
     @Override
diff --git a/policy/src/main/java/org/apache/brooklyn/policy/action/PeriodicEffectorPolicy.java b/policy/src/main/java/org/apache/brooklyn/policy/action/PeriodicEffectorPolicy.java
index 418f0a1ac4..43a302cddd 100644
--- a/policy/src/main/java/org/apache/brooklyn/policy/action/PeriodicEffectorPolicy.java
+++ b/policy/src/main/java/org/apache/brooklyn/policy/action/PeriodicEffectorPolicy.java
@@ -18,6 +18,7 @@
  */
 package org.apache.brooklyn.policy.action;
 
+import java.util.Date;
 import java.util.Map;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicBoolean;
@@ -30,7 +31,6 @@
 import org.apache.brooklyn.core.config.ConfigKeys;
 import org.apache.brooklyn.core.sensor.Sensors;
 import org.apache.brooklyn.util.collections.MutableMap;
-import org.apache.brooklyn.util.text.Strings;
 import org.apache.brooklyn.util.time.Duration;
 import org.apache.brooklyn.util.time.DurationPredicates;
 import org.apache.brooklyn.util.time.Time;
@@ -89,12 +89,12 @@ public void onEvent(SensorEvent event) {
             synchronized (mutex) {
                 LOG.debug("{}: Got event {}", PeriodicEffectorPolicy.this, event);
                 if (event.getSensor().getName().equals(START_SCHEDULER.getName())) {
-                    Boolean start = Boolean.parseBoolean(Strings.toString(event.getValue()));
+                    Boolean start = (Boolean) event.getValue();
                     if (start && running.compareAndSet(false, true)) {
                         Duration period = Preconditions.checkNotNull(config().get(PERIOD), "The period must be configured for this policy");
-                        String time = config().get(TIME);
+                        Date time = config().get(TIME);
                         Duration wait = config().get(WAIT);
-                        if (Strings.isNonBlank(time)) {
+                        if (time != null) {
                             wait = getWaitUntil(time);
                         } else if (wait == null) {
                             wait = period;
diff --git a/policy/src/main/java/org/apache/brooklyn/policy/action/ScheduledEffectorPolicy.java b/policy/src/main/java/org/apache/brooklyn/policy/action/ScheduledEffectorPolicy.java
index 528552d080..bfa931b1c4 100644
--- a/policy/src/main/java/org/apache/brooklyn/policy/action/ScheduledEffectorPolicy.java
+++ b/policy/src/main/java/org/apache/brooklyn/policy/action/ScheduledEffectorPolicy.java
@@ -18,6 +18,7 @@
  */
 package org.apache.brooklyn.policy.action;
 
+import java.util.Date;
 import java.util.Map;
 import java.util.concurrent.TimeUnit;
 
@@ -27,7 +28,6 @@
 import org.apache.brooklyn.api.sensor.SensorEventListener;
 import org.apache.brooklyn.core.sensor.Sensors;
 import org.apache.brooklyn.util.collections.MutableMap;
-import org.apache.brooklyn.util.text.Strings;
 import org.apache.brooklyn.util.time.Duration;
 import org.apache.brooklyn.util.time.Time;
 import org.slf4j.Logger;
@@ -52,7 +52,7 @@ public class ScheduledEffectorPolicy extends AbstractScheduledEffectorPolicy {
     private static final Logger LOG = LoggerFactory.getLogger(ScheduledEffectorPolicy.class);
 
     public static final AttributeSensor INVOKE_IMMEDIATELY = Sensors.newBooleanSensor("scheduler.invoke.now", "Invoke the configured effector immediately when this becomes true");
-    public static final AttributeSensor INVOKE_AT = Sensors.newStringSensor("scheduler.invoke.at", "Invoke the configured effector at this time");
+    public static final AttributeSensor INVOKE_AT = Sensors.newSensor(Date.class, "scheduler.invoke.at", "Invoke the configured effector at this time");
 
     public ScheduledEffectorPolicy() {
         this(MutableMap.of());
@@ -69,16 +69,16 @@ public void setEntity(final EntityLocal entity) {
         subscriptions().subscribe(entity, INVOKE_IMMEDIATELY, handler);
         subscriptions().subscribe(entity, INVOKE_AT, handler);
 
-        String time = config().get(TIME);
+        Date time = config().get(TIME);
         Duration wait = config().get(WAIT);
-        if (Strings.isNonBlank(time)) {
+        if (time != null) {
             scheduleAt(time);
         } else if (wait != null) {
             executor.schedule(this, wait.toMilliseconds(), TimeUnit.MILLISECONDS);
         }
     }
 
-    protected void scheduleAt(String time) {
+    protected void scheduleAt(Date time) {
         Duration wait = getWaitUntil(time);
         LOG.debug("{}: Scheduling {} at {} (in {})", new Object[] { this, effector.getName(), time, Time.fromDurationToTimeStringRounded().apply(wait) });
         executor.schedule(this, wait.toMilliseconds(), TimeUnit.MILLISECONDS);
@@ -90,13 +90,13 @@ public void onEvent(SensorEvent event) {
             synchronized (mutex) {
                 LOG.debug("{}: Got event {}", ScheduledEffectorPolicy.this, event);
                 if (event.getSensor().getName().equals(INVOKE_AT.getName())) {
-                    String time = (String) event.getValue();
-                    if (Strings.isNonBlank(time)) {
+                    Date time = (Date) event.getValue();
+                    if (time != null) {
                         scheduleAt(time);
                     }
                 }
                 if (event.getSensor().getName().equals(INVOKE_IMMEDIATELY.getName())) {
-                    Boolean invoke = Boolean.parseBoolean(Strings.toString(event.getValue()));
+                    Boolean invoke = (Boolean) event.getValue();
                     if (invoke) {
                         executor.submit(ScheduledEffectorPolicy.this);
                     }

From cf45d4760980d15134bd3eb078e344c87f2ad040 Mon Sep 17 00:00:00 2001
From: Andrew Donald Kennedy 
Date: Thu, 6 Apr 2017 01:15:08 +0100
Subject: [PATCH 34/43] Revert to string time representation and DateFormat

---
 .../AbstractScheduledEffectorPolicy.java      | 33 +++++++++++++------
 .../policy/action/PeriodicEffectorPolicy.java |  3 +-
 .../action/ScheduledEffectorPolicy.java       |  6 ++--
 3 files changed, 27 insertions(+), 15 deletions(-)

diff --git a/policy/src/main/java/org/apache/brooklyn/policy/action/AbstractScheduledEffectorPolicy.java b/policy/src/main/java/org/apache/brooklyn/policy/action/AbstractScheduledEffectorPolicy.java
index 2efa56ff56..eaf5883aff 100644
--- a/policy/src/main/java/org/apache/brooklyn/policy/action/AbstractScheduledEffectorPolicy.java
+++ b/policy/src/main/java/org/apache/brooklyn/policy/action/AbstractScheduledEffectorPolicy.java
@@ -18,6 +18,9 @@
  */
 package org.apache.brooklyn.policy.action;
 
+import java.text.DateFormat;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
 import java.time.LocalTime;
 import java.util.Calendar;
 import java.util.Date;
@@ -56,6 +59,9 @@ public abstract class AbstractScheduledEffectorPolicy extends AbstractPolicy imp
 
     private static final Logger LOG = LoggerFactory.getLogger(AbstractScheduledEffectorPolicy.class);
 
+    private static final String TIME_FORMAT = "HH:mm:ss";
+    private static final DateFormat FORMATTER = SimpleDateFormat.getTimeInstance();
+
     public static final ConfigKey EFFECTOR = ConfigKeys.builder(String.class)
             .name("effector")
             .description("The effector to be executed by this policy")
@@ -69,9 +75,9 @@ public abstract class AbstractScheduledEffectorPolicy extends AbstractPolicy imp
             .defaultValue(ImmutableMap.of())
             .build();
 
-    public static final ConfigKey TIME = ConfigKeys.builder(Date.class)
+    public static final ConfigKey TIME = ConfigKeys.builder(String.class)
             .name("time")
-            .description("An optional time when this policy should be first executed")
+            .description("An optional time when this policy should be first executed, formatted as HH:mm:ss")
             .build();
 
     public static final ConfigKey WAIT = ConfigKeys.builder(Duration.class)
@@ -113,15 +119,22 @@ protected Effector getEffector() {
         return effector.get();
     }
 
-    protected Duration getWaitUntil(Date time) {
-        Calendar now = Calendar.getInstance();
-        Calendar when = Calendar.getInstance();
-        when.setTime(time);
-        when.set(now.get(Calendar.YEAR), now.get(Calendar.MONTH), now.get(Calendar.DATE));
-        if (when.before(now)) {
-            when.add(Calendar.DATE, 1);
+    protected Duration getWaitUntil(String time) {
+        try {
+            Calendar now = Calendar.getInstance();
+            Calendar when = Calendar.getInstance();
+            boolean formatted = time.contains(":"); // FIXME deprecated TimeDuration coercion
+            Date parsed = formatted ? FORMATTER.parse(time) : new Date(Long.parseLong(time) * 1000);
+            when.setTime(parsed);
+            when.set(now.get(Calendar.YEAR), now.get(Calendar.MONTH), now.get(Calendar.DATE));
+            if (when.before(now)) {
+                when.add(Calendar.DATE, 1);
+            }
+            return Duration.millis(Math.max(0, when.getTimeInMillis() - now.getTimeInMillis()));
+        } catch (ParseException | NumberFormatException e) {
+            LOG.warn("{}: Time should be formatted as {}: {}", new Object[] { this, TIME_FORMAT, e.getMessage() });
+            throw Exceptions.propagate(e);
         }
-        return Duration.millis(Math.max(0, when.getTimeInMillis() - now.getTimeInMillis()));
     }
 
     @Override
diff --git a/policy/src/main/java/org/apache/brooklyn/policy/action/PeriodicEffectorPolicy.java b/policy/src/main/java/org/apache/brooklyn/policy/action/PeriodicEffectorPolicy.java
index 43a302cddd..d1322244d8 100644
--- a/policy/src/main/java/org/apache/brooklyn/policy/action/PeriodicEffectorPolicy.java
+++ b/policy/src/main/java/org/apache/brooklyn/policy/action/PeriodicEffectorPolicy.java
@@ -92,7 +92,7 @@ public void onEvent(SensorEvent event) {
                     Boolean start = (Boolean) event.getValue();
                     if (start && running.compareAndSet(false, true)) {
                         Duration period = Preconditions.checkNotNull(config().get(PERIOD), "The period must be configured for this policy");
-                        Date time = config().get(TIME);
+                        String time = config().get(TIME);
                         Duration wait = config().get(WAIT);
                         if (time != null) {
                             wait = getWaitUntil(time);
@@ -103,7 +103,6 @@ public void onEvent(SensorEvent event) {
                         LOG.debug("{}: Scheduling {} every {} in {}", new Object[] { PeriodicEffectorPolicy.this, effector.getName(),
                                 Time.fromDurationToTimeStringRounded().apply(period), Time.fromDurationToTimeStringRounded().apply(wait) });
                         executor.scheduleAtFixedRate(PeriodicEffectorPolicy.this, wait.toMilliseconds(), period.toMilliseconds(), TimeUnit.MILLISECONDS);
-                        LOG.debug("{}: Scheduled", PeriodicEffectorPolicy.this);
                     }
                 }
             }
diff --git a/policy/src/main/java/org/apache/brooklyn/policy/action/ScheduledEffectorPolicy.java b/policy/src/main/java/org/apache/brooklyn/policy/action/ScheduledEffectorPolicy.java
index bfa931b1c4..3be57b9fe8 100644
--- a/policy/src/main/java/org/apache/brooklyn/policy/action/ScheduledEffectorPolicy.java
+++ b/policy/src/main/java/org/apache/brooklyn/policy/action/ScheduledEffectorPolicy.java
@@ -69,7 +69,7 @@ public void setEntity(final EntityLocal entity) {
         subscriptions().subscribe(entity, INVOKE_IMMEDIATELY, handler);
         subscriptions().subscribe(entity, INVOKE_AT, handler);
 
-        Date time = config().get(TIME);
+        String time = config().get(TIME);
         Duration wait = config().get(WAIT);
         if (time != null) {
             scheduleAt(time);
@@ -78,7 +78,7 @@ public void setEntity(final EntityLocal entity) {
         }
     }
 
-    protected void scheduleAt(Date time) {
+    protected void scheduleAt(String time) {
         Duration wait = getWaitUntil(time);
         LOG.debug("{}: Scheduling {} at {} (in {})", new Object[] { this, effector.getName(), time, Time.fromDurationToTimeStringRounded().apply(wait) });
         executor.schedule(this, wait.toMilliseconds(), TimeUnit.MILLISECONDS);
@@ -90,7 +90,7 @@ public void onEvent(SensorEvent event) {
             synchronized (mutex) {
                 LOG.debug("{}: Got event {}", ScheduledEffectorPolicy.this, event);
                 if (event.getSensor().getName().equals(INVOKE_AT.getName())) {
-                    Date time = (Date) event.getValue();
+                    String time = (String) event.getValue();
                     if (time != null) {
                         scheduleAt(time);
                     }

From 4dbbe728d2fa4c93f30ede07d7f103390e7e3129 Mon Sep 17 00:00:00 2001
From: Andrew Donald Kennedy 
Date: Sat, 8 Apr 2017 02:20:33 +0100
Subject: [PATCH 35/43] Added new SetSensorEffector to set attributes on
 entities

---
 .../core/effector/SetSensorEffector.java      | 105 ++++++++++++++++++
 1 file changed, 105 insertions(+)
 create mode 100644 core/src/main/java/org/apache/brooklyn/core/effector/SetSensorEffector.java

diff --git a/core/src/main/java/org/apache/brooklyn/core/effector/SetSensorEffector.java b/core/src/main/java/org/apache/brooklyn/core/effector/SetSensorEffector.java
new file mode 100644
index 0000000000..ca3816644f
--- /dev/null
+++ b/core/src/main/java/org/apache/brooklyn/core/effector/SetSensorEffector.java
@@ -0,0 +1,105 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.brooklyn.core.effector;
+
+import java.util.Map;
+
+import org.apache.brooklyn.api.effector.Effector;
+import org.apache.brooklyn.api.sensor.AttributeSensor;
+import org.apache.brooklyn.config.ConfigKey;
+import org.apache.brooklyn.core.config.ConfigKeys;
+import org.apache.brooklyn.core.effector.Effectors.EffectorBuilder;
+import org.apache.brooklyn.core.entity.EntityInitializers;
+import org.apache.brooklyn.util.core.config.ConfigBag;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.common.annotations.Beta;
+import com.google.common.base.Preconditions;
+import com.google.common.reflect.TypeToken;
+
+/**
+ * 
{@code
+ * brooklyn.initializers:
+ *   - type: org.apache.brooklyn.core.effector.SetSensorEffector
+ *     brooklyn.config:
+ *       name: setStatus
+ *       sensor: $brooklyn:sensor("myentity.status")
+ * }
+ */ +@Beta +public class SetSensorEffector extends AddEffector { + + private static final Logger LOG = LoggerFactory.getLogger(SetSensorEffector.class); + + public static final ConfigKey> SENSOR = ConfigKeys.newConfigKey( + new TypeToken>() { }, + "sensor", + "The sensor whose value is to be set"); + + public static final ConfigKey VALUE = ConfigKeys.newConfigKey(Object.class, + "value", + "The value to be set on the sensor"); + + private final Object mutex = new Object[0]; + + public SetSensorEffector(Map params) { + this(ConfigBag.newInstance(params)); + } + + public SetSensorEffector(ConfigBag params) { + this(newEffectorBuilder(params).build()); + } + + public SetSensorEffector(Effector effector) { + super(effector); + } + + public static EffectorBuilder newEffectorBuilder(ConfigBag params) { + EffectorBuilder eb = AddEffector.newEffectorBuilder(Object.class, params); + EffectorBody body = new Body(eb.buildAbstract(), params); + eb.impl(body); + return eb; + } + + protected static class Body extends EffectorBody { + protected final Effector effector; + protected final ConfigBag config; + protected final Object mutex = new Object[0]; + + public Body(Effector eff, ConfigBag config) { + this.effector = eff; + this.config = config; + + Preconditions.checkNotNull(config.getAllConfigRaw().get(SENSOR.getName()), "The sensor must be supplied when defining this effector"); + } + + @Override + public Object call(final ConfigBag params) { + synchronized (mutex) { + LOG.debug("{}: Effector called with config {}, params {}", new Object[] { this, config, params }); + AttributeSensor sensor = EntityInitializers.resolve(config, SENSOR); + Object value = EntityInitializers.resolve(params, VALUE); + Object old = entity().sensors().set(sensor, value); + LOG.debug("{}: Effector set {} to {} (was {})", new Object[] { this, sensor.getName(), value, old }); + return old; + } + } + } +} From 2c12d9e732880c0b144ad1d842266363dbf2f433 Mon Sep 17 00:00:00 2001 From: Andrew Donald Kennedy Date: Sun, 9 Apr 2017 15:10:26 +0100 Subject: [PATCH 36/43] Accept now or immediately as time specification for policy scheduling --- .../action/AbstractScheduledEffectorPolicy.java | 11 ++++++++--- .../policy/action/PeriodicEffectorPolicy.java | 1 - .../policy/action/ScheduledEffectorPolicy.java | 5 ++++- 3 files changed, 12 insertions(+), 5 deletions(-) diff --git a/policy/src/main/java/org/apache/brooklyn/policy/action/AbstractScheduledEffectorPolicy.java b/policy/src/main/java/org/apache/brooklyn/policy/action/AbstractScheduledEffectorPolicy.java index eaf5883aff..e1e13a9271 100644 --- a/policy/src/main/java/org/apache/brooklyn/policy/action/AbstractScheduledEffectorPolicy.java +++ b/policy/src/main/java/org/apache/brooklyn/policy/action/AbstractScheduledEffectorPolicy.java @@ -21,7 +21,6 @@ import java.text.DateFormat; import java.text.ParseException; import java.text.SimpleDateFormat; -import java.time.LocalTime; import java.util.Calendar; import java.util.Date; import java.util.Map; @@ -59,7 +58,10 @@ public abstract class AbstractScheduledEffectorPolicy extends AbstractPolicy imp private static final Logger LOG = LoggerFactory.getLogger(AbstractScheduledEffectorPolicy.class); - private static final String TIME_FORMAT = "HH:mm:ss"; + public static final String TIME_FORMAT = "HH:mm:ss"; + public static final String NOW = "now"; + public static final String IMMEDIATELY = "immediately"; + private static final DateFormat FORMATTER = SimpleDateFormat.getTimeInstance(); public static final ConfigKey EFFECTOR = ConfigKeys.builder(String.class) @@ -113,13 +115,16 @@ public void destroy(){ protected Effector getEffector() { String effectorName = config().get(EFFECTOR); Maybe> effector = entity.getEntityType().getEffectorByName(effectorName); - if (effector.isAbsent()) { + if (effector.isAbsentOrNull()) { throw new IllegalStateException("Cannot find effector " + effectorName); } return effector.get(); } protected Duration getWaitUntil(String time) { + if (time.equalsIgnoreCase(NOW) || time.equalsIgnoreCase(IMMEDIATELY)) { + return Duration.ZERO; + } try { Calendar now = Calendar.getInstance(); Calendar when = Calendar.getInstance(); diff --git a/policy/src/main/java/org/apache/brooklyn/policy/action/PeriodicEffectorPolicy.java b/policy/src/main/java/org/apache/brooklyn/policy/action/PeriodicEffectorPolicy.java index d1322244d8..119fe64e57 100644 --- a/policy/src/main/java/org/apache/brooklyn/policy/action/PeriodicEffectorPolicy.java +++ b/policy/src/main/java/org/apache/brooklyn/policy/action/PeriodicEffectorPolicy.java @@ -18,7 +18,6 @@ */ package org.apache.brooklyn.policy.action; -import java.util.Date; import java.util.Map; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; diff --git a/policy/src/main/java/org/apache/brooklyn/policy/action/ScheduledEffectorPolicy.java b/policy/src/main/java/org/apache/brooklyn/policy/action/ScheduledEffectorPolicy.java index 3be57b9fe8..74a310a688 100644 --- a/policy/src/main/java/org/apache/brooklyn/policy/action/ScheduledEffectorPolicy.java +++ b/policy/src/main/java/org/apache/brooklyn/policy/action/ScheduledEffectorPolicy.java @@ -74,13 +74,16 @@ public void setEntity(final EntityLocal entity) { if (time != null) { scheduleAt(time); } else if (wait != null) { + LOG.debug("{}: Scheduling {} in {} ({} ms)", + new Object[] { this, effector.getName(), Time.fromDurationToTimeStringRounded().apply(wait), wait.toMilliseconds() }); executor.schedule(this, wait.toMilliseconds(), TimeUnit.MILLISECONDS); } } protected void scheduleAt(String time) { Duration wait = getWaitUntil(time); - LOG.debug("{}: Scheduling {} at {} (in {})", new Object[] { this, effector.getName(), time, Time.fromDurationToTimeStringRounded().apply(wait) }); + LOG.debug("{}: Scheduling {} at {} (in {})", + new Object[] { this, effector.getName(), time, Time.fromDurationToTimeStringRounded().apply(wait) }); executor.schedule(this, wait.toMilliseconds(), TimeUnit.MILLISECONDS); } From 12c5970a29a627113913ea6530b2da54f2d5ced5 Mon Sep 17 00:00:00 2001 From: Andrew Donald Kennedy Date: Sun, 9 Apr 2017 22:55:58 +0100 Subject: [PATCH 37/43] Update Javadoc and remove @Beta annotations from effectors --- .../core/effector/AddChildrenEffector.java | 8 +- .../brooklyn/core/effector/AddSensor.java | 104 +------------- .../core/effector/SetSensorEffector.java | 2 + .../composite/AbstractCompositeEffector.java | 3 + .../effector/composite/ChoiceEffector.java | 6 + .../effector/composite/ComposeEffector.java | 8 +- .../core/effector/composite/LoopEffector.java | 8 +- .../effector/composite/ReplaceEffector.java | 11 +- .../effector/composite/SequenceEffector.java | 7 +- .../effector/composite/TransformEffector.java | 7 +- .../core/effector/ssh/SshEffectorTasks.java | 9 +- .../brooklyn/core/sensor/AddSensor.java | 128 ++++++++++++++++++ .../core/sensor/http/HttpRequestSensor.java | 4 +- .../sensor/password/CreatePasswordSensor.java | 2 +- .../core/sensor/ssh/SshCommandSensor.java | 26 ++-- 15 files changed, 200 insertions(+), 133 deletions(-) create mode 100644 core/src/main/java/org/apache/brooklyn/core/sensor/AddSensor.java diff --git a/core/src/main/java/org/apache/brooklyn/core/effector/AddChildrenEffector.java b/core/src/main/java/org/apache/brooklyn/core/effector/AddChildrenEffector.java index 3f5d77a84c..fe79774312 100644 --- a/core/src/main/java/org/apache/brooklyn/core/effector/AddChildrenEffector.java +++ b/core/src/main/java/org/apache/brooklyn/core/effector/AddChildrenEffector.java @@ -35,17 +35,17 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import com.google.common.annotations.Beta; import com.google.common.collect.Iterables; -/** Entity initializer which defines an effector which adds a child blueprint to an entity. +/** + * Entity initializer which defines an effector which adds a child blueprint to an entity. *

* One of the config keys {@link #BLUEPRINT_YAML} (containing a YAML blueprint (map or string)) * or {@link #BLUEPRINT_TYPE} (containing a string referring to a catalog type) should be supplied, but not both. * Parameters defined here are supplied as config during the entity creation. * - * @since 0.7.0 */ -@Beta + * @since 0.7.0 + */ public class AddChildrenEffector extends AddEffector { private static final Logger log = LoggerFactory.getLogger(AddChildrenEffector.class); diff --git a/core/src/main/java/org/apache/brooklyn/core/effector/AddSensor.java b/core/src/main/java/org/apache/brooklyn/core/effector/AddSensor.java index 92cc4ec140..12b2ebf114 100644 --- a/core/src/main/java/org/apache/brooklyn/core/effector/AddSensor.java +++ b/core/src/main/java/org/apache/brooklyn/core/effector/AddSensor.java @@ -20,112 +20,20 @@ import java.util.Map; -import org.apache.brooklyn.api.entity.Entity; -import org.apache.brooklyn.api.entity.EntityInitializer; -import org.apache.brooklyn.api.entity.EntityLocal; -import org.apache.brooklyn.api.sensor.AttributeSensor; -import org.apache.brooklyn.config.ConfigKey; -import org.apache.brooklyn.core.config.ConfigKeys; -import org.apache.brooklyn.core.entity.EntityInternal; -import org.apache.brooklyn.core.sensor.Sensors; -import org.apache.brooklyn.util.core.ClassLoaderUtils; import org.apache.brooklyn.util.core.config.ConfigBag; -import org.apache.brooklyn.util.guava.Maybe; -import org.apache.brooklyn.util.javalang.Boxing; -import org.apache.brooklyn.util.time.Duration; - -import com.google.common.annotations.Beta; -import com.google.common.base.Preconditions; /** - * Creates a new {@link AttributeSensor} on an entity. - *

- * The configuration can include the sensor {@code name}, {@code period} and {@code targetType}. - * For the targetType, currently this only supports classes on the initial classpath, not those in - * OSGi bundles added at runtime. - * - * @since 0.7.0 + * @deprecated use {@link org.apache.brooklyn.core.sensor.AddSensor} instead */ -@Beta -public class AddSensor implements EntityInitializer { - - public static final ConfigKey SENSOR_NAME = ConfigKeys.newStringConfigKey("name", "The name of the sensor to create"); - public static final ConfigKey SENSOR_PERIOD = ConfigKeys.newConfigKey(Duration.class, "period", "Period, including units e.g. 1m or 5s or 200ms; default 5 minutes", Duration.FIVE_MINUTES); - public static final ConfigKey SENSOR_TYPE = ConfigKeys.newStringConfigKey("targetType", "Target type for the value; default String", "java.lang.String"); - - protected final String name; - protected final Duration period; - protected final String type; - protected AttributeSensor sensor; - protected final ConfigBag params; +@Deprecated +public class AddSensor extends org.apache.brooklyn.core.sensor.AddSensor { public AddSensor(Map params) { - this(ConfigBag.newInstance(params)); + super(params); } public AddSensor(final ConfigBag params) { - this.name = Preconditions.checkNotNull(params.get(SENSOR_NAME), "Name must be supplied when defining a sensor"); - this.period = params.get(SENSOR_PERIOD); - this.type = params.get(SENSOR_TYPE); - this.params = params; - } - - @Override - public void apply(EntityLocal entity) { - sensor = newSensor(entity); - ((EntityInternal) entity).getMutableEntityType().addSensor(sensor); - } - - private AttributeSensor newSensor(Entity entity) { - String className = getFullClassName(type); - Class clazz = getType(entity, className); - return Sensors.newSensor(clazz, name); - } - - @SuppressWarnings("unchecked") - protected Class getType(Entity entity, String className) { - try { - // TODO use OSGi loader (low priority however); also ensure that allows primitives - Maybe> primitive = Boxing.getPrimitiveType(className); - if (primitive.isPresent()) return (Class) primitive.get(); - - return (Class) new ClassLoaderUtils(this, entity).loadClass(className); - } catch (ClassNotFoundException e) { - if (!className.contains(".")) { - // could be assuming "java.lang" package; try again with that - try { - return (Class) Class.forName("java.lang."+className); - } catch (ClassNotFoundException e2) { - throw new IllegalArgumentException("Invalid target type for sensor "+name+": " + className+" (also tried java.lang."+className+")"); - } - } else { - throw new IllegalArgumentException("Invalid target type for sensor "+name+": " + className); - } - } - } - - protected String getFullClassName(String className) { - if (className.equalsIgnoreCase("string")) { - return "java.lang.String"; - } else if (className.equalsIgnoreCase("int") || className.equalsIgnoreCase("integer")) { - return "java.lang.Integer"; - } else if (className.equalsIgnoreCase("long")) { - return "java.lang.Long"; - } else if (className.equalsIgnoreCase("float")) { - return "java.lang.Float"; - } else if (className.equalsIgnoreCase("double")) { - return "java.lang.Double"; - } else if (className.equalsIgnoreCase("bool") || className.equalsIgnoreCase("boolean")) { - return "java.lang.Boolean"; - } else if (className.equalsIgnoreCase("byte")) { - return "java.lang.Byte"; - } else if (className.equalsIgnoreCase("char") || className.equalsIgnoreCase("character")) { - return "java.lang.Character"; - } else if (className.equalsIgnoreCase("object")) { - return "java.lang.Object"; - } else { - return className; - } + super(params); } -} +} \ No newline at end of file diff --git a/core/src/main/java/org/apache/brooklyn/core/effector/SetSensorEffector.java b/core/src/main/java/org/apache/brooklyn/core/effector/SetSensorEffector.java index ca3816644f..d386f1a9f6 100644 --- a/core/src/main/java/org/apache/brooklyn/core/effector/SetSensorEffector.java +++ b/core/src/main/java/org/apache/brooklyn/core/effector/SetSensorEffector.java @@ -42,6 +42,8 @@ * name: setStatus * sensor: $brooklyn:sensor("myentity.status") * } + * + * @since 0.11.0 */ @Beta public class SetSensorEffector extends AddEffector { diff --git a/core/src/main/java/org/apache/brooklyn/core/effector/composite/AbstractCompositeEffector.java b/core/src/main/java/org/apache/brooklyn/core/effector/composite/AbstractCompositeEffector.java index cf7c3650dd..a36d816fa0 100644 --- a/core/src/main/java/org/apache/brooklyn/core/effector/composite/AbstractCompositeEffector.java +++ b/core/src/main/java/org/apache/brooklyn/core/effector/composite/AbstractCompositeEffector.java @@ -34,6 +34,9 @@ import com.google.common.annotations.Beta; import com.google.common.collect.Iterables; +/** + * @since 0.11.0 + */ @Beta public abstract class AbstractCompositeEffector extends AddEffector { diff --git a/core/src/main/java/org/apache/brooklyn/core/effector/composite/ChoiceEffector.java b/core/src/main/java/org/apache/brooklyn/core/effector/composite/ChoiceEffector.java index 51f4bfc912..563a038123 100644 --- a/core/src/main/java/org/apache/brooklyn/core/effector/composite/ChoiceEffector.java +++ b/core/src/main/java/org/apache/brooklyn/core/effector/composite/ChoiceEffector.java @@ -36,6 +36,12 @@ import com.google.common.annotations.Beta; import com.google.common.base.Preconditions; +/** + * Execute an effector, and depending on the result, execute either + * the success or failure effector. + * + * @since 0.11.0 + */ @Beta public class ChoiceEffector extends AbstractCompositeEffector { diff --git a/core/src/main/java/org/apache/brooklyn/core/effector/composite/ComposeEffector.java b/core/src/main/java/org/apache/brooklyn/core/effector/composite/ComposeEffector.java index bda5276c5e..8a6c489068 100644 --- a/core/src/main/java/org/apache/brooklyn/core/effector/composite/ComposeEffector.java +++ b/core/src/main/java/org/apache/brooklyn/core/effector/composite/ComposeEffector.java @@ -38,6 +38,12 @@ import com.google.common.collect.ImmutableList; import com.google.common.reflect.TypeToken; +/** + * Execute a series of effectors using the output from one as + * the input for the next. + * + * @since 0.11.0 + */ @Beta public class ComposeEffector extends AbstractCompositeEffector { @@ -68,7 +74,7 @@ protected static class Body extends AbstractCompositeEffector.Body { public Body(Effector eff, ConfigBag config) { super(eff, config); - Preconditions.checkNotNull(config.getAllConfigRaw().get(COMPOSE.getName()), "Effector names must be supplied when defining this effector"); + Preconditions.checkNotNull(config.getAllConfigRaw().get(COMPOSE.getName()), "Compose effector names must be supplied when defining this effector"); } @Override diff --git a/core/src/main/java/org/apache/brooklyn/core/effector/composite/LoopEffector.java b/core/src/main/java/org/apache/brooklyn/core/effector/composite/LoopEffector.java index 63e8b9f1e2..1209e1a19f 100644 --- a/core/src/main/java/org/apache/brooklyn/core/effector/composite/LoopEffector.java +++ b/core/src/main/java/org/apache/brooklyn/core/effector/composite/LoopEffector.java @@ -38,6 +38,12 @@ import com.google.common.base.Preconditions; import com.google.common.collect.Lists; +/** + * Execute an effector repeatedly against each element of an + * input collection. + * + * @since 0.11.0 + */ @Beta public class LoopEffector extends AbstractCompositeEffector { @@ -71,7 +77,7 @@ protected static class Body extends AbstractCompositeEffector.Body { public Body(Effector eff, ConfigBag config) { super(eff, config); - Preconditions.checkNotNull(config.getAllConfigRaw().get(LOOP.getName()), "Effector names must be supplied when defining this effector"); + Preconditions.checkNotNull(config.getAllConfigRaw().get(LOOP.getName()), "Loop effector names must be supplied when defining this effector"); } @Override diff --git a/core/src/main/java/org/apache/brooklyn/core/effector/composite/ReplaceEffector.java b/core/src/main/java/org/apache/brooklyn/core/effector/composite/ReplaceEffector.java index a9a27929d4..f14774a401 100644 --- a/core/src/main/java/org/apache/brooklyn/core/effector/composite/ReplaceEffector.java +++ b/core/src/main/java/org/apache/brooklyn/core/effector/composite/ReplaceEffector.java @@ -39,10 +39,17 @@ import com.google.common.annotations.Beta; import com.google.common.base.Preconditions; +/** + * Replace an effector with another, optionally executing the originsl + * {@link ReplaceAction#POST before} or {@link ReplaceAction#PRE after} + * the replacement. + * + * @since 0.11.0 + */ @Beta public final class ReplaceEffector extends AbstractCompositeEffector { - private static final Logger LOG = LoggerFactory.getLogger(TransformEffector.class); + private static final Logger LOG = LoggerFactory.getLogger(ReplaceEffector.class); public enum ReplaceAction { PRE, @@ -92,7 +99,7 @@ protected static class Body extends AbstractCompositeEffector.Body { public Body(Effector eff, ConfigBag config) { super(eff, config); - Preconditions.checkNotNull(config.getAllConfigRaw().get(REPLACE.getName()), "Effector details must be supplied when defining this effector"); + Preconditions.checkNotNull(config.getAllConfigRaw().get(REPLACE.getName()), "Replace effector details must be supplied when defining this effector"); } @Override diff --git a/core/src/main/java/org/apache/brooklyn/core/effector/composite/SequenceEffector.java b/core/src/main/java/org/apache/brooklyn/core/effector/composite/SequenceEffector.java index 7174dc214b..e7a0afb486 100644 --- a/core/src/main/java/org/apache/brooklyn/core/effector/composite/SequenceEffector.java +++ b/core/src/main/java/org/apache/brooklyn/core/effector/composite/SequenceEffector.java @@ -38,6 +38,11 @@ import com.google.common.collect.ImmutableList; import com.google.common.reflect.TypeToken; +/** + * Execute a sequence of effectors with the same input. + * + * @since 0.11.0 + */ @Beta public class SequenceEffector extends AbstractCompositeEffector { @@ -68,7 +73,7 @@ protected static class Body extends AbstractCompositeEffector.Body { public Body(Effector eff, ConfigBag config) { super(eff, config); - Preconditions.checkNotNull(config.getAllConfigRaw().get(SEQUENCE.getName()), "Effector names must be supplied when defining this effector"); + Preconditions.checkNotNull(config.getAllConfigRaw().get(SEQUENCE.getName()), "Sequence effector details must be supplied when defining this effector"); } @Override diff --git a/core/src/main/java/org/apache/brooklyn/core/effector/composite/TransformEffector.java b/core/src/main/java/org/apache/brooklyn/core/effector/composite/TransformEffector.java index d7fc9729e7..df07aa58a7 100644 --- a/core/src/main/java/org/apache/brooklyn/core/effector/composite/TransformEffector.java +++ b/core/src/main/java/org/apache/brooklyn/core/effector/composite/TransformEffector.java @@ -37,6 +37,11 @@ import com.google.common.base.Preconditions; import com.google.common.reflect.TypeToken; +/** + * Execute a {@link Function} as an effector body. + * + * @since 0.11.0 + */ @Beta public class TransformEffector extends AbstractCompositeEffector { @@ -71,7 +76,7 @@ protected static class Body extends AbstractCompositeEffector.Body { public Body(Effector eff, ConfigBag config) { super(eff, config); - Preconditions.checkNotNull(config.getAllConfigRaw().get(FUNCTION.getName()), "Function must be supplied when defining this effector"); + Preconditions.checkNotNull(config.getAllConfigRaw().get(FUNCTION.getName()), "Transform function must be supplied when defining this effector"); } @Override diff --git a/core/src/main/java/org/apache/brooklyn/core/effector/ssh/SshEffectorTasks.java b/core/src/main/java/org/apache/brooklyn/core/effector/ssh/SshEffectorTasks.java index e28581773d..3a502facb5 100644 --- a/core/src/main/java/org/apache/brooklyn/core/effector/ssh/SshEffectorTasks.java +++ b/core/src/main/java/org/apache/brooklyn/core/effector/ssh/SshEffectorTasks.java @@ -58,7 +58,6 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import com.google.common.annotations.Beta; import com.google.common.base.Function; import com.google.common.collect.Maps; @@ -72,7 +71,6 @@ * @see SshTasks * @since 0.6.0 */ -@Beta public class SshEffectorTasks { private static final Logger log = LoggerFactory.getLogger(SshEffectorTasks.class); @@ -291,13 +289,14 @@ public static SshEffectorTaskFactory isPidFromFileRunning(String pidFil }); } - /** extracts the values for the main brooklyn.ssh.config.* config keys (i.e. those declared in ConfigKeys) + /** + * Extracts the values for the main brooklyn.ssh.config.* config keys (i.e. those declared in ConfigKeys) * as declared on the entity, and inserts them in a map using the unprefixed state, for ssh. *

* currently this is computed for each call, which may be wasteful, but it is reliable in the face of config changes. * we could cache the Map. note that we do _not_ cache (or even own) the SshTool; - * the SshTool is created or re-used by the SshMachineLocation making use of these properties */ - @Beta + * the SshTool is created or re-used by the SshMachineLocation making use of these properties + */ public static Map getSshFlags(Entity entity, Location optionalLocation) { Set> sshConfig = MutableSet.of(); diff --git a/core/src/main/java/org/apache/brooklyn/core/sensor/AddSensor.java b/core/src/main/java/org/apache/brooklyn/core/sensor/AddSensor.java new file mode 100644 index 0000000000..12ef257e81 --- /dev/null +++ b/core/src/main/java/org/apache/brooklyn/core/sensor/AddSensor.java @@ -0,0 +1,128 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.brooklyn.core.sensor; + +import java.util.Map; + +import org.apache.brooklyn.api.entity.Entity; +import org.apache.brooklyn.api.entity.EntityInitializer; +import org.apache.brooklyn.api.entity.EntityLocal; +import org.apache.brooklyn.api.sensor.AttributeSensor; +import org.apache.brooklyn.config.ConfigKey; +import org.apache.brooklyn.core.config.ConfigKeys; +import org.apache.brooklyn.core.entity.EntityInternal; +import org.apache.brooklyn.util.core.ClassLoaderUtils; +import org.apache.brooklyn.util.core.config.ConfigBag; +import org.apache.brooklyn.util.guava.Maybe; +import org.apache.brooklyn.util.javalang.Boxing; +import org.apache.brooklyn.util.time.Duration; + +import com.google.common.base.Preconditions; + +/** + * Creates a new {@link AttributeSensor} on an entity. + *

+ * The configuration can include the sensor {@code name}, {@code period} and {@code targetType}. + * For the targetType, currently this only supports classes on the initial classpath, not those in + * OSGi bundles added at runtime. + * + * @since 0.7.0 + */ +public class AddSensor implements EntityInitializer { + + public static final ConfigKey SENSOR_NAME = ConfigKeys.newStringConfigKey("name", "The name of the sensor to create"); + public static final ConfigKey SENSOR_PERIOD = ConfigKeys.newConfigKey(Duration.class, "period", "Period, including units e.g. 1m or 5s or 200ms; default 5 minutes", Duration.FIVE_MINUTES); + public static final ConfigKey SENSOR_TYPE = ConfigKeys.newStringConfigKey("targetType", "Target type for the value; default String", "java.lang.String"); + + protected final String name; + protected final Duration period; + protected final String type; + protected AttributeSensor sensor; + protected final ConfigBag params; + + public AddSensor(Map params) { + this(ConfigBag.newInstance(params)); + } + + public AddSensor(final ConfigBag params) { + this.name = Preconditions.checkNotNull(params.get(SENSOR_NAME), "Name must be supplied when defining a sensor"); + this.period = params.get(SENSOR_PERIOD); + this.type = params.get(SENSOR_TYPE); + this.params = params; + } + + @Override + public void apply(EntityLocal entity) { + sensor = newSensor(entity); + ((EntityInternal) entity).getMutableEntityType().addSensor(sensor); + } + + private AttributeSensor newSensor(Entity entity) { + String className = getFullClassName(type); + Class clazz = getType(entity, className); + return Sensors.newSensor(clazz, name); + } + + @SuppressWarnings("unchecked") + protected Class getType(Entity entity, String className) { + try { + // TODO use OSGi loader (low priority however); also ensure that allows primitives + Maybe> primitive = Boxing.getPrimitiveType(className); + if (primitive.isPresent()) return (Class) primitive.get(); + + return (Class) new ClassLoaderUtils(this, entity).loadClass(className); + } catch (ClassNotFoundException e) { + if (!className.contains(".")) { + // could be assuming "java.lang" package; try again with that + try { + return (Class) Class.forName("java.lang."+className); + } catch (ClassNotFoundException e2) { + throw new IllegalArgumentException("Invalid target type for sensor "+name+": " + className+" (also tried java.lang."+className+")"); + } + } else { + throw new IllegalArgumentException("Invalid target type for sensor "+name+": " + className); + } + } + } + + protected String getFullClassName(String className) { + if (className.equalsIgnoreCase("string")) { + return "java.lang.String"; + } else if (className.equalsIgnoreCase("int") || className.equalsIgnoreCase("integer")) { + return "java.lang.Integer"; + } else if (className.equalsIgnoreCase("long")) { + return "java.lang.Long"; + } else if (className.equalsIgnoreCase("float")) { + return "java.lang.Float"; + } else if (className.equalsIgnoreCase("double")) { + return "java.lang.Double"; + } else if (className.equalsIgnoreCase("bool") || className.equalsIgnoreCase("boolean")) { + return "java.lang.Boolean"; + } else if (className.equalsIgnoreCase("byte")) { + return "java.lang.Byte"; + } else if (className.equalsIgnoreCase("char") || className.equalsIgnoreCase("character")) { + return "java.lang.Character"; + } else if (className.equalsIgnoreCase("object")) { + return "java.lang.Object"; + } else { + return className; + } + } + +} diff --git a/core/src/main/java/org/apache/brooklyn/core/sensor/http/HttpRequestSensor.java b/core/src/main/java/org/apache/brooklyn/core/sensor/http/HttpRequestSensor.java index 22e24f1950..9358f3d1f1 100644 --- a/core/src/main/java/org/apache/brooklyn/core/sensor/http/HttpRequestSensor.java +++ b/core/src/main/java/org/apache/brooklyn/core/sensor/http/HttpRequestSensor.java @@ -25,8 +25,8 @@ import org.apache.brooklyn.config.ConfigKey; import org.apache.brooklyn.core.config.ConfigKeys; import org.apache.brooklyn.core.config.MapConfigKey; -import org.apache.brooklyn.core.effector.AddSensor; import org.apache.brooklyn.core.entity.EntityInitializers; +import org.apache.brooklyn.core.sensor.AddSensor; import org.apache.brooklyn.core.sensor.ssh.SshCommandSensor; import org.apache.brooklyn.feed.http.HttpFeed; import org.apache.brooklyn.feed.http.HttpPollConfig; @@ -35,7 +35,6 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import com.google.common.annotations.Beta; import com.google.common.base.Functions; import com.google.common.base.Supplier; @@ -47,7 +46,6 @@ * * @see SshCommandSensor */ -@Beta public final class HttpRequestSensor extends AddSensor { private static final Logger LOG = LoggerFactory.getLogger(HttpRequestSensor.class); diff --git a/core/src/main/java/org/apache/brooklyn/core/sensor/password/CreatePasswordSensor.java b/core/src/main/java/org/apache/brooklyn/core/sensor/password/CreatePasswordSensor.java index 7b7a908bdf..7d7dda1200 100644 --- a/core/src/main/java/org/apache/brooklyn/core/sensor/password/CreatePasswordSensor.java +++ b/core/src/main/java/org/apache/brooklyn/core/sensor/password/CreatePasswordSensor.java @@ -23,7 +23,7 @@ import org.apache.brooklyn.api.entity.EntityLocal; import org.apache.brooklyn.config.ConfigKey; import org.apache.brooklyn.core.config.ConfigKeys; -import org.apache.brooklyn.core.effector.AddSensor; +import org.apache.brooklyn.core.sensor.AddSensor; import org.apache.brooklyn.util.core.config.ConfigBag; import org.apache.brooklyn.util.text.Identifiers; diff --git a/core/src/main/java/org/apache/brooklyn/core/sensor/ssh/SshCommandSensor.java b/core/src/main/java/org/apache/brooklyn/core/sensor/ssh/SshCommandSensor.java index fc93d7425d..24d19c55a2 100644 --- a/core/src/main/java/org/apache/brooklyn/core/sensor/ssh/SshCommandSensor.java +++ b/core/src/main/java/org/apache/brooklyn/core/sensor/ssh/SshCommandSensor.java @@ -22,28 +22,17 @@ import java.util.concurrent.ExecutionException; import org.apache.brooklyn.api.entity.Entity; -import org.apache.brooklyn.feed.CommandPollConfig; -import org.apache.brooklyn.feed.ssh.SshFeed; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import com.google.common.annotations.Beta; -import com.google.common.base.Function; -import com.google.common.base.Functions; -import com.google.common.base.Preconditions; -import com.google.common.base.Supplier; - import org.apache.brooklyn.api.entity.EntityInitializer; import org.apache.brooklyn.api.entity.EntityLocal; import org.apache.brooklyn.config.ConfigKey; import org.apache.brooklyn.core.config.ConfigKeys; import org.apache.brooklyn.core.config.MapConfigKey; -import org.apache.brooklyn.core.effector.AddSensor; import org.apache.brooklyn.core.entity.BrooklynConfigKeys; import org.apache.brooklyn.core.entity.EntityInternal; +import org.apache.brooklyn.core.sensor.AddSensor; import org.apache.brooklyn.core.sensor.http.HttpRequestSensor; -import org.apache.brooklyn.feed.AbstractCommandFeed; -import org.apache.brooklyn.feed.ssh.SshPollConfig; +import org.apache.brooklyn.feed.CommandPollConfig; +import org.apache.brooklyn.feed.ssh.SshFeed; import org.apache.brooklyn.feed.ssh.SshValueFunctions; import org.apache.brooklyn.util.collections.MutableMap; import org.apache.brooklyn.util.core.config.ConfigBag; @@ -53,6 +42,13 @@ import org.apache.brooklyn.util.exceptions.Exceptions; import org.apache.brooklyn.util.os.Os; import org.apache.brooklyn.util.text.Strings; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.google.common.base.Function; +import com.google.common.base.Functions; +import com.google.common.base.Preconditions; +import com.google.common.base.Supplier; /** * Configurable {@link EntityInitializer} which adds an SSH sensor feed running the command supplied @@ -61,7 +57,6 @@ * * @see HttpRequestSensor */ -@Beta public final class SshCommandSensor extends AddSensor { private static final Logger LOG = LoggerFactory.getLogger(SshCommandSensor.class); @@ -142,7 +137,6 @@ public T apply(String input) { entity.addFeed(feed); } - @Beta public static String makeCommandExecutingInDirectory(String command, String executionDir, Entity entity) { String finalCommand = command; String execDir = executionDir; From 8ab2a8b33c990ae94b453f24f5645dc24f657f8f Mon Sep 17 00:00:00 2001 From: Andrew Donald Kennedy Date: Sun, 9 Apr 2017 23:13:20 +0100 Subject: [PATCH 38/43] Add parallel effector and parallelize loop effector --- .../composite/AbstractCompositeEffector.java | 9 +- .../core/effector/composite/LoopEffector.java | 17 ++- .../effector/composite/ParallelEffector.java | 113 ++++++++++++++++++ 3 files changed, 130 insertions(+), 9 deletions(-) create mode 100644 core/src/main/java/org/apache/brooklyn/core/effector/composite/ParallelEffector.java diff --git a/core/src/main/java/org/apache/brooklyn/core/effector/composite/AbstractCompositeEffector.java b/core/src/main/java/org/apache/brooklyn/core/effector/composite/AbstractCompositeEffector.java index a36d816fa0..db11058f97 100644 --- a/core/src/main/java/org/apache/brooklyn/core/effector/composite/AbstractCompositeEffector.java +++ b/core/src/main/java/org/apache/brooklyn/core/effector/composite/AbstractCompositeEffector.java @@ -23,6 +23,7 @@ import org.apache.brooklyn.api.effector.Effector; import org.apache.brooklyn.api.entity.Entity; +import org.apache.brooklyn.api.mgmt.Task; import org.apache.brooklyn.core.effector.AddEffector; import org.apache.brooklyn.core.effector.EffectorBody; import org.apache.brooklyn.util.core.config.ConfigBag; @@ -162,15 +163,17 @@ protected String getInputParameter(Object effectorDetails) { return inputParameter; } - protected Object invokeEffectorNamed(Entity target, String effectorName, ConfigBag params) { + protected Task submitEffectorNamed(Entity target, String effectorName, ConfigBag params) { LOG.debug("{} invoking {} with params {}", new Object[] { this, effectorName, params }); Maybe> effector = target.getEntityType().getEffectorByName(effectorName); if (effector.isAbsent()) { throw new IllegalStateException("Cannot find effector " + effectorName); } - return target.invoke(effector.get(), params.getAllConfig()).getUnchecked(); + return target.invoke(effector.get(), params.getAllConfig()); } + protected Object invokeEffectorNamed(Entity target, String effectorName, ConfigBag params) { + return submitEffectorNamed(target, effectorName, params).getUnchecked(); + } } - } diff --git a/core/src/main/java/org/apache/brooklyn/core/effector/composite/LoopEffector.java b/core/src/main/java/org/apache/brooklyn/core/effector/composite/LoopEffector.java index 1209e1a19f..9d5860867a 100644 --- a/core/src/main/java/org/apache/brooklyn/core/effector/composite/LoopEffector.java +++ b/core/src/main/java/org/apache/brooklyn/core/effector/composite/LoopEffector.java @@ -24,6 +24,7 @@ import org.apache.brooklyn.api.effector.Effector; import org.apache.brooklyn.api.entity.Entity; +import org.apache.brooklyn.api.mgmt.Task; import org.apache.brooklyn.config.ConfigKey; import org.apache.brooklyn.core.config.ConfigKeys; import org.apache.brooklyn.core.effector.AddEffector; @@ -36,6 +37,7 @@ import com.google.common.annotations.Beta; import com.google.common.base.Preconditions; +import com.google.common.collect.Iterables; import com.google.common.collect.Lists; /** @@ -92,8 +94,6 @@ public List call(final ConfigBag params) { } Collection inputCollection = (Collection) inputObject; - List result = Lists.newArrayList(); - String effectorName = getEffectorName(effectorDetails); String inputArgument = getInputArgument(effectorDetails); Entity targetEntity = getTargetEntity(effectorDetails); @@ -103,13 +103,18 @@ public List call(final ConfigBag params) { throw new IllegalArgumentException("Input is not set for this effector: " + effectorDetails); } - for (Object inputEach : inputCollection) { - params.putStringKey(inputArgument, inputEach); + List> tasks = Lists.newArrayList(); + for (Object each : inputCollection) { + params.putStringKey(inputArgument, each); + tasks.add(submitEffectorNamed(targetEntity, effectorName, params)); + } - result.add(invokeEffectorNamed(targetEntity, effectorName, params)); + List result = Lists.newArrayList(); + for (Task each : tasks) { + result.add(each.getUnchecked()); } - LOG.debug("{} effector {} returned {}", new Object[] { this, effector.getName(), result }); + LOG.debug("{} effector {} returned {}", new Object[] { this, effector.getName(), Iterables.toString(result) }); return result; } } diff --git a/core/src/main/java/org/apache/brooklyn/core/effector/composite/ParallelEffector.java b/core/src/main/java/org/apache/brooklyn/core/effector/composite/ParallelEffector.java new file mode 100644 index 0000000000..b1b73e4d07 --- /dev/null +++ b/core/src/main/java/org/apache/brooklyn/core/effector/composite/ParallelEffector.java @@ -0,0 +1,113 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.brooklyn.core.effector.composite; + +import java.util.List; +import java.util.Map; + +import org.apache.brooklyn.api.effector.Effector; +import org.apache.brooklyn.api.entity.Entity; +import org.apache.brooklyn.api.mgmt.Task; +import org.apache.brooklyn.config.ConfigKey; +import org.apache.brooklyn.core.config.ConfigKeys; +import org.apache.brooklyn.core.effector.AddEffector; +import org.apache.brooklyn.core.effector.EffectorBody; +import org.apache.brooklyn.core.effector.Effectors.EffectorBuilder; +import org.apache.brooklyn.core.entity.EntityInitializers; +import org.apache.brooklyn.util.core.config.ConfigBag; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.google.common.annotations.Beta; +import com.google.common.base.Preconditions; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.Lists; +import com.google.common.reflect.TypeToken; + +/** + * Execute a sequence of effectors with the same input. + * + * @since 0.11.0 + */ +@Beta +public class ParallelEffector extends AbstractCompositeEffector { + + private static final Logger LOG = LoggerFactory.getLogger(ParallelEffector.class); + + public static final ConfigKey> PARALLEL = ConfigKeys.newConfigKey( + new TypeToken>() { }, + "parallel", + "Effector details list for the parallel effector", + ImmutableList.of()); + + public ParallelEffector(ConfigBag params) { + super(newEffectorBuilder(params).build()); + } + + public ParallelEffector(Map params) { + this(ConfigBag.newInstance(params)); + } + + public static EffectorBuilder newEffectorBuilder(ConfigBag params) { + EffectorBuilder eff = AddEffector.newEffectorBuilder(Object.class, params); + EffectorBody body = new Body(eff.buildAbstract(), params); + eff.impl(body); + return eff; + } + + protected static class Body extends AbstractCompositeEffector.Body { + + public Body(Effector eff, ConfigBag config) { + super(eff, config); + Preconditions.checkNotNull(config.getAllConfigRaw().get(PARALLEL.getName()), "Parallel effector names must be supplied when defining this effector"); + } + + @Override + public Object call(final ConfigBag params) { + synchronized (mutex) { + LOG.debug("{} called with config {}, params {}", new Object[] { this, config, params }); + List effectors = EntityInitializers.resolve(config, PARALLEL); + + List> tasks = Lists.newArrayList(); + for (Object effectorDetails : effectors) { + String effectorName = getEffectorName(effectorDetails); + String inputArgument = getInputArgument(effectorDetails); + Entity targetEntity = getTargetEntity(effectorDetails); + LOG.debug("{} executing {}({}) on {}", new Object[] { this, effectorName, inputArgument, targetEntity }); + + if (inputArgument == null) { + throw new IllegalArgumentException("Input is not set for this effector: " + effectorDetails); + } + Object input = params.getStringKey(inputArgument); + params.putStringKey(inputArgument, input); + + tasks.add(submitEffectorNamed(targetEntity, effectorName, params)); + } + + Object result = null; + for (Task each : tasks) { + result = each.getUnchecked(); + } + + LOG.debug("{} effector {} returned {}", new Object[] { this, effector.getName(), result }); + return result; + } + } + } +} From 453d1035e6567d01bb0b0e98bf6af8852ebfefbf Mon Sep 17 00:00:00 2001 From: Andrew Donald Kennedy Date: Mon, 10 Apr 2017 05:48:08 +0100 Subject: [PATCH 39/43] Use deep resolving task for policy effector args --- .../AbstractScheduledEffectorPolicy.java | 25 ++++++------------- 1 file changed, 7 insertions(+), 18 deletions(-) diff --git a/policy/src/main/java/org/apache/brooklyn/policy/action/AbstractScheduledEffectorPolicy.java b/policy/src/main/java/org/apache/brooklyn/policy/action/AbstractScheduledEffectorPolicy.java index e1e13a9271..2afff65e3c 100644 --- a/policy/src/main/java/org/apache/brooklyn/policy/action/AbstractScheduledEffectorPolicy.java +++ b/policy/src/main/java/org/apache/brooklyn/policy/action/AbstractScheduledEffectorPolicy.java @@ -24,13 +24,11 @@ import java.util.Calendar; import java.util.Date; import java.util.Map; -import java.util.concurrent.Callable; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import org.apache.brooklyn.api.effector.Effector; import org.apache.brooklyn.api.entity.EntityLocal; -import org.apache.brooklyn.api.mgmt.Task; import org.apache.brooklyn.config.ConfigKey; import org.apache.brooklyn.core.config.ConfigKeys; import org.apache.brooklyn.core.entity.EntityInitializers; @@ -50,7 +48,6 @@ import com.google.common.base.Predicates; import com.google.common.collect.ImmutableMap; import com.google.common.collect.Iterables; -import com.google.common.collect.Maps; import com.google.common.reflect.TypeToken; @Beta @@ -146,22 +143,14 @@ protected Duration getWaitUntil(String time) { public void run() { synchronized (mutex) { try { - final ConfigBag bag = ResolvingConfigBag.newInstanceExtending(getManagementContext(), config().getBag()); - final Map args = EntityInitializers.resolve(bag, EFFECTOR_ARGUMENTS); + ConfigBag bag = ResolvingConfigBag.newInstanceExtending(getManagementContext(), config().getBag()); + Map args = EntityInitializers.resolve(bag, EFFECTOR_ARGUMENTS); LOG.debug("{}: Resolving arguments for {}: {}", new Object[] { this, effector.getName(), Iterables.toString(args.keySet()) }); - bag.putAll(args); - Task> resolve = Tasks.create("resolveArguments", new Callable>() { - @Override - public Map call() { - Map resolved = Maps.newLinkedHashMap(); - for (String key : args.keySet()) { - resolved.put(key, bag.getStringKey(key)); - } - return resolved; - } - }); - getManagementContext().getExecutionContext(entity).submit(resolve); - Map resolved = resolve.getUnchecked(); + Map resolved = (Map) Tasks.resolving(args, Object.class) + .deep(true) + .context(entity) + .get(); + LOG.debug("{}: Invoking effector on {}, {}({})", new Object[] { this, entity, effector.getName(), resolved }); Object result = entity.invoke(effector, resolved).getUnchecked(); LOG.debug("{}: Effector {} returned {}", new Object[] { this, effector.getName(), result }); From 275b547691df51a152c39a0da6b6123c95cf7cb2 Mon Sep 17 00:00:00 2001 From: Andrew Donald Kennedy Date: Tue, 11 Apr 2017 20:47:00 +0100 Subject: [PATCH 40/43] Add new RepeatEffector implementation --- .../effector/composite/RepeatEffector.java | 118 ++++++++++++++++++ 1 file changed, 118 insertions(+) create mode 100644 core/src/main/java/org/apache/brooklyn/core/effector/composite/RepeatEffector.java diff --git a/core/src/main/java/org/apache/brooklyn/core/effector/composite/RepeatEffector.java b/core/src/main/java/org/apache/brooklyn/core/effector/composite/RepeatEffector.java new file mode 100644 index 0000000000..35515cfe0d --- /dev/null +++ b/core/src/main/java/org/apache/brooklyn/core/effector/composite/RepeatEffector.java @@ -0,0 +1,118 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.brooklyn.core.effector.composite; + +import java.util.List; +import java.util.Map; + +import org.apache.brooklyn.api.effector.Effector; +import org.apache.brooklyn.api.entity.Entity; +import org.apache.brooklyn.config.ConfigKey; +import org.apache.brooklyn.core.config.ConfigKeys; +import org.apache.brooklyn.core.effector.AddEffector; +import org.apache.brooklyn.core.effector.EffectorBody; +import org.apache.brooklyn.core.effector.Effectors.EffectorBuilder; +import org.apache.brooklyn.core.entity.EntityInitializers; +import org.apache.brooklyn.util.core.config.ConfigBag; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.google.common.annotations.Beta; +import com.google.common.base.Preconditions; +import com.google.common.collect.Iterables; +import com.google.common.collect.Lists; + +/** + * Execute an effector a selected number of times. + * + * @since 0.11.0 + */ +@Beta +public class RepeatEffector extends AbstractCompositeEffector { + + private static final Logger LOG = LoggerFactory.getLogger(RepeatEffector.class); + + public static final ConfigKey REPEAT = ConfigKeys.newConfigKey( + Object.class, + "repeat", + "Effector details list for the repeat effector"); + + public static final ConfigKey COUNT = ConfigKeys.newIntegerConfigKey( + "count", + "Number of times to rpeat the effector", + 1); + + public RepeatEffector(ConfigBag params) { + super(newEffectorBuilder(params).build()); + } + + public RepeatEffector(Map params) { + this(ConfigBag.newInstance(params)); + } + + public static EffectorBuilder newEffectorBuilder(ConfigBag params) { + EffectorBuilder eff = AddEffector.newEffectorBuilder(List.class, params); + EffectorBody body = new Body(eff.buildAbstract(), params); + eff.impl(body); + return eff; + } + + protected static class Body extends AbstractCompositeEffector.Body { + + public Body(Effector eff, ConfigBag config) { + super(eff, config); + Preconditions.checkNotNull(config.getAllConfigRaw().get(REPEAT.getName()), "Repeat effector details must be supplied when defining this effector"); + } + + @Override + public List call(final ConfigBag params) { + synchronized (mutex) { + LOG.debug("{} called with config {}, params {}", new Object[] { this, config, params }); + Object effectorDetails = EntityInitializers.resolve(config, REPEAT); + int count = EntityInitializers.resolve(config, COUNT); + + String effectorName = getEffectorName(effectorDetails); + String inputArgument = getInputArgument(effectorDetails); + String inputParameter = getInputParameter(effectorDetails); + Entity targetEntity = getTargetEntity(effectorDetails); + LOG.debug("{} executing {}({}:{}) on {}", new Object[] { this, effectorName, inputArgument, inputParameter, targetEntity }); + + if (inputArgument == null) { + throw new IllegalArgumentException("Input is not set for this effector: " + effectorDetails); + } + if (inputParameter == null) { + Object input = params.getStringKey(inputArgument); + params.putStringKey(inputArgument, input); + } else { + Object input = params.getStringKey(inputParameter); + params.putStringKey(inputArgument, input); + } + + List results = Lists.newArrayList(); + for (int i = 0; i < count ; i++) { + Object result = invokeEffectorNamed(targetEntity, effectorName, params); + results.add(result); + } + + LOG.debug("{} effector {} returned {}", new Object[] { this, effector.getName(), Iterables.toString(results) }); + return results; + } + } + } +} From 6c3bcea454f1b1228916446686073a80760cf94d Mon Sep 17 00:00:00 2001 From: Andrew Donald Kennedy Date: Tue, 11 Apr 2017 21:22:44 +0100 Subject: [PATCH 41/43] Use config key builder for composite effectors --- .../effector/composite/ChoiceEffector.java | 35 +++++++++++-------- .../effector/composite/ComposeEffector.java | 12 ++++--- .../core/effector/composite/LoopEffector.java | 17 +++++---- .../effector/composite/ParallelEffector.java | 12 ++++--- .../effector/composite/RepeatEffector.java | 23 +++++++----- .../effector/composite/ReplaceEffector.java | 22 ++++++------ .../effector/composite/SequenceEffector.java | 12 ++++--- .../effector/composite/TransformEffector.java | 19 +++++----- ...nvokeEffectorOnCollectionSensorChange.java | 6 ++-- 9 files changed, 91 insertions(+), 67 deletions(-) diff --git a/core/src/main/java/org/apache/brooklyn/core/effector/composite/ChoiceEffector.java b/core/src/main/java/org/apache/brooklyn/core/effector/composite/ChoiceEffector.java index 563a038123..12dd9e54e2 100644 --- a/core/src/main/java/org/apache/brooklyn/core/effector/composite/ChoiceEffector.java +++ b/core/src/main/java/org/apache/brooklyn/core/effector/composite/ChoiceEffector.java @@ -35,6 +35,7 @@ import com.google.common.annotations.Beta; import com.google.common.base.Preconditions; +import com.google.common.base.Predicates; /** * Execute an effector, and depending on the result, execute either @@ -47,24 +48,27 @@ public class ChoiceEffector extends AbstractCompositeEffector { private static final Logger LOG = LoggerFactory.getLogger(ChoiceEffector.class); - public static final ConfigKey INPUT = ConfigKeys.newStringConfigKey( - "input", - "Choice input parameter"); + public static final ConfigKey INPUT = ConfigKeys.builder(String.class) + .name("input") + .description("Choice input parameter") + .build(); - public static final ConfigKey CHOICE = ConfigKeys.newConfigKey( - Object.class, - "choice", - "Effector details for the choice effector"); + public static final ConfigKey CHOICE = ConfigKeys.builder(Object.class) + .name("choice") + .description("Effector details for the choice effector") + .constraint(Predicates.notNull()) + .build(); - public static final ConfigKey SUCCESS = ConfigKeys.newConfigKey( - Object.class, - "success", - "Effector details for the success effector"); + public static final ConfigKey SUCCESS = ConfigKeys.builder(Object.class) + .name("success") + .description("Effector details for the success effector") + .constraint(Predicates.notNull()) + .build(); - public static final ConfigKey FAILURE = ConfigKeys.newConfigKey( - Object.class, - "failure", - "Effector details for the failure effector"); + public static final ConfigKey FAILURE = ConfigKeys.builder(Object.class) + .name("failure") + .description("Effector details for the failure effector") + .build(); public ChoiceEffector(ConfigBag params) { super(newEffectorBuilder(params).build()); @@ -109,6 +113,7 @@ public Object call(final ConfigBag params) { Object output = invokeEffectorNamed(choiceTargetEntity, choiceEffectorName, params); Boolean success = Boolean.parseBoolean(Strings.toString(output)); + LOG.debug("{} result of {} was {}/{}", new Object[] { this, choiceEffectorName, Strings.toString(output), success }); Object effectorDetails = EntityInitializers.resolve(config, success ? SUCCESS : FAILURE); diff --git a/core/src/main/java/org/apache/brooklyn/core/effector/composite/ComposeEffector.java b/core/src/main/java/org/apache/brooklyn/core/effector/composite/ComposeEffector.java index 8a6c489068..09e39166c8 100644 --- a/core/src/main/java/org/apache/brooklyn/core/effector/composite/ComposeEffector.java +++ b/core/src/main/java/org/apache/brooklyn/core/effector/composite/ComposeEffector.java @@ -35,6 +35,7 @@ import com.google.common.annotations.Beta; import com.google.common.base.Preconditions; +import com.google.common.base.Predicates; import com.google.common.collect.ImmutableList; import com.google.common.reflect.TypeToken; @@ -49,11 +50,12 @@ public class ComposeEffector extends AbstractCompositeEffector { private static final Logger LOG = LoggerFactory.getLogger(ComposeEffector.class); - public static final ConfigKey> COMPOSE = ConfigKeys.newConfigKey( - new TypeToken>() { }, - "compose", - "Effector details list for the compose effector", - ImmutableList.of()); + public static final ConfigKey> COMPOSE = ConfigKeys.builder(new TypeToken>() { }) + .name("compose") + .description("Effector details list for the compose effector") + .constraint(Predicates.notNull()) + .defaultValue(ImmutableList.of()) + .build(); public ComposeEffector(ConfigBag params) { super(newEffectorBuilder(params).build()); diff --git a/core/src/main/java/org/apache/brooklyn/core/effector/composite/LoopEffector.java b/core/src/main/java/org/apache/brooklyn/core/effector/composite/LoopEffector.java index 9d5860867a..58bdc310ee 100644 --- a/core/src/main/java/org/apache/brooklyn/core/effector/composite/LoopEffector.java +++ b/core/src/main/java/org/apache/brooklyn/core/effector/composite/LoopEffector.java @@ -37,6 +37,7 @@ import com.google.common.annotations.Beta; import com.google.common.base.Preconditions; +import com.google.common.base.Predicates; import com.google.common.collect.Iterables; import com.google.common.collect.Lists; @@ -51,14 +52,16 @@ public class LoopEffector extends AbstractCompositeEffector { private static final Logger LOG = LoggerFactory.getLogger(LoopEffector.class); - public static final ConfigKey INPUT = ConfigKeys.newStringConfigKey( - "input", - "Loop input parameter"); + public static final ConfigKey INPUT = ConfigKeys.builder(String.class) + .name("input") + .description("Loop input parameter") + .build(); - public static final ConfigKey LOOP = ConfigKeys.newConfigKey( - Object.class, - "loop", - "Effector details for the loop effector"); + public static final ConfigKey LOOP = ConfigKeys.builder(Object.class) + .name("loop") + .description("Effector details for the loop effector") + .constraint(Predicates.notNull()) + .build(); public LoopEffector(ConfigBag params) { super(newEffectorBuilder(params).build()); diff --git a/core/src/main/java/org/apache/brooklyn/core/effector/composite/ParallelEffector.java b/core/src/main/java/org/apache/brooklyn/core/effector/composite/ParallelEffector.java index b1b73e4d07..cfe5ca2c5d 100644 --- a/core/src/main/java/org/apache/brooklyn/core/effector/composite/ParallelEffector.java +++ b/core/src/main/java/org/apache/brooklyn/core/effector/composite/ParallelEffector.java @@ -36,6 +36,7 @@ import com.google.common.annotations.Beta; import com.google.common.base.Preconditions; +import com.google.common.base.Predicates; import com.google.common.collect.ImmutableList; import com.google.common.collect.Lists; import com.google.common.reflect.TypeToken; @@ -50,11 +51,12 @@ public class ParallelEffector extends AbstractCompositeEffector { private static final Logger LOG = LoggerFactory.getLogger(ParallelEffector.class); - public static final ConfigKey> PARALLEL = ConfigKeys.newConfigKey( - new TypeToken>() { }, - "parallel", - "Effector details list for the parallel effector", - ImmutableList.of()); + public static final ConfigKey> PARALLEL = ConfigKeys.builder(new TypeToken>() { }) + .name("parallel") + .description("Effector details list for the parallel effector") + .constraint(Predicates.notNull()) + .defaultValue(ImmutableList.of()) + .build(); public ParallelEffector(ConfigBag params) { super(newEffectorBuilder(params).build()); diff --git a/core/src/main/java/org/apache/brooklyn/core/effector/composite/RepeatEffector.java b/core/src/main/java/org/apache/brooklyn/core/effector/composite/RepeatEffector.java index 35515cfe0d..faa55ff041 100644 --- a/core/src/main/java/org/apache/brooklyn/core/effector/composite/RepeatEffector.java +++ b/core/src/main/java/org/apache/brooklyn/core/effector/composite/RepeatEffector.java @@ -30,11 +30,13 @@ import org.apache.brooklyn.core.effector.Effectors.EffectorBuilder; import org.apache.brooklyn.core.entity.EntityInitializers; import org.apache.brooklyn.util.core.config.ConfigBag; +import org.apache.brooklyn.util.math.MathPredicates; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.google.common.annotations.Beta; import com.google.common.base.Preconditions; +import com.google.common.base.Predicates; import com.google.common.collect.Iterables; import com.google.common.collect.Lists; @@ -48,15 +50,18 @@ public class RepeatEffector extends AbstractCompositeEffector { private static final Logger LOG = LoggerFactory.getLogger(RepeatEffector.class); - public static final ConfigKey REPEAT = ConfigKeys.newConfigKey( - Object.class, - "repeat", - "Effector details list for the repeat effector"); + public static final ConfigKey REPEAT = ConfigKeys.builder(Object.class) + .name("repeat") + .description("Effector details list for the repeat effector") + .constraint(Predicates.notNull()) + .build(); - public static final ConfigKey COUNT = ConfigKeys.newIntegerConfigKey( - "count", - "Number of times to rpeat the effector", - 1); + public static final ConfigKey COUNT = ConfigKeys.builder(Integer.class) + .name("count") + .description("Number of times to repeat the effector") + .constraint(MathPredicates.greaterThan(0d)) + .defaultValue(1) + .build(); public RepeatEffector(ConfigBag params) { super(newEffectorBuilder(params).build()); @@ -85,7 +90,7 @@ public List call(final ConfigBag params) { synchronized (mutex) { LOG.debug("{} called with config {}, params {}", new Object[] { this, config, params }); Object effectorDetails = EntityInitializers.resolve(config, REPEAT); - int count = EntityInitializers.resolve(config, COUNT); + Integer count = EntityInitializers.resolve(config, COUNT); String effectorName = getEffectorName(effectorDetails); String inputArgument = getInputArgument(effectorDetails); diff --git a/core/src/main/java/org/apache/brooklyn/core/effector/composite/ReplaceEffector.java b/core/src/main/java/org/apache/brooklyn/core/effector/composite/ReplaceEffector.java index f14774a401..54e3aec530 100644 --- a/core/src/main/java/org/apache/brooklyn/core/effector/composite/ReplaceEffector.java +++ b/core/src/main/java/org/apache/brooklyn/core/effector/composite/ReplaceEffector.java @@ -38,6 +38,7 @@ import com.google.common.annotations.Beta; import com.google.common.base.Preconditions; +import com.google.common.base.Predicates; /** * Replace an effector with another, optionally executing the originsl @@ -59,16 +60,17 @@ public enum ReplaceAction { public static final String ORIGINAL = "original-"; - public static final ConfigKey ACTION = ConfigKeys.newConfigKey( - ReplaceAction.class, - "action", - "Action to take with the replaced effector", - ReplaceAction.OVERRIDE); - - public static final ConfigKey REPLACE = ConfigKeys.newConfigKey( - Object.class, - "replace", - "Effector details for the replace effector"); + public static final ConfigKey ACTION = ConfigKeys.builder(ReplaceAction.class) + .name("action") + .description("Action to take with the replaced effector") + .defaultValue(ReplaceAction.OVERRIDE) + .build(); + + public static final ConfigKey REPLACE = ConfigKeys.builder(Object.class) + .name("replace") + .description("Effector details for the replace effector") + .constraint(Predicates.notNull()) + .build(); public ReplaceEffector(ConfigBag params) { super(newEffectorBuilder(params).build()); diff --git a/core/src/main/java/org/apache/brooklyn/core/effector/composite/SequenceEffector.java b/core/src/main/java/org/apache/brooklyn/core/effector/composite/SequenceEffector.java index e7a0afb486..0dc1ee02ce 100644 --- a/core/src/main/java/org/apache/brooklyn/core/effector/composite/SequenceEffector.java +++ b/core/src/main/java/org/apache/brooklyn/core/effector/composite/SequenceEffector.java @@ -35,6 +35,7 @@ import com.google.common.annotations.Beta; import com.google.common.base.Preconditions; +import com.google.common.base.Predicates; import com.google.common.collect.ImmutableList; import com.google.common.reflect.TypeToken; @@ -48,11 +49,12 @@ public class SequenceEffector extends AbstractCompositeEffector { private static final Logger LOG = LoggerFactory.getLogger(SequenceEffector.class); - public static final ConfigKey> SEQUENCE = ConfigKeys.newConfigKey( - new TypeToken>() { }, - "sequence", - "Effector details list for the sequence effector", - ImmutableList.of()); + public static final ConfigKey> SEQUENCE = ConfigKeys.builder(new TypeToken>() { }) + .name("sequence") + .description("Effector details list for the sequence effector") + .constraint(Predicates.notNull()) + .defaultValue(ImmutableList.of()) + .build(); public SequenceEffector(ConfigBag params) { super(newEffectorBuilder(params).build()); diff --git a/core/src/main/java/org/apache/brooklyn/core/effector/composite/TransformEffector.java b/core/src/main/java/org/apache/brooklyn/core/effector/composite/TransformEffector.java index df07aa58a7..b3bd55b3e6 100644 --- a/core/src/main/java/org/apache/brooklyn/core/effector/composite/TransformEffector.java +++ b/core/src/main/java/org/apache/brooklyn/core/effector/composite/TransformEffector.java @@ -35,6 +35,7 @@ import com.google.common.base.Function; import com.google.common.base.Functions; import com.google.common.base.Preconditions; +import com.google.common.base.Predicates; import com.google.common.reflect.TypeToken; /** @@ -47,15 +48,17 @@ public class TransformEffector extends AbstractCompositeEffector { private static final Logger LOG = LoggerFactory.getLogger(TransformEffector.class); - public static final ConfigKey INPUT = ConfigKeys.newStringConfigKey( - "input", - "Transformer input parameter"); + public static final ConfigKey INPUT = ConfigKeys.builder(String.class) + .name("input") + .description("Transformer input parameter") + .build(); - public static final ConfigKey> FUNCTION = ConfigKeys.newConfigKey( - new TypeToken>() { }, - "function", - "Transformer function to apply", - Functions.identity()); + public static final ConfigKey> FUNCTION = ConfigKeys.builder(new TypeToken>() { }) + .name("function") + .description("Transformer function to apply") + .constraint(Predicates.notNull()) + .defaultValue(Functions.identity()) + .build(); public TransformEffector(ConfigBag params) { super(newEffectorBuilder(params).build()); diff --git a/core/src/main/java/org/apache/brooklyn/policy/InvokeEffectorOnCollectionSensorChange.java b/core/src/main/java/org/apache/brooklyn/policy/InvokeEffectorOnCollectionSensorChange.java index 352d40d13d..8352245cb9 100644 --- a/core/src/main/java/org/apache/brooklyn/policy/InvokeEffectorOnCollectionSensorChange.java +++ b/core/src/main/java/org/apache/brooklyn/policy/InvokeEffectorOnCollectionSensorChange.java @@ -159,14 +159,14 @@ public void onEvent(SensorEvent> event) { } private void onAdded(Object newElement) { - onEvent(getOnAddedEffector(), newElement); + invokeEffector(getOnAddedEffector(), newElement); } private void onRemoved(Object newElement) { - onEvent(getOnRemovedEffector(), newElement); + invokeEffector(getOnRemovedEffector(), newElement); } - private void onEvent(String effectorName, Object parameter) { + private void invokeEffector(String effectorName, Object parameter) { Maybe> effector = getEffector(effectorName); if (effector.isPresentAndNonNull()) { final Map parameters; From 55087faca51bd615eae583f0a2a3c0953a3f6d0e Mon Sep 17 00:00:00 2001 From: Andrew Donald Kennedy Date: Wed, 12 Apr 2017 11:10:18 +0100 Subject: [PATCH 42/43] Add removal strategy for dynamic cluster using predicates --- .../group/EntityPredicateRemovalStrategy.java | 52 +++++++++++++++++++ 1 file changed, 52 insertions(+) create mode 100644 core/src/main/java/org/apache/brooklyn/entity/group/EntityPredicateRemovalStrategy.java diff --git a/core/src/main/java/org/apache/brooklyn/entity/group/EntityPredicateRemovalStrategy.java b/core/src/main/java/org/apache/brooklyn/entity/group/EntityPredicateRemovalStrategy.java new file mode 100644 index 0000000000..9b8a6eb3a8 --- /dev/null +++ b/core/src/main/java/org/apache/brooklyn/entity/group/EntityPredicateRemovalStrategy.java @@ -0,0 +1,52 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.brooklyn.entity.group; + +import java.util.Collection; + +import javax.annotation.Nullable; + +import org.apache.brooklyn.api.entity.Entity; +import org.apache.brooklyn.config.ConfigKey; +import org.apache.brooklyn.core.config.ConfigKeys; +import org.apache.brooklyn.core.entity.EntityPredicates; + +import com.google.common.base.Optional; +import com.google.common.base.Predicate; +import com.google.common.base.Predicates; +import com.google.common.collect.Iterables; +import com.google.common.reflect.TypeToken; + +public class EntityPredicateRemovalStrategy extends RemovalStrategy { + + public static final ConfigKey> ENTITY_PREDICATE = ConfigKeys.builder(new TypeToken>() { }) + .name("predicate") + .description("A predicate that will match the entities to be removed") + .constraint(Predicates.notNull()) + .defaultValue(Predicates.not(EntityPredicates.isServiceUp())) + .build(); + + @Nullable + @Override + public Entity apply(@Nullable Collection input) { + Predicate predicate = config().get(ENTITY_PREDICATE); + Optional entity = Iterables.tryFind(input, predicate); + return entity.orNull(); + } +} From 7f28d19943afb8e236921c1230e5d1c21ed85afb Mon Sep 17 00:00:00 2001 From: Andrew Donald Kennedy Date: Tue, 11 Apr 2017 12:07:04 +0100 Subject: [PATCH 43/43] New effector to remove entities --- .../core/effector/RemoveEntityEffector.java | 106 ++++++++++++++++++ 1 file changed, 106 insertions(+) create mode 100644 core/src/main/java/org/apache/brooklyn/core/effector/RemoveEntityEffector.java diff --git a/core/src/main/java/org/apache/brooklyn/core/effector/RemoveEntityEffector.java b/core/src/main/java/org/apache/brooklyn/core/effector/RemoveEntityEffector.java new file mode 100644 index 0000000000..641adbf4e5 --- /dev/null +++ b/core/src/main/java/org/apache/brooklyn/core/effector/RemoveEntityEffector.java @@ -0,0 +1,106 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.brooklyn.core.effector; + +import java.util.Map; + +import org.apache.brooklyn.api.effector.Effector; +import org.apache.brooklyn.api.entity.Entity; +import org.apache.brooklyn.config.ConfigKey; +import org.apache.brooklyn.core.config.ConfigKeys; +import org.apache.brooklyn.core.effector.Effectors.EffectorBuilder; +import org.apache.brooklyn.core.entity.EntityInitializers; +import org.apache.brooklyn.core.entity.EntityPredicates; +import org.apache.brooklyn.util.core.config.ConfigBag; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.google.common.annotations.Beta; +import com.google.common.base.Optional; +import com.google.common.base.Predicate; +import com.google.common.collect.Iterables; +import com.google.common.reflect.TypeToken; + +/** + * @since 0.11.0 + */ +@Beta +public class RemoveEntityEffector extends AddEffector { + + private static final Logger LOG = LoggerFactory.getLogger(RemoveEntityEffector.class); + + public static final ConfigKey ENTITY_ID = ConfigKeys.builder(String.class) + .name("entityId") + .description("The id of the entity to be removed") + .build(); + + public static final ConfigKey> ENTITY_PREDICATE = ConfigKeys.builder(new TypeToken>() { }) + .name("predicate") + .description("A predicate that will match the entity to be removed") + .build(); + + public RemoveEntityEffector(ConfigBag params) { + super(newEffectorBuilder(params).build()); + } + + public RemoveEntityEffector(Map params) { + this(ConfigBag.newInstance(params)); + } + + public static EffectorBuilder newEffectorBuilder(ConfigBag params) { + EffectorBuilder eff = (EffectorBuilder) AddEffector.newEffectorBuilder(Boolean.class, params); + eff.impl(new Body(eff.buildAbstract(), params)); + return eff; + } + + protected static class Body extends EffectorBody { + protected final Effector effector; + protected final ConfigBag config; + + protected Object mutex = new Object[0]; + + public Body(Effector eff, ConfigBag config) { + this.effector = eff; + this.config = config; + } + + @Override + public Boolean call(final ConfigBag params) { + synchronized (mutex) { + ConfigBag all = ConfigBag.newInstanceCopying(config).putAll(params); + Predicate predicate = EntityInitializers.resolve(all, ENTITY_PREDICATE); + if (predicate == null) { + String entityId = EntityInitializers.resolve(all, ENTITY_ID); + predicate = EntityPredicates.idEqualTo(entityId); + } + Optional child = Iterables.tryFind(entity().getChildren(), predicate); + if (child.isPresent()) { + boolean success = entity().removeChild(child.get()); + if (success) { + LOG.debug("{}: Removed child {} from {}", new Object[] { this, child.get(), entity() }); + return true; + } + } + LOG.warn("{}: Could not find child of {} using {}", new Object[] { this, entity(), predicate }); + return false; + } + } + } + +}