/*
* 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.mgmt.rebind;
import static com.google.common.base.Preconditions.checkNotNull;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Callable;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicInteger;
import org.apache.brooklyn.api.entity.Application;
import org.apache.brooklyn.api.entity.Entity;
import org.apache.brooklyn.api.mgmt.ExecutionContext;
import org.apache.brooklyn.api.mgmt.Task;
import org.apache.brooklyn.api.mgmt.ha.ManagementNodeState;
import org.apache.brooklyn.api.mgmt.ha.MementoCopyMode;
import org.apache.brooklyn.api.mgmt.rebind.ChangeListener;
import org.apache.brooklyn.api.mgmt.rebind.PersistenceExceptionHandler;
import org.apache.brooklyn.api.mgmt.rebind.RebindExceptionHandler;
import org.apache.brooklyn.api.mgmt.rebind.RebindManager;
import org.apache.brooklyn.api.mgmt.rebind.mementos.BrooklynMementoPersister;
import org.apache.brooklyn.api.mgmt.rebind.mementos.BrooklynMementoRawData;
import org.apache.brooklyn.api.mgmt.rebind.mementos.TreeNode;
import org.apache.brooklyn.api.objs.BrooklynObject;
import org.apache.brooklyn.config.ConfigKey;
import org.apache.brooklyn.core.BrooklynFeatureEnablement;
import org.apache.brooklyn.core.config.ConfigKeys;
import org.apache.brooklyn.core.enricher.AbstractEnricher;
import org.apache.brooklyn.core.entity.Entities;
import org.apache.brooklyn.core.mgmt.ha.HighAvailabilityManagerImpl;
import org.apache.brooklyn.core.mgmt.internal.ManagementContextInternal;
import org.apache.brooklyn.core.mgmt.persist.BrooklynMementoPersisterToObjectStore;
import org.apache.brooklyn.core.mgmt.persist.BrooklynPersistenceUtils;
import org.apache.brooklyn.core.mgmt.persist.PersistenceActivityMetrics;
import org.apache.brooklyn.core.mgmt.persist.BrooklynPersistenceUtils.CreateBackupMode;
import org.apache.brooklyn.core.mgmt.rebind.transformer.CompoundTransformer;
import org.apache.brooklyn.core.server.BrooklynServerConfig;
import org.apache.brooklyn.util.collections.MutableList;
import org.apache.brooklyn.util.collections.MutableMap;
import org.apache.brooklyn.util.collections.QuorumCheck;
import org.apache.brooklyn.util.collections.QuorumCheck.QuorumChecks;
import org.apache.brooklyn.util.core.task.BasicExecutionContext;
import org.apache.brooklyn.util.core.task.ScheduledTask;
import org.apache.brooklyn.util.core.task.Tasks;
import org.apache.brooklyn.util.exceptions.Exceptions;
import org.apache.brooklyn.util.exceptions.RuntimeInterruptedException;
import org.apache.brooklyn.util.time.Duration;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.common.annotations.Beta;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Function;
import com.google.common.collect.Iterators;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
/** Manages the persistence/rebind process.
* <p>
* Lifecycle is to create an instance of this, set it up (e.g. {@link #setPeriodicPersistPeriod(Duration)},
* {@link #setPersister(BrooklynMementoPersister)}; however noting that persist period must be set before the persister).
* <p>
* Usually done for you by the conveniences (such as the launcher). */
public class RebindManagerImpl implements RebindManager {
// TODO Use ImmediateDeltaChangeListener if the period is set to 0?
public static final ConfigKey<RebindFailureMode> DANGLING_REFERENCE_FAILURE_MODE =
ConfigKeys.newConfigKey(RebindFailureMode.class, "rebind.failureMode.danglingRef",
"Action to take if a dangling reference is discovered during rebind", RebindFailureMode.CONTINUE);
public static final ConfigKey<RebindFailureMode> REBIND_FAILURE_MODE =
ConfigKeys.newConfigKey(RebindFailureMode.class, "rebind.failureMode.rebind",
"Action to take if a failure occurs during rebind", RebindFailureMode.FAIL_AT_END);
public static final ConfigKey<RebindFailureMode> ADD_CONFIG_FAILURE_MODE =
ConfigKeys.newConfigKey(RebindFailureMode.class, "rebind.failureMode.addConfig",
"Action to take if a failure occurs when setting a config value. It could happen coercion of the value type to fail.", RebindFailureMode.FAIL_AT_END);
public static final ConfigKey<RebindFailureMode> ADD_POLICY_FAILURE_MODE =
ConfigKeys.newConfigKey(RebindFailureMode.class, "rebind.failureMode.addPolicy",
"Action to take if a failure occurs when adding a policy or enricher", RebindFailureMode.CONTINUE);
public static final ConfigKey<RebindFailureMode> LOAD_POLICY_FAILURE_MODE =
ConfigKeys.newConfigKey(RebindFailureMode.class, "rebind.failureMode.loadPolicy",
"Action to take if a failure occurs when loading a policy or enricher", RebindFailureMode.CONTINUE);
public static final ConfigKey<QuorumCheck> DANGLING_REFERENCES_MIN_REQUIRED_HEALTHY =
ConfigKeys.newConfigKey(QuorumCheck.class, "rebind.failureMode.danglingRefs.minRequiredHealthy",
"Number of items which must be rebinded at various sizes; "
+ "a small number of dangling references is possible if items are in the process of being created or deleted, "
+ "and that should be resolved on retry; the default set here allows max 2 dangling up to 10 items, "
+ "then linear regression to allow max 5% at 100 items and above",
QuorumChecks.newLinearRange("[[0,-2],[10,8],[100,95],[200,190]]"));
public static final Logger LOG = LoggerFactory.getLogger(RebindManagerImpl.class);
private final ManagementContextInternal managementContext;
private volatile Duration periodicPersistPeriod = Duration.ONE_SECOND;
private volatile boolean persistenceRunning = false;
private volatile PeriodicDeltaChangeListener persistenceRealChangeListener;
private volatile ChangeListener persistencePublicChangeListener;
private volatile boolean readOnlyRunning = false;
private volatile ScheduledTask readOnlyTask = null;
private transient Semaphore rebindActive = new Semaphore(1);
private transient AtomicInteger readOnlyRebindCount = new AtomicInteger(Integer.MIN_VALUE);
private volatile BrooklynMementoPersister persistenceStoreAccess;
final boolean persistPoliciesEnabled;
final boolean persistEnrichersEnabled;
final boolean persistFeedsEnabled;
final boolean persistCatalogItemsEnabled;
private RebindFailureMode danglingRefFailureMode;
private RebindFailureMode rebindFailureMode;
private RebindFailureMode addConfigFailureMode;
private RebindFailureMode addPolicyFailureMode;
private RebindFailureMode loadPolicyFailureMode;
private QuorumCheck danglingRefsQuorumRequiredHealthy;
private boolean isAwaitingInitialRebind;
private PersistenceActivityMetrics rebindMetrics = new PersistenceActivityMetrics();
private PersistenceActivityMetrics persistMetrics = new PersistenceActivityMetrics();
Integer firstRebindAppCount, firstRebindEntityCount, firstRebindItemCount;
/**
* For tracking if rebinding, for {@link AbstractEnricher#isRebinding()} etc.
*
* TODO What is a better way to do this?!
*
* @author aled
*/
@Beta
public static class RebindTracker {
private static ThreadLocal<Boolean> rebinding = new ThreadLocal<Boolean>();
public static boolean isRebinding() {
return (rebinding.get() == Boolean.TRUE);
}
static void reset() {
rebinding.set(Boolean.FALSE);
}
static void setRebinding() {
rebinding.set(Boolean.TRUE);
}
}
public RebindManagerImpl(ManagementContextInternal managementContext) {
this.managementContext = managementContext;
this.persistencePublicChangeListener = ChangeListener.NOOP;
this.persistPoliciesEnabled = BrooklynFeatureEnablement.isEnabled(BrooklynFeatureEnablement.FEATURE_POLICY_PERSISTENCE_PROPERTY);
this.persistEnrichersEnabled = BrooklynFeatureEnablement.isEnabled(BrooklynFeatureEnablement.FEATURE_ENRICHER_PERSISTENCE_PROPERTY);
this.persistFeedsEnabled = BrooklynFeatureEnablement.isEnabled(BrooklynFeatureEnablement.FEATURE_FEED_PERSISTENCE_PROPERTY);
this.persistCatalogItemsEnabled = BrooklynFeatureEnablement.isEnabled(BrooklynFeatureEnablement.FEATURE_CATALOG_PERSISTENCE_PROPERTY);
danglingRefFailureMode = managementContext.getConfig().getConfig(DANGLING_REFERENCE_FAILURE_MODE);
rebindFailureMode = managementContext.getConfig().getConfig(REBIND_FAILURE_MODE);
addConfigFailureMode = managementContext.getConfig().getConfig(ADD_CONFIG_FAILURE_MODE);
addPolicyFailureMode = managementContext.getConfig().getConfig(ADD_POLICY_FAILURE_MODE);
loadPolicyFailureMode = managementContext.getConfig().getConfig(LOAD_POLICY_FAILURE_MODE);
danglingRefsQuorumRequiredHealthy = managementContext.getConfig().getConfig(DANGLING_REFERENCES_MIN_REQUIRED_HEALTHY);
LOG.debug("{} initialized, settings: policies={}, enrichers={}, feeds={}, catalog={}",
new Object[]{this, persistPoliciesEnabled, persistEnrichersEnabled, persistFeedsEnabled, persistCatalogItemsEnabled});
}
public ManagementContextInternal getManagementContext() {
return managementContext;
}
/**
* Must be called before setPerister()
*/
public void setPeriodicPersistPeriod(Duration period) {
if (persistenceStoreAccess!=null) throw new IllegalStateException("Cannot set period after persister is generated.");
this.periodicPersistPeriod = period;
}
/**
* @deprecated since 0.7.0; use {@link #setPeriodicPersistPeriod(Duration)}
*/
public void setPeriodicPersistPeriod(long periodMillis) {
setPeriodicPersistPeriod(Duration.of(periodMillis, TimeUnit.MILLISECONDS));
}
public boolean isPersistenceRunning() {
return persistenceRunning;
}
public boolean isReadOnlyRunning() {
return readOnlyRunning;
}
@Override
public void setPersister(BrooklynMementoPersister val) {
PersistenceExceptionHandler exceptionHandler = PersistenceExceptionHandlerImpl.builder()
.build();
setPersister(val, exceptionHandler);
}
@Override
public void setPersister(BrooklynMementoPersister val, PersistenceExceptionHandler exceptionHandler) {
if (persistenceStoreAccess != null && persistenceStoreAccess != val) {
throw new IllegalStateException("Dynamically changing persister is not supported: old="+persistenceStoreAccess+"; new="+val);
}
if (persistenceRealChangeListener!=null) {
// TODO should probably throw here, but previously we have not -- so let's log for now to be sure it's not happening
LOG.warn("Persister reset after listeners have been set", new Throwable("Source of persister reset"));
}
this.persistenceStoreAccess = checkNotNull(val, "persister");
this.persistenceRealChangeListener = new PeriodicDeltaChangeListener(managementContext.getServerExecutionContext(), persistenceStoreAccess, exceptionHandler, persistMetrics, periodicPersistPeriod);
this.persistencePublicChangeListener = new SafeChangeListener(persistenceRealChangeListener);
if (persistenceRunning) {
persistenceRealChangeListener.start();
}
}
@Override
@VisibleForTesting
public BrooklynMementoPersister getPersister() {
return persistenceStoreAccess;
}
@Override
public void startPersistence() {
if (readOnlyRunning) {
throw new IllegalStateException("Cannot start read-only when already running with persistence");
}
LOG.debug("Starting persistence ("+this+"), mgmt "+managementContext.getManagementNodeId());
if (!persistenceRunning) {
if (managementContext.getBrooklynProperties().getConfig(BrooklynServerConfig.PERSISTENCE_BACKUPS_REQUIRED_ON_PROMOTION)) {
BrooklynPersistenceUtils.createBackup(managementContext, CreateBackupMode.PROMOTION, MementoCopyMode.REMOTE);
}
}
persistenceRunning = true;
readOnlyRebindCount.set(Integer.MIN_VALUE);
persistenceStoreAccess.enableWriteAccess();
if (persistenceRealChangeListener != null) persistenceRealChangeListener.start();
}
@Override
public void stopPersistence() {
LOG.debug("Stopping persistence ("+this+"), mgmt "+managementContext.getManagementNodeId());
persistenceRunning = false;
if (persistenceRealChangeListener != null) persistenceRealChangeListener.stop();
if (persistenceStoreAccess != null) persistenceStoreAccess.disableWriteAccess(true);
LOG.debug("Stopped rebind (persistence), mgmt "+managementContext.getManagementNodeId());
}
@SuppressWarnings("unchecked")
@Override
public void startReadOnly(final ManagementNodeState mode) {
if (!ManagementNodeState.isHotProxy(mode)) {
throw new IllegalStateException("Read-only rebind thread only permitted for hot proxy modes; not "+mode);
}
if (persistenceRunning) {
throw new IllegalStateException("Cannot start read-only when already running with persistence");
}
if (readOnlyRunning || readOnlyTask!=null) {
LOG.warn("Cannot request read-only mode for "+this+" when already running - "+readOnlyTask+"; ignoring");
return;
}
LOG.debug("Starting read-only rebinding ("+this+"), mgmt "+managementContext.getManagementNodeId());
if (persistenceRealChangeListener != null) persistenceRealChangeListener.stop();
if (persistenceStoreAccess != null) persistenceStoreAccess.disableWriteAccess(true);
readOnlyRunning = true;
readOnlyRebindCount.set(0);
try {
rebind(null, null, mode);
} catch (Exception e) {
throw Exceptions.propagate(e);
}
Callable<Task<?>> taskFactory = new Callable<Task<?>>() {
@Override public Task<Void> call() {
return Tasks.<Void>builder().dynamic(false).displayName("rebind (periodic run").body(new Callable<Void>() {
public Void call() {
try {
rebind(null, null, mode);
return null;
} catch (RuntimeInterruptedException e) {
LOG.debug("Interrupted rebinding (re-interrupting): "+e);
if (LOG.isTraceEnabled())
LOG.trace("Interrupted rebinding (re-interrupting), details: "+e, e);
Thread.currentThread().interrupt();
return null;
} catch (Exception e) {
// Don't rethrow: the behaviour of executionManager is different from a scheduledExecutorService,
// if we throw an exception, then our task will never get executed again
if (!readOnlyRunning) {
LOG.debug("Problem rebinding (read-only running has probably just been turned off): "+e);
if (LOG.isTraceEnabled()) {
LOG.trace("Problem rebinding (read-only running has probably just been turned off), details: "+e, e);
}
} else {
LOG.error("Problem rebinding: "+Exceptions.collapseText(e), e);
}
return null;
} catch (Throwable t) {
LOG.warn("Problem rebinding (rethrowing)", t);
throw Exceptions.propagate(t);
}
}}).build();
}
};
readOnlyTask = (ScheduledTask) managementContext.getServerExecutionContext().submit(
new ScheduledTask(MutableMap.of("displayName", "Periodic read-only rebind"), taskFactory).period(periodicPersistPeriod));
}
@Override
public void stopReadOnly() {
readOnlyRunning = false;
if (readOnlyTask!=null) {
LOG.debug("Stopping read-only rebinding ("+this+"), mgmt "+managementContext.getManagementNodeId());
readOnlyTask.cancel(true);
readOnlyTask.blockUntilEnded();
boolean reallyEnded = Tasks.blockUntilInternalTasksEnded(readOnlyTask, Duration.TEN_SECONDS);
if (!reallyEnded) {
LOG.warn("Rebind (read-only) tasks took too long to die after interrupt (ignoring): "+readOnlyTask);
}
readOnlyTask = null;
LOG.debug("Stopped read-only rebinding ("+this+"), mgmt "+managementContext.getManagementNodeId());
}
}
@Override
public void start() {
ManagementNodeState target = getRebindMode();
if (target==ManagementNodeState.HOT_STANDBY || target==ManagementNodeState.HOT_BACKUP) {
startReadOnly(target);
} else if (target==ManagementNodeState.MASTER) {
startPersistence();
} else {
LOG.warn("Nothing to start in "+this+" when HA mode is "+target);
}
}
@Override
public void stop() {
stopReadOnly();
stopPersistence();
if (persistenceStoreAccess != null) persistenceStoreAccess.stop(true);
}
public void rebindPartialActive(CompoundTransformer transformer, Iterator<BrooklynObject> objectsToRebind) {
final ClassLoader classLoader =
managementContext.getCatalogClassLoader();
// TODO we might want different exception handling for partials;
// failure at various points should leave proxies in a sensible state,
// either pointing at old or at new, though this is relatively untested,
// and some things e.g. policies might not be properly started
final RebindExceptionHandler exceptionHandler =
RebindExceptionHandlerImpl.builder()
.danglingRefFailureMode(danglingRefFailureMode)
.danglingRefQuorumRequiredHealthy(danglingRefsQuorumRequiredHealthy)
.rebindFailureMode(rebindFailureMode)
.addConfigFailureMode(addConfigFailureMode)
.addPolicyFailureMode(addPolicyFailureMode)
.loadPolicyFailureMode(loadPolicyFailureMode)
.build();
final ManagementNodeState mode = getRebindMode();
ActivePartialRebindIteration iteration = new ActivePartialRebindIteration(this, mode, classLoader, exceptionHandler,
rebindActive, readOnlyRebindCount, rebindMetrics, persistenceStoreAccess);
iteration.setObjectIterator(Iterators.transform(objectsToRebind,
new Function<BrooklynObject,BrooklynObject>() {
@Override
public BrooklynObject apply(BrooklynObject obj) {
// entities must be deproxied
if (obj instanceof Entity) obj = Entities.deproxy((Entity)obj);
return obj;
}
}));
if (transformer!=null) iteration.applyTransformer(transformer);
iteration.run();
}
public void rebindPartialActive(CompoundTransformer transformer, String ...objectsToRebindIds) {
List<BrooklynObject> objectsToRebind = MutableList.of();
for (String objectId: objectsToRebindIds) {
BrooklynObject obj = managementContext.lookup(objectId);
objectsToRebind.add(obj);
}
rebindPartialActive(transformer, objectsToRebind.iterator());
}
protected ManagementNodeState getRebindMode() {
if (managementContext==null) throw new IllegalStateException("Invalid "+this+": no management context");
if (!(managementContext.getHighAvailabilityManager() instanceof HighAvailabilityManagerImpl))
throw new IllegalStateException("Invalid "+this+": unknown HA manager type "+managementContext.getHighAvailabilityManager());
ManagementNodeState target = ((HighAvailabilityManagerImpl)managementContext.getHighAvailabilityManager()).getTransitionTargetNodeState();
return target;
}
@Override
@VisibleForTesting
public void waitForPendingComplete(Duration timeout, boolean canTrigger) throws InterruptedException, TimeoutException {
if (persistenceStoreAccess == null || !persistenceRunning) return;
persistenceRealChangeListener.waitForPendingComplete(timeout, canTrigger);
persistenceStoreAccess.waitForWritesCompleted(timeout);
}
@Override
@VisibleForTesting
public void forcePersistNow() {
forcePersistNow(false, null);
}
@Override
@VisibleForTesting
public void forcePersistNow(boolean full, PersistenceExceptionHandler exceptionHandler) {
if (full) {
BrooklynMementoRawData memento = BrooklynPersistenceUtils.newStateMemento(managementContext, MementoCopyMode.LOCAL);
if (exceptionHandler==null) {
exceptionHandler = persistenceRealChangeListener.getExceptionHandler();
}
persistenceStoreAccess.checkpoint(memento, exceptionHandler);
} else {
if (!persistenceRealChangeListener.persistNowSafely()) {
throw new IllegalStateException("Forced persistence failed; see logs fore more detail");
}
}
}
@Override
public ChangeListener getChangeListener() {
return persistencePublicChangeListener;
}
@Override
public List<Application> rebind() {
return rebind(null, null, null);
}
@Override
public List<Application> rebind(final ClassLoader classLoader) {
return rebind(classLoader, null, null);
}
@Override
public List<Application> rebind(final ClassLoader classLoader, final RebindExceptionHandler exceptionHandler) {
return rebind(classLoader, exceptionHandler, null);
}
@Override
public List<Application> rebind(ClassLoader classLoaderO, RebindExceptionHandler exceptionHandlerO, ManagementNodeState modeO) {
final ClassLoader classLoader = classLoaderO!=null ? classLoaderO :
managementContext.getCatalogClassLoader();
final RebindExceptionHandler exceptionHandler = exceptionHandlerO!=null ? exceptionHandlerO :
RebindExceptionHandlerImpl.builder()
.danglingRefFailureMode(danglingRefFailureMode)
.danglingRefQuorumRequiredHealthy(danglingRefsQuorumRequiredHealthy)
.rebindFailureMode(rebindFailureMode)
.addConfigFailureMode(addConfigFailureMode)
.addPolicyFailureMode(addPolicyFailureMode)
.loadPolicyFailureMode(loadPolicyFailureMode)
.build();
final ManagementNodeState mode = modeO!=null ? modeO : getRebindMode();
if (mode!=ManagementNodeState.MASTER && mode!=ManagementNodeState.HOT_STANDBY && mode!=ManagementNodeState.HOT_BACKUP)
throw new IllegalStateException("Must be either master or hot standby/backup to rebind (mode "+mode+")");
ExecutionContext ec = BasicExecutionContext.getCurrentExecutionContext();
if (ec == null) {
ec = managementContext.getServerExecutionContext();
Task<List<Application>> task = ec.submit(new Callable<List<Application>>() {
@Override public List<Application> call() throws Exception {
return rebindImpl(classLoader, exceptionHandler, mode);
}});
try {
return task.get();
} catch (Exception e) {
throw Exceptions.propagate(e);
}
} else {
return rebindImpl(classLoader, exceptionHandler, mode);
}
}
@Override
public BrooklynMementoRawData retrieveMementoRawData() {
RebindExceptionHandler exceptionHandler = RebindExceptionHandlerImpl.builder()
.danglingRefFailureMode(danglingRefFailureMode)
.rebindFailureMode(rebindFailureMode)
.addConfigFailureMode(addConfigFailureMode)
.addPolicyFailureMode(addPolicyFailureMode)
.loadPolicyFailureMode(loadPolicyFailureMode)
.build();
return loadMementoRawData(exceptionHandler);
}
/**
* Uses the persister to retrieve (and thus deserialize) the memento.
*
* In so doing, it instantiates the entities + locations, registering them with the rebindContext.
*/
protected BrooklynMementoRawData loadMementoRawData(final RebindExceptionHandler exceptionHandler) {
try {
if (persistenceStoreAccess==null) {
throw new IllegalStateException("Persistence not configured; cannot load memento data from persistent backing store");
}
if (!(persistenceStoreAccess instanceof BrooklynMementoPersisterToObjectStore)) {
throw new IllegalStateException("Cannot load raw memento with persister "+persistenceStoreAccess);
}
return ((BrooklynMementoPersisterToObjectStore)persistenceStoreAccess).loadMementoRawData(exceptionHandler);
} catch (RuntimeException e) {
throw exceptionHandler.onFailed(e);
}
}
protected List<Application> rebindImpl(final ClassLoader classLoader, final RebindExceptionHandler exceptionHandler, ManagementNodeState mode) {
RebindIteration iteration = new InitialFullRebindIteration(this, mode, classLoader, exceptionHandler,
rebindActive, readOnlyRebindCount, rebindMetrics, persistenceStoreAccess);
iteration.run();
if (firstRebindAppCount==null) {
firstRebindAppCount = iteration.getApplications().size();
firstRebindEntityCount = iteration.getRebindContext().getEntities().size();
firstRebindItemCount = iteration.getRebindContext().getAllBrooklynObjects().size();
}
isAwaitingInitialRebind = false;
return iteration.getApplications();
}
/**
* Sorts the map of nodes, so that a node's parent is guaranteed to come before that node
* (unless the parent is missing).
*
* Relies on ordering guarantees of returned map (i.e. LinkedHashMap, which guarantees insertion order
* even if a key is re-inserted into the map).
*
* TODO Inefficient implementation!
*/
@VisibleForTesting
static <T extends TreeNode> Map<String, T> sortParentFirst(Map<String, T> nodes) {
Map<String, T> result = Maps.newLinkedHashMap();
for (T node : nodes.values()) {
List<T> tempchain = Lists.newLinkedList();
T nodeinchain = node;
while (nodeinchain != null) {
tempchain.add(0, nodeinchain);
nodeinchain = (nodeinchain.getParent() == null) ? null : nodes.get(nodeinchain.getParent());
}
for (T n : tempchain) {
result.put(n.getId(), n);
}
}
return result;
}
public boolean isAwaitingInitialRebind() {
return isAwaitingInitialRebind;
}
public void setAwaitingInitialRebind(boolean isAwaitingInitialRebind) {
this.isAwaitingInitialRebind = isAwaitingInitialRebind;
}
/**
* Wraps a ChangeListener, to log and never propagate any exceptions that it throws.
*
* Catches Throwable, because really don't want a problem to propagate up to user code,
* to cause business-level operations to fail. For example, if there is a linkage error
* due to some problem in the serialization dependencies then just log it. For things
* more severe (e.g. OutOfMemoryError) then the catch+log means we'll report that we
* failed to persist, and we'd expect other threads to throw the OutOfMemoryError so
* we shouldn't lose anything.
*/
private static class SafeChangeListener implements ChangeListener {
private final ChangeListener delegate;
public SafeChangeListener(ChangeListener delegate) {
this.delegate = delegate;
}
@Override
public void onManaged(BrooklynObject instance) {
try {
delegate.onManaged(instance);
} catch (Throwable t) {
LOG.error("Error persisting mememento onManaged("+instance+"); continuing.", t);
}
}
@Override
public void onChanged(BrooklynObject instance) {
try {
delegate.onChanged(instance);
} catch (Throwable t) {
LOG.error("Error persisting mememento onChanged("+instance+"); continuing.", t);
}
}
@Override
public void onUnmanaged(BrooklynObject instance) {
try {
delegate.onUnmanaged(instance);
} catch (Throwable t) {
LOG.error("Error persisting mememento onUnmanaged("+instance+"); continuing.", t);
}
}
}
public int getReadOnlyRebindCount() {
return readOnlyRebindCount.get();
}
@Override
public Map<String, Object> getMetrics() {
Map<String,Object> result = MutableMap.of();
result.put("rebind", rebindMetrics.asMap());
result.put("persist", persistMetrics.asMap());
if (readOnlyRebindCount.get()>=0)
result.put("rebindReadOnlyCount", readOnlyRebindCount);
// include first rebind counts, so we know whether we rebinded or not
result.put("firstRebindCounts", MutableMap.of(
"applications", firstRebindAppCount,
"entities", firstRebindEntityCount,
"allItems", firstRebindItemCount));
return result;
}
@Override
public String toString() {
return super.toString()+"[mgmt="+managementContext.getManagementNodeId()+"]";
}
}