/* * 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.enricher.AbstractEnricher; 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.enricher.stock.AbstractMultipleSensorAggregator; import org.apache.brooklyn.enricher.stock.Enrichers; import org.apache.brooklyn.enricher.stock.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.sensors().modify(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).sensors().set(Attributes.SERVICE_STATE_EXPECTED, new Lifecycle.Transition(state, new Date())); Maybe<Enricher> enricher = EntityAdjuncts.tryFindWithUniqueTag(entity.enrichers(), 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; } subscriptions().subscribe(entity, SERVICE_PROBLEMS, this); subscriptions().subscribe(entity, SERVICE_UP, this); subscriptions().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.enrichers().add(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); } }