http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/c27cf1d0/core/src/main/java/org/apache/brooklyn/core/entity/internal/EntityConfigMap.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/org/apache/brooklyn/core/entity/internal/EntityConfigMap.java b/core/src/main/java/org/apache/brooklyn/core/entity/internal/EntityConfigMap.java new file mode 100644 index 0000000..c9a05ef --- /dev/null +++ b/core/src/main/java/org/apache/brooklyn/core/entity/internal/EntityConfigMap.java @@ -0,0 +1,306 @@ +/* + * 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.entity.internal; + +import static com.google.common.base.Preconditions.checkNotNull; +import static org.apache.brooklyn.util.GroovyJavaMethods.elvis; + +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Set; + +import org.apache.brooklyn.api.mgmt.ExecutionContext; +import org.apache.brooklyn.api.mgmt.Task; +import org.apache.brooklyn.config.ConfigInheritance; +import org.apache.brooklyn.config.ConfigKey; +import org.apache.brooklyn.core.config.Sanitizer; +import org.apache.brooklyn.core.config.StructuredConfigKey; +import org.apache.brooklyn.core.config.internal.AbstractConfigMapImpl; +import org.apache.brooklyn.core.entity.AbstractEntity; +import org.apache.brooklyn.util.collections.MutableMap; +import org.apache.brooklyn.util.core.config.ConfigBag; +import org.apache.brooklyn.util.core.flags.FlagUtils; +import org.apache.brooklyn.util.core.flags.SetFromFlag; +import org.apache.brooklyn.util.core.flags.TypeCoercions; +import org.apache.brooklyn.util.core.internal.ConfigKeySelfExtracting; +import org.apache.brooklyn.util.guava.Maybe; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.google.common.base.Predicate; +import com.google.common.collect.Maps; +import com.google.common.collect.Sets; + +public class EntityConfigMap extends AbstractConfigMapImpl { + + private static final Logger LOG = LoggerFactory.getLogger(EntityConfigMap.class); + + /** entity against which config resolution / task execution will occur */ + private final AbstractEntity entity; + + /** + * Map of configuration information that is defined at start-up time for the entity. These + * configuration parameters are shared and made accessible to the "children" of this + * entity. + */ + private final Map<ConfigKey<?>,Object> inheritedConfig = Collections.synchronizedMap(new LinkedHashMap<ConfigKey<?>, Object>()); + // TODO do we really want to have *both* bags and maps for these? danger that they get out of synch. + // have added some logic (Oct 2014) so that the same changes are applied to both, in most places at least; + // i (alex) think we should prefer ConfigBag (the input keys don't matter, it is more a question of retrieval keys), + // but first we need ConfigBag to support StructuredConfigKeys + private final ConfigBag localConfigBag; + private final ConfigBag inheritedConfigBag; + + public EntityConfigMap(AbstractEntity entity, Map<ConfigKey<?>, Object> storage) { + this.entity = checkNotNull(entity, "entity must be specified"); + this.ownConfig = checkNotNull(storage, "storage map must be specified"); + + // TODO store ownUnused in backing-storage + this.localConfigBag = ConfigBag.newInstance(); + this.inheritedConfigBag = ConfigBag.newInstance(); + } + + @SuppressWarnings("unchecked") + public <T> T getConfig(ConfigKey<T> key, T defaultValue) { + // FIXME What about inherited task in config?! + // alex says: think that should work, no? + // FIXME What if someone calls getConfig on a task, before setting parent app? + // alex says: not supported (throw exception, or return the task) + + // In case this entity class has overridden the given key (e.g. to set default), then retrieve this entity's key + // TODO If ask for a config value that's not in our configKeys, should we really continue with rest of method and return key.getDefaultValue? + // e.g. SshBasedJavaAppSetup calls setAttribute(JMX_USER), which calls getConfig(JMX_USER) + // but that example doesn't have a default... + ConfigKey<T> ownKey = entity!=null ? (ConfigKey<T>)elvis(entity.getEntityType().getConfigKey(key.getName()), key) : key; + + ConfigInheritance inheritance = key.getInheritance(); + if (inheritance==null) inheritance = ownKey.getInheritance(); + if (inheritance==null) { + // TODO we could warn by introducing a temporary "ALWAYS_BUT_WARNING" instance + inheritance = getDefaultInheritance(); + } + + // TODO We're notifying of config-changed because currently persistence needs to know when the + // attributeWhenReady is complete (so it can persist the result). + // Long term, we'll just persist tasks properly so the call to onConfigChanged will go! + + // Don't use groovy truth: if the set value is e.g. 0, then would ignore set value and return default! + if (ownKey instanceof ConfigKeySelfExtracting) { + Object rawval = ownConfig.get(key); + T result = null; + boolean complete = false; + if (((ConfigKeySelfExtracting<T>)ownKey).isSet(ownConfig)) { + ExecutionContext exec = entity.getExecutionContext(); + result = ((ConfigKeySelfExtracting<T>)ownKey).extractValue(ownConfig, exec); + complete = true; + } else if (isInherited(ownKey, inheritance) && + ((ConfigKeySelfExtracting<T>)ownKey).isSet(inheritedConfig)) { + ExecutionContext exec = entity.getExecutionContext(); + result = ((ConfigKeySelfExtracting<T>)ownKey).extractValue(inheritedConfig, exec); + complete = true; + } else if (localConfigBag.containsKey(ownKey)) { + // TODO configBag.get doesn't handle tasks/attributeWhenReady - it only uses TypeCoercions + result = localConfigBag.get(ownKey); + complete = true; + } else if (isInherited(ownKey, inheritance) && + inheritedConfigBag.containsKey(ownKey)) { + result = inheritedConfigBag.get(ownKey); + complete = true; + } + + if (rawval instanceof Task) { + entity.getManagementSupport().getEntityChangeListener().onConfigChanged(key); + } + if (complete) { + return result; + } + } else { + LOG.warn("Config key {} of {} is not a ConfigKeySelfExtracting; cannot retrieve value; returning default", ownKey, this); + } + return TypeCoercions.coerce((defaultValue != null) ? defaultValue : ownKey.getDefaultValue(), key.getTypeToken()); + } + + private <T> boolean isInherited(ConfigKey<T> key) { + return isInherited(key, key.getInheritance()); + } + private <T> boolean isInherited(ConfigKey<T> key, ConfigInheritance inheritance) { + if (inheritance==null) inheritance = getDefaultInheritance(); + return inheritance.isInherited(key, entity.getParent(), entity); + } + private ConfigInheritance getDefaultInheritance() { + return ConfigInheritance.ALWAYS; + } + + @Override + public Maybe<Object> getConfigRaw(ConfigKey<?> key, boolean includeInherited) { + if (ownConfig.containsKey(key)) return Maybe.of(ownConfig.get(key)); + if (includeInherited && inheritedConfig.containsKey(key)) return Maybe.of(inheritedConfig.get(key)); + return Maybe.absent(); + } + + /** an immutable copy of the config visible at this entity, local and inherited (preferring local) */ + public Map<ConfigKey<?>,Object> getAllConfig() { + Map<ConfigKey<?>,Object> result = new LinkedHashMap<ConfigKey<?>,Object>(inheritedConfig.size()+ownConfig.size()); + result.putAll(inheritedConfig); + result.putAll(ownConfig); + return Collections.unmodifiableMap(result); + } + + /** an immutable copy of the config defined at this entity, ie not inherited */ + public Map<ConfigKey<?>,Object> getLocalConfig() { + Map<ConfigKey<?>,Object> result = new LinkedHashMap<ConfigKey<?>,Object>(ownConfig.size()); + result.putAll(ownConfig); + return Collections.unmodifiableMap(result); + } + + /** Creates an immutable copy of the config visible at this entity, local and inherited (preferring local), including those that did not match config keys */ + public ConfigBag getAllConfigBag() { + return ConfigBag.newInstanceCopying(localConfigBag) + .putAll(ownConfig) + .putIfAbsent(inheritedConfig) + .putIfAbsent(inheritedConfigBag) + .seal(); + } + + /** Creates an immutable copy of the config defined at this entity, ie not inherited, including those that did not match config keys */ + public ConfigBag getLocalConfigBag() { + return ConfigBag.newInstanceCopying(localConfigBag) + .putAll(ownConfig) + .seal(); + } + + @SuppressWarnings("unchecked") + public Object setConfig(ConfigKey<?> key, Object v) { + Object val = coerceConfigVal(key, v); + Object oldVal; + if (key instanceof StructuredConfigKey) { + oldVal = ((StructuredConfigKey)key).applyValueToMap(val, ownConfig); + // TODO ConfigBag does not handle structured config keys; quick fix is to remove (and should also remove any subkeys; + // as it stands if someone set string a.b.c in the config bag then removed structured key a.b, then got a.b.c they'd get a vale); + // long term fix is to support structured config keys in ConfigBag, at which point i think we could remove ownConfig altogether + localConfigBag.remove(key); + } else { + oldVal = ownConfig.put(key, val); + localConfigBag.put((ConfigKey<Object>)key, v); + } + entity.config().refreshInheritedConfigOfChildren(); + return oldVal; + } + + public void setLocalConfig(Map<ConfigKey<?>, ?> vals) { + ownConfig.clear(); + localConfigBag.clear(); + ownConfig.putAll(vals); + localConfigBag.putAll(vals); + } + + public void setInheritedConfig(Map<ConfigKey<?>, ?> valsO, ConfigBag configBagVals) { + Map<ConfigKey<?>, ?> vals = filterUninheritable(valsO); + + inheritedConfig.clear(); + inheritedConfig.putAll(vals); + + // The configBagVals contains all inherited, including strings that did not match a config key on the parent. + // They might match a config-key on this entity though, so need to check that: + // - if it matches one of our keys, set it in inheritedConfig + // - otherwise add it to our inheritedConfigBag + Set<String> valKeyNames = Sets.newLinkedHashSet(); + for (ConfigKey<?> key : vals.keySet()) { + valKeyNames.add(key.getName()); + } + Map<String,Object> valsUnmatched = MutableMap.<String,Object>builder() + .putAll(configBagVals.getAllConfig()) + .removeAll(valKeyNames) + .build(); + inheritedConfigBag.clear(); + Map<ConfigKey<?>, SetFromFlag> annotatedConfigKeys = FlagUtils.getAnnotatedConfigKeys(entity.getClass()); + Map<String, ConfigKey<?>> renamedConfigKeys = Maps.newLinkedHashMap(); + for (Map.Entry<ConfigKey<?>, SetFromFlag> entry: annotatedConfigKeys.entrySet()) { + String rename = entry.getValue().value(); + if (rename != null) { + renamedConfigKeys.put(rename, entry.getKey()); + } + } + for (Map.Entry<String,Object> entry : valsUnmatched.entrySet()) { + String name = entry.getKey(); + Object value = entry.getValue(); + ConfigKey<?> key = renamedConfigKeys.get(name); + if (key == null) key = entity.getEntityType().getConfigKey(name); + if (key != null) { + if (!isInherited(key)) { + // no-op + } else if (inheritedConfig.containsKey(key)) { + LOG.warn("Entity "+entity+" inherited duplicate config for key "+key+", via explicit config and string name "+name+"; using value of key"); + } else { + inheritedConfig.put(key, value); + } + } else { + // a config bag has discarded the keys, so we must assume default inheritance for things given that way + // unless we can infer a key; not a big deal, as we should have the key in inheritedConfig for everything + // which originated with a key ... but still, it would be nice to clean up the use of config bag! + inheritedConfigBag.putStringKey(name, value); + } + } + } + + private Map<ConfigKey<?>, ?> filterUninheritable(Map<ConfigKey<?>, ?> vals) { + Map<ConfigKey<?>, Object> result = Maps.newLinkedHashMap(); + for (Map.Entry<ConfigKey<?>, ?> entry : vals.entrySet()) { + if (isInherited(entry.getKey())) { + result.put(entry.getKey(), entry.getValue()); + } + } + return result; + } + + public void addToLocalBag(Map<String,?> vals) { + localConfigBag.putAll(vals); + // quick fix for problem that ownConfig can get out of synch + ownConfig.putAll(localConfigBag.getAllConfigAsConfigKeyMap()); + } + + public void removeFromLocalBag(String key) { + localConfigBag.remove(key); + ownConfig.remove(key); + } + + public void clearInheritedConfig() { + inheritedConfig.clear(); + inheritedConfigBag.clear(); + } + + @Override + public EntityConfigMap submap(Predicate<ConfigKey<?>> filter) { + EntityConfigMap m = new EntityConfigMap(entity, Maps.<ConfigKey<?>, Object>newLinkedHashMap()); + for (Map.Entry<ConfigKey<?>,Object> entry: inheritedConfig.entrySet()) + if (filter.apply(entry.getKey())) + m.inheritedConfig.put(entry.getKey(), entry.getValue()); + for (Map.Entry<ConfigKey<?>,Object> entry: ownConfig.entrySet()) + if (filter.apply(entry.getKey())) + m.ownConfig.put(entry.getKey(), entry.getValue()); + return m; + } + + @Override + public String toString() { + return super.toString()+"[own="+Sanitizer.sanitize(ownConfig)+"; inherited="+Sanitizer.sanitize(inheritedConfig)+"]"; + } + +}
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/c27cf1d0/core/src/main/java/org/apache/brooklyn/core/entity/internal/EntityTransientCopyInternal.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/org/apache/brooklyn/core/entity/internal/EntityTransientCopyInternal.java b/core/src/main/java/org/apache/brooklyn/core/entity/internal/EntityTransientCopyInternal.java new file mode 100644 index 0000000..09a8fdf --- /dev/null +++ b/core/src/main/java/org/apache/brooklyn/core/entity/internal/EntityTransientCopyInternal.java @@ -0,0 +1,121 @@ +/* + * 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.entity.internal; + +import java.util.Collection; +import java.util.Map; + +import javax.annotation.Nullable; + +import org.apache.brooklyn.api.effector.Effector; +import org.apache.brooklyn.api.entity.Application; +import org.apache.brooklyn.api.entity.Entity; +import org.apache.brooklyn.api.entity.EntityType; +import org.apache.brooklyn.api.entity.Group; +import org.apache.brooklyn.api.location.Location; +import org.apache.brooklyn.api.mgmt.ExecutionContext; +import org.apache.brooklyn.api.mgmt.ManagementContext; +import org.apache.brooklyn.api.mgmt.rebind.RebindSupport; +import org.apache.brooklyn.api.mgmt.rebind.mementos.EntityMemento; +import org.apache.brooklyn.api.objs.BrooklynObject.TagSupport; +import org.apache.brooklyn.api.policy.Policy; +import org.apache.brooklyn.api.sensor.AttributeSensor; +import org.apache.brooklyn.api.sensor.Enricher; +import org.apache.brooklyn.config.ConfigKey; +import org.apache.brooklyn.config.ConfigKey.HasConfigKey; +import org.apache.brooklyn.core.entity.EntityInternal; +import org.apache.brooklyn.core.entity.EntityInternal.FeedSupport; +import org.apache.brooklyn.core.mgmt.internal.EntityManagementSupport; +import org.apache.brooklyn.core.objs.proxy.EntityProxyImpl; +import org.apache.brooklyn.util.core.config.ConfigBag; +import org.apache.brooklyn.util.guava.Maybe; + +import com.google.common.annotations.Beta; + +/** + * Selected methods from {@link EntityInternal} and parents which are permitted + * for entities being loaded in read-only mode, enforced by {@link EntityProxyImpl}. + * <p> + * Some of these methods do expose write capabilities, but such modifications are likely + * to be temporary, discarded on next rebind. Callers must take care with any such invocations. + * (The primary intent of this interface is to catch and prevent *most* such invocations!) + */ +@Beta +public interface EntityTransientCopyInternal { + + // TODO For feeds() and config(), need to ensure mutator methods on returned object are not invoked. + + // from Entity + + String getId(); + long getCreationTime(); + String getDisplayName(); + @Nullable String getIconUrl(); + EntityType getEntityType(); + Application getApplication(); + String getApplicationId(); + Entity getParent(); + Collection<Entity> getChildren(); + Collection<Policy> getPolicies(); + Collection<Enricher> getEnrichers(); + Collection<Group> getGroups(); + Collection<Location> getLocations(); + <T> T getAttribute(AttributeSensor<T> sensor); + <T> T getConfig(ConfigKey<T> key); + <T> T getConfig(HasConfigKey<T> key); + Maybe<Object> getConfigRaw(ConfigKey<?> key, boolean includeInherited); + Maybe<Object> getConfigRaw(HasConfigKey<?> key, boolean includeInherited); + TagSupport tags(); + String getCatalogItemId(); + + + // from entity local + + @Deprecated <T> T getConfig(ConfigKey<T> key, T defaultValue); + @Deprecated <T> T getConfig(HasConfigKey<T> key, T defaultValue); + + + // from EntityInternal: + + @Deprecated EntityConfigMap getConfigMap(); + @Deprecated Map<ConfigKey<?>,Object> getAllConfig(); + // for rebind mainly: + @Deprecated ConfigBag getAllConfigBag(); + @Deprecated ConfigBag getLocalConfigBag(); + @SuppressWarnings("rawtypes") + Map<AttributeSensor, Object> getAllAttributes(); + EntityManagementSupport getManagementSupport(); + ManagementContext getManagementContext(); + Effector<?> getEffector(String effectorName); + @Deprecated FeedSupport getFeedSupport(); + FeedSupport feeds(); + RebindSupport<EntityMemento> getRebindSupport(); + // for REST calls on read-only entities which want to resolve values + ExecutionContext getExecutionContext(); + void setCatalogItemId(String id); + + /** more methods, but which are only on selected entities */ + public interface SpecialEntityTransientCopyInternal { + // from Group + Collection<Entity> getMembers(); + boolean hasMember(Entity member); + Integer getCurrentSize(); + } + +} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/c27cf1d0/core/src/main/java/org/apache/brooklyn/core/entity/lifecycle/Lifecycle.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/org/apache/brooklyn/core/entity/lifecycle/Lifecycle.java b/core/src/main/java/org/apache/brooklyn/core/entity/lifecycle/Lifecycle.java new file mode 100644 index 0000000..dfbdbd7 --- /dev/null +++ b/core/src/main/java/org/apache/brooklyn/core/entity/lifecycle/Lifecycle.java @@ -0,0 +1,185 @@ +/* + * 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.entity.lifecycle; + +import java.io.Serializable; +import java.util.Date; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.apache.brooklyn.api.location.Location; +import org.apache.brooklyn.core.config.render.RendererHints; +import org.apache.brooklyn.util.core.flags.TypeCoercions; +import org.apache.brooklyn.util.text.StringFunctions; + +import com.google.common.base.CaseFormat; +import com.google.common.base.Function; +import com.google.common.base.Objects; +import com.google.common.base.Preconditions; + +/** + * An enumeration representing the status of an {@link org.apache.brooklyn.api.entity.Entity}. + */ +public enum Lifecycle { + /** + * The entity has just been created. + * + * This stage encompasses the contruction. Once this stage is + * complete, the basic set of {@link brooklyn.event.Sensor}s will be available, apart from any that require the entity to be active or + * deployed to a {@link Location}. + */ + CREATED, + + /** + * The entity is starting. + * <p> + * This stage is typically entered when the {@link brooklyn.entity.trait.Startable#START} {@link brooklyn.entity.Effector} + * is called, to undertake the startup operations from the management plane. + * When this completes the entity will normally transition to + * {@link Lifecycle#RUNNING}. + */ + STARTING, + + /** + * The entity service is expected to be running. In healthy operation, {@link Attributes#SERVICE_UP} will be true, + * or will shortly be true if all service start actions have been completed and we are merely waiting for it to be running. + */ + RUNNING, + + /** + * The entity is stopping. + * + * This stage is activated when the {@link brooklyn.entity.trait.Startable#STOP} effector is called. The entity service is stopped. + * Sensors that provide data from the running entity may be cleared and subscriptions cancelled. + */ + STOPPING, + + /** + * The entity is not expected to be active. + * + * This stage is entered when an entity is stopped, or may be entered when an entity is + * fully created but not started. It may or may not be removed from the location(s) it was assigned, + * and it will typically not be providing new sensor data apart. + */ + STOPPED, + + /** + * The entity is destroyed. + * + * The entity will be unmanaged and removed from any groups and from its parent. + */ + DESTROYED, + + /** + * Entity error state. + * + * This stage is reachable from any other stage if an error occurs or an exception is thrown. + */ + ON_FIRE; + + /** + * The text representation of the {@link #name()}. + * + * This is formatted as lower case characters, with hyphens instead of spaces. + */ + public String value() { + return CaseFormat.UPPER_UNDERSCORE.to(CaseFormat.LOWER_HYPHEN, name()); + } + + /** @see #value() */ + @Override + public String toString() { return value(); } + + /** + * Creates a {@link Lifecycle} from a text representation. + * + * This accepts the text representations output by the {@link #value()} method for each entry. + * + * @see #value() + */ + public static Lifecycle fromValue(String v) { + try { + return valueOf(CaseFormat.LOWER_HYPHEN.to(CaseFormat.UPPER_UNDERSCORE, v)); + } catch (IllegalArgumentException iae) { + return ON_FIRE; + } + } + + public static class Transition implements Serializable { + private static final long serialVersionUID = 603419184398753502L; + + final Lifecycle state; + final long timestampUtc; + + public Transition(Lifecycle state, Date timestamp) { + this.state = Preconditions.checkNotNull(state, "state"); + this.timestampUtc = Preconditions.checkNotNull(timestamp, "timestamp").getTime(); + } + + public Lifecycle getState() { + return state; + } + public Date getTimestamp() { + return new Date(timestampUtc); + } + + @Override + public int hashCode() { + return Objects.hashCode(state, timestampUtc); + } + + @Override + public boolean equals(Object obj) { + if (!(obj instanceof Transition)) return false; + if (!state.equals(((Transition)obj).getState())) return false; + if (timestampUtc != ((Transition)obj).timestampUtc) return false; + return true; + } + + @Override + public String toString() { + return state+" @ "+timestampUtc+" / "+new Date(timestampUtc); + } + } + + protected static class TransitionCoalesceFunction implements Function<String, Transition> { + private static final Pattern TRANSITION_PATTERN = Pattern.compile("^([\\w-]+)\\s+@\\s+(\\d+).*"); + + @Override + public Transition apply(final String input) { + if (input != null) { + Matcher m = TRANSITION_PATTERN.matcher(input); + if (m.matches()) { + Lifecycle state = Lifecycle.valueOf(m.group(1).toUpperCase().replace('-', '_')); + long time = Long.parseLong(m.group(2)); + return new Transition(state, new Date(time)); + } else { + throw new IllegalStateException("Serialized Lifecycle.Transition can't be parsed: " + input); + } + } else { + return null; + } + } + } + + static { + TypeCoercions.registerAdapter(String.class, Transition.class, new TransitionCoalesceFunction()); + RendererHints.register(Transition.class, RendererHints.displayValue(StringFunctions.toStringFunction())); + } +} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/c27cf1d0/core/src/main/java/org/apache/brooklyn/core/entity/lifecycle/PolicyDescriptor.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/org/apache/brooklyn/core/entity/lifecycle/PolicyDescriptor.java b/core/src/main/java/org/apache/brooklyn/core/entity/lifecycle/PolicyDescriptor.java new file mode 100644 index 0000000..ee063cb --- /dev/null +++ b/core/src/main/java/org/apache/brooklyn/core/entity/lifecycle/PolicyDescriptor.java @@ -0,0 +1,68 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.brooklyn.core.entity.lifecycle; + +import org.apache.brooklyn.api.policy.Policy; +import org.apache.brooklyn.core.entity.AbstractEntity; + +import com.google.common.base.Objects; + +/** Emitted as part of {@link AbstractEntity#POLICY_ADDED} and {@link AbstractEntity#POLICY_REMOVED} */ +public class PolicyDescriptor { + + private final String id; + private final String type; + private final String name; + + public PolicyDescriptor(Policy policy) { + this.id = policy.getId(); + this.type = policy.getPolicyType().getName(); + this.name = policy.getDisplayName(); + } + public String getId() { + return id; + } + + public String getPolicyType() { + return type; + } + + public String getName() { + return name; + } + + @Override + public boolean equals(Object other) { + if (!(other instanceof PolicyDescriptor)) { + return false; + } + PolicyDescriptor o = (PolicyDescriptor) other; + return Objects.equal(id, o.id) && Objects.equal(type, o.type) && Objects.equal(name, o.name); + } + + @Override + public int hashCode() { + return id.hashCode(); + } + + @Override + public String toString() { + return Objects.toStringHelper(this).add("id", id).add("type", type).add("name", name).omitNullValues().toString(); + } +} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/c27cf1d0/core/src/main/java/org/apache/brooklyn/core/entity/lifecycle/QuorumCheck.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/org/apache/brooklyn/core/entity/lifecycle/QuorumCheck.java b/core/src/main/java/org/apache/brooklyn/core/entity/lifecycle/QuorumCheck.java new file mode 100644 index 0000000..29f8deb --- /dev/null +++ b/core/src/main/java/org/apache/brooklyn/core/entity/lifecycle/QuorumCheck.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.entity.lifecycle; + +import java.io.Serializable; + +/** + * For checking if a group/cluster is quorate. That is, whether the group has sufficient + * healthy members. + * @deprecated since 0.7.0 use {@link org.apache.brooklyn.util.collections.QuorumCheck}. + * but keep this for a while as old quorum checks might be persisted. + */ +@Deprecated +public interface QuorumCheck extends org.apache.brooklyn.util.collections.QuorumCheck { + + /** + * @param sizeHealthy Number of healthy members + * @param totalSize Total number of members one would expect to be healthy (i.e. ignoring stopped members) + * @return Whether this group is healthy + */ + public boolean isQuorate(int sizeHealthy, int totalSize); + + public static class QuorumChecks { + /** + * Checks that all members that should be up are up (i.e. ignores stopped nodes). + */ + public static QuorumCheck all() { + return new NumericQuorumCheck(0, 1.0, false); + } + /** + * Checks all members that should be up are up, and that there is at least one such member. + */ + public static QuorumCheck allAndAtLeastOne() { + return new NumericQuorumCheck(1, 1.0, false); + } + /** + * Requires at least one member that should be up is up. + */ + public static QuorumCheck atLeastOne() { + return new NumericQuorumCheck(1, 0.0, false); + } + /** + * Requires at least one member to be up if the total size is non-zero. + * i.e. okay if empty, or if non-empty and something is healthy, but not okay if not-empty and nothing is healthy. + * "Empty" means that no members are supposed to be up (e.g. there may be stopped members). + */ + public static QuorumCheck atLeastOneUnlessEmpty() { + return new NumericQuorumCheck(1, 0.0, true); + } + /** + * Always "healthy" + */ + public static QuorumCheck alwaysTrue() { + return new NumericQuorumCheck(0, 0.0, true); + } + public static QuorumCheck newInstance(int minRequiredSize, double minRequiredRatio, boolean allowEmpty) { + return new NumericQuorumCheck(minRequiredSize, minRequiredRatio, allowEmpty); + } + } + + /** @deprecated since 0.7.0 use {@link org.apache.brooklyn.util.collections.QuorumCheck}. + * but keep this until we have a transition defined. + */ + @Deprecated + public static class NumericQuorumCheck implements QuorumCheck, Serializable { + private static final long serialVersionUID = -5090669237460159621L; + + protected final int minRequiredSize; + protected final double minRequiredRatio; + protected final boolean allowEmpty; + + public NumericQuorumCheck(int minRequiredSize, double minRequiredRatio, boolean allowEmpty) { + this.minRequiredSize = minRequiredSize; + this.minRequiredRatio = minRequiredRatio; + this.allowEmpty = allowEmpty; + } + + @Override + public boolean isQuorate(int sizeHealthy, int totalSize) { + if (allowEmpty && totalSize==0) return true; + if (sizeHealthy < minRequiredSize) return false; + if (sizeHealthy < totalSize*minRequiredRatio-0.000000001) return false; + return true; + } + + @Override + public String toString() { + return "QuorumCheck[require="+minRequiredSize+","+((int)100*minRequiredRatio)+"%"+(allowEmpty ? "|0" : "")+"]"; + } + } + +} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/c27cf1d0/core/src/main/java/org/apache/brooklyn/core/entity/lifecycle/ServiceStateLogic.java ---------------------------------------------------------------------- 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 new file mode 100644 index 0000000..654662f --- /dev/null +++ b/core/src/main/java/org/apache/brooklyn/core/entity/lifecycle/ServiceStateLogic.java @@ -0,0 +1,639 @@ +/* + * 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.entity.lifecycle; + +import java.util.Collection; +import java.util.Date; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import javax.annotation.Nullable; + +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.api.entity.Group; +import org.apache.brooklyn.api.sensor.AttributeSensor; +import org.apache.brooklyn.api.sensor.Enricher; +import org.apache.brooklyn.api.sensor.EnricherSpec; +import org.apache.brooklyn.api.sensor.EnricherSpec.ExtensibleEnricherSpec; +import org.apache.brooklyn.api.sensor.Sensor; +import org.apache.brooklyn.api.sensor.SensorEvent; +import org.apache.brooklyn.api.sensor.SensorEventListener; +import org.apache.brooklyn.config.ConfigInheritance; +import org.apache.brooklyn.config.ConfigKey; +import org.apache.brooklyn.core.BrooklynLogging; +import org.apache.brooklyn.core.BrooklynLogging.LoggingLevel; +import org.apache.brooklyn.core.config.ConfigKeys; +import org.apache.brooklyn.core.entity.Attributes; +import org.apache.brooklyn.core.entity.Entities; +import org.apache.brooklyn.core.entity.EntityAdjuncts; +import org.apache.brooklyn.core.entity.EntityInternal; +import org.apache.brooklyn.core.entity.EntityPredicates; +import org.apache.brooklyn.core.entity.lifecycle.Lifecycle.Transition; +import org.apache.brooklyn.sensor.enricher.AbstractEnricher; +import org.apache.brooklyn.sensor.enricher.AbstractMultipleSensorAggregator; +import org.apache.brooklyn.sensor.enricher.Enrichers; +import org.apache.brooklyn.sensor.enricher.UpdatingMap; +import org.apache.brooklyn.util.collections.CollectionFunctionals; +import org.apache.brooklyn.util.collections.MutableList; +import org.apache.brooklyn.util.collections.MutableMap; +import org.apache.brooklyn.util.collections.MutableSet; +import org.apache.brooklyn.util.collections.QuorumCheck; +import org.apache.brooklyn.util.core.task.ValueResolver; +import org.apache.brooklyn.util.guava.Functionals; +import org.apache.brooklyn.util.guava.Maybe; +import org.apache.brooklyn.util.repeat.Repeater; +import org.apache.brooklyn.util.text.Strings; +import org.apache.brooklyn.util.time.Duration; +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.Stopwatch; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableSet; +import com.google.common.reflect.TypeToken; + +/** Logic, sensors and enrichers, and conveniences, for computing service status */ +public class ServiceStateLogic { + + private static final Logger log = LoggerFactory.getLogger(ServiceStateLogic.class); + + public static final AttributeSensor<Boolean> SERVICE_UP = Attributes.SERVICE_UP; + public static final AttributeSensor<Map<String,Object>> SERVICE_NOT_UP_INDICATORS = Attributes.SERVICE_NOT_UP_INDICATORS; + public static final AttributeSensor<Map<String,Object>> SERVICE_NOT_UP_DIAGNOSTICS = Attributes.SERVICE_NOT_UP_DIAGNOSTICS; + + public static final AttributeSensor<Lifecycle> SERVICE_STATE_ACTUAL = Attributes.SERVICE_STATE_ACTUAL; + public static final AttributeSensor<Lifecycle.Transition> SERVICE_STATE_EXPECTED = Attributes.SERVICE_STATE_EXPECTED; + public static final AttributeSensor<Map<String,Object>> SERVICE_PROBLEMS = Attributes.SERVICE_PROBLEMS; + + /** static only; not for instantiation */ + private ServiceStateLogic() {} + + public static <TKey,TVal> TVal getMapSensorEntry(EntityLocal entity, AttributeSensor<Map<TKey,TVal>> sensor, TKey key) { + Map<TKey, TVal> map = entity.getAttribute(sensor); + if (map==null) return null; + return map.get(key); + } + + @SuppressWarnings("unchecked") + public static <TKey,TVal> void clearMapSensorEntry(EntityLocal entity, AttributeSensor<Map<TKey,TVal>> sensor, TKey key) { + updateMapSensorEntry(entity, sensor, key, (TVal)Entities.REMOVE); + } + + /** update the given key in the given map sensor */ + public static <TKey,TVal> void updateMapSensorEntry(EntityLocal entity, AttributeSensor<Map<TKey,TVal>> sensor, final TKey key, final TVal v) { + /* + * Important to *not* modify the existing attribute value; must make a copy, modify that, and publish. + * This is because a Propagator enricher will set this same value on another entity. There was very + * strange behaviour when this was done for a SERVICE_UP_INDICATORS sensor - the updates done here + * applied to the attribute of both entities! + * + * Need to do this update atomically (i.e. sequentially) because there is no threading control for + * what is calling updateMapSensorEntity. It is called directly on start, on initialising enrichers, + * and in event listeners. These calls could be concurrent. + */ + Function<Map<TKey,TVal>, Maybe<Map<TKey,TVal>>> modifier = new Function<Map<TKey,TVal>, Maybe<Map<TKey,TVal>>>() { + @Override public Maybe<Map<TKey, TVal>> apply(Map<TKey, TVal> map) { + boolean created = (map==null); + if (created) map = MutableMap.of(); + + boolean changed; + if (v == Entities.REMOVE) { + changed = map.containsKey(key); + if (changed) { + map = MutableMap.copyOf(map); + map.remove(key); + } + } else { + TVal oldV = map.get(key); + if (oldV==null) { + changed = (v!=null || !map.containsKey(key)); + } else { + changed = !oldV.equals(v); + } + if (changed) { + map = MutableMap.copyOf(map); + map.put(key, (TVal)v); + } + } + if (changed || created) { + return Maybe.of(map); + } else { + return Maybe.absent(); + } + } + }; + + if (!Entities.isNoLongerManaged(entity)) { + entity.modifyAttribute(sensor, modifier); + } + } + + public static void setExpectedState(Entity entity, Lifecycle state) { + if (state==Lifecycle.RUNNING) { + Boolean up = ((EntityInternal)entity).getAttribute(Attributes.SERVICE_UP); + if (!Boolean.TRUE.equals(up) && !Boolean.TRUE.equals(Entities.isReadOnly(entity))) { + // pause briefly to allow any recent problem-clearing processing to complete + Stopwatch timer = Stopwatch.createStarted(); + boolean nowUp = Repeater.create() + .every(ValueResolver.REAL_QUICK_PERIOD) + .limitTimeTo(ValueResolver.PRETTY_QUICK_WAIT) + .until(entity, EntityPredicates.attributeEqualTo(Attributes.SERVICE_UP, true)) + .run(); + if (nowUp) { + log.debug("Had to wait "+Duration.of(timer)+" for "+entity+" "+Attributes.SERVICE_UP+" to be true before setting "+state); + } else { + log.warn("Service is not up when setting "+state+" on "+entity+"; delayed "+Duration.of(timer)+" " + + "but "+Attributes.SERVICE_UP+" did not recover from "+up+"; not-up-indicators="+entity.getAttribute(Attributes.SERVICE_NOT_UP_INDICATORS)); + } + } + } + ((EntityInternal)entity).setAttribute(Attributes.SERVICE_STATE_EXPECTED, new Lifecycle.Transition(state, new Date())); + + Maybe<Enricher> enricher = EntityAdjuncts.tryFindWithUniqueTag(entity.getEnrichers(), ComputeServiceState.DEFAULT_ENRICHER_UNIQUE_TAG); + if (enricher.isPresent() && enricher.get() instanceof ComputeServiceState) { + ((ComputeServiceState)enricher.get()).onEvent(null); + } + } + public static Lifecycle getExpectedState(Entity entity) { + Transition expected = entity.getAttribute(Attributes.SERVICE_STATE_EXPECTED); + if (expected==null) return null; + return expected.getState(); + } + public static boolean isExpectedState(Entity entity, Lifecycle state) { + return getExpectedState(entity)==state; + } + + public static class ServiceNotUpLogic { + public static final String DEFAULT_ENRICHER_UNIQUE_TAG = "service.isUp if no service.notUp.indicators"; + + /** static only; not for instantiation */ + private ServiceNotUpLogic() {} + + public static final EnricherSpec<?> newEnricherForServiceUpIfNotUpIndicatorsEmpty() { + return Enrichers.builder() + .transforming(SERVICE_NOT_UP_INDICATORS).<Object>publishing(Attributes.SERVICE_UP) + .suppressDuplicates(true) + .computing( + Functionals.<Map<String,?>> + ifNotEquals(null).<Object>apply(Functions.forPredicate(CollectionFunctionals.<String>mapSizeEquals(0))) + .defaultValue(Entities.REMOVE) ) + .uniqueTag(DEFAULT_ENRICHER_UNIQUE_TAG) + .build(); + } + + /** puts the given value into the {@link Attributes#SERVICE_NOT_UP_INDICATORS} map as if the + * {@link UpdatingMap} enricher for the given key */ + public static void updateNotUpIndicator(EntityLocal entity, String key, Object value) { + updateMapSensorEntry(entity, Attributes.SERVICE_NOT_UP_INDICATORS, key, value); + } + /** clears any entry for the given key in the {@link Attributes#SERVICE_NOT_UP_INDICATORS} map */ + public static void clearNotUpIndicator(EntityLocal entity, String key) { + clearMapSensorEntry(entity, Attributes.SERVICE_NOT_UP_INDICATORS, key); + } + /** as {@link #updateNotUpIndicator(EntityLocal, String, Object)} using the given sensor as the key */ + public static void updateNotUpIndicator(EntityLocal entity, Sensor<?> sensor, Object value) { + updateMapSensorEntry(entity, Attributes.SERVICE_NOT_UP_INDICATORS, sensor.getName(), value); + } + /** as {@link #clearNotUpIndicator(EntityLocal, String)} using the given sensor as the key */ + public static void clearNotUpIndicator(EntityLocal entity, Sensor<?> sensor) { + clearMapSensorEntry(entity, Attributes.SERVICE_NOT_UP_INDICATORS, sensor.getName()); + } + + public static void updateNotUpIndicatorRequiringNonEmptyList(EntityLocal entity, AttributeSensor<? extends Collection<?>> collectionSensor) { + Collection<?> nodes = entity.getAttribute(collectionSensor); + if (nodes==null || nodes.isEmpty()) ServiceNotUpLogic.updateNotUpIndicator(entity, collectionSensor, "Should have at least one entry"); + else ServiceNotUpLogic.clearNotUpIndicator(entity, collectionSensor); + } + public static void updateNotUpIndicatorRequiringNonEmptyMap(EntityLocal entity, AttributeSensor<? extends Map<?,?>> mapSensor) { + Map<?, ?> nodes = entity.getAttribute(mapSensor); + if (nodes==null || nodes.isEmpty()) ServiceNotUpLogic.updateNotUpIndicator(entity, mapSensor, "Should have at least one entry"); + else ServiceNotUpLogic.clearNotUpIndicator(entity, mapSensor); + } + + } + + /** Enricher which sets {@link Attributes#SERVICE_STATE_ACTUAL} on changes to + * {@link Attributes#SERVICE_STATE_EXPECTED}, {@link Attributes#SERVICE_PROBLEMS}, and {@link Attributes#SERVICE_UP} + * <p> + * The default implementation uses {@link #computeActualStateWhenExpectedRunning(Map, Boolean)} if the last expected transition + * was to {@link Lifecycle#RUNNING} and + * {@link #computeActualStateWhenNotExpectedRunning(Map, Boolean, org.apache.brooklyn.core.entity.lifecycle.Lifecycle.Transition)} otherwise. + * If these methods return null, the {@link Attributes#SERVICE_STATE_ACTUAL} sensor will be cleared (removed). + * Either of these methods can be overridden for custom logic, and that custom enricher can be created using + * {@link ServiceStateLogic#newEnricherForServiceState(Class)} and added to an entity. + */ + public static class ComputeServiceState extends AbstractEnricher implements SensorEventListener<Object> { + + public static final String DEFAULT_ENRICHER_UNIQUE_TAG = "service.state.actual"; + + public ComputeServiceState() {} + public ComputeServiceState(Map<?,?> flags) { super(flags); } + + @Override + public void init() { + super.init(); + if (uniqueTag==null) uniqueTag = DEFAULT_ENRICHER_UNIQUE_TAG; + } + + @Override + public void setEntity(EntityLocal entity) { + super.setEntity(entity); + if (suppressDuplicates==null) { + // only publish on changes, unless it is configured otherwise + suppressDuplicates = true; + } + + subscribe(entity, SERVICE_PROBLEMS, this); + subscribe(entity, SERVICE_UP, this); + subscribe(entity, SERVICE_STATE_EXPECTED, this); + onEvent(null); + } + + @Override + public void onEvent(@Nullable SensorEvent<Object> event) { + Preconditions.checkNotNull(entity, "Cannot handle subscriptions or compute state until associated with an entity"); + + Map<String, Object> serviceProblems = entity.getAttribute(SERVICE_PROBLEMS); + Boolean serviceUp = entity.getAttribute(SERVICE_UP); + Lifecycle.Transition serviceExpected = entity.getAttribute(SERVICE_STATE_EXPECTED); + + if (serviceExpected!=null && serviceExpected.getState()==Lifecycle.RUNNING) { + setActualState( computeActualStateWhenExpectedRunning(serviceProblems, serviceUp) ); + } else { + setActualState( computeActualStateWhenNotExpectedRunning(serviceProblems, serviceUp, serviceExpected) ); + } + } + + protected Lifecycle computeActualStateWhenExpectedRunning(Map<String, Object> problems, Boolean serviceUp) { + if (Boolean.TRUE.equals(serviceUp) && (problems==null || problems.isEmpty())) { + return Lifecycle.RUNNING; + } else { + if (!Lifecycle.ON_FIRE.equals(entity.getAttribute(SERVICE_STATE_ACTUAL))) { + BrooklynLogging.log(log, BrooklynLogging.levelDependingIfReadOnly(entity, LoggingLevel.WARN, LoggingLevel.TRACE, LoggingLevel.DEBUG), + "Setting "+entity+" "+Lifecycle.ON_FIRE+" due to problems when expected running, up="+serviceUp+", "+ + (problems==null || problems.isEmpty() ? "not-up-indicators: "+entity.getAttribute(SERVICE_NOT_UP_INDICATORS) : "problems: "+problems)); + } + return Lifecycle.ON_FIRE; + } + } + + protected Lifecycle computeActualStateWhenNotExpectedRunning(Map<String, Object> problems, Boolean up, Lifecycle.Transition stateTransition) { + if (stateTransition!=null) { + // if expected state is present but not running, just echo the expected state (ignore problems and up-ness) + return stateTransition.getState(); + + } else if (problems!=null && !problems.isEmpty()) { + // if there is no expected state, then if service is not up, say stopped, else say on fire (whether service up is true or not present) + if (Boolean.FALSE.equals(up)) { + return Lifecycle.STOPPED; + } else { + BrooklynLogging.log(log, BrooklynLogging.levelDependingIfReadOnly(entity, LoggingLevel.WARN, LoggingLevel.TRACE, LoggingLevel.DEBUG), + "Setting "+entity+" "+Lifecycle.ON_FIRE+" due to problems when expected "+stateTransition+" / up="+up+": "+problems); + return Lifecycle.ON_FIRE; + } + } else { + // no expected transition and no problems + // if the problems map is non-null, then infer from service up; + // if there is no problems map, then leave unchanged (user may have set it explicitly) + if (problems!=null) + return (up==null ? null /* remove if up is not set */ : + up ? Lifecycle.RUNNING : Lifecycle.STOPPED); + else + return entity.getAttribute(SERVICE_STATE_ACTUAL); + } + } + + protected void setActualState(@Nullable Lifecycle state) { + if (log.isTraceEnabled()) log.trace("{} setting actual state {}", this, state); + if (((EntityInternal)entity).getManagementSupport().isNoLongerManaged()) { + // won't catch everything, but catches some + BrooklynLogging.log(log, BrooklynLogging.levelDebugOrTraceIfReadOnly(entity), + entity+" is no longer managed when told to set actual state to "+state+"; suppressing"); + return; + } + emit(SERVICE_STATE_ACTUAL, (state==null ? Entities.REMOVE : state)); + } + + } + + public static final EnricherSpec<?> newEnricherForServiceStateFromProblemsAndUp() { + return newEnricherForServiceState(ComputeServiceState.class); + } + public static final EnricherSpec<?> newEnricherForServiceState(Class<? extends Enricher> type) { + return EnricherSpec.create(type); + } + + public static class ServiceProblemsLogic { + /** static only; not for instantiation */ + private ServiceProblemsLogic() {} + + /** puts the given value into the {@link Attributes#SERVICE_PROBLEMS} map as if the + * {@link UpdatingMap} enricher for the given sensor reported this value */ + public static void updateProblemsIndicator(EntityLocal entity, Sensor<?> sensor, Object value) { + updateMapSensorEntry(entity, Attributes.SERVICE_PROBLEMS, sensor.getName(), value); + } + /** clears any entry for the given sensor in the {@link Attributes#SERVICE_PROBLEMS} map */ + public static void clearProblemsIndicator(EntityLocal entity, Sensor<?> sensor) { + clearMapSensorEntry(entity, Attributes.SERVICE_PROBLEMS, sensor.getName()); + } + /** as {@link #updateProblemsIndicator(EntityLocal, Sensor, Object)} */ + public static void updateProblemsIndicator(EntityLocal entity, Effector<?> eff, Object value) { + updateMapSensorEntry(entity, Attributes.SERVICE_PROBLEMS, eff.getName(), value); + } + /** as {@link #clearProblemsIndicator(EntityLocal, Sensor)} */ + public static void clearProblemsIndicator(EntityLocal entity, Effector<?> eff) { + clearMapSensorEntry(entity, Attributes.SERVICE_PROBLEMS, eff.getName()); + } + /** as {@link #updateProblemsIndicator(EntityLocal, Sensor, Object)} */ + public static void updateProblemsIndicator(EntityLocal entity, String key, Object value) { + updateMapSensorEntry(entity, Attributes.SERVICE_PROBLEMS, key, value); + } + /** as {@link #clearProblemsIndicator(EntityLocal, Sensor)} */ + public static void clearProblemsIndicator(EntityLocal entity, String key) { + clearMapSensorEntry(entity, Attributes.SERVICE_PROBLEMS, key); + } + } + + public static class ComputeServiceIndicatorsFromChildrenAndMembers extends AbstractMultipleSensorAggregator<Void> implements SensorEventListener<Object> { + /** standard unique tag identifying instances of this enricher at runtime, also used for the map sensor if no unique tag specified */ + public final static String DEFAULT_UNIQUE_TAG = "service-lifecycle-indicators-from-children-and-members"; + + /** as {@link #DEFAULT_UNIQUE_TAG}, but when a second distinct instance is responsible for computing service up */ + public final static String DEFAULT_UNIQUE_TAG_UP = "service-not-up-indicators-from-children-and-members"; + + public static final ConfigKey<QuorumCheck> UP_QUORUM_CHECK = ConfigKeys.builder(QuorumCheck.class, "enricher.service_state.children_and_members.quorum.up") + .description("Logic for checking whether this service is up, based on children and/or members, defaulting to allowing none but if there are any requiring at least one to be up") + .defaultValue(QuorumCheck.QuorumChecks.atLeastOneUnlessEmpty()) + .inheritance(ConfigInheritance.NONE) + .build(); + public static final ConfigKey<QuorumCheck> RUNNING_QUORUM_CHECK = ConfigKeys.builder(QuorumCheck.class, "enricher.service_state.children_and_members.quorum.running") + .description("Logic for checking whether this service is healthy, based on children and/or members running, defaulting to requiring none to be ON-FIRE") + .defaultValue(QuorumCheck.QuorumChecks.all()) + .inheritance(ConfigInheritance.NONE) + .build(); + // TODO items below should probably also have inheritance NONE ? + public static final ConfigKey<Boolean> DERIVE_SERVICE_NOT_UP = ConfigKeys.newBooleanConfigKey("enricher.service_state.children_and_members.service_up.publish", "Whether to derive a service-not-up indicator from children", true); + public static final ConfigKey<Boolean> DERIVE_SERVICE_PROBLEMS = ConfigKeys.newBooleanConfigKey("enricher.service_state.children_and_members.service_problems.publish", "Whether to derive a service-problem indicator from children", true); + public static final ConfigKey<Boolean> IGNORE_ENTITIES_WITH_SERVICE_UP_NULL = ConfigKeys.newBooleanConfigKey("enricher.service_state.children_and_members.ignore_entities.service_up_null", "Whether to ignore children reporting null values for service up", true); + @SuppressWarnings("serial") + public static final ConfigKey<Set<Lifecycle>> IGNORE_ENTITIES_WITH_THESE_SERVICE_STATES = ConfigKeys.newConfigKey(new TypeToken<Set<Lifecycle>>() {}, + "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.<Lifecycle>builder().addAll(Lifecycle.values()).add(null).remove(Lifecycle.RUNNING).remove(Lifecycle.ON_FIRE).build().asUnmodifiable()); + + protected String getKeyForMapSensor() { + return Preconditions.checkNotNull(super.getUniqueTag()); + } + + @Override + protected void setEntityLoadingConfig() { + fromChildren = true; + fromMembers = true; + // above sets default + super.setEntityLoadingConfig(); + if (isAggregatingMembers() && (!(entity instanceof Group))) { + if (fromChildren) fromMembers=false; + else throw new IllegalStateException("Cannot monitor only members for non-group entity "+entity+": "+this); + } + Preconditions.checkNotNull(getKeyForMapSensor()); + } + + @Override + protected void setEntityLoadingTargetConfig() { + if (getConfig(TARGET_SENSOR)!=null) + throw new IllegalArgumentException("Must not set "+TARGET_SENSOR+" when using "+this); + } + + @Override + public void setEntity(EntityLocal entity) { + super.setEntity(entity); + if (suppressDuplicates==null) { + // only publish on changes, unless it is configured otherwise + suppressDuplicates = true; + } + } + + final static Set<ConfigKey<?>> RECONFIGURABLE_KEYS = ImmutableSet.<ConfigKey<?>>of( + UP_QUORUM_CHECK, RUNNING_QUORUM_CHECK, + DERIVE_SERVICE_NOT_UP, DERIVE_SERVICE_NOT_UP, + IGNORE_ENTITIES_WITH_SERVICE_UP_NULL, IGNORE_ENTITIES_WITH_THESE_SERVICE_STATES); + + @Override + protected <T> void doReconfigureConfig(ConfigKey<T> key, T val) { + if (RECONFIGURABLE_KEYS.contains(key)) { + return; + } else { + super.doReconfigureConfig(key, val); + } + } + + @Override + protected void onChanged() { + super.onChanged(); + if (entity != null && isRunning()) + onUpdated(); + } + + private final List<Sensor<?>> SOURCE_SENSORS = ImmutableList.<Sensor<?>>of(SERVICE_UP, SERVICE_STATE_ACTUAL); + @Override + protected Collection<Sensor<?>> getSourceSensors() { + return SOURCE_SENSORS; + } + + @Override + protected void onUpdated() { + if (entity==null || !Entities.isManaged(entity)) { + // either invoked during setup or entity has become unmanaged; just ignore + BrooklynLogging.log(log, BrooklynLogging.levelDebugOrTraceIfReadOnly(entity), + "Ignoring {} onUpdated when entity is not in valid state ({})", this, entity); + return; + } + + // override superclass to publish multiple sensors + if (getConfig(DERIVE_SERVICE_PROBLEMS)) { + updateMapSensor(SERVICE_PROBLEMS, computeServiceProblems()); + } + + if (getConfig(DERIVE_SERVICE_NOT_UP)) { + updateMapSensor(SERVICE_NOT_UP_INDICATORS, computeServiceNotUp()); + } + } + + protected Object computeServiceNotUp() { + Map<Entity, Boolean> values = getValues(SERVICE_UP); + List<Entity> violators = MutableList.of(); + boolean ignoreNull = getConfig(IGNORE_ENTITIES_WITH_SERVICE_UP_NULL); + Set<Lifecycle> ignoreStates = getConfig(IGNORE_ENTITIES_WITH_THESE_SERVICE_STATES); + int entries=0; + int numUp=0; + for (Map.Entry<Entity, Boolean> state: values.entrySet()) { + if (ignoreNull && state.getValue()==null) + continue; + entries++; + Lifecycle entityState = state.getKey().getAttribute(SERVICE_STATE_ACTUAL); + + if (Boolean.TRUE.equals(state.getValue())) numUp++; + else if (!ignoreStates.contains(entityState)) { + violators.add(state.getKey()); + } + } + + QuorumCheck qc = getConfig(UP_QUORUM_CHECK); + if (qc!=null) { + if (qc.isQuorate(numUp, violators.size()+numUp)) + // quorate + return null; + + if (values.isEmpty()) return "No entities present"; + if (entries==0) return "No entities publishing service up"; + if (violators.isEmpty()) return "Not enough entities"; + } else { + if (violators.isEmpty()) + return null; + } + + if (violators.size()==1) return violators.get(0)+" is not up"; + if (violators.size()==entries) return "None of the entities are up"; + return violators.size()+" entities are not up, including "+violators.get(0); + } + + protected Object computeServiceProblems() { + Map<Entity, Lifecycle> values = getValues(SERVICE_STATE_ACTUAL); + int numRunning=0; + List<Entity> onesNotHealthy=MutableList.of(); + Set<Lifecycle> ignoreStates = getConfig(IGNORE_ENTITIES_WITH_THESE_SERVICE_STATES); + for (Map.Entry<Entity,Lifecycle> state: values.entrySet()) { + if (state.getValue()==Lifecycle.RUNNING) numRunning++; + else if (!ignoreStates.contains(state.getValue())) + onesNotHealthy.add(state.getKey()); + } + + QuorumCheck qc = getConfig(RUNNING_QUORUM_CHECK); + if (qc!=null) { + if (qc.isQuorate(numRunning, onesNotHealthy.size()+numRunning)) + // quorate + return null; + + if (onesNotHealthy.isEmpty()) + return "Not enough entities running to be quorate"; + } else { + if (onesNotHealthy.isEmpty()) + return null; + } + + return "Required entit"+Strings.ies(onesNotHealthy.size())+" not healthy: "+ + (onesNotHealthy.size()>3 ? onesNotHealthy.get(0)+" and "+(onesNotHealthy.size()-1)+" others" + : Strings.join(onesNotHealthy, ", ")); + } + + protected void updateMapSensor(AttributeSensor<Map<String, Object>> sensor, Object value) { + if (log.isTraceEnabled()) log.trace("{} updating map sensor {} with {}", new Object[] { this, sensor, value }); + + if (value!=null) { + updateMapSensorEntry(entity, sensor, getKeyForMapSensor(), value); + } else { + clearMapSensorEntry(entity, sensor, getKeyForMapSensor()); + } + } + + /** not used; see specific `computeXxx` methods, invoked by overridden onUpdated */ + @Override + protected Object compute() { + return null; + } + } + + public static class ComputeServiceIndicatorsFromChildrenAndMembersSpec extends ExtensibleEnricherSpec<ComputeServiceIndicatorsFromChildrenAndMembers,ComputeServiceIndicatorsFromChildrenAndMembersSpec> { + private static final long serialVersionUID = -607444925297963712L; + + protected ComputeServiceIndicatorsFromChildrenAndMembersSpec() { + this(ComputeServiceIndicatorsFromChildrenAndMembers.class); + } + + protected ComputeServiceIndicatorsFromChildrenAndMembersSpec(Class<? extends ComputeServiceIndicatorsFromChildrenAndMembers> clazz) { + super(clazz); + } + + public void addTo(Entity entity) { + entity.addEnricher(this); + } + + public ComputeServiceIndicatorsFromChildrenAndMembersSpec checkChildrenAndMembers() { + configure(ComputeServiceIndicatorsFromChildrenAndMembers.FROM_MEMBERS, true); + configure(ComputeServiceIndicatorsFromChildrenAndMembers.FROM_CHILDREN, true); + return self(); + } + public ComputeServiceIndicatorsFromChildrenAndMembersSpec checkMembersOnly() { + configure(ComputeServiceIndicatorsFromChildrenAndMembers.FROM_MEMBERS, true); + configure(ComputeServiceIndicatorsFromChildrenAndMembers.FROM_CHILDREN, false); + return self(); + } + public ComputeServiceIndicatorsFromChildrenAndMembersSpec checkChildrenOnly() { + configure(ComputeServiceIndicatorsFromChildrenAndMembers.FROM_MEMBERS, false); + configure(ComputeServiceIndicatorsFromChildrenAndMembers.FROM_CHILDREN, true); + return self(); + } + + public ComputeServiceIndicatorsFromChildrenAndMembersSpec requireUpChildren(QuorumCheck check) { + configure(ComputeServiceIndicatorsFromChildrenAndMembers.UP_QUORUM_CHECK, check); + return self(); + } + public ComputeServiceIndicatorsFromChildrenAndMembersSpec requireRunningChildren(QuorumCheck check) { + configure(ComputeServiceIndicatorsFromChildrenAndMembers.RUNNING_QUORUM_CHECK, check); + return self(); + } + } + + /** provides the default {@link ComputeServiceIndicatorsFromChildrenAndMembers} enricher, + * using the default unique tag ({@link ComputeServiceIndicatorsFromChildrenAndMembers#DEFAULT_UNIQUE_TAG}), + * configured here to require none on fire, and either no children or at least one up child, + * the spec can be further configured as appropriate */ + public static ComputeServiceIndicatorsFromChildrenAndMembersSpec newEnricherFromChildren() { + return new ComputeServiceIndicatorsFromChildrenAndMembersSpec() + .uniqueTag(ComputeServiceIndicatorsFromChildrenAndMembers.DEFAULT_UNIQUE_TAG); + } + + /** as {@link #newEnricherFromChildren()} but only publishing service not-up indicators, + * using a different unique tag ({@link ComputeServiceIndicatorsFromChildrenAndMembers#DEFAULT_UNIQUE_TAG_UP}), + * listening to children only, ignoring lifecycle/service-state, + * and using the same logic + * (viz looking only at children (not members) and requiring either no children or at least one child up) by default */ + public static ComputeServiceIndicatorsFromChildrenAndMembersSpec newEnricherFromChildrenUp() { + return newEnricherFromChildren() + .uniqueTag(ComputeServiceIndicatorsFromChildrenAndMembers.DEFAULT_UNIQUE_TAG_UP) + .checkChildrenOnly() + .configure(ComputeServiceIndicatorsFromChildrenAndMembers.DERIVE_SERVICE_PROBLEMS, false); + } + + /** as {@link #newEnricherFromChildren()} but only publishing service problems, + * listening to children and members, ignoring service up, + * and using the same logic + * (viz looking at children and members and requiring none are on fire) by default */ + public static ComputeServiceIndicatorsFromChildrenAndMembersSpec newEnricherFromChildrenState() { + return newEnricherFromChildren() + .configure(ComputeServiceIndicatorsFromChildrenAndMembers.DERIVE_SERVICE_NOT_UP, false); + } + +} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/c27cf1d0/core/src/main/java/org/apache/brooklyn/core/entity/trait/Changeable.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/org/apache/brooklyn/core/entity/trait/Changeable.java b/core/src/main/java/org/apache/brooklyn/core/entity/trait/Changeable.java new file mode 100644 index 0000000..d8fec0e --- /dev/null +++ b/core/src/main/java/org/apache/brooklyn/core/entity/trait/Changeable.java @@ -0,0 +1,35 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.brooklyn.core.entity.trait; + +import org.apache.brooklyn.api.entity.Entity; +import org.apache.brooklyn.api.sensor.AttributeSensor; +import org.apache.brooklyn.sensor.core.BasicNotificationSensor; +import org.apache.brooklyn.sensor.core.Sensors; + +/** + * A collection of entities that can change. + */ +public interface Changeable { + + AttributeSensor<Integer> GROUP_SIZE = Sensors.newIntegerSensor("group.members.count", "Number of members"); + + BasicNotificationSensor<Entity> MEMBER_ADDED = new BasicNotificationSensor<Entity>(Entity.class, "group.members.added", "Entity added to group members"); + BasicNotificationSensor<Entity> MEMBER_REMOVED = new BasicNotificationSensor<Entity>(Entity.class, "group.members.removed", "Entity removed from group members"); +} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/c27cf1d0/core/src/main/java/org/apache/brooklyn/core/entity/trait/MemberReplaceable.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/org/apache/brooklyn/core/entity/trait/MemberReplaceable.java b/core/src/main/java/org/apache/brooklyn/core/entity/trait/MemberReplaceable.java new file mode 100644 index 0000000..a67b1af --- /dev/null +++ b/core/src/main/java/org/apache/brooklyn/core/entity/trait/MemberReplaceable.java @@ -0,0 +1,45 @@ +/* + * 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.entity.trait; + +import java.util.NoSuchElementException; + +import org.apache.brooklyn.core.annotation.Effector; +import org.apache.brooklyn.core.annotation.EffectorParam; +import org.apache.brooklyn.effector.core.MethodEffector; +import org.apache.brooklyn.entity.group.StopFailedRuntimeException; + +public interface MemberReplaceable { + + MethodEffector<String> REPLACE_MEMBER = new MethodEffector<String>(MemberReplaceable.class, "replaceMember"); + + /** + * Replaces the entity with the given ID, if it is a member. + * <p> + * First adds a new member, then removes this one. + * + * @param memberId entity id of a member to be replaced + * @return the id of the new entity + * @throws NoSuchElementException If entity cannot be resolved, or it is not a member + * @throws StopFailedRuntimeException If stop failed, after successfully starting replacement + */ + @Effector(description="Replaces the entity with the given ID, if it is a member; first adds a new member, then removes this one. "+ + "Returns id of the new entity; or throws exception if couldn't be replaced.") + String replaceMember(@EffectorParam(name="memberId", description="The entity id of a member to be replaced") String memberId); +} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/c27cf1d0/core/src/main/java/org/apache/brooklyn/core/entity/trait/Resizable.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/org/apache/brooklyn/core/entity/trait/Resizable.java b/core/src/main/java/org/apache/brooklyn/core/entity/trait/Resizable.java new file mode 100644 index 0000000..ebf37d0 --- /dev/null +++ b/core/src/main/java/org/apache/brooklyn/core/entity/trait/Resizable.java @@ -0,0 +1,50 @@ +/* + * 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.entity.trait; + + +import org.apache.brooklyn.core.annotation.Effector; +import org.apache.brooklyn.core.annotation.EffectorParam; +import org.apache.brooklyn.effector.core.MethodEffector; + +/** + * Defines an entity group that can be re-sized dynamically. + * <p/> + * By invoking the {@link #resize(Integer)} effector, the number of child nodes + * can be reduced (by shutting down some of them) or increased (by provisioning new entities.) + */ +public interface Resizable { + + MethodEffector<Integer> RESIZE = new MethodEffector<Integer>(Resizable.class, "resize"); + + /** + * Grow or shrink this entity to the desired size. + * + * @param desiredSize the new size of the entity group. + * @return the new size of the group. + */ + @Effector(description="Changes the size of the entity (e.g. the number of nodes in a cluster)") + Integer resize(@EffectorParam(name="desiredSize", description="The new size of the cluster") Integer desiredSize); + + /** + * @return the current size of the group. + */ + Integer getCurrentSize(); +} + http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/c27cf1d0/core/src/main/java/org/apache/brooklyn/core/entity/trait/Startable.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/org/apache/brooklyn/core/entity/trait/Startable.java b/core/src/main/java/org/apache/brooklyn/core/entity/trait/Startable.java new file mode 100644 index 0000000..304dba2 --- /dev/null +++ b/core/src/main/java/org/apache/brooklyn/core/entity/trait/Startable.java @@ -0,0 +1,123 @@ +/* + * 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.entity.trait; + +import java.util.Collection; + +import org.apache.brooklyn.api.effector.Effector; +import org.apache.brooklyn.api.location.Location; +import org.apache.brooklyn.api.sensor.AttributeSensor; +import org.apache.brooklyn.config.ConfigKey; +import org.apache.brooklyn.core.annotation.EffectorParam; +import org.apache.brooklyn.core.config.ConfigKeys; +import org.apache.brooklyn.core.entity.Attributes; +import org.apache.brooklyn.effector.core.EffectorBody; +import org.apache.brooklyn.effector.core.Effectors; +import org.apache.brooklyn.effector.core.MethodEffector; +import org.apache.brooklyn.util.core.config.ConfigBag; +import org.apache.brooklyn.util.core.task.Tasks; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * This interface describes an {@link org.apache.brooklyn.api.entity.Entity} that can be started and stopped. + * + * The {@link Effector}s are {@link #START}, {@link #STOP} and {@link #RESTART}. The start effector takes + * a collection of {@link Location} objects as an argument which will cause the entity to be started or stopped in all + * these locations. The other effectors will stop or restart the entity in the location(s) it is already running in. + */ +public interface Startable { + + + AttributeSensor<Boolean> SERVICE_UP = Attributes.SERVICE_UP; + + public static class StartEffectorBody extends EffectorBody<Void> { + public static final ConfigKey<Object> LOCATIONS = ConfigKeys.newConfigKey(Object.class, "locations", + "The location or locations to start in, as a string, a location object, a list of strings, " + + "or a list of location objects"); + @Override public Void call(ConfigBag parameters) { + parameters.put(LOCATIONS, entity().getManagementContext().getLocationRegistry().resolveList(parameters.get(LOCATIONS))); + return new MethodEffector<Void>(Startable.class, "start").call(entity(), parameters.getAllConfig()); + } + } + + public static class StopEffectorBody extends EffectorBody<Void> { + private static final Logger log = LoggerFactory.getLogger(Startable.class); + + @Override public Void call(ConfigBag parameters) { + if (!parameters.isEmpty()) { + log.warn("Parameters "+parameters+" not supported for call to "+entity()+" - "+Tasks.current()); + } + + return new MethodEffector<Void>(Startable.class, "stop").call(entity(), parameters.getAllConfig()); + } + } + + public static class RestartEffectorBody extends EffectorBody<Void> { + private static final Logger log = LoggerFactory.getLogger(Startable.class); + + @Override public Void call(ConfigBag parameters) { + if (!parameters.isEmpty()) { + log.warn("Parameters "+parameters+" not supported for call to "+entity()+" - "+Tasks.current()); + } + return new MethodEffector<Void>(Startable.class, "restart").call(entity(), parameters.getAllConfig()); + } + } + + org.apache.brooklyn.api.effector.Effector<Void> START = Effectors.effector(new MethodEffector<Void>(Startable.class, "start")) + // override start to take strings etc + .parameter(StartEffectorBody.LOCATIONS) + .impl(new StartEffectorBody()) + .build(); + + org.apache.brooklyn.api.effector.Effector<Void> STOP = Effectors.effector(new MethodEffector<Void>(Startable.class, "stop")) + .impl(new StopEffectorBody()) + .build(); + + org.apache.brooklyn.api.effector.Effector<Void> RESTART = Effectors.effector(new MethodEffector<Void>(Startable.class, "restart")) + .impl(new RestartEffectorBody()) + .build(); + + /** + * Start the entity in the given collection of locations. + * <p> + * Some entities may define custom {@link Effector} implementations which support + * a richer set of parameters. See the entity-specific {@link #START} effector declaration. + */ + @org.apache.brooklyn.core.annotation.Effector(description="Start the process/service represented by an entity") + void start(@EffectorParam(name="locations") Collection<? extends Location> locations); + + /** + * Stop the entity. + * <p> + * Some entities may define custom {@link Effector} implementations which support + * a richer set of parameters. See the entity-specific {@link #STOP} effector declaration. + */ + @org.apache.brooklyn.core.annotation.Effector(description="Stop the process/service represented by an entity") + void stop(); + + /** + * Restart the entity. + * <p> + * Some entities may define custom {@link Effector} implementations which support + * a richer set of parameters. See the entity-specific {@link #RESTART} effector declaration. + */ + @org.apache.brooklyn.core.annotation.Effector(description="Restart the process/service represented by an entity") + void restart(); +}
