/* * 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.Collections; import java.util.List; import java.util.Set; import org.apache.brooklyn.api.mgmt.rebind.mementos.EntityMemento; import org.apache.brooklyn.config.ConfigKey; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.apache.brooklyn.api.catalog.CatalogItem; import org.apache.brooklyn.api.entity.Entity; import org.apache.brooklyn.api.entity.EntityLocal; import org.apache.brooklyn.api.location.Location; import org.apache.brooklyn.api.mgmt.rebind.RebindContext; import org.apache.brooklyn.api.mgmt.rebind.RebindExceptionHandler; import org.apache.brooklyn.api.mgmt.rebind.RebindManager; import org.apache.brooklyn.api.mgmt.rebind.RebindManager.RebindFailureMode; import org.apache.brooklyn.api.objs.BrooklynObject; import org.apache.brooklyn.api.objs.BrooklynObjectType; import org.apache.brooklyn.api.policy.Policy; import org.apache.brooklyn.api.sensor.Enricher; import org.apache.brooklyn.api.sensor.Feed; import org.apache.brooklyn.util.collections.MutableList; import org.apache.brooklyn.util.collections.QuorumCheck; import org.apache.brooklyn.util.exceptions.Exceptions; import org.apache.brooklyn.util.text.Strings; import com.google.common.collect.ImmutableList; import com.google.common.collect.Lists; import com.google.common.collect.Sets; /** Stateful handler, meant for a single rebind pass */ public class RebindExceptionHandlerImpl implements RebindExceptionHandler { private static final Logger LOG = LoggerFactory.getLogger(RebindExceptionHandlerImpl.class); protected final RebindManager.RebindFailureMode danglingRefFailureMode; protected final RebindManager.RebindFailureMode rebindFailureMode; protected final RebindManager.RebindFailureMode addConfigFailureMode; protected final RebindFailureMode addPolicyFailureMode; protected final RebindFailureMode loadPolicyFailureMode; protected final QuorumCheck danglingRefsQuorumRequiredHealthy; protected final Set<String> missingEntities = Sets.newConcurrentHashSet(); protected final Set<String> missingLocations = Sets.newConcurrentHashSet(); protected final Set<String> missingPolicies = Sets.newConcurrentHashSet(); protected final Set<String> missingEnrichers = Sets.newConcurrentHashSet(); protected final Set<String> missingFeeds = Sets.newConcurrentHashSet(); protected final Set<String> missingCatalogItems = Sets.newConcurrentHashSet(); protected final Set<String> missingUntypedItems = Sets.newConcurrentHashSet(); protected final Set<String> creationFailedIds = Sets.newConcurrentHashSet(); protected final Set<Exception> addPolicyFailures = Sets.newConcurrentHashSet(); protected final Set<Exception> loadPolicyFailures = Sets.newConcurrentHashSet(); protected final Set<String> warnings = Collections.synchronizedSet(Sets.<String>newLinkedHashSet()); protected final Set<Exception> exceptions = Collections.synchronizedSet(Sets.<Exception>newLinkedHashSet()); protected RebindContext context; protected boolean started = false; protected boolean done = false; public static Builder builder() { return new Builder(); } public static class Builder { private RebindManager.RebindFailureMode danglingRefFailureMode = RebindManager.RebindFailureMode.CONTINUE; private RebindManager.RebindFailureMode rebindFailureMode = RebindManager.RebindFailureMode.FAIL_AT_END; private RebindManager.RebindFailureMode addConfigFailureMode = RebindManager.RebindFailureMode.FAIL_AT_END; private RebindManager.RebindFailureMode addPolicyFailureMode = RebindManager.RebindFailureMode.CONTINUE; private RebindManager.RebindFailureMode deserializePolicyFailureMode = RebindManager.RebindFailureMode.CONTINUE; private QuorumCheck danglingRefsQuorumRequiredHealthy = RebindManagerImpl.DANGLING_REFERENCES_MIN_REQUIRED_HEALTHY.getDefaultValue(); public Builder danglingRefFailureMode(RebindManager.RebindFailureMode val) { danglingRefFailureMode = val; return this; } public Builder rebindFailureMode(RebindManager.RebindFailureMode val) { rebindFailureMode = val; return this; } public Builder addPolicyFailureMode(RebindManager.RebindFailureMode val) { addPolicyFailureMode = val; return this; } public Builder loadPolicyFailureMode(RebindManager.RebindFailureMode val) { deserializePolicyFailureMode = val; return this; } public Builder addConfigFailureMode(RebindManager.RebindFailureMode val) { this.addConfigFailureMode = val; return this; } public Builder danglingRefQuorumRequiredHealthy(QuorumCheck val) { danglingRefsQuorumRequiredHealthy = val; return this; } public RebindExceptionHandler build() { return new RebindExceptionHandlerImpl(this); } } public RebindExceptionHandlerImpl(Builder builder) { this.danglingRefFailureMode = checkNotNull(builder.danglingRefFailureMode, "danglingRefFailureMode"); this.rebindFailureMode = checkNotNull(builder.rebindFailureMode, "rebindFailureMode"); this.addConfigFailureMode = checkNotNull(builder.addConfigFailureMode, "addConfigFailureMode"); this.addPolicyFailureMode = checkNotNull(builder.addPolicyFailureMode, "addPolicyFailureMode"); this.loadPolicyFailureMode = checkNotNull(builder.deserializePolicyFailureMode, "deserializePolicyFailureMode"); this.danglingRefsQuorumRequiredHealthy = checkNotNull(builder.danglingRefsQuorumRequiredHealthy, "danglingRefsQuorumRequiredHealthy"); } protected void warn(String message) { warn(message, null); } protected void warn(String message, Throwable optionalError) { if (optionalError==null) LOG.warn(message); else LOG.warn(message, optionalError); warnings.add(message); } @Override public void onStart(RebindContext context) { if (done) { throw new IllegalStateException(this+" has already been used on a finished run"); } if (started) { throw new IllegalStateException(this+" has already been used on a started run"); } this.context = context; started = true; } @Override public void onLoadMementoFailed(BrooklynObjectType type, String msg, Exception e) { Exceptions.propagateIfFatal(e); String errmsg = "problem loading memento: "+msg; switch (type) { case FEED: case POLICY: case ENRICHER: switch (loadPolicyFailureMode) { case FAIL_FAST: throw new IllegalStateException("Rebind: aborting due to "+errmsg, e); case FAIL_AT_END: loadPolicyFailures.add(new IllegalStateException(errmsg, e)); break; case CONTINUE: warn(errmsg+"; continuing: "+e, e); break; default: throw new IllegalStateException("Unexpected state '"+loadPolicyFailureMode+"' for loadPolicyFailureMode"); } break; default: exceptions.add(new IllegalStateException(errmsg, e)); onErrorImpl(errmsg, e); } } @Override public Entity onDanglingEntityRef(String id) { missingEntities.add(id); if (danglingRefFailureMode == RebindManager.RebindFailureMode.FAIL_FAST) { throw new IllegalStateException("No entity found with id "+id); } else { warn("No entity found with id "+id+"; dangling reference on rebind"); return null; } } @Override public Location onDanglingLocationRef(String id) { missingLocations.add(id); if (danglingRefFailureMode == RebindManager.RebindFailureMode.FAIL_FAST) { throw new IllegalStateException("No location found with id "+id); } else { warn("No location found with id "+id+"; dangling reference on rebind"); return null; } } @Override public Policy onDanglingPolicyRef(String id) { missingPolicies.add(id); if (danglingRefFailureMode == RebindManager.RebindFailureMode.FAIL_FAST) { throw new IllegalStateException("No policy found with id "+id); } else { warn("No policy found with id "+id+"; dangling reference on rebind"); return null; } } @Override public Enricher onDanglingEnricherRef(String id) { missingEnrichers.add(id); if (danglingRefFailureMode == RebindManager.RebindFailureMode.FAIL_FAST) { throw new IllegalStateException("No enricher found with id "+id); } else { warn("No enricher found with id "+id+"; dangling reference on rebind"); return null; } } @Override public Feed onDanglingFeedRef(String id) { missingFeeds.add(id); if (danglingRefFailureMode == RebindManager.RebindFailureMode.FAIL_FAST) { throw new IllegalStateException("No feed found with id "+id); } else { warn("No feed found with id "+id+"; dangling reference on rebind"); return null; } } @Override public CatalogItem<?, ?> onDanglingCatalogItemRef(String id) { missingCatalogItems.add(id); if (danglingRefFailureMode == RebindManager.RebindFailureMode.FAIL_FAST) { throw new IllegalStateException("No catalog item found with id "+id); } else { warn("No catalog item found with id "+id+"; dangling reference on rebind"); return null; } } @Override public CatalogItem<?, ?> onDanglingUntypedItemRef(String id) { missingUntypedItems.add(id); if (danglingRefFailureMode == RebindManager.RebindFailureMode.FAIL_FAST) { throw new IllegalStateException("No item found with id "+id); } else { warn("No item found with id "+id+"; dangling reference on rebind"); return null; } } @Override public void onCreateFailed(BrooklynObjectType type, String id, String instanceType, Exception e) { Exceptions.propagateIfFatal(e); String errmsg = "problem creating "+type+" "+id+" of type "+instanceType; creationFailedIds.add(id); exceptions.add(new IllegalStateException(errmsg, e)); onErrorImpl(errmsg, e); } @Override public void onNotFound(BrooklynObjectType type, String id) { if (creationFailedIds.contains(id)) { // already know about this; ignore } else { String errmsg = type.toCamelCase()+" '"+id+"' not found"; exceptions.add(new IllegalStateException(errmsg)); onErrorImpl(errmsg); } } @Override public void onRebindFailed(BrooklynObjectType type, BrooklynObject instance, Exception e) { Exceptions.propagateIfFatal(e); String errmsg = "problem rebinding "+type.toCamelCase()+" "+instance.getId()+" ("+instance+")"; switch (type) { case FEED: case ENRICHER: case POLICY: switch (addPolicyFailureMode) { case FAIL_FAST: throw new IllegalStateException("Rebind: aborting due to "+errmsg, e); case FAIL_AT_END: addPolicyFailures.add(new IllegalStateException(errmsg, e)); break; case CONTINUE: warn(errmsg+"; continuing", e); creationFailedIds.add(instance.getId()); break; default: throw new IllegalStateException("Unexpected state '"+addPolicyFailureMode+"' for addPolicyFailureMode"); } break; default: exceptions.add(new IllegalStateException(errmsg, e)); onErrorImpl(errmsg, e); break; } } public void onAddConfigFailed(EntityMemento entityMemento, ConfigKey<?> key, Exception e) { Exceptions.propagateIfFatal(e); String errmsg = "Failed to rebind " + key + " with value " + entityMemento.getConfig().get(key) + " for entity " + entityMemento; switch (addConfigFailureMode) { case FAIL_FAST: throw new IllegalStateException(errmsg, e); case FAIL_AT_END: exceptions.add(new IllegalStateException(errmsg, e)); break; case CONTINUE: warn(errmsg + "; continuing", e); break; default: throw new IllegalStateException("Unexpected state '"+addPolicyFailureMode+"' for addPolicyFailureMode"); } } @Override public void onAddPolicyFailed(EntityLocal entity, Policy policy, Exception e) { Exceptions.propagateIfFatal(e); String errmsg = "problem adding policy "+policy.getId()+" ("+policy+") to entity "+entity.getId()+" ("+entity+")"; switch (addPolicyFailureMode) { case FAIL_FAST: throw new IllegalStateException("Rebind: aborting due to "+errmsg, e); case FAIL_AT_END: addPolicyFailures.add(new IllegalStateException(errmsg, e)); break; case CONTINUE: warn(errmsg+"; continuing", e); break; default: throw new IllegalStateException("Unexpected state '"+addPolicyFailureMode+"' for addPolicyFailureMode"); } } @Override public void onAddEnricherFailed(EntityLocal entity, Enricher enricher, Exception e) { Exceptions.propagateIfFatal(e); String errmsg = "problem adding enricher "+enricher.getId()+" ("+enricher+") to entity "+entity.getId()+" ("+entity+")"; switch (addPolicyFailureMode) { case FAIL_FAST: throw new IllegalStateException("Rebind: aborting due to "+errmsg, e); case FAIL_AT_END: addPolicyFailures.add(new IllegalStateException(errmsg, e)); break; case CONTINUE: warn(errmsg+"; continuing", e); break; default: throw new IllegalStateException("Unexpected state '"+addPolicyFailureMode+"' for addPolicyFailureMode"); } } @Override public void onAddFeedFailed(EntityLocal entity, Feed feed, Exception e) { Exceptions.propagateIfFatal(e); String errmsg = "problem adding feed "+feed.getId()+" ("+feed+") to entity "+entity.getId()+" ("+entity+")"; switch (addPolicyFailureMode) { case FAIL_FAST: throw new IllegalStateException("Rebind: aborting due to "+errmsg, e); case FAIL_AT_END: addPolicyFailures.add(new IllegalStateException(errmsg, e)); break; case CONTINUE: warn(errmsg+"; continuing", e); break; default: throw new IllegalStateException("Unexpected state '"+addPolicyFailureMode+"' for addPolicyFailureMode"); } } @Override public void onManageFailed(BrooklynObjectType type, BrooklynObject instance, Exception e) { Exceptions.propagateIfFatal(e); String errmsg = "problem managing "+type.toCamelCase()+" "+instance.getId()+" ("+instance+")"; exceptions.add(new IllegalStateException(errmsg, e)); onErrorImpl(errmsg, e); } protected void onErrorImpl(String errmsg) { onErrorImpl(errmsg, null); } protected void onErrorImpl(String errmsg, Exception e) { if (rebindFailureMode == RebindManager.RebindFailureMode.FAIL_FAST) { throw new IllegalStateException("Rebind: aborting due to "+errmsg, e); } else { if (Thread.currentThread().isInterrupted()) { if (LOG.isDebugEnabled()) LOG.debug("Rebind: while interrupted, received "+errmsg+"/"+e+"; throwing interruption", e); throw Exceptions.propagate(new InterruptedException("Detected interruption while not sleeping, due to secondary error rebinding: "+errmsg+"/"+e)); } warn("Rebind: continuing after "+errmsg, e); } } @Override public void onDone() { onDoneImpl(null); } @Override public RuntimeException onFailed(Exception e) { if (done) throw Exceptions.propagate(e); onDoneImpl(e); exceptions.add(e); throw new IllegalStateException("Rebind failed", e); // should have thrown exception above } protected void onDoneImpl(Exception e) { Exceptions.propagateIfFatal(e); List<Exception> allExceptions = Lists.newArrayList(); if (done) { allExceptions.add(new IllegalStateException(this+" has already been informed of rebind done")); } done = true; List<String> danglingIds = MutableList.copyOf(missingEntities).appendAll(missingLocations).appendAll(missingPolicies).appendAll(missingEnrichers).appendAll(missingFeeds).appendAll(missingCatalogItems).appendAll(missingUntypedItems); int totalDangling = danglingIds.size(); if (totalDangling>0) { int totalFound = context.getAllBrooklynObjects().size(); int totalItems = totalFound + totalDangling; if (context==null) { allExceptions.add(new IllegalStateException("Dangling references ("+totalDangling+" of "+totalItems+") present without rebind context")); } else { if (!danglingRefsQuorumRequiredHealthy.isQuorate(totalFound, totalItems)) { warn("Dangling item"+Strings.s(totalDangling)+" ("+totalDangling+" of "+totalItems+") found on rebind exceeds quorum, assuming failed: "+danglingIds); allExceptions.add(new IllegalStateException("Too many dangling references: "+totalDangling+" of "+totalItems)); } else { LOG.info("Dangling item"+Strings.s(totalDangling)+" ("+totalDangling+" of "+totalItems+") found on rebind, assuming deleted: "+danglingIds); } } } if (e != null) { allExceptions.add(e); } if (addPolicyFailureMode != RebindManager.RebindFailureMode.CONTINUE) { allExceptions.addAll(addPolicyFailures); } if (loadPolicyFailureMode != RebindManager.RebindFailureMode.CONTINUE) { allExceptions.addAll(loadPolicyFailures); } if (danglingRefFailureMode != RebindManager.RebindFailureMode.CONTINUE) { if (!missingEntities.isEmpty()) { allExceptions.add(new IllegalStateException("Missing referenced entit" + Strings.ies(missingEntities) + ": " + missingEntities)); } if (!missingLocations.isEmpty()) { allExceptions.add(new IllegalStateException("Missing referenced location" + Strings.s(missingLocations) + ": " + missingLocations)); } if (!missingPolicies.isEmpty()) { allExceptions.add(new IllegalStateException("Missing referenced polic" + Strings.ies(missingPolicies) + ": " + ": " + missingPolicies)); } if (!missingEnrichers.isEmpty()) { allExceptions.add(new IllegalStateException("Missing referenced enricher" + Strings.s(missingEnrichers) + ": " + missingEnrichers)); } if (!missingFeeds.isEmpty()) { allExceptions.add(new IllegalStateException("Missing referenced feed" + Strings.s(missingFeeds) + ": " + missingFeeds)); } if (!missingCatalogItems.isEmpty()) { allExceptions.add(new IllegalStateException("Missing referenced catalog item" + Strings.s(missingCatalogItems) + ": " + missingCatalogItems)); } if (!missingUntypedItems.isEmpty()) { allExceptions.add(new IllegalStateException("Missing referenced untyped items" + Strings.s(missingUntypedItems) + ": " + missingUntypedItems)); } } if (rebindFailureMode != RebindManager.RebindFailureMode.CONTINUE) { allExceptions.addAll(exceptions); } if (!started) { allExceptions.add(new IllegalStateException(this+" was not informed of start of rebind run")); } if (allExceptions.isEmpty()) { return; // no errors } else { RuntimeException compoundException = Exceptions.create("Failure rebinding", allExceptions); LOG.debug(compoundException.getMessage()+" (rethrowing)"); throw compoundException; } } @Override public List<Exception> getExceptions() { return ImmutableList.copyOf(exceptions); } @Override public List<String> getWarnings() { return ImmutableList.copyOf(warnings); } }