You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@brooklyn.apache.org by he...@apache.org on 2014/11/15 01:05:13 UTC
[04/21] incubator-brooklyn git commit: Use a linear range QuorumCheck
to fail if there are too many dangling references.
Use a linear range QuorumCheck to fail if there are too many dangling references.
This introduces QuorumChecks.newLinearRange which allows specifying points in a place
to define a set of line segments defining the quorum.
This can be TypeCoerced from a string, e.g. `[[5,5],[10,10],[100,70],[200,140]]`
(with things like `all` and `atLeastOne` also supported).
Such a range is then used to cause rebind to fail if there are a lot of dangling references.
The precise number is allowing 2 for small values, scaling to 5% at big values,
but this can be tweaked using RebindManagerImpl.DANGLING_REFERENCES_MIN_REQUIRED_HEALTHY
aka rebind.failureMode.danglingRefs.minRequiredHealthy . (e.g. set this to all in
brooklyn.properties to cause rebind to fail if there are any dangling references;
but note this can occasionally fail if something is deleted as different items are
not persisted simultaneously).
Also:
* There are also some tidies to how `RebindContext` is used, since many of the
dangling references weren't being caught
* We are stricter about persisting proxies, since that doesn't work (type written as `$Proxy16`)
* We tolerate remote timestamps in persisted objects (since these can occur in backups, currently);
we don't *use* those timestamps for anything (except in tests when they can be `prefer`red),
but there is no harm in having them, if someone copied across backup persistence files
Project: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/commit/f39c6a3f
Tree: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/tree/f39c6a3f
Diff: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/diff/f39c6a3f
Branch: refs/heads/master
Commit: f39c6a3fc3f40cd3469c38b7d61908d317c1d7de
Parents: 7532be8
Author: Alex Heneveld <al...@cloudsoftcorp.com>
Authored: Wed Nov 12 09:02:42 2014 +0000
Committer: Alex Heneveld <al...@cloudsoftcorp.com>
Committed: Thu Nov 13 23:38:52 2014 +0000
----------------------------------------------------------------------
.../brooklyn/entity/rebind/RebindContext.java | 30 ++--
.../entity/rebind/RebindExceptionHandler.java | 2 +-
.../brooklyn/config/BrooklynServerPaths.java | 2 +-
.../java/brooklyn/entity/basic/Entities.java | 8 +-
.../entity/rebind/BasicEntityRebindSupport.java | 15 +-
.../rebind/BasicLocationRebindSupport.java | 4 +-
.../rebind/PersistenceExceptionHandlerImpl.java | 2 +-
.../entity/rebind/RebindContextImpl.java | 44 ++++--
.../rebind/RebindContextLookupContext.java | 6 +-
.../rebind/RebindExceptionHandlerImpl.java | 32 ++++-
.../entity/rebind/RebindManagerImpl.java | 26 +++-
.../entity/rebind/dto/MementosGenerators.java | 5 +
.../persister/BrooklynPersistenceUtils.java | 3 +
.../ha/HighAvailabilityManagerImpl.java | 2 +-
...ntPlaneSyncRecordPersisterToObjectStore.java | 13 +-
.../java/brooklyn/util/flags/TypeCoercions.java | 8 ++
.../HighAvailabilityManagerSplitBrainTest.java | 2 +-
.../ha/HighAvailabilityManagerTestFixture.java | 2 +-
.../brooklyn/management/ha/HotStandbyTest.java | 2 +-
.../brooklyn/management/ha/WarmStandbyTest.java | 2 +-
.../brooklyn/util/collections/MutableList.java | 7 +
.../brooklyn/util/collections/QuorumCheck.java | 142 ++++++++++++++++++-
.../util/collections/QuorumChecksTest.java | 105 ++++++++++++++
23 files changed, 392 insertions(+), 72 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/f39c6a3f/api/src/main/java/brooklyn/entity/rebind/RebindContext.java
----------------------------------------------------------------------
diff --git a/api/src/main/java/brooklyn/entity/rebind/RebindContext.java b/api/src/main/java/brooklyn/entity/rebind/RebindContext.java
index 1ddd178..17935b3 100644
--- a/api/src/main/java/brooklyn/entity/rebind/RebindContext.java
+++ b/api/src/main/java/brooklyn/entity/rebind/RebindContext.java
@@ -18,41 +18,35 @@
*/
package brooklyn.entity.rebind;
+import java.util.Map;
+
import brooklyn.basic.BrooklynObject;
-import brooklyn.catalog.CatalogItem;
-import brooklyn.entity.Entity;
-import brooklyn.entity.Feed;
-import brooklyn.location.Location;
-import brooklyn.policy.Enricher;
-import brooklyn.policy.Policy;
+import brooklyn.mementos.BrooklynMementoPersister.LookupContext;
+
+import com.google.common.annotations.Beta;
/**
* Gives access to things that are being currently rebinding. This is used during a
* rebind to wire everything back together again, e.g. to find the necessary entity
* instances even before they are available through
* {@code managementContext.getEntityManager().getEnties()}.
- *
+ * <p>
* Users are not expected to implement this class. It is for use by {@link Rebindable}
* instances, and will generally be created by the {@link RebindManager}.
+ * <p>
*/
+@Beta
public interface RebindContext {
- Entity getEntity(String id);
-
- Location getLocation(String id);
-
- Policy getPolicy(String id);
-
- Enricher getEnricher(String id);
-
- Feed getFeed(String id);
+ /** Returns an unmodifiable view of all objects by ID */
+ Map<String,BrooklynObject> getAllBrooklynObjects();
- CatalogItem<?, ?> getCatalogItem(String id);
-
Class<?> loadClass(String typeName) throws ClassNotFoundException;
RebindExceptionHandler getExceptionHandler();
boolean isReadOnly(BrooklynObject item);
+ LookupContext lookup();
+
}
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/f39c6a3f/api/src/main/java/brooklyn/entity/rebind/RebindExceptionHandler.java
----------------------------------------------------------------------
diff --git a/api/src/main/java/brooklyn/entity/rebind/RebindExceptionHandler.java b/api/src/main/java/brooklyn/entity/rebind/RebindExceptionHandler.java
index 34c4f05..61bcdaa 100644
--- a/api/src/main/java/brooklyn/entity/rebind/RebindExceptionHandler.java
+++ b/api/src/main/java/brooklyn/entity/rebind/RebindExceptionHandler.java
@@ -96,7 +96,7 @@ public interface RebindExceptionHandler {
RuntimeException onFailed(Exception e);
/** invoked before the rebind pass */
- void onStart();
+ void onStart(RebindContext context);
/** invoked after the complete rebind pass, always on success and possibly on failure */
void onDone();
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/f39c6a3f/core/src/main/java/brooklyn/config/BrooklynServerPaths.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/config/BrooklynServerPaths.java b/core/src/main/java/brooklyn/config/BrooklynServerPaths.java
index 2bcb763..ee294e9 100644
--- a/core/src/main/java/brooklyn/config/BrooklynServerPaths.java
+++ b/core/src/main/java/brooklyn/config/BrooklynServerPaths.java
@@ -205,7 +205,7 @@ public class BrooklynServerPaths {
}
@Override
protected String getDefaultDirForAnyFilesystem() {
- return backupContainerFor(super.getDefaultDirForAnyFilesystem());
+ return backupContainerFor(DEFAULT_PERSISTENCE_CONTAINER_NAME);
}
@Override
protected String getDefaultContainerForAnyNonFilesystem() {
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/f39c6a3f/core/src/main/java/brooklyn/entity/basic/Entities.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/entity/basic/Entities.java b/core/src/main/java/brooklyn/entity/basic/Entities.java
index 7d17b7a..d073372 100644
--- a/core/src/main/java/brooklyn/entity/basic/Entities.java
+++ b/core/src/main/java/brooklyn/entity/basic/Entities.java
@@ -773,10 +773,14 @@ public class Entities {
/** Unwraps a proxy to retrieve the real item, if available.
* <p>
- * Only intended for use in tests. For normal operations, callers should ensure the method is
- * available on an interface and accessed via the proxy. */
+ * Only intended for use in tests and occasional internal usage, e.g. persistence.
+ * For normal operations, callers should ensure the method is available on an interface and accessed via the proxy. */
@Beta @VisibleForTesting
public static AbstractEntity deproxy(Entity e) {
+ if (!(Proxy.isProxyClass(e.getClass()))) {
+ log.warn("Attempt to deproxy non-proxy "+e, new Throwable("Location of attempt to deproxy non-proxy "+e));
+ return (AbstractEntity) e;
+ }
return (AbstractEntity) ((EntityProxyImpl)Proxy.getInvocationHandler(e)).getDelegate();
}
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/f39c6a3f/core/src/main/java/brooklyn/entity/rebind/BasicEntityRebindSupport.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/entity/rebind/BasicEntityRebindSupport.java b/core/src/main/java/brooklyn/entity/rebind/BasicEntityRebindSupport.java
index bb31a1f..6bb19cb 100644
--- a/core/src/main/java/brooklyn/entity/rebind/BasicEntityRebindSupport.java
+++ b/core/src/main/java/brooklyn/entity/rebind/BasicEntityRebindSupport.java
@@ -118,7 +118,7 @@ public class BasicEntityRebindSupport extends AbstractBrooklynObjectRebindSuppor
@Override
public void addPolicies(RebindContext rebindContext, EntityMemento memento) {
for (String policyId : memento.getPolicies()) {
- AbstractPolicy policy = (AbstractPolicy) rebindContext.getPolicy(policyId);
+ AbstractPolicy policy = (AbstractPolicy) rebindContext.lookup().lookupPolicy(policyId);
if (policy != null) {
try {
entity.addPolicy(policy);
@@ -128,6 +128,7 @@ public class BasicEntityRebindSupport extends AbstractBrooklynObjectRebindSuppor
} else {
LOG.warn("Policy not found; discarding policy {} of entity {}({})",
new Object[] {policyId, memento.getType(), memento.getId()});
+ rebindContext.getExceptionHandler().onDanglingPolicyRef(policyId);
}
}
}
@@ -135,7 +136,7 @@ public class BasicEntityRebindSupport extends AbstractBrooklynObjectRebindSuppor
@Override
public void addEnrichers(RebindContext rebindContext, EntityMemento memento) {
for (String enricherId : memento.getEnrichers()) {
- AbstractEnricher enricher = (AbstractEnricher) rebindContext.getEnricher(enricherId);
+ AbstractEnricher enricher = (AbstractEnricher) rebindContext.lookup().lookupEnricher(enricherId);
if (enricher != null) {
try {
entity.addEnricher(enricher);
@@ -152,7 +153,7 @@ public class BasicEntityRebindSupport extends AbstractBrooklynObjectRebindSuppor
@Override
public void addFeeds(RebindContext rebindContext, EntityMemento memento) {
for (String feedId : memento.getFeeds()) {
- AbstractFeed feed = (AbstractFeed) rebindContext.getFeed(feedId);
+ AbstractFeed feed = (AbstractFeed) rebindContext.lookup().lookupFeed(feedId);
if (feed != null) {
try {
((EntityInternal)entity).feeds().addFeed(feed);
@@ -178,7 +179,7 @@ public class BasicEntityRebindSupport extends AbstractBrooklynObjectRebindSuppor
if (memento.getMembers().size() > 0) {
if (entity instanceof AbstractGroupImpl) {
for (String memberId : memento.getMembers()) {
- Entity member = rebindContext.getEntity(memberId);
+ Entity member = rebindContext.lookup().lookupEntity(memberId);
if (member != null) {
((AbstractGroupImpl)entity).addMemberInternal(member);
} else {
@@ -198,7 +199,7 @@ public class BasicEntityRebindSupport extends AbstractBrooklynObjectRebindSuppor
protected void addChildren(RebindContext rebindContext, EntityMemento memento) {
for (String childId : memento.getChildren()) {
- Entity child = rebindContext.getEntity(childId);
+ Entity child = rebindContext.lookup().lookupEntity(childId);
if (child != null) {
entity.addChild(proxy(child));
} else {
@@ -209,7 +210,7 @@ public class BasicEntityRebindSupport extends AbstractBrooklynObjectRebindSuppor
}
protected void setParent(RebindContext rebindContext, EntityMemento memento) {
- Entity parent = (memento.getParent() != null) ? rebindContext.getEntity(memento.getParent()) : null;
+ Entity parent = (memento.getParent() != null) ? rebindContext.lookup().lookupEntity(memento.getParent()) : null;
if (parent != null) {
entity.setParent(proxy(parent));
} else if (memento.getParent() != null){
@@ -220,7 +221,7 @@ public class BasicEntityRebindSupport extends AbstractBrooklynObjectRebindSuppor
protected void addLocations(RebindContext rebindContext, EntityMemento memento) {
for (String id : memento.getLocations()) {
- Location loc = rebindContext.getLocation(id);
+ Location loc = rebindContext.lookup().lookupLocation(id);
if (loc != null) {
((EntityInternal)entity).addLocations(ImmutableList.of(loc));
} else {
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/f39c6a3f/core/src/main/java/brooklyn/entity/rebind/BasicLocationRebindSupport.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/entity/rebind/BasicLocationRebindSupport.java b/core/src/main/java/brooklyn/entity/rebind/BasicLocationRebindSupport.java
index 2ca5d2a..fc7947b 100644
--- a/core/src/main/java/brooklyn/entity/rebind/BasicLocationRebindSupport.java
+++ b/core/src/main/java/brooklyn/entity/rebind/BasicLocationRebindSupport.java
@@ -116,7 +116,7 @@ public class BasicLocationRebindSupport extends AbstractBrooklynObjectRebindSupp
protected void addChildren(RebindContext rebindContext, LocationMemento memento) {
for (String childId : memento.getChildren()) {
- Location child = rebindContext.getLocation(childId);
+ Location child = rebindContext.lookup().lookupLocation(childId);
if (child != null) {
location.addChild(child);
} else {
@@ -126,7 +126,7 @@ public class BasicLocationRebindSupport extends AbstractBrooklynObjectRebindSupp
}
protected void setParent(RebindContext rebindContext, LocationMemento memento) {
- Location parent = (memento.getParent() != null) ? rebindContext.getLocation(memento.getParent()) : null;
+ Location parent = (memento.getParent() != null) ? rebindContext.lookup().lookupLocation(memento.getParent()) : null;
if (parent != null) {
location.setParent(parent);
} else if (memento.getParent() != null) {
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/f39c6a3f/core/src/main/java/brooklyn/entity/rebind/PersistenceExceptionHandlerImpl.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/entity/rebind/PersistenceExceptionHandlerImpl.java b/core/src/main/java/brooklyn/entity/rebind/PersistenceExceptionHandlerImpl.java
index bce5c83..a3f7655 100644
--- a/core/src/main/java/brooklyn/entity/rebind/PersistenceExceptionHandlerImpl.java
+++ b/core/src/main/java/brooklyn/entity/rebind/PersistenceExceptionHandlerImpl.java
@@ -90,7 +90,7 @@ public class PersistenceExceptionHandlerImpl implements PersistenceExceptionHand
if (!isNew) {
if (LOG.isDebugEnabled()) LOG.debug("Repeating problem: "+errmsg, e);
} else {
- LOG.warn("Problem: "+errmsg, e);
+ LOG.warn("Problem persisting (ignoring): "+errmsg, e);
}
} else {
if (!isNew) {
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/f39c6a3f/core/src/main/java/brooklyn/entity/rebind/RebindContextImpl.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/entity/rebind/RebindContextImpl.java b/core/src/main/java/brooklyn/entity/rebind/RebindContextImpl.java
index bcac4a7..a581c7d 100644
--- a/core/src/main/java/brooklyn/entity/rebind/RebindContextImpl.java
+++ b/core/src/main/java/brooklyn/entity/rebind/RebindContextImpl.java
@@ -28,8 +28,10 @@ import brooklyn.catalog.CatalogItem;
import brooklyn.entity.Entity;
import brooklyn.entity.Feed;
import brooklyn.location.Location;
+import brooklyn.mementos.BrooklynMementoPersister.LookupContext;
import brooklyn.policy.Enricher;
import brooklyn.policy.Policy;
+import brooklyn.util.collections.MutableMap;
import com.google.common.collect.Maps;
@@ -44,6 +46,7 @@ public class RebindContextImpl implements RebindContext {
private final ClassLoader classLoader;
private final RebindExceptionHandler exceptionHandler;
+ private LookupContext lookupContext;
private boolean allAreReadOnly = false;
@@ -92,69 +95,73 @@ public class RebindContextImpl implements RebindContext {
catalogItems.remove(item.getId());
}
- @Override
public Entity getEntity(String id) {
return entities.get(id);
}
- @Override
public Location getLocation(String id) {
return locations.get(id);
}
- @Override
public Policy getPolicy(String id) {
return policies.get(id);
}
- @Override
public Enricher getEnricher(String id) {
return enrichers.get(id);
}
- @Override
public CatalogItem<?, ?> getCatalogItem(String id) {
return catalogItems.get(id);
}
- @Override
public Feed getFeed(String id) {
return feeds.get(id);
}
- @Override
public Class<?> loadClass(String className) throws ClassNotFoundException {
return classLoader.loadClass(className);
}
- @Override
public RebindExceptionHandler getExceptionHandler() {
return exceptionHandler;
}
- protected Collection<Location> getLocations() {
+ public Collection<Location> getLocations() {
return locations.values();
}
- protected Collection<Entity> getEntities() {
+ public Collection<Entity> getEntities() {
return entities.values();
}
- protected Collection<Policy> getPolicies() {
+ public Collection<Policy> getPolicies() {
return policies.values();
}
- protected Collection<Enricher> getEnrichers() {
+ public Collection<Enricher> getEnrichers() {
return enrichers.values();
}
- protected Collection<Feed> getFeeds() {
+ public Collection<Feed> getFeeds() {
return feeds.values();
}
- protected Collection<CatalogItem<?, ?>> getCatalogItems() {
+ public Collection<CatalogItem<?, ?>> getCatalogItems() {
return catalogItems.values();
}
+
+ @Override
+ public Map<String,BrooklynObject> getAllBrooklynObjects() {
+ MutableMap<String,BrooklynObject> result = MutableMap.of();
+ result.putAll(locations);
+ result.putAll(entities);
+ result.putAll(policies);
+ result.putAll(enrichers);
+ result.putAll(feeds);
+ result.putAll(catalogItems);
+ return result.asUnmodifiable();
+ }
public void setAllReadOnly() {
allAreReadOnly = true;
@@ -163,5 +170,14 @@ public class RebindContextImpl implements RebindContext {
public boolean isReadOnly(BrooklynObject item) {
return allAreReadOnly;
}
+
+ public void setLookupContext(LookupContext lookupContext) {
+ this.lookupContext = lookupContext;
+ }
+
+ @Override
+ public LookupContext lookup() {
+ return lookupContext;
+ }
}
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/f39c6a3f/core/src/main/java/brooklyn/entity/rebind/RebindContextLookupContext.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/entity/rebind/RebindContextLookupContext.java b/core/src/main/java/brooklyn/entity/rebind/RebindContextLookupContext.java
index bf80ef9..6392a5f 100644
--- a/core/src/main/java/brooklyn/entity/rebind/RebindContextLookupContext.java
+++ b/core/src/main/java/brooklyn/entity/rebind/RebindContextLookupContext.java
@@ -41,13 +41,13 @@ public class RebindContextLookupContext implements LookupContext {
@Nullable
protected final ManagementContext managementContext;
- protected final RebindContext rebindContext;
+ protected final RebindContextImpl rebindContext;
protected final RebindExceptionHandler exceptionHandler;
- public RebindContextLookupContext(RebindContext rebindContext, RebindExceptionHandler exceptionHandler) {
+ public RebindContextLookupContext(RebindContextImpl rebindContext, RebindExceptionHandler exceptionHandler) {
this(null, rebindContext, exceptionHandler);
}
- public RebindContextLookupContext(ManagementContext managementContext, RebindContext rebindContext, RebindExceptionHandler exceptionHandler) {
+ public RebindContextLookupContext(ManagementContext managementContext, RebindContextImpl rebindContext, RebindExceptionHandler exceptionHandler) {
this.managementContext = managementContext;
this.rebindContext = rebindContext;
this.exceptionHandler = exceptionHandler;
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/f39c6a3f/core/src/main/java/brooklyn/entity/rebind/RebindExceptionHandlerImpl.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/entity/rebind/RebindExceptionHandlerImpl.java b/core/src/main/java/brooklyn/entity/rebind/RebindExceptionHandlerImpl.java
index e5f3111..ac41c8b 100644
--- a/core/src/main/java/brooklyn/entity/rebind/RebindExceptionHandlerImpl.java
+++ b/core/src/main/java/brooklyn/entity/rebind/RebindExceptionHandlerImpl.java
@@ -36,6 +36,8 @@ import brooklyn.entity.rebind.RebindManager.RebindFailureMode;
import brooklyn.location.Location;
import brooklyn.policy.Enricher;
import brooklyn.policy.Policy;
+import brooklyn.util.collections.MutableList;
+import brooklyn.util.collections.QuorumCheck;
import brooklyn.util.exceptions.Exceptions;
import brooklyn.util.text.Strings;
@@ -51,6 +53,7 @@ public class RebindExceptionHandlerImpl implements RebindExceptionHandler {
protected final RebindManager.RebindFailureMode rebindFailureMode;
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();
@@ -63,6 +66,7 @@ public class RebindExceptionHandlerImpl implements RebindExceptionHandler {
protected final Set<Exception> loadPolicyFailures = Sets.newConcurrentHashSet();
protected final List<Exception> exceptions = Collections.synchronizedList(Lists.<Exception>newArrayList());
+ protected RebindContext context;
protected boolean started = false;
protected boolean done = false;
@@ -75,6 +79,7 @@ public class RebindExceptionHandlerImpl implements RebindExceptionHandler {
private RebindManager.RebindFailureMode rebindFailureMode = 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;
@@ -92,6 +97,10 @@ public class RebindExceptionHandlerImpl implements RebindExceptionHandler {
deserializePolicyFailureMode = val;
return this;
}
+ public Builder danglingRefQuorumRequiredHealthy(QuorumCheck val) {
+ danglingRefsQuorumRequiredHealthy = val;
+ return this;
+ }
public RebindExceptionHandler build() {
return new RebindExceptionHandlerImpl(this);
}
@@ -102,15 +111,18 @@ public class RebindExceptionHandlerImpl implements RebindExceptionHandler {
this.rebindFailureMode = checkNotNull(builder.rebindFailureMode, "rebindFailureMode");
this.addPolicyFailureMode = checkNotNull(builder.addPolicyFailureMode, "addPolicyFailureMode");
this.loadPolicyFailureMode = checkNotNull(builder.deserializePolicyFailureMode, "deserializePolicyFailureMode");
+ this.danglingRefsQuorumRequiredHealthy = checkNotNull(builder.danglingRefsQuorumRequiredHealthy, "danglingRefsQuorumRequiredHealthy");
}
- public void onStart() {
+ @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;
}
@@ -364,6 +376,24 @@ public class RebindExceptionHandlerImpl implements RebindExceptionHandler {
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);
+ 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)) {
+ LOG.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);
}
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/f39c6a3f/core/src/main/java/brooklyn/entity/rebind/RebindManagerImpl.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/entity/rebind/RebindManagerImpl.java b/core/src/main/java/brooklyn/entity/rebind/RebindManagerImpl.java
index 3ba8bf1..ac9f726 100644
--- a/core/src/main/java/brooklyn/entity/rebind/RebindManagerImpl.java
+++ b/core/src/main/java/brooklyn/entity/rebind/RebindManagerImpl.java
@@ -91,6 +91,8 @@ import brooklyn.policy.Enricher;
import brooklyn.policy.Policy;
import brooklyn.policy.basic.AbstractPolicy;
import brooklyn.util.collections.MutableMap;
+import brooklyn.util.collections.QuorumCheck;
+import brooklyn.util.collections.QuorumCheck.QuorumChecks;
import brooklyn.util.exceptions.Exceptions;
import brooklyn.util.exceptions.RuntimeInterruptedException;
import brooklyn.util.flags.FlagUtils;
@@ -136,7 +138,15 @@ public class RebindManagerImpl implements RebindManager {
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;
@@ -163,6 +173,7 @@ public class RebindManagerImpl implements RebindManager {
private RebindFailureMode rebindFailureMode;
private RebindFailureMode addPolicyFailureMode;
private RebindFailureMode loadPolicyFailureMode;
+ private QuorumCheck danglingRefsQuorumRequiredHealthy;
/**
* For tracking if rebinding, for {@link AbstractEnricher#isRebinding()} etc.
@@ -201,6 +212,8 @@ public class RebindManagerImpl implements RebindManager {
rebindFailureMode = managementContext.getConfig().getConfig(REBIND_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});
@@ -446,6 +459,7 @@ public class RebindManagerImpl implements RebindManager {
final RebindExceptionHandler exceptionHandler = exceptionHandlerO!=null ? exceptionHandlerO :
RebindExceptionHandlerImpl.builder()
.danglingRefFailureMode(danglingRefFailureMode)
+ .danglingRefQuorumRequiredHealthy(danglingRefsQuorumRequiredHealthy)
.rebindFailureMode(rebindFailureMode)
.addPolicyFailureMode(addPolicyFailureMode)
.loadPolicyFailureMode(loadPolicyFailureMode)
@@ -514,10 +528,11 @@ public class RebindManagerImpl implements RebindManager {
RebindTracker.setRebinding();
try {
Stopwatch timer = Stopwatch.createStarted();
- exceptionHandler.onStart();
-
Reflections reflections = new Reflections(classLoader);
RebindContextImpl rebindContext = new RebindContextImpl(exceptionHandler, classLoader);
+
+ exceptionHandler.onStart(rebindContext);
+
if (mode==ManagementNodeState.HOT_STANDBY) {
rebindContext.setAllReadOnly();
} else {
@@ -525,6 +540,7 @@ public class RebindManagerImpl implements RebindManager {
}
LookupContext realLookupContext = new RebindContextLookupContext(managementContext, rebindContext, exceptionHandler);
+ rebindContext.setLookupContext(realLookupContext);
// Mutli-phase deserialization.
//
@@ -847,7 +863,7 @@ public class RebindManagerImpl implements RebindManager {
// Reconstruct entities
logRebindingDebug("RebindManager reconstructing entities");
for (EntityMemento entityMemento : sortParentFirst(memento.getEntityMementos()).values()) {
- Entity entity = rebindContext.getEntity(entityMemento.getId());
+ Entity entity = rebindContext.lookup().lookupEntity(entityMemento.getId());
logRebindingDebug("RebindManager reconstructing entity {}", entityMemento);
if (entity == null) {
@@ -1042,7 +1058,7 @@ public class RebindManagerImpl implements RebindManager {
private BrooklynClassLoadingContext getLoadingContextFromCatalogItemId(String catalogItemId, ClassLoader classLoader, RebindContext rebindContext) {
Preconditions.checkNotNull(catalogItemId, "catalogItemId required (should not be null)");
- CatalogItem<?, ?> catalogItem = rebindContext.getCatalogItem(catalogItemId);
+ CatalogItem<?, ?> catalogItem = rebindContext.lookup().lookupCatalogItem(catalogItemId);
if (catalogItem != null) {
return CatalogUtils.newClassLoadingContext(managementContext, catalogItem);
} else {
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/f39c6a3f/core/src/main/java/brooklyn/entity/rebind/dto/MementosGenerators.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/entity/rebind/dto/MementosGenerators.java b/core/src/main/java/brooklyn/entity/rebind/dto/MementosGenerators.java
index 94c2b59..7e8d598 100644
--- a/core/src/main/java/brooklyn/entity/rebind/dto/MementosGenerators.java
+++ b/core/src/main/java/brooklyn/entity/rebind/dto/MementosGenerators.java
@@ -21,6 +21,7 @@ package brooklyn.entity.rebind.dto;
import static com.google.common.base.Preconditions.checkNotNull;
import java.lang.reflect.Modifier;
+import java.lang.reflect.Proxy;
import java.util.Map;
import java.util.Set;
@@ -375,6 +376,10 @@ public class MementosGenerators {
}
private static void populateBrooklynObjectMementoBuilder(BrooklynObject instance, AbstractMemento.Builder<?> builder) {
+ if (Proxy.isProxyClass(instance.getClass())) {
+ throw new IllegalStateException("Attempt to create memento from proxy "+instance+" (would fail with wrong type)");
+ }
+
builder.id = instance.getId();
builder.displayName = instance.getDisplayName();
builder.catalogItemId = instance.getCatalogItemId();
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/f39c6a3f/core/src/main/java/brooklyn/entity/rebind/persister/BrooklynPersistenceUtils.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/entity/rebind/persister/BrooklynPersistenceUtils.java b/core/src/main/java/brooklyn/entity/rebind/persister/BrooklynPersistenceUtils.java
index 356234d..54990b3 100644
--- a/core/src/main/java/brooklyn/entity/rebind/persister/BrooklynPersistenceUtils.java
+++ b/core/src/main/java/brooklyn/entity/rebind/persister/BrooklynPersistenceUtils.java
@@ -29,6 +29,8 @@ import brooklyn.config.BrooklynServerConfig;
import brooklyn.config.BrooklynServerPaths;
import brooklyn.entity.Entity;
import brooklyn.entity.Feed;
+import brooklyn.entity.basic.AbstractEntity;
+import brooklyn.entity.basic.Entities;
import brooklyn.entity.basic.EntityInternal;
import brooklyn.entity.rebind.BrooklynObjectType;
import brooklyn.entity.rebind.PersistenceExceptionHandler;
@@ -169,6 +171,7 @@ public class BrooklynPersistenceUtils {
for (Location instance: mgmt.getLocationManager().getLocations())
result.location(instance.getId(), serializer.toString(newObjectMemento(instance)));
for (Entity instance: mgmt.getEntityManager().getEntities()) {
+ instance = Entities.deproxy(instance);
result.entity(instance.getId(), serializer.toString(newObjectMemento(instance)));
for (Feed instanceAdjunct: ((EntityInternal)instance).feeds().getFeeds())
result.feed(instanceAdjunct.getId(), serializer.toString(newObjectMemento(instanceAdjunct)));
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/f39c6a3f/core/src/main/java/brooklyn/management/ha/HighAvailabilityManagerImpl.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/management/ha/HighAvailabilityManagerImpl.java b/core/src/main/java/brooklyn/management/ha/HighAvailabilityManagerImpl.java
index c756824..329578e 100644
--- a/core/src/main/java/brooklyn/management/ha/HighAvailabilityManagerImpl.java
+++ b/core/src/main/java/brooklyn/management/ha/HighAvailabilityManagerImpl.java
@@ -211,7 +211,7 @@ public class HighAvailabilityManagerImpl implements HighAvailabilityManager {
* Defaults to null which means to use the remote timestamp.
* Only for testing as this records the remote timestamp in the object.
* <p>
- * If this is supplied, one must also set {@link ManagementPlaneSyncRecordPersisterToObjectStore#allowRemoteTimestampInMemento()}. */
+ * If this is supplied, one must also set {@link ManagementPlaneSyncRecordPersisterToObjectStore#useRemoteTimestampInMemento()}. */
@VisibleForTesting
public HighAvailabilityManagerImpl setRemoteTicker(Ticker val) {
this.optionalRemoteTickerUtc = val;
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/f39c6a3f/core/src/main/java/brooklyn/management/ha/ManagementPlaneSyncRecordPersisterToObjectStore.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/management/ha/ManagementPlaneSyncRecordPersisterToObjectStore.java b/core/src/main/java/brooklyn/management/ha/ManagementPlaneSyncRecordPersisterToObjectStore.java
index b9cf465..55b645c 100644
--- a/core/src/main/java/brooklyn/management/ha/ManagementPlaneSyncRecordPersisterToObjectStore.java
+++ b/core/src/main/java/brooklyn/management/ha/ManagementPlaneSyncRecordPersisterToObjectStore.java
@@ -102,7 +102,7 @@ public class ManagementPlaneSyncRecordPersisterToObjectStore implements Manageme
@VisibleForTesting
/** allows, when testing, to be able to override file times / blobstore times with time from the ticker */
- private boolean allowRemoteTimestampInMemento = false;
+ private boolean preferRemoteTimestampInMemento = false;
/**
* @param mgmt not used much at present but handy to ensure we know it so that obj store is prepared
@@ -131,8 +131,8 @@ public class ManagementPlaneSyncRecordPersisterToObjectStore implements Manageme
}
@VisibleForTesting
- public void allowRemoteTimestampInMemento() {
- allowRemoteTimestampInMemento = true;
+ public void preferRemoteTimestampInMemento() {
+ preferRemoteTimestampInMemento = true;
}
@Override
@@ -210,11 +210,12 @@ public class ManagementPlaneSyncRecordPersisterToObjectStore implements Manageme
// shouldn't happen
throw Exceptions.propagate(new IllegalStateException("Node record "+nodeFile+" could not be deserialized when "+mgmt.getManagementNodeId()+" was scanning: "+nodeContents, problem));
} else {
- if (memento.getRemoteTimestamp()!=null) {
+ if (memento.getRemoteTimestamp()!=null && preferRemoteTimestampInMemento) {
// in test mode, the remote timestamp is stored in the file
- if (!allowRemoteTimestampInMemento)
- throw new IllegalStateException("Remote timestamps not allowed in memento: "+nodeContents);
} else {
+ if (memento.getRemoteTimestamp()!=null) {
+ LOG.debug("Ignoring remote timestamp in memento file ("+memento+"); looks like this data has been manually copied in");
+ }
Date lastModifiedDate = objectAccessor.getLastModifiedDate();
((BasicManagementNodeSyncRecord)memento).setRemoteTimestamp(lastModifiedDate!=null ? lastModifiedDate.getTime() : null);
}
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/f39c6a3f/core/src/main/java/brooklyn/util/flags/TypeCoercions.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/util/flags/TypeCoercions.java b/core/src/main/java/brooklyn/util/flags/TypeCoercions.java
index ec08ad0..26b9dea 100644
--- a/core/src/main/java/brooklyn/util/flags/TypeCoercions.java
+++ b/core/src/main/java/brooklyn/util/flags/TypeCoercions.java
@@ -51,6 +51,8 @@ import brooklyn.entity.basic.EntityFactory;
import brooklyn.event.AttributeSensor;
import brooklyn.event.basic.BasicAttributeSensor;
import brooklyn.util.JavaGroovyEquivalents;
+import brooklyn.util.collections.QuorumCheck;
+import brooklyn.util.collections.QuorumCheck.QuorumChecks;
import brooklyn.util.exceptions.Exceptions;
import brooklyn.util.guava.Maybe;
import brooklyn.util.javalang.Enums;
@@ -696,6 +698,12 @@ public class TypeCoercions {
return JavaStringEscapes.unwrapJsonishListIfPossible(input);
}
});
+ registerAdapter(String.class, QuorumCheck.class, new Function<String,QuorumCheck>() {
+ @Override
+ public QuorumCheck apply(final String input) {
+ return QuorumChecks.of(input);
+ }
+ });
registerAdapter(String.class, Map.class, new Function<String,Map>() {
@Override
public Map apply(final String input) {
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/f39c6a3f/core/src/test/java/brooklyn/management/ha/HighAvailabilityManagerSplitBrainTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/brooklyn/management/ha/HighAvailabilityManagerSplitBrainTest.java b/core/src/test/java/brooklyn/management/ha/HighAvailabilityManagerSplitBrainTest.java
index 4c736ca..4ead25d 100644
--- a/core/src/test/java/brooklyn/management/ha/HighAvailabilityManagerSplitBrainTest.java
+++ b/core/src/test/java/brooklyn/management/ha/HighAvailabilityManagerSplitBrainTest.java
@@ -101,7 +101,7 @@ public class HighAvailabilityManagerSplitBrainTest {
objectStore.injectManagementContext(mgmt);
objectStore.prepareForSharedUse(PersistMode.CLEAN, HighAvailabilityMode.DISABLED);
persister = new ManagementPlaneSyncRecordPersisterToObjectStore(mgmt, objectStore, classLoader);
- ((ManagementPlaneSyncRecordPersisterToObjectStore)persister).allowRemoteTimestampInMemento();
+ ((ManagementPlaneSyncRecordPersisterToObjectStore)persister).preferRemoteTimestampInMemento();
BrooklynMementoPersisterToObjectStore persisterObj = new BrooklynMementoPersisterToObjectStore(objectStore, mgmt.getBrooklynProperties(), classLoader);
mgmt.getRebindManager().setPersister(persisterObj, PersistenceExceptionHandlerImpl.builder().build());
ha = ((HighAvailabilityManagerImpl)mgmt.getHighAvailabilityManager())
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/f39c6a3f/core/src/test/java/brooklyn/management/ha/HighAvailabilityManagerTestFixture.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/brooklyn/management/ha/HighAvailabilityManagerTestFixture.java b/core/src/test/java/brooklyn/management/ha/HighAvailabilityManagerTestFixture.java
index 2637ae1..449b2d9 100644
--- a/core/src/test/java/brooklyn/management/ha/HighAvailabilityManagerTestFixture.java
+++ b/core/src/test/java/brooklyn/management/ha/HighAvailabilityManagerTestFixture.java
@@ -83,7 +83,7 @@ public abstract class HighAvailabilityManagerTestFixture {
objectStore.injectManagementContext(managementContext);
objectStore.prepareForSharedUse(PersistMode.CLEAN, HighAvailabilityMode.DISABLED);
persister = new ManagementPlaneSyncRecordPersisterToObjectStore(managementContext, objectStore, classLoader);
- ((ManagementPlaneSyncRecordPersisterToObjectStore)persister).allowRemoteTimestampInMemento();
+ ((ManagementPlaneSyncRecordPersisterToObjectStore)persister).preferRemoteTimestampInMemento();
BrooklynMementoPersisterToObjectStore persisterObj = new BrooklynMementoPersisterToObjectStore(
objectStore,
managementContext.getBrooklynProperties(),
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/f39c6a3f/core/src/test/java/brooklyn/management/ha/HotStandbyTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/brooklyn/management/ha/HotStandbyTest.java b/core/src/test/java/brooklyn/management/ha/HotStandbyTest.java
index 3cb6896..76fa635 100644
--- a/core/src/test/java/brooklyn/management/ha/HotStandbyTest.java
+++ b/core/src/test/java/brooklyn/management/ha/HotStandbyTest.java
@@ -90,7 +90,7 @@ public class HotStandbyTest {
objectStore.injectManagementContext(mgmt);
objectStore.prepareForSharedUse(PersistMode.CLEAN, HighAvailabilityMode.DISABLED);
persister = new ManagementPlaneSyncRecordPersisterToObjectStore(mgmt, objectStore, classLoader);
- ((ManagementPlaneSyncRecordPersisterToObjectStore)persister).allowRemoteTimestampInMemento();
+ ((ManagementPlaneSyncRecordPersisterToObjectStore)persister).preferRemoteTimestampInMemento();
BrooklynMementoPersisterToObjectStore persisterObj = new BrooklynMementoPersisterToObjectStore(objectStore, mgmt.getBrooklynProperties(), classLoader);
((RebindManagerImpl)mgmt.getRebindManager()).setPeriodicPersistPeriod(persistOrRebindPeriod);
mgmt.getRebindManager().setPersister(persisterObj, PersistenceExceptionHandlerImpl.builder().build());
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/f39c6a3f/core/src/test/java/brooklyn/management/ha/WarmStandbyTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/brooklyn/management/ha/WarmStandbyTest.java b/core/src/test/java/brooklyn/management/ha/WarmStandbyTest.java
index a7b6b8a..29bb45e 100644
--- a/core/src/test/java/brooklyn/management/ha/WarmStandbyTest.java
+++ b/core/src/test/java/brooklyn/management/ha/WarmStandbyTest.java
@@ -74,7 +74,7 @@ public class WarmStandbyTest {
objectStore.injectManagementContext(mgmt);
objectStore.prepareForSharedUse(PersistMode.CLEAN, HighAvailabilityMode.DISABLED);
persister = new ManagementPlaneSyncRecordPersisterToObjectStore(mgmt, objectStore, classLoader);
- ((ManagementPlaneSyncRecordPersisterToObjectStore)persister).allowRemoteTimestampInMemento();
+ ((ManagementPlaneSyncRecordPersisterToObjectStore)persister).preferRemoteTimestampInMemento();
BrooklynMementoPersisterToObjectStore persisterObj = new BrooklynMementoPersisterToObjectStore(objectStore, mgmt.getBrooklynProperties(), classLoader);
mgmt.getRebindManager().setPersister(persisterObj, PersistenceExceptionHandlerImpl.builder().build());
ha = ((HighAvailabilityManagerImpl)mgmt.getHighAvailabilityManager())
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/f39c6a3f/utils/common/src/main/java/brooklyn/util/collections/MutableList.java
----------------------------------------------------------------------
diff --git a/utils/common/src/main/java/brooklyn/util/collections/MutableList.java b/utils/common/src/main/java/brooklyn/util/collections/MutableList.java
index c467f0e..c5cab8d 100644
--- a/utils/common/src/main/java/brooklyn/util/collections/MutableList.java
+++ b/utils/common/src/main/java/brooklyn/util/collections/MutableList.java
@@ -179,6 +179,13 @@ public class MutableList<V> extends ArrayList<V> {
public ImmutableList<V> buildImmutable() {
return ImmutableList.copyOf(result);
}
+
+ public Builder<V> addLists(Iterable<? extends V> ...items) {
+ for (Iterable<? extends V> item: items) {
+ addAll(item);
+ }
+ return this;
+ }
}
/** as {@link List#add(Object)} but fluent style */
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/f39c6a3f/utils/common/src/main/java/brooklyn/util/collections/QuorumCheck.java
----------------------------------------------------------------------
diff --git a/utils/common/src/main/java/brooklyn/util/collections/QuorumCheck.java b/utils/common/src/main/java/brooklyn/util/collections/QuorumCheck.java
index 98bfddc..1536f6f 100644
--- a/utils/common/src/main/java/brooklyn/util/collections/QuorumCheck.java
+++ b/utils/common/src/main/java/brooklyn/util/collections/QuorumCheck.java
@@ -19,6 +19,14 @@
package brooklyn.util.collections;
import java.io.Serializable;
+import java.util.Iterator;
+import java.util.List;
+
+import brooklyn.util.yaml.Yamls;
+
+import com.google.common.base.Objects;
+import com.google.common.base.Preconditions;
+import com.google.common.collect.Iterables;
/**
* For checking if a group/cluster is quorate. That is, whether the group has sufficient
@@ -44,13 +52,13 @@ public interface QuorumCheck {
* 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, "atLeastOne");
+ return new NumericQuorumCheck(1, 1.0, false, "allAndAtLeastOne");
}
/**
* Requires at least one member that should be up is up.
*/
public static QuorumCheck atLeastOne() {
- return new NumericQuorumCheck(1, 0.0, false);
+ return new NumericQuorumCheck(1, 0.0, false, "atLeastOne");
}
/**
* Requires at least one member to be up if the total size is non-zero.
@@ -58,17 +66,68 @@ public interface QuorumCheck {
* "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);
+ return new NumericQuorumCheck(1, 0.0, true, "atLeastOneUnlessEmpty");
}
/**
* Always "healthy"
*/
public static QuorumCheck alwaysTrue() {
- return new NumericQuorumCheck(0, 0.0, true);
+ return new NumericQuorumCheck(0, 0.0, true, "alwaysHealthy");
}
+
public static QuorumCheck newInstance(int minRequiredSize, double minRequiredRatio, boolean allowEmpty) {
return new NumericQuorumCheck(minRequiredSize, minRequiredRatio, allowEmpty);
}
+
+ /** See {@link QuorumChecks#newLinearRange(String,String)} */
+ public static QuorumCheck newLinearRange(String range) {
+ return newLinearRange(range, null);
+ }
+
+ /** Given a JSON representation of a list of points (where a point is a list of 2 numbers),
+ * with the points in increasing x-coordinate value,
+ * this constructs a quorum check which does linear interpolation on those coordinates,
+ * with extensions to either side.
+ * The x-coordinate is taken as the total size, and the y-coordinate as the minimum required size.
+ * <p>
+ * It sounds complicated but it gives a very easy and powerful way to define quorum checks.
+ * For instance:
+ * <p>
+ * <code>[[0,0],[1,1]]</code> says that if 0 items are expected, at least 0 is required;
+ * if 1 is expected, 1 is required; and by extension if 10 are expected, 10 are required.
+ * In other words, this is the same as {@link #all()}.
+ * <p>
+ * <code>[[0,1],[1,1],[2,2]]</code> is the same as the previous for x (number expected) greater-than or equal to 1;
+ * but if 0 is expected, 1 is required, and so it fails when 0 are present.
+ * In other words, {@link #allAndAtLeastOne()}.
+ * <p>
+ * <code>[[5,5],[10,10],[100,70],[200,140]]</code> has {@link #all()} behavior up to 10 expected
+ * (line extended to the left, for less than 5); but then gently tapers off to requiring only 70% at 100
+ * (with 30 of 40 = 75% required at that intermediate point along the line [[10,10],[100,70]]);
+ * and then from 100 onwards it is a straight 70%.
+ * <p>
+ * The type of linear regression described in the last example is quite useful in practise,
+ * to be stricter for smaller clusters (or possibly more lax for small values in some cases,
+ * such as when tolerating dangling references during rebind).
+ */
+ @SuppressWarnings({ "rawtypes", "unchecked" })
+ public static QuorumCheck newLinearRange(String range, String name) {
+ return LinearRangeQuorumCheck.of(name, (Iterable)Iterables.getOnlyElement( Yamls.parseAll(range) ));
+ }
+
+ private static final List<QuorumCheck> NAMED_CHECKS = MutableList
+ .of(all(), allAndAtLeastOne(), atLeastOne(), atLeastOneUnlessEmpty(), alwaysTrue());
+
+ public static QuorumCheck of(String nameOrRange) {
+ if (nameOrRange==null) return null;
+ for (QuorumCheck qc: NAMED_CHECKS) {
+ if (qc instanceof NumericQuorumCheck) {
+ if (Objects.equal(nameOrRange, ((NumericQuorumCheck)qc).getName()))
+ return qc;
+ }
+ }
+ return newLinearRange(nameOrRange);
+ }
}
public static class NumericQuorumCheck implements QuorumCheck, Serializable {
@@ -96,11 +155,82 @@ public interface QuorumCheck {
if (sizeHealthy < totalSize*minRequiredRatio-0.000000001) return false;
return true;
}
+
+ public String getName() {
+ return name;
+ }
@Override
public String toString() {
- return "QuorumCheck[require="+minRequiredSize+","+((int)100*minRequiredRatio)+"%"+(allowEmpty ? "|0" : "")+"]";
+ return "QuorumCheck["+(name!=null?name+";":"")+"require="+minRequiredSize+","+((int)100*minRequiredRatio)+"%"+(allowEmpty ? "|0" : "")+"]";
}
}
-
+
+ /** See {@link QuorumChecks#newLinearRange(String,String)} */
+ public static class LinearRangeQuorumCheck implements QuorumCheck, Serializable {
+
+ private static final long serialVersionUID = -6425548115925898645L;
+
+ private static class Point {
+ final double size, minRequiredAtSize;
+ public Point(double size, double minRequiredAtSize) { this.size = size; this.minRequiredAtSize = minRequiredAtSize; }
+ public static Point ofIntegerCoords(Iterable<Integer> coords) {
+ Preconditions.checkNotNull(coords==null, "coords");
+ Preconditions.checkArgument(Iterables.size(coords)==2, "A point must consist of two coordinates; invalid data: "+coords);
+ Iterator<Integer> ci = coords.iterator();
+ return new Point(ci.next(), ci.next());
+ }
+ public static List<Point> listOfIntegerCoords(Iterable<? extends Iterable<Integer>> points) {
+ MutableList<Point> result = MutableList.of();
+ for (Iterable<Integer> point: points) result.add(ofIntegerCoords(point));
+ return result.asUnmodifiable();
+ }
+ @Override
+ public String toString() {
+ return "("+size+","+minRequiredAtSize+")";
+ }
+ }
+
+ protected final String name;
+ protected final List<Point> points;
+
+ public static LinearRangeQuorumCheck of(String name, Iterable<? extends Iterable<Integer>> points) {
+ return new LinearRangeQuorumCheck(name, Point.listOfIntegerCoords(points));
+ }
+ public static LinearRangeQuorumCheck of(Iterable<? extends Iterable<Integer>> points) {
+ return new LinearRangeQuorumCheck(null, Point.listOfIntegerCoords(points));
+ }
+
+ protected LinearRangeQuorumCheck(String name, Iterable<Point> points) {
+ Preconditions.checkArgument(Iterables.size(points)>=2, "At least two points must be supplied for "+name+": "+points);
+ this.name = name;
+ this.points = MutableList.copyOf(points).asUnmodifiable();
+ // check valid
+ Point last = null;
+ for (Point p: points) {
+ if (last!=null) {
+ if (p.size <= last.size) throw new IllegalStateException("Points must be supplied in order of increasing totalSize (x coordinate); instead have "+last+" and "+p);
+ }
+ }
+ }
+
+ @Override
+ public boolean isQuorate(int sizeHealthy, int totalSize) {
+ Point next = points.get(0);
+ Point prev = null;
+ for (int i=1; i<points.size(); i++) {
+ prev = next;
+ next = points.get(i);
+ if (next.size>totalSize) break;
+ }
+ double minRequiredAtSize = (totalSize-prev.size)/(next.size-prev.size) * (next.minRequiredAtSize-prev.minRequiredAtSize) + prev.minRequiredAtSize;
+ return (sizeHealthy > minRequiredAtSize-0.000000001);
+ }
+
+ @Override
+ public String toString() {
+ return "LinearRangeQuorumCheck["+(name!=null ? name+":" : "")+points+"]";
+ }
+ }
+
}
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/f39c6a3f/utils/common/src/test/java/brooklyn/util/collections/QuorumChecksTest.java
----------------------------------------------------------------------
diff --git a/utils/common/src/test/java/brooklyn/util/collections/QuorumChecksTest.java b/utils/common/src/test/java/brooklyn/util/collections/QuorumChecksTest.java
new file mode 100644
index 0000000..bf53d68
--- /dev/null
+++ b/utils/common/src/test/java/brooklyn/util/collections/QuorumChecksTest.java
@@ -0,0 +1,105 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package brooklyn.util.collections;
+
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+import brooklyn.util.collections.QuorumCheck.QuorumChecks;
+
+public class QuorumChecksTest {
+
+ @Test
+ public void testAll() {
+ QuorumCheck q = QuorumChecks.all();
+ Assert.assertTrue(q.isQuorate(2, 2));
+ Assert.assertFalse(q.isQuorate(1, 2));
+ Assert.assertTrue(q.isQuorate(0, 0));
+ }
+
+ @Test
+ public void testAlwaysTrue() {
+ QuorumCheck q = QuorumChecks.alwaysTrue();
+ Assert.assertTrue(q.isQuorate(0, 2));
+ Assert.assertTrue(q.isQuorate(1, 2));
+ Assert.assertTrue(q.isQuorate(0, 0));
+ }
+
+ @Test
+ public void testAtLeastOne() {
+ QuorumCheck q = QuorumChecks.atLeastOne();
+ Assert.assertTrue(q.isQuorate(2, 2));
+ Assert.assertTrue(q.isQuorate(1, 2));
+ Assert.assertFalse(q.isQuorate(0, 0));
+ }
+
+ @Test
+ public void testAllAndAtLeastOne() {
+ QuorumCheck q = QuorumChecks.atLeastOne();
+ Assert.assertFalse(q.isQuorate(0, 2));
+ Assert.assertTrue(q.isQuorate(1, 2));
+ Assert.assertFalse(q.isQuorate(0, 0));
+ }
+
+ @Test
+ public void testAtLeastOneUnlessEmpty() {
+ QuorumCheck q = QuorumChecks.atLeastOneUnlessEmpty();
+ Assert.assertFalse(q.isQuorate(0, 2));
+ Assert.assertTrue(q.isQuorate(1, 2));
+ Assert.assertTrue(q.isQuorate(0, 0));
+ }
+
+ @Test
+ public void testAtLeastOneUnlessEmptyString() {
+ QuorumCheck q = QuorumChecks.of("atLeastOneUnlessEmpty");
+ Assert.assertFalse(q.isQuorate(0, 2));
+ Assert.assertTrue(q.isQuorate(1, 2));
+ Assert.assertTrue(q.isQuorate(0, 0));
+ }
+
+ @Test
+ public void testLinearTwoPointsNeedMinTwo() {
+ QuorumCheck q = QuorumChecks.of("[ [0,2], [1,2] ]");
+ Assert.assertTrue(q.isQuorate(2, 2));
+ Assert.assertTrue(q.isQuorate(2, 10));
+ Assert.assertFalse(q.isQuorate(1, 1));
+ }
+
+ @Test
+ public void testLinearNeedHalfToTenAndTenPercentAtHundred() {
+ QuorumCheck q = QuorumChecks.of("[ [0,0], [10,5], [100,10], [200, 20] ]");
+ Assert.assertTrue(q.isQuorate(2, 2));
+ Assert.assertTrue(q.isQuorate(1, 2));
+ Assert.assertTrue(q.isQuorate(0, 0));
+ Assert.assertFalse(q.isQuorate(1, 10));
+ Assert.assertTrue(q.isQuorate(6, 10));
+ Assert.assertFalse(q.isQuorate(7, 50));
+ Assert.assertTrue(q.isQuorate(8, 50));
+ Assert.assertFalse(q.isQuorate(9, 100));
+ Assert.assertTrue(q.isQuorate(11, 100));
+ Assert.assertFalse(q.isQuorate(19, 200));
+ Assert.assertTrue(q.isQuorate(21, 200));
+ Assert.assertFalse(q.isQuorate(29, 300));
+ Assert.assertTrue(q.isQuorate(31, 300));
+ }
+
+
+
+
+}