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/10/01 20:03:01 UTC
[01/15] git commit: use `BrooklynFeatureEnablement` to set whether
standby or hot-standby should be the default. failure going to hot_standby
sets not to failure state,
not standby state. rename `EntityReadOnlyInternal` to
`EntityTransientCopyInternal`,
Repository: incubator-brooklyn
Updated Branches:
refs/heads/master 8a1fd77ea -> ff11b4fc0
use `BrooklynFeatureEnablement` to set whether standby or hot-standby should be the default.
failure going to hot_standby sets not to failure state, not standby state.
rename `EntityReadOnlyInternal` to `EntityTransientCopyInternal`, and remove setDisplayName, as per code review.
better tests (working) for propagating initial items, and test (failing) for update/removal of items being rebinded.
Project: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/commit/a1d83eee
Tree: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/tree/a1d83eee
Diff: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/diff/a1d83eee
Branch: refs/heads/master
Commit: a1d83eee5ec738bcd7fc8098a6b54451ee8d4d4d
Parents: 3a8a33a
Author: Alex Heneveld <al...@cloudsoftcorp.com>
Authored: Tue Sep 30 10:31:18 2014 +0100
Committer: Alex Heneveld <al...@cloudsoftcorp.com>
Committed: Wed Oct 1 16:40:15 2014 +0100
----------------------------------------------------------------------
.../brooklyn/management/ManagementContext.java | 10 +-
.../entity/basic/EntityReadOnlyInternal.java | 102 -------------
.../basic/EntityTransientCopyInternal.java | 106 ++++++++++++++
.../entity/proxying/EntityProxyImpl.java | 4 +-
.../internal/BrooklynFeatureEnablement.java | 7 +
.../ha/HighAvailabilityManagerImpl.java | 25 ++--
.../internal/AbstractManagementContext.java | 16 +++
.../NonDeploymentManagementContext.java | 14 ++
.../HighAvailabilityManagerSplitBrainTest.java | 8 +-
.../ha/HighAvailabilityManagerTestFixture.java | 2 +-
.../brooklyn/management/ha/HotStandbyTest.java | 142 +++++++++++++++++--
11 files changed, 306 insertions(+), 130 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/a1d83eee/api/src/main/java/brooklyn/management/ManagementContext.java
----------------------------------------------------------------------
diff --git a/api/src/main/java/brooklyn/management/ManagementContext.java b/api/src/main/java/brooklyn/management/ManagementContext.java
index 29cb20e..6e3a3b1 100644
--- a/api/src/main/java/brooklyn/management/ManagementContext.java
+++ b/api/src/main/java/brooklyn/management/ManagementContext.java
@@ -21,6 +21,7 @@ package brooklyn.management;
import java.net.URI;
import java.util.Collection;
+import brooklyn.basic.BrooklynObject;
import brooklyn.catalog.BrooklynCatalog;
import brooklyn.config.StringConfigMap;
import brooklyn.entity.Application;
@@ -211,5 +212,12 @@ public interface ManagementContext {
/** Active entitlements checker instance. */
EntitlementManager getEntitlementManager();
-
+
+
+ /** as {@link #lookup(String, Class)} but not constraining the return type */
+ public BrooklynObject lookup(String id);
+ /** finds an entity with the given ID known at this management context */
+ // TODO in future support policies etc
+ public <T> T lookup(String id, Class<T> type);
+
}
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/a1d83eee/core/src/main/java/brooklyn/entity/basic/EntityReadOnlyInternal.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/entity/basic/EntityReadOnlyInternal.java b/core/src/main/java/brooklyn/entity/basic/EntityReadOnlyInternal.java
deleted file mode 100644
index 6261a4d..0000000
--- a/core/src/main/java/brooklyn/entity/basic/EntityReadOnlyInternal.java
+++ /dev/null
@@ -1,102 +0,0 @@
-/*
- * 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.entity.basic;
-
-import java.util.Collection;
-import java.util.Map;
-import java.util.Set;
-
-import javax.annotation.Nonnull;
-import javax.annotation.Nullable;
-
-import brooklyn.basic.BrooklynObjectInternal;
-import brooklyn.config.ConfigKey;
-import brooklyn.config.ConfigKey.HasConfigKey;
-import brooklyn.entity.Application;
-import brooklyn.entity.Effector;
-import brooklyn.entity.Entity;
-import brooklyn.entity.EntityType;
-import brooklyn.entity.Group;
-import brooklyn.entity.basic.EntityInternal.FeedSupport;
-import brooklyn.entity.rebind.RebindSupport;
-import brooklyn.entity.rebind.Rebindable;
-import brooklyn.event.AttributeSensor;
-import brooklyn.location.Location;
-import brooklyn.management.ManagementContext;
-import brooklyn.management.internal.EntityManagementSupport;
-import brooklyn.mementos.EntityMemento;
-import brooklyn.policy.Enricher;
-import brooklyn.policy.Policy;
-import brooklyn.util.config.ConfigBag;
-import brooklyn.util.guava.Maybe;
-
-import com.google.common.annotations.Beta;
-
-/**
- * Selected methods from {@link EntityInternal} and parents which are permitted
- * in read-only mode.
- */
-@Beta
-public interface EntityReadOnlyInternal {
-
- // from Entity
-
- String getId();
- long getCreationTime();
- String getDisplayName();
- @Nullable String getIconUrl();
- EntityType getEntityType();
- Application getApplication();
- String getApplicationId();
- Entity getParent();
- Collection<Entity> getChildren();
- Collection<Policy> getPolicies();
- Collection<Enricher> getEnrichers();
- Collection<Group> getGroups();
- Collection<Location> getLocations();
- <T> T getAttribute(AttributeSensor<T> sensor);
- <T> T getConfig(ConfigKey<T> key);
- <T> T getConfig(HasConfigKey<T> key);
- Maybe<Object> getConfigRaw(ConfigKey<?> key, boolean includeInherited);
- Maybe<Object> getConfigRaw(HasConfigKey<?> key, boolean includeInherited);
- @Deprecated Set<Object> getTags();
- @Deprecated boolean containsTag(@Nonnull Object tag);
-
-
- // from entity local
-
- void setDisplayName(String displayName);
- @Deprecated <T> T getConfig(ConfigKey<T> key, T defaultValue);
- @Deprecated <T> T getConfig(HasConfigKey<T> key, T defaultValue);
-
- // from EntityInternal:
-
- EntityConfigMap getConfigMap();
- Map<ConfigKey<?>,Object> getAllConfig();
- // for rebind mainly
- ConfigBag getAllConfigBag();
- ConfigBag getLocalConfigBag();
- Map<AttributeSensor, Object> getAllAttributes();
- EntityManagementSupport getManagementSupport();
- ManagementContext getManagementContext();
- Effector<?> getEffector(String effectorName);
- FeedSupport getFeedSupport();
- RebindSupport<EntityMemento> getRebindSupport();
-
-}
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/a1d83eee/core/src/main/java/brooklyn/entity/basic/EntityTransientCopyInternal.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/entity/basic/EntityTransientCopyInternal.java b/core/src/main/java/brooklyn/entity/basic/EntityTransientCopyInternal.java
new file mode 100644
index 0000000..0f69fe1
--- /dev/null
+++ b/core/src/main/java/brooklyn/entity/basic/EntityTransientCopyInternal.java
@@ -0,0 +1,106 @@
+/*
+ * 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.entity.basic;
+
+import java.util.Collection;
+import java.util.Map;
+import java.util.Set;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import brooklyn.config.ConfigKey;
+import brooklyn.config.ConfigKey.HasConfigKey;
+import brooklyn.entity.Application;
+import brooklyn.entity.Effector;
+import brooklyn.entity.Entity;
+import brooklyn.entity.EntityType;
+import brooklyn.entity.Group;
+import brooklyn.entity.basic.EntityInternal.FeedSupport;
+import brooklyn.entity.proxying.EntityProxyImpl;
+import brooklyn.entity.rebind.RebindSupport;
+import brooklyn.event.AttributeSensor;
+import brooklyn.location.Location;
+import brooklyn.management.ManagementContext;
+import brooklyn.management.internal.EntityManagementSupport;
+import brooklyn.mementos.EntityMemento;
+import brooklyn.policy.Enricher;
+import brooklyn.policy.Policy;
+import brooklyn.util.config.ConfigBag;
+import brooklyn.util.guava.Maybe;
+
+import com.google.common.annotations.Beta;
+
+/**
+ * Selected methods from {@link EntityInternal} and parents which are permitted
+ * for entities being loaded in read-only mode, enforced by {@link EntityProxyImpl}.
+ * <p>
+ * Some of these methods do expose write capabilities, but such modifications are likely
+ * to be temporary, discarded on next rebind. Callers must take care with any such invocations.
+ * (The primary intent of this interface is to catch and prevent *most* such invocations!)
+ */
+@Beta
+public interface EntityTransientCopyInternal {
+
+ // from Entity
+
+ String getId();
+ long getCreationTime();
+ String getDisplayName();
+ @Nullable String getIconUrl();
+ EntityType getEntityType();
+ Application getApplication();
+ String getApplicationId();
+ Entity getParent();
+ Collection<Entity> getChildren();
+ Collection<Policy> getPolicies();
+ Collection<Enricher> getEnrichers();
+ Collection<Group> getGroups();
+ Collection<Location> getLocations();
+ <T> T getAttribute(AttributeSensor<T> sensor);
+ <T> T getConfig(ConfigKey<T> key);
+ <T> T getConfig(HasConfigKey<T> key);
+ Maybe<Object> getConfigRaw(ConfigKey<?> key, boolean includeInherited);
+ Maybe<Object> getConfigRaw(HasConfigKey<?> key, boolean includeInherited);
+ @Deprecated Set<Object> getTags();
+ @Deprecated boolean containsTag(@Nonnull Object tag);
+
+
+ // from entity local
+
+ @Deprecated <T> T getConfig(ConfigKey<T> key, T defaultValue);
+ @Deprecated <T> T getConfig(HasConfigKey<T> key, T defaultValue);
+
+
+ // from EntityInternal:
+
+ EntityConfigMap getConfigMap();
+ Map<ConfigKey<?>,Object> getAllConfig();
+ // for rebind mainly:
+ ConfigBag getAllConfigBag();
+ ConfigBag getLocalConfigBag();
+ @SuppressWarnings("rawtypes")
+ Map<AttributeSensor, Object> getAllAttributes();
+ EntityManagementSupport getManagementSupport();
+ ManagementContext getManagementContext();
+ Effector<?> getEffector(String effectorName);
+ FeedSupport getFeedSupport();
+ RebindSupport<EntityMemento> getRebindSupport();
+
+}
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/a1d83eee/core/src/main/java/brooklyn/entity/proxying/EntityProxyImpl.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/entity/proxying/EntityProxyImpl.java b/core/src/main/java/brooklyn/entity/proxying/EntityProxyImpl.java
index 7cc5793..45364ba 100644
--- a/core/src/main/java/brooklyn/entity/proxying/EntityProxyImpl.java
+++ b/core/src/main/java/brooklyn/entity/proxying/EntityProxyImpl.java
@@ -33,7 +33,7 @@ import brooklyn.entity.Entity;
import brooklyn.entity.basic.AbstractEntity;
import brooklyn.entity.basic.EntityInternal;
import brooklyn.entity.basic.EntityLocal;
-import brooklyn.entity.basic.EntityReadOnlyInternal;
+import brooklyn.entity.basic.EntityTransientCopyInternal;
import brooklyn.management.ManagementContext;
import brooklyn.management.internal.EffectorUtils;
import brooklyn.management.internal.EntityManagerInternal;
@@ -81,7 +81,7 @@ public class EntityProxyImpl implements java.lang.reflect.InvocationHandler {
private static final Set<MethodSignature> ENTITY_PERMITTED_READ_ONLY_METHODS = Sets.newLinkedHashSet();
static {
- for (Method m : EntityReadOnlyInternal.class.getMethods()) {
+ for (Method m : EntityTransientCopyInternal.class.getMethods()) {
ENTITY_PERMITTED_READ_ONLY_METHODS.add(new MethodSignature(m));
}
if (!ENTITY_NON_EFFECTOR_METHODS.containsAll(ENTITY_PERMITTED_READ_ONLY_METHODS)) {
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/a1d83eee/core/src/main/java/brooklyn/internal/BrooklynFeatureEnablement.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/internal/BrooklynFeatureEnablement.java b/core/src/main/java/brooklyn/internal/BrooklynFeatureEnablement.java
index d5ec2ba..eb98144 100644
--- a/core/src/main/java/brooklyn/internal/BrooklynFeatureEnablement.java
+++ b/core/src/main/java/brooklyn/internal/BrooklynFeatureEnablement.java
@@ -23,6 +23,8 @@ import java.util.Map;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import brooklyn.management.ha.HighAvailabilityMode;
+
import com.google.common.collect.Maps;
/**
@@ -44,6 +46,10 @@ public class BrooklynFeatureEnablement {
public static final String FEATURE_FEED_PERSISTENCE_PROPERTY = "brooklyn.experimental.feature.feedPersistence";
public static final String FEATURE_CATALOG_PERSISTENCE_PROPERTY = "brooklyn.experimental.feature.catalogPersistence";
+
+ /** whether the default standby mode is {@link HighAvailabilityMode#HOT_STANDBY} or falling back to the traditional
+ * {@link HighAvailabilityMode#STANDBY} */
+ public static final String FEATURE_DEFAULT_STANDBY_IS_HOT_PROPERTY = "brooklyn.experimental.feature.defaultStandbyIsHot";
private static final Map<String, Boolean> FEATURE_ENABLEMENTS = Maps.newLinkedHashMap();
@@ -58,6 +64,7 @@ public class BrooklynFeatureEnablement {
setDefault(FEATURE_ENRICHER_PERSISTENCE_PROPERTY, true);
setDefault(FEATURE_FEED_PERSISTENCE_PROPERTY, true);
setDefault(FEATURE_CATALOG_PERSISTENCE_PROPERTY, true);
+ setDefault(FEATURE_DEFAULT_STANDBY_IS_HOT_PROPERTY, false);
}
static {
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/a1d83eee/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 52fffdc..0595db5 100644
--- a/core/src/main/java/brooklyn/management/ha/HighAvailabilityManagerImpl.java
+++ b/core/src/main/java/brooklyn/management/ha/HighAvailabilityManagerImpl.java
@@ -40,6 +40,7 @@ import brooklyn.entity.rebind.RebindManager;
import brooklyn.entity.rebind.plane.dto.BasicManagementNodeSyncRecord;
import brooklyn.entity.rebind.plane.dto.ManagementPlaneSyncRecordImpl;
import brooklyn.entity.rebind.plane.dto.ManagementPlaneSyncRecordImpl.Builder;
+import brooklyn.internal.BrooklynFeatureEnablement;
import brooklyn.management.Task;
import brooklyn.management.ha.BasicMasterChooser.AlphabeticMasterChooser;
import brooklyn.management.ha.ManagementPlaneSyncRecordPersister.Delta;
@@ -204,8 +205,9 @@ public class HighAvailabilityManagerImpl implements HighAvailabilityManager {
@Override
public void start(HighAvailabilityMode startMode) {
ownNodeId = managementContext.getManagementNodeId();
- nodeState = ManagementNodeState.STANDBY;
nodeStateTransitionComplete = true;
+ // always start in standby; it may get promoted to master or hot_standby in this method
+ nodeState = ManagementNodeState.STANDBY;
running = true;
// TODO Small race in that we first check, and then we'll do checkMaster() on first poll,
@@ -225,8 +227,10 @@ public class HighAvailabilityManagerImpl implements HighAvailabilityManager {
ManagementNodeSyncRecord masterNodeDetails = getManagementPlaneSyncState().getManagementNodes().get(masterNodeId);
LOG.info("Management node "+ownNodeId+" started as HA " + nodeState + " autodetected, master is "+masterNodeId +
(masterNodeDetails==null || masterNodeDetails.getUri()==null ? " (no url)" : " at "+masterNodeDetails.getUri()));
- } else {
+ } else if (nodeState == ManagementNodeState.MASTER) {
LOG.info("Management node "+ownNodeId+" starting as HA MASTER autodetected");
+ } else {
+ throw new IllegalStateException("Management node "+ownNodeId+" started as auto in unexpected mode "+nodeState);
}
break;
case MASTER:
@@ -250,9 +254,15 @@ public class HighAvailabilityManagerImpl implements HighAvailabilityManager {
throw new IllegalStateException("Unexpected high availability start-mode "+startMode+" for "+this);
}
- if (nodeState==ManagementNodeState.STANDBY && startMode!=HighAvailabilityMode.STANDBY) {
- // if standby not explicitly requested, we need promote to hot standby
- // TODO may want a flag which enables/disables hot standby; for now, always enabled unless told to start in standby
+ if (startMode==HighAvailabilityMode.AUTO) {
+ if (BrooklynFeatureEnablement.isEnabled(BrooklynFeatureEnablement.FEATURE_DEFAULT_STANDBY_IS_HOT_PROPERTY)) {
+ startMode = HighAvailabilityMode.HOT_STANDBY;
+ } else {
+ startMode = HighAvailabilityMode.STANDBY;
+ }
+ }
+ if (nodeState==ManagementNodeState.STANDBY && startMode==HighAvailabilityMode.HOT_STANDBY) {
+ // if it should be hot standby, then we need to promote
nodeStateTransitionComplete = false;
// inform the world that we are transitioning (not eligible for promotion while going in to hot standby)
publishHealth();
@@ -624,9 +634,8 @@ public class HighAvailabilityManagerImpl implements HighAvailabilityManager {
return true;
} catch (Exception e) {
Exceptions.propagateIfFatal(e);
- // TODO maybe move to failure state? and record problems somewhere else for investigation subsequently.
- nodeState = ManagementNodeState.STANDBY;
- LOG.warn("Unable to promote "+ownNodeId+" to hot standby: "+e, e);
+ LOG.warn("Unable to promote "+ownNodeId+" to hot standby, switching to FAILED: "+e, e);
+ demoteToFailed();
return false;
}
}
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/a1d83eee/core/src/main/java/brooklyn/management/internal/AbstractManagementContext.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/management/internal/AbstractManagementContext.java b/core/src/main/java/brooklyn/management/internal/AbstractManagementContext.java
index b91e99b..4c6d7d9 100644
--- a/core/src/main/java/brooklyn/management/internal/AbstractManagementContext.java
+++ b/core/src/main/java/brooklyn/management/internal/AbstractManagementContext.java
@@ -33,6 +33,7 @@ import javax.annotation.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import brooklyn.basic.BrooklynObject;
import brooklyn.catalog.BrooklynCatalog;
import brooklyn.catalog.CatalogLoadMode;
import brooklyn.catalog.internal.BasicBrooklynCatalog;
@@ -405,4 +406,19 @@ public abstract class AbstractManagementContext implements ManagementContextInte
public Maybe<URI> getManagementNodeUri() {
return uri;
}
+
+
+ public BrooklynObject lookup(String id) {
+ return lookup(id, BrooklynObject.class);
+ }
+
+ @SuppressWarnings("unchecked")
+ public <T> T lookup(String id, Class<T> type) {
+ Object result;
+ result = getEntityManager().getEntity(id);
+ if (result!=null && type.isInstance(result)) return (T)result;
+ // TODO policies, etc
+ return null;
+ }
+
}
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/a1d83eee/core/src/main/java/brooklyn/management/internal/NonDeploymentManagementContext.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/management/internal/NonDeploymentManagementContext.java b/core/src/main/java/brooklyn/management/internal/NonDeploymentManagementContext.java
index 1738a9e..dbe50d9 100644
--- a/core/src/main/java/brooklyn/management/internal/NonDeploymentManagementContext.java
+++ b/core/src/main/java/brooklyn/management/internal/NonDeploymentManagementContext.java
@@ -34,6 +34,7 @@ import java.util.concurrent.TimeoutException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import brooklyn.basic.BrooklynObject;
import brooklyn.catalog.BrooklynCatalog;
import brooklyn.config.BrooklynProperties;
import brooklyn.config.StringConfigMap;
@@ -417,6 +418,18 @@ public class NonDeploymentManagementContext implements ManagementContextInternal
initialManagementContext.removePropertiesReloadListener(listener);
}
+ @Override
+ public BrooklynObject lookup(String id) {
+ checkInitialManagementContextReal();
+ return initialManagementContext.lookup(id);
+ }
+
+ @Override
+ public <T> T lookup(String id, Class<T> type) {
+ checkInitialManagementContextReal();
+ return initialManagementContext.lookup(id, type);
+ }
+
/**
* For when the initial management context is not "real"; the changeListener is a no-op, but everything else forbidden.
*
@@ -551,4 +564,5 @@ public class NonDeploymentManagementContext implements ManagementContextInternal
throw new IllegalStateException("Non-deployment context "+NonDeploymentManagementContext.this+" is not valid for this operation.");
}
}
+
}
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/a1d83eee/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 f7399d4..da1fd0a 100644
--- a/core/src/test/java/brooklyn/management/ha/HighAvailabilityManagerSplitBrainTest.java
+++ b/core/src/test/java/brooklyn/management/ha/HighAvailabilityManagerSplitBrainTest.java
@@ -187,6 +187,7 @@ public class HighAvailabilityManagerSplitBrainTest {
HaMgmtNode n1 = newNode();
HaMgmtNode n2 = newNode();
+ // first auto should become master
n1.ha.start(HighAvailabilityMode.AUTO);
ManagementPlaneSyncRecord memento1 = n1.ha.getManagementPlaneSyncState();
@@ -196,7 +197,8 @@ public class HighAvailabilityManagerSplitBrainTest {
assertEquals(memento1.getManagementNodes().get(n1.ownNodeId).getRemoteTimestamp(), time0);
assertEquals(memento1.getManagementNodes().get(n1.ownNodeId).getStatus(), ManagementNodeState.MASTER);
- n2.ha.start(HighAvailabilityMode.AUTO);
+ // second - make explicit hot; that's a strictly more complex case than cold standby, so provides pretty good coverage
+ n2.ha.start(HighAvailabilityMode.HOT_STANDBY);
ManagementPlaneSyncRecord memento2 = n2.ha.getManagementPlaneSyncState();
log.info(n2+" HA: "+memento2);
@@ -339,9 +341,9 @@ public class HighAvailabilityManagerSplitBrainTest {
timer.stop();
}
assertEquals(Collections.frequency(counts, ManagementNodeState.MASTER), 1);
- assertEquals(Collections.frequency(counts, ManagementNodeState.HOT_STANDBY), nodes.size()-1);
+ assertEquals(Collections.frequency(counts, ManagementNodeState.HOT_STANDBY)+Collections.frequency(counts, ManagementNodeState.STANDBY), nodes.size()-1);
assertEquals(Collections.frequency(savedCounts, ManagementNodeState.MASTER), 1);
- assertEquals(Collections.frequency(savedCounts, ManagementNodeState.HOT_STANDBY), nodes.size()-1);
+ assertEquals(Collections.frequency(savedCounts, ManagementNodeState.HOT_STANDBY)+Collections.frequency(savedCounts, ManagementNodeState.STANDBY), nodes.size()-1);
}});
} catch (Throwable t) {
log.warn("Failed to stabilize (rethrowing): "+t, t);
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/a1d83eee/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 c646f69..6fde3fa 100644
--- a/core/src/test/java/brooklyn/management/ha/HighAvailabilityManagerTestFixture.java
+++ b/core/src/test/java/brooklyn/management/ha/HighAvailabilityManagerTestFixture.java
@@ -216,7 +216,7 @@ public abstract class HighAvailabilityManagerTestFixture {
.setMaster("node1")
.build());
- manager.start(HighAvailabilityMode.AUTO);
+ manager.start(HighAvailabilityMode.HOT_STANDBY);
ManagementPlaneSyncRecord state = manager.getManagementPlaneSyncState();
assertEquals(state.getManagementNodes().get("node1").getStatus(), ManagementNodeState.MASTER);
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/a1d83eee/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 cfb6056..03eec67 100644
--- a/core/src/test/java/brooklyn/management/ha/HotStandbyTest.java
+++ b/core/src/test/java/brooklyn/management/ha/HotStandbyTest.java
@@ -32,7 +32,9 @@ import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;
import brooklyn.entity.Application;
+import brooklyn.entity.Entity;
import brooklyn.entity.basic.Entities;
+import brooklyn.entity.proxying.EntitySpec;
import brooklyn.entity.rebind.PersistenceExceptionHandlerImpl;
import brooklyn.entity.rebind.persister.BrooklynMementoPersisterToObjectStore;
import brooklyn.entity.rebind.persister.InMemoryObjectStore;
@@ -41,12 +43,16 @@ import brooklyn.entity.rebind.persister.PersistMode;
import brooklyn.entity.rebind.persister.PersistenceObjectStore;
import brooklyn.location.Location;
import brooklyn.management.internal.ManagementContextInternal;
+import brooklyn.test.EntityTestUtils;
import brooklyn.test.entity.LocalManagementContextForTests;
import brooklyn.test.entity.TestApplication;
import brooklyn.test.entity.TestEntity;
import brooklyn.util.collections.MutableList;
import brooklyn.util.collections.MutableMap;
import brooklyn.util.time.Duration;
+import brooklyn.util.time.Time;
+
+import com.google.common.collect.Iterables;
@Test
public class HotStandbyTest {
@@ -127,36 +133,146 @@ public class HotStandbyTest {
return new InMemoryObjectStore(sharedBackingStore, sharedBackingStoreDates);
}
- @Test
- public void testHotStandby() throws Exception {
+ private HaMgmtNode createMaster() throws Exception {
HaMgmtNode n1 = newNode();
n1.ha.start(HighAvailabilityMode.AUTO);
assertEquals(n1.ha.getNodeState(), ManagementNodeState.MASTER);
-
+ return n1;
+ }
+
+ private HaMgmtNode createHotStandby() throws Exception {
+ HaMgmtNode n2 = newNode();
+ n2.ha.start(HighAvailabilityMode.HOT_STANDBY);
+ assertEquals(n2.ha.getNodeState(), ManagementNodeState.HOT_STANDBY);
+ return n2;
+ }
+
+ private TestApplication createFirstAppAndPersist(HaMgmtNode n1) throws Exception {
TestApplication app = TestApplication.Factory.newManagedInstanceForTests(n1.mgmt);
+ app.setDisplayName("First App");
app.start(MutableList.<Location>of());
+ app.setConfig(TestEntity.CONF_NAME, "first-app");
app.setAttribute(TestEntity.SEQUENCE, 3);
n1.mgmt.getRebindManager().forcePersistNow();
-
- HaMgmtNode n2 = newNode();
- n2.ha.start(HighAvailabilityMode.AUTO);
- assertEquals(n2.ha.getNodeState(), ManagementNodeState.HOT_STANDBY);
+ return app;
+ }
+
+ private Application waitForRebindSequenceNumber(HaMgmtNode master, HaMgmtNode hotStandby, Application app, int expectedSensorSequenceValue) {
+ Time.sleep(Duration.FIVE_SECONDS);
+ Application appRO = hotStandby.mgmt.lookup(app.getId(), Application.class);
+ // TODO drop the sleep and the new lookup, when the implementation can do in-place replace
+
+ EntityTestUtils.assertAttributeEqualsEventually(appRO, TestEntity.SEQUENCE, expectedSensorSequenceValue);
+ return appRO;
+ }
+
+ @Test
+ public void testHotStandbySeesChangedNameConfigAndSensorValueButDoesntAllowChange() throws Exception {
+ HaMgmtNode n1 = createMaster();
+ TestApplication app = createFirstAppAndPersist(n1);
+ HaMgmtNode n2 = createHotStandby();
assertEquals(n2.mgmt.getApplications().size(), 1);
- Application app2 = n2.mgmt.getApplications().iterator().next();
- assertEquals(app2.getAttribute(TestEntity.SEQUENCE), (Integer)3);
+ Application appRO = n2.mgmt.lookup(app.getId(), Application.class);
+ Assert.assertNotNull(appRO);
+ Assert.assertTrue(appRO instanceof TestApplication);
+ assertEquals(appRO.getDisplayName(), "First App");
+ assertEquals(appRO.getConfig(TestEntity.CONF_NAME), "first-app");
+ assertEquals(appRO.getAttribute(TestEntity.SEQUENCE), (Integer)3);
try {
- ((TestApplication)app2).setAttribute(TestEntity.SEQUENCE, 4);
+ ((TestApplication)appRO).setAttribute(TestEntity.SEQUENCE, 4);
Assert.fail("Should not have allowed sensor to be set");
} catch (Exception e) {
Assert.assertTrue(e.toString().toLowerCase().contains("read-only"), "Error message did not contain expected text: "+e);
}
}
-
+
+ public void testHotStandbySeesChangedNameConfigAndSensorValue() throws Exception {
+ HaMgmtNode n1 = createMaster();
+ TestApplication app = createFirstAppAndPersist(n1);
+ HaMgmtNode n2 = createHotStandby();
+
+ assertEquals(n2.mgmt.getApplications().size(), 1);
+ Application appRO = n2.mgmt.lookup(app.getId(), Application.class);
+ Assert.assertNotNull(appRO);
+ assertEquals(appRO.getChildren().size(), 0);
+
+ // test changes
+
+ app.setDisplayName("First App Renamed");
+ app.setConfig(TestEntity.CONF_NAME, "first-app-renamed");
+ app.setAttribute(TestEntity.SEQUENCE, 4);
+
+ appRO = waitForRebindSequenceNumber(n1, n2, app, 4);
+ assertEquals(n2.mgmt.getEntityManager().getEntities().size(), 1);
+ assertEquals(appRO.getDisplayName(), "First App Renamed");
+ assertEquals(appRO.getConfig(TestEntity.CONF_NAME), "first-app-renamed");
+
+ // and change again for good measure!
+
+ app.setDisplayName("First App");
+ app.setConfig(TestEntity.CONF_NAME, "first-app-restored");
+ app.setAttribute(TestEntity.SEQUENCE, 5);
+
+ appRO = waitForRebindSequenceNumber(n1, n2, app, 4);
+ assertEquals(n2.mgmt.getEntityManager().getEntities().size(), 1);
+ assertEquals(appRO.getDisplayName(), "First App");
+ assertEquals(appRO.getConfig(TestEntity.CONF_NAME), "first-app-restored");
+ }
+
+
@Test(groups="Integration", invocationCount=50)
- public void testHotStandbyManyTimes() throws Exception {
- testHotStandby();
+ public void testHotStandbySeesAdditionAndRemovalManyTimes() throws Exception {
+ testHotStandbySeesStructuralChangesIncludingRemoval();
}
+
+ @Test(groups="Integration") // due to time (could speed up by forcing persist and rebind)
+ public void testHotStandbySeesStructuralChangesIncludingRemoval() throws Exception {
+ HaMgmtNode n1 = createMaster();
+ TestApplication app = createFirstAppAndPersist(n1);
+ HaMgmtNode n2 = createHotStandby();
+
+ assertEquals(n2.mgmt.getApplications().size(), 1);
+ Application appRO = n2.mgmt.lookup(app.getId(), Application.class);
+ Assert.assertNotNull(appRO);
+ assertEquals(appRO.getChildren().size(), 0);
+
+ // test additions - new child, new app
+
+ TestEntity child = app.addChild(EntitySpec.create(TestEntity.class).configure(TestEntity.CONF_NAME, "first-child"));
+ TestApplication app2 = TestApplication.Factory.newManagedInstanceForTests(n1.mgmt);
+
+ app.setAttribute(TestEntity.SEQUENCE, 4);
+ appRO = waitForRebindSequenceNumber(n1, n2, app, 4);
+
+ assertEquals(appRO.getChildren().size(), 1);
+ Entity childRO = Iterables.getOnlyElement(appRO.getChildren());
+ assertEquals(childRO.getId(), child.getId());
+ assertEquals(childRO.getConfig(TestEntity.CONF_NAME), "first-child");
+
+ assertEquals(n2.mgmt.getApplications().size(), 2);
+ Application app2RO = n2.mgmt.lookup(app2.getId(), Application.class);
+ Assert.assertNotNull(app2RO);
+ assertEquals(app2RO.getConfig(TestEntity.CONF_NAME), "second-app");
+
+ assertEquals(n2.mgmt.getEntityManager().getEntities().size(), 3);
+
+ // now test removals
+
+ Entities.unmanage(child);
+ Entities.unmanage(app2);
+
+ app.setAttribute(TestEntity.SEQUENCE, 5);
+ appRO = waitForRebindSequenceNumber(n1, n2, app, 5);
+
+ EntityTestUtils.assertAttributeEqualsEventually(appRO, TestEntity.SEQUENCE, 5);
+ assertEquals(n2.mgmt.getEntityManager().getEntities().size(), 1);
+ assertEquals(appRO.getChildren().size(), 0);
+ assertEquals(n2.mgmt.getApplications().size(), 1);
+ Assert.assertNull(n2.mgmt.lookup(app2.getId(), Application.class));
+ Assert.assertNull(n2.mgmt.lookup(child.getId(), Application.class));
+ }
+
}
[15/15] git commit: This closes #205
Posted by he...@apache.org.
This closes #205
Project: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/commit/ff11b4fc
Tree: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/tree/ff11b4fc
Diff: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/diff/ff11b4fc
Branch: refs/heads/master
Commit: ff11b4fc0ffd1f508985de565adbfe0835d06cf4
Parents: 1f2a378 6971809
Author: Alex Heneveld <al...@cloudsoftcorp.com>
Authored: Wed Oct 1 18:33:40 2014 +0100
Committer: Alex Heneveld <al...@cloudsoftcorp.com>
Committed: Wed Oct 1 18:33:40 2014 +0100
----------------------------------------------------------------------
usage/jsgui/src/main/webapp/assets/tpl/help/page.html | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
----------------------------------------------------------------------
[05/15] git commit: hot standby / read-only rebind is working. more
to do, but this is the guts of it.
Posted by he...@apache.org.
hot standby / read-only rebind is working. more to do, but this is the guts of it.
Project: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/commit/3a8a33a8
Tree: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/tree/3a8a33a8
Diff: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/diff/3a8a33a8
Branch: refs/heads/master
Commit: 3a8a33a8346736f7b44dc71e4355168c7fdb06fc
Parents: ea4b812
Author: Alex Heneveld <al...@cloudsoftcorp.com>
Authored: Mon Sep 29 16:07:08 2014 +0100
Committer: Alex Heneveld <al...@cloudsoftcorp.com>
Committed: Wed Oct 1 16:40:15 2014 +0100
----------------------------------------------------------------------
.../brooklyn/entity/rebind/RebindContext.java | 4 +
.../brooklyn/entity/rebind/RebindSupport.java | 2 +-
.../brooklyn/enricher/basic/Transformer.java | 1 +
.../brooklyn/entity/basic/AbstractEntity.java | 9 +-
.../java/brooklyn/entity/basic/Entities.java | 13 +-
.../brooklyn/entity/basic/EntityInternal.java | 6 +-
.../entity/basic/EntityReadOnlyInternal.java | 102 +++++++++++++
.../entity/basic/ServiceStateLogic.java | 6 +-
.../entity/proxying/EntityProxyImpl.java | 43 +++++-
.../entity/proxying/InternalEntityFactory.java | 30 +++-
.../AbstractBrooklynObjectRebindSupport.java | 3 +-
.../entity/rebind/RebindContextImpl.java | 12 ++
.../rebind/RebindExceptionHandlerImpl.java | 5 +
.../entity/rebind/RebindManagerImpl.java | 38 +++--
.../BrooklynMementoPersisterToObjectStore.java | 8 +-
.../brooklyn/event/basic/BasicSensorEvent.java | 20 ++-
.../internal/EntityManagementSupport.java | 23 +++
.../internal/EntityManagerInternal.java | 11 ++
.../management/internal/LocalEntityManager.java | 77 ++++++----
.../internal/NonDeploymentEntityManager.java | 11 ++
.../NonDeploymentManagementContext.java | 11 +-
.../brooklyn/management/ha/HotStandbyTest.java | 20 ++-
.../brooklyn/management/ha/WarmStandbyTest.java | 149 +++++++++++++++++++
.../entity/proxy/nginx/UrlMappingImpl.java | 7 +-
.../brooklyn/entity/proxy/UrlMappingTest.java | 8 +
25 files changed, 553 insertions(+), 66 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/3a8a33a8/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 e3f7cf9..1ddd178 100644
--- a/api/src/main/java/brooklyn/entity/rebind/RebindContext.java
+++ b/api/src/main/java/brooklyn/entity/rebind/RebindContext.java
@@ -18,6 +18,7 @@
*/
package brooklyn.entity.rebind;
+import brooklyn.basic.BrooklynObject;
import brooklyn.catalog.CatalogItem;
import brooklyn.entity.Entity;
import brooklyn.entity.Feed;
@@ -51,4 +52,7 @@ public interface RebindContext {
Class<?> loadClass(String typeName) throws ClassNotFoundException;
RebindExceptionHandler getExceptionHandler();
+
+ boolean isReadOnly(BrooklynObject item);
+
}
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/3a8a33a8/api/src/main/java/brooklyn/entity/rebind/RebindSupport.java
----------------------------------------------------------------------
diff --git a/api/src/main/java/brooklyn/entity/rebind/RebindSupport.java b/api/src/main/java/brooklyn/entity/rebind/RebindSupport.java
index 435fbc6..878e93a 100644
--- a/api/src/main/java/brooklyn/entity/rebind/RebindSupport.java
+++ b/api/src/main/java/brooklyn/entity/rebind/RebindSupport.java
@@ -45,7 +45,7 @@ public interface RebindSupport<T extends Memento> {
* Implementations should be very careful to not invoke or inspect these other entities/locations,
* as they may also be being reconstructed at this time.
*
- * Called before rebind.
+ * Called during rebind, after creation and before the call to start management.
*/
void reconstruct(RebindContext rebindContext, T memento);
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/3a8a33a8/core/src/main/java/brooklyn/enricher/basic/Transformer.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/enricher/basic/Transformer.java b/core/src/main/java/brooklyn/enricher/basic/Transformer.java
index 71770d8..d8244c3 100644
--- a/core/src/main/java/brooklyn/enricher/basic/Transformer.java
+++ b/core/src/main/java/brooklyn/enricher/basic/Transformer.java
@@ -72,6 +72,7 @@ public class Transformer<T,U> extends AbstractEnricher implements SensorEventLis
if (transformationFromEvent != null) {
transformation = transformationFromEvent;
} else {
+ // TODO new named class
transformation = new Function<SensorEvent<T>, U>() {
@Override public U apply(SensorEvent<T> input) {
return transformationFromValue.apply(input.getValue());
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/3a8a33a8/core/src/main/java/brooklyn/entity/basic/AbstractEntity.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/entity/basic/AbstractEntity.java b/core/src/main/java/brooklyn/entity/basic/AbstractEntity.java
index 92ae2c2..3c3c65f 100644
--- a/core/src/main/java/brooklyn/entity/basic/AbstractEntity.java
+++ b/core/src/main/java/brooklyn/entity/basic/AbstractEntity.java
@@ -676,7 +676,10 @@ public abstract class AbstractEntity extends AbstractBrooklynObject implements E
}
@Override
- public synchronized ManagementContext getManagementContext() {
+ public ManagementContext getManagementContext() {
+ // NB Sept 2014 - removed synch keyword above due to deadlock;
+ // it also synchs in ManagementSupport..getManagementContext();
+ // no apparent reason why it was here also. @aledsage can you review?
return getManagementSupport().getManagementContext();
}
@@ -1423,7 +1426,7 @@ public abstract class AbstractEntity extends AbstractBrooklynObject implements E
@Override
protected void finalize() throws Throwable {
super.finalize();
- if (!getManagementSupport().wasDeployed())
- LOG.warn("Entity "+this+" was never deployed -- explicit call to manage(Entity) required.");
+ if (!getManagementSupport().wasDeployed() && !Boolean.TRUE.equals(getManagementSupport().isReadOnly()))
+ LOG.warn("Entity "+this+" ("+Integer.toHexString(System.identityHashCode(this))+") was never deployed -- explicit call to manage(Entity) required.");
}
}
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/3a8a33a8/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 9a75ab2..aa669db 100644
--- a/core/src/main/java/brooklyn/entity/basic/Entities.java
+++ b/core/src/main/java/brooklyn/entity/basic/Entities.java
@@ -67,6 +67,7 @@ import brooklyn.management.Task;
import brooklyn.management.TaskAdaptable;
import brooklyn.management.TaskFactory;
import brooklyn.management.internal.EffectorUtils;
+import brooklyn.management.internal.EntityManagerInternal;
import brooklyn.management.internal.LocalManagementContext;
import brooklyn.management.internal.ManagementContextInternal;
import brooklyn.management.internal.NonDeploymentManagementContext;
@@ -657,8 +658,10 @@ public class Entities {
*/
public static void destroy(Entity e) {
if (isManaged(e)) {
- if (e instanceof Startable) Entities.invokeEffector((EntityLocal)e, e, Startable.STOP).getUnchecked();
- if (e instanceof EntityInternal) ((EntityInternal)e).destroy();
+ if (!isReadOnly(e)) {
+ if (e instanceof Startable) Entities.invokeEffector((EntityLocal)e, e, Startable.STOP).getUnchecked();
+ if (e instanceof EntityInternal) ((EntityInternal)e).destroy();
+ }
unmanage(e);
log.debug("destroyed and unmanaged "+e+"; mgmt now "+
(e.getApplicationId()==null ? "(no app)" : e.getApplication().getManagementContext())+" - managed? "+isManaged(e));
@@ -747,6 +750,12 @@ public class Entities {
return ((EntityInternal)e).getManagementSupport().isNoLongerManaged();
}
+ /** as {@link EntityManagerInternal#isReadOnly(Entity)} */
+ @Beta
+ public static Boolean isReadOnly(Entity e) {
+ return ((EntityInternal)e).getManagementSupport().isReadOnly();
+ }
+
/**
* Brings this entity under management only if its ancestor is managed.
* <p>
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/3a8a33a8/core/src/main/java/brooklyn/entity/basic/EntityInternal.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/entity/basic/EntityInternal.java b/core/src/main/java/brooklyn/entity/basic/EntityInternal.java
index ab1fd59..43413e0 100644
--- a/core/src/main/java/brooklyn/entity/basic/EntityInternal.java
+++ b/core/src/main/java/brooklyn/entity/basic/EntityInternal.java
@@ -80,13 +80,13 @@ public interface EntityInternal extends BrooklynObjectInternal, EntityLocal, Reb
ConfigBag getLocalConfigBag();
@Beta
- public Map<AttributeSensor, Object> getAllAttributes();
+ Map<AttributeSensor, Object> getAllAttributes();
@Beta
- public void removeAttribute(AttributeSensor<?> attribute);
+ void removeAttribute(AttributeSensor<?> attribute);
@Beta
- public void refreshInheritedConfig();
+ void refreshInheritedConfig();
/**
* Must be called before the entity is started.
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/3a8a33a8/core/src/main/java/brooklyn/entity/basic/EntityReadOnlyInternal.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/entity/basic/EntityReadOnlyInternal.java b/core/src/main/java/brooklyn/entity/basic/EntityReadOnlyInternal.java
new file mode 100644
index 0000000..6261a4d
--- /dev/null
+++ b/core/src/main/java/brooklyn/entity/basic/EntityReadOnlyInternal.java
@@ -0,0 +1,102 @@
+/*
+ * 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.entity.basic;
+
+import java.util.Collection;
+import java.util.Map;
+import java.util.Set;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import brooklyn.basic.BrooklynObjectInternal;
+import brooklyn.config.ConfigKey;
+import brooklyn.config.ConfigKey.HasConfigKey;
+import brooklyn.entity.Application;
+import brooklyn.entity.Effector;
+import brooklyn.entity.Entity;
+import brooklyn.entity.EntityType;
+import brooklyn.entity.Group;
+import brooklyn.entity.basic.EntityInternal.FeedSupport;
+import brooklyn.entity.rebind.RebindSupport;
+import brooklyn.entity.rebind.Rebindable;
+import brooklyn.event.AttributeSensor;
+import brooklyn.location.Location;
+import brooklyn.management.ManagementContext;
+import brooklyn.management.internal.EntityManagementSupport;
+import brooklyn.mementos.EntityMemento;
+import brooklyn.policy.Enricher;
+import brooklyn.policy.Policy;
+import brooklyn.util.config.ConfigBag;
+import brooklyn.util.guava.Maybe;
+
+import com.google.common.annotations.Beta;
+
+/**
+ * Selected methods from {@link EntityInternal} and parents which are permitted
+ * in read-only mode.
+ */
+@Beta
+public interface EntityReadOnlyInternal {
+
+ // from Entity
+
+ String getId();
+ long getCreationTime();
+ String getDisplayName();
+ @Nullable String getIconUrl();
+ EntityType getEntityType();
+ Application getApplication();
+ String getApplicationId();
+ Entity getParent();
+ Collection<Entity> getChildren();
+ Collection<Policy> getPolicies();
+ Collection<Enricher> getEnrichers();
+ Collection<Group> getGroups();
+ Collection<Location> getLocations();
+ <T> T getAttribute(AttributeSensor<T> sensor);
+ <T> T getConfig(ConfigKey<T> key);
+ <T> T getConfig(HasConfigKey<T> key);
+ Maybe<Object> getConfigRaw(ConfigKey<?> key, boolean includeInherited);
+ Maybe<Object> getConfigRaw(HasConfigKey<?> key, boolean includeInherited);
+ @Deprecated Set<Object> getTags();
+ @Deprecated boolean containsTag(@Nonnull Object tag);
+
+
+ // from entity local
+
+ void setDisplayName(String displayName);
+ @Deprecated <T> T getConfig(ConfigKey<T> key, T defaultValue);
+ @Deprecated <T> T getConfig(HasConfigKey<T> key, T defaultValue);
+
+ // from EntityInternal:
+
+ EntityConfigMap getConfigMap();
+ Map<ConfigKey<?>,Object> getAllConfig();
+ // for rebind mainly
+ ConfigBag getAllConfigBag();
+ ConfigBag getLocalConfigBag();
+ Map<AttributeSensor, Object> getAllAttributes();
+ EntityManagementSupport getManagementSupport();
+ ManagementContext getManagementContext();
+ Effector<?> getEffector(String effectorName);
+ FeedSupport getFeedSupport();
+ RebindSupport<EntityMemento> getRebindSupport();
+
+}
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/3a8a33a8/core/src/main/java/brooklyn/entity/basic/ServiceStateLogic.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/entity/basic/ServiceStateLogic.java b/core/src/main/java/brooklyn/entity/basic/ServiceStateLogic.java
index 3510121..2ba57b4 100644
--- a/core/src/main/java/brooklyn/entity/basic/ServiceStateLogic.java
+++ b/core/src/main/java/brooklyn/entity/basic/ServiceStateLogic.java
@@ -90,8 +90,6 @@ public class ServiceStateLogic {
public static <TKey,TVal> void updateMapSensorEntry(EntityLocal entity, AttributeSensor<Map<TKey,TVal>> sensor, TKey key, TVal v) {
Map<TKey, TVal> map = entity.getAttribute(sensor);
- // TODO synchronize
-
boolean created = (map==null);
if (created) map = MutableMap.of();
@@ -109,8 +107,10 @@ public class ServiceStateLogic {
if (changed)
map.put(key, (TVal)v);
}
- if (changed || created)
+ if (changed || created) {
+ // TODO synchronize; then emit a copy to prevent CME's e.g. UrlMappingTest
entity.setAttribute(sensor, map);
+ }
}
public static void setExpectedState(Entity entity, Lifecycle state) {
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/3a8a33a8/core/src/main/java/brooklyn/entity/proxying/EntityProxyImpl.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/entity/proxying/EntityProxyImpl.java b/core/src/main/java/brooklyn/entity/proxying/EntityProxyImpl.java
index e46047b..7cc5793 100644
--- a/core/src/main/java/brooklyn/entity/proxying/EntityProxyImpl.java
+++ b/core/src/main/java/brooklyn/entity/proxying/EntityProxyImpl.java
@@ -22,6 +22,7 @@ import static com.google.common.base.Preconditions.checkNotNull;
import java.lang.reflect.Method;
import java.util.Arrays;
+import java.util.LinkedHashSet;
import java.util.Set;
import org.slf4j.Logger;
@@ -32,7 +33,10 @@ import brooklyn.entity.Entity;
import brooklyn.entity.basic.AbstractEntity;
import brooklyn.entity.basic.EntityInternal;
import brooklyn.entity.basic.EntityLocal;
+import brooklyn.entity.basic.EntityReadOnlyInternal;
+import brooklyn.management.ManagementContext;
import brooklyn.management.internal.EffectorUtils;
+import brooklyn.management.internal.EntityManagerInternal;
import com.google.common.base.Objects;
import com.google.common.collect.Sets;
@@ -53,6 +57,7 @@ public class EntityProxyImpl implements java.lang.reflect.InvocationHandler {
private static final Logger LOG = LoggerFactory.getLogger(EntityProxyImpl.class);
private final Entity delegate;
+ private Boolean isMaster;
private static final Set<MethodSignature> OBJECT_METHODS = Sets.newLinkedHashSet();
static {
@@ -60,7 +65,7 @@ public class EntityProxyImpl implements java.lang.reflect.InvocationHandler {
OBJECT_METHODS.add(new MethodSignature(m));
}
}
-
+
private static final Set<MethodSignature> ENTITY_NON_EFFECTOR_METHODS = Sets.newLinkedHashSet();
static {
for (Method m : Entity.class.getMethods()) {
@@ -74,6 +79,19 @@ public class EntityProxyImpl implements java.lang.reflect.InvocationHandler {
}
}
+ private static final Set<MethodSignature> ENTITY_PERMITTED_READ_ONLY_METHODS = Sets.newLinkedHashSet();
+ static {
+ for (Method m : EntityReadOnlyInternal.class.getMethods()) {
+ ENTITY_PERMITTED_READ_ONLY_METHODS.add(new MethodSignature(m));
+ }
+ if (!ENTITY_NON_EFFECTOR_METHODS.containsAll(ENTITY_PERMITTED_READ_ONLY_METHODS)) {
+ Set<MethodSignature> extras = new LinkedHashSet<EntityProxyImpl.MethodSignature>(ENTITY_PERMITTED_READ_ONLY_METHODS);
+ extras.removeAll(ENTITY_NON_EFFECTOR_METHODS);
+ throw new IllegalStateException("Entity read-only methods contains items not known as Entity methods: "+
+ extras);
+ }
+ }
+
public EntityProxyImpl(Entity entity) {
this.delegate = checkNotNull(entity, "entity");
}
@@ -83,6 +101,15 @@ public class EntityProxyImpl implements java.lang.reflect.InvocationHandler {
return delegate.toString();
}
+ protected boolean isMaster() {
+ if (isMaster!=null) return isMaster;
+ ManagementContext mgmt = ((EntityInternal)delegate).getManagementContext();
+ Boolean isReadOnlyNow = ((EntityManagerInternal)mgmt.getEntityManager()).isReadOnly(delegate);
+ if (isReadOnlyNow==null) return false;
+ isMaster = !isReadOnlyNow;
+ return isMaster;
+ }
+
public Object invoke(Object proxy, final Method m, final Object[] args) throws Throwable {
if (proxy == null) {
throw new IllegalArgumentException("Static methods not supported via proxy on entity "+delegate);
@@ -93,6 +120,15 @@ public class EntityProxyImpl implements java.lang.reflect.InvocationHandler {
Object result;
if (OBJECT_METHODS.contains(sig)) {
result = m.invoke(this, args);
+ } else if (!isMaster()) {
+ if (ENTITY_PERMITTED_READ_ONLY_METHODS.contains(sig)) {
+ result = m.invoke(delegate, args);
+ } else if (isMaster==null) {
+ // rebinding or caller manipulating before management; permit all access
+ result = m.invoke(delegate, args);
+ } else {
+ throw new UnsupportedOperationException("Call to '"+sig+"' not permitted on read-only entity "+delegate);
+ }
} else if (ENTITY_NON_EFFECTOR_METHODS.contains(sig)) {
result = m.invoke(delegate, args);
} else {
@@ -139,6 +175,11 @@ public class EntityProxyImpl implements java.lang.reflect.InvocationHandler {
MethodSignature o = (MethodSignature) obj;
return name.equals(o.name) && Arrays.equals(parameterTypes, o.parameterTypes);
}
+
+ @Override
+ public String toString() {
+ return name+Arrays.toString(parameterTypes);
+ }
}
@Override
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/3a8a33a8/core/src/main/java/brooklyn/entity/proxying/InternalEntityFactory.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/entity/proxying/InternalEntityFactory.java b/core/src/main/java/brooklyn/entity/proxying/InternalEntityFactory.java
index 78fa574..c2e21e1 100644
--- a/core/src/main/java/brooklyn/entity/proxying/InternalEntityFactory.java
+++ b/core/src/main/java/brooklyn/entity/proxying/InternalEntityFactory.java
@@ -35,6 +35,7 @@ import brooklyn.entity.Group;
import brooklyn.entity.basic.AbstractApplication;
import brooklyn.entity.basic.AbstractEntity;
import brooklyn.entity.basic.BrooklynTaskTags;
+import brooklyn.entity.basic.Entities;
import brooklyn.entity.basic.EntityInternal;
import brooklyn.entity.basic.EntityLocal;
import brooklyn.management.internal.LocalEntityManager;
@@ -51,6 +52,7 @@ import brooklyn.util.flags.FlagUtils;
import brooklyn.util.javalang.AggregateClassLoader;
import brooklyn.util.task.Tasks;
+import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Iterables;
import com.google.common.collect.Sets;
@@ -83,6 +85,7 @@ public class InternalEntityFactory extends InternalFactory {
this.policyFactory = checkNotNull(policyFactory, "policyFactory");
}
+ @VisibleForTesting
public <T extends Entity> T createEntityProxy(EntitySpec<T> spec, T entity) {
Set<Class<?>> interfaces = Sets.newLinkedHashSet();
if (spec.getType().isInterface()) {
@@ -96,9 +99,10 @@ public class InternalEntityFactory extends InternalFactory {
}
@SuppressWarnings("unchecked")
- public <T extends Entity> T createEntityProxy(Iterable<Class<?>> interfaces, T entity) {
- // TODO Don't want the proxy to have to implement EntityLocal, but required by how
- // AbstractEntity.parent is used (e.g. parent.getAllConfig)
+ protected <T extends Entity> T createEntityProxy(Iterable<Class<?>> interfaces, T entity) {
+ // We don't especially want the proxy to have to implement EntityLocal,
+ // but required by how AbstractEntity.parent is used (e.g. parent.getAllConfig).
+ // However within EntityProxyImpl we place add'l guards to prevent read-only access to such methods
Set<Class<?>> allInterfaces = MutableSet.<Class<?>>builder()
.add(EntityProxy.class, Entity.class, EntityLocal.class, EntityInternal.class)
.addAll(interfaces)
@@ -149,6 +153,12 @@ public class InternalEntityFactory extends InternalFactory {
}
}
+ /** creates a new entity instance from a spec, with all children, policies, etc,
+ * fully initialized ({@link AbstractEntity#init()} invoked) and ready for
+ * management -- commonly the caller will next call
+ * {@link Entities#manage(Entity)} (if it's in a managed application)
+ * or {@link Entities#startManagement(brooklyn.entity.Application, brooklyn.management.ManagementContext)}
+ * (if it's an application) */
public <T extends Entity> T createEntity(EntitySpec<T> spec) {
/* Order is important here. Changed Jul 2014 when supporting children in spec.
* (Previously was much simpler, and parent was set right after running initializers; and there were no children.)
@@ -317,9 +327,16 @@ public class InternalEntityFactory extends InternalFactory {
}
/**
- * Constructs an entity (if new-style, calls no-arg constructor; if old-style, uses spec to pass in config).
- * Sets the entity's proxy. If {@link EntitySpec#id(String)} was set then uses that to override the entity's id,
+ * Constructs an entity, i.e. instantiate the actual class given a spec,
+ * and sets the entity's proxy. Used by this factory to {@link #createEntity(EntitySpec)}
+ * and also used during rebind.
+ * <p>
+ * If {@link EntitySpec#id(String)} was set then uses that to override the entity's id,
* but that behaviour is deprecated.
+ * <p>
+ * The new-style no-arg constructor is preferred, and
+ * configuration from the {@link EntitySpec} is <b>not</b> normally applied,
+ * although for old-style entities flags from the spec are passed to the constructor.
*/
public <T extends Entity> T constructEntity(Class<? extends T> clazz, EntitySpec<T> spec) {
@SuppressWarnings("deprecation")
@@ -331,7 +348,10 @@ public class InternalEntityFactory extends InternalFactory {
/**
* Constructs a new-style entity (fails if no no-arg constructor).
* Sets the entity's id and proxy.
+ * <p>
+ * As {@link #constructEntity(Class, EntitySpec)} but when no spec is used.
*/
+ // TODO would it be cleaner to have callers just create a spec? and deprecate this?
public <T extends Entity> T constructEntity(Class<T> clazz, Iterable<Class<?>> interfaces, String entityId) {
if (!isNewStyle(clazz)) {
throw new IllegalStateException("Cannot construct old-style entity "+clazz);
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/3a8a33a8/core/src/main/java/brooklyn/entity/rebind/AbstractBrooklynObjectRebindSupport.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/entity/rebind/AbstractBrooklynObjectRebindSupport.java b/core/src/main/java/brooklyn/entity/rebind/AbstractBrooklynObjectRebindSupport.java
index ffe92db..64d7cee 100644
--- a/core/src/main/java/brooklyn/entity/rebind/AbstractBrooklynObjectRebindSupport.java
+++ b/core/src/main/java/brooklyn/entity/rebind/AbstractBrooklynObjectRebindSupport.java
@@ -53,7 +53,8 @@ public abstract class AbstractBrooklynObjectRebindSupport<T extends Memento> imp
addCustoms(rebindContext, memento);
doReconstruct(rebindContext, memento);
- instance.rebind();
+ if (!rebindContext.isReadOnly(instance))
+ instance.rebind();
}
protected abstract void addConfig(RebindContext rebindContext, T memento);
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/3a8a33a8/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 97269c2..bcac4a7 100644
--- a/core/src/main/java/brooklyn/entity/rebind/RebindContextImpl.java
+++ b/core/src/main/java/brooklyn/entity/rebind/RebindContextImpl.java
@@ -23,6 +23,7 @@ import static com.google.common.base.Preconditions.checkNotNull;
import java.util.Collection;
import java.util.Map;
+import brooklyn.basic.BrooklynObject;
import brooklyn.catalog.CatalogItem;
import brooklyn.entity.Entity;
import brooklyn.entity.Feed;
@@ -44,6 +45,8 @@ public class RebindContextImpl implements RebindContext {
private final ClassLoader classLoader;
private final RebindExceptionHandler exceptionHandler;
+ private boolean allAreReadOnly = false;
+
public RebindContextImpl(RebindExceptionHandler exceptionHandler, ClassLoader classLoader) {
this.exceptionHandler = checkNotNull(exceptionHandler, "exceptionHandler");
this.classLoader = checkNotNull(classLoader, "classLoader");
@@ -152,4 +155,13 @@ public class RebindContextImpl implements RebindContext {
protected Collection<CatalogItem<?, ?>> getCatalogItems() {
return catalogItems.values();
}
+
+ public void setAllReadOnly() {
+ allAreReadOnly = true;
+ }
+
+ public boolean isReadOnly(BrooklynObject item) {
+ return allAreReadOnly;
+ }
+
}
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/3a8a33a8/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 de37f42..954d95b 100644
--- a/core/src/main/java/brooklyn/entity/rebind/RebindExceptionHandlerImpl.java
+++ b/core/src/main/java/brooklyn/entity/rebind/RebindExceptionHandlerImpl.java
@@ -332,6 +332,11 @@ public class RebindExceptionHandlerImpl implements RebindExceptionHandler {
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 interruptiong while not sleeping, due to secondary error rebinding: "+errmsg+"/"+e));
+ }
LOG.warn("Rebind: continuing after "+errmsg, e);
}
}
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/3a8a33a8/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 47f3770..cddb43f 100644
--- a/core/src/main/java/brooklyn/entity/rebind/RebindManagerImpl.java
+++ b/core/src/main/java/brooklyn/entity/rebind/RebindManagerImpl.java
@@ -45,7 +45,6 @@ import brooklyn.entity.Feed;
import brooklyn.entity.basic.AbstractApplication;
import brooklyn.entity.basic.AbstractEntity;
import brooklyn.entity.basic.ConfigKeys;
-import brooklyn.entity.basic.Entities;
import brooklyn.entity.basic.EntityInternal;
import brooklyn.entity.proxying.InternalEntityFactory;
import brooklyn.entity.proxying.InternalFactory;
@@ -61,6 +60,7 @@ import brooklyn.management.ExecutionContext;
import brooklyn.management.Task;
import brooklyn.management.ha.HighAvailabilityManagerImpl;
import brooklyn.management.ha.ManagementNodeState;
+import brooklyn.management.internal.EntityManagerInternal;
import brooklyn.management.internal.ManagementContextInternal;
import brooklyn.mementos.BrooklynMemento;
import brooklyn.mementos.BrooklynMementoManifest;
@@ -85,6 +85,7 @@ import brooklyn.util.javalang.Reflections;
import brooklyn.util.task.BasicExecutionContext;
import brooklyn.util.task.BasicTask;
import brooklyn.util.task.ScheduledTask;
+import brooklyn.util.task.Tasks;
import brooklyn.util.text.Strings;
import brooklyn.util.time.Duration;
@@ -308,6 +309,10 @@ public class RebindManagerImpl implements RebindManager {
if (readOnlyTask!=null) {
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, mgmt "+managementContext.getManagementNodeId());
@@ -440,11 +445,9 @@ public class RebindManagerImpl implements RebindManager {
throw exceptionHandler.onFailed(e);
}
}
-
+
protected List<Application> rebindImpl(final ClassLoader classLoader, final RebindExceptionHandler exceptionHandler, ManagementNodeState mode) throws IOException {
checkNotNull(classLoader, "classLoader");
- boolean isReadOnly = mode==ManagementNodeState.HOT_STANDBY;
- if (!isReadOnly) Preconditions.checkState(mode==ManagementNodeState.MASTER, "Must be either master or read only to rebind (mode "+mode+")");
RebindTracker.setRebinding();
try {
@@ -452,6 +455,12 @@ public class RebindManagerImpl implements RebindManager {
Reflections reflections = new Reflections(classLoader);
RebindContextImpl rebindContext = new RebindContextImpl(exceptionHandler, classLoader);
+ if (mode==ManagementNodeState.HOT_STANDBY) {
+ rebindContext.setAllReadOnly();
+ } else {
+ Preconditions.checkState(mode==ManagementNodeState.MASTER, "Must be either master or read only to rebind (mode "+mode+")");
+ }
+
LookupContext realLookupContext = new RebindContextLookupContext(managementContext, rebindContext, exceptionHandler);
// Mutli-phase deserialization.
@@ -477,9 +486,15 @@ public class RebindManagerImpl implements RebindManager {
boolean isEmpty = mementoManifest.isEmpty();
if (!isEmpty) {
- LOG.info("Rebinding from "+getPersister().getBackingStoreDescription()+"...");
+ if (mode==ManagementNodeState.HOT_STANDBY)
+ LOG.debug("Rebinding (read-only) from "+getPersister().getBackingStoreDescription()+"...");
+ else
+ LOG.info("Rebinding from "+getPersister().getBackingStoreDescription()+"...");
} else {
- LOG.info("Rebind check: no existing state; will persist new items to "+getPersister().getBackingStoreDescription());
+ if (mode==ManagementNodeState.HOT_STANDBY)
+ LOG.debug("Rebind check (read-only): no existing state, reading from "+getPersister().getBackingStoreDescription());
+ else
+ LOG.info("Rebind check: no existing state; will persist new items to "+getPersister().getBackingStoreDescription());
}
@@ -511,6 +526,9 @@ public class RebindManagerImpl implements RebindManager {
try {
Entity entity = newEntity(entityId, entityType, reflections);
+ if (rebindContext.isReadOnly(entity)) {
+ ((EntityInternal)entity).getManagementSupport().setReadOnly();
+ }
rebindContext.registerEntity(entityId, entity);
} catch (Exception e) {
exceptionHandler.onCreateFailed(BrooklynObjectType.ENTITY, entityId, entityType, e);
@@ -774,8 +792,10 @@ public class RebindManagerImpl implements RebindManager {
exceptionHandler.onNotFound(BrooklynObjectType.ENTITY, appId);
} else {
try {
- // TODO if read only we must do something special here!
- Entities.startManagement((Application)entity, managementContext);
+ if (rebindContext.isReadOnly(entity)) {
+ ((EntityManagerInternal)managementContext.getEntityManager()).setReadOnly(entity);
+ }
+ managementContext.getEntityManager().manage(entity);
} catch (Exception e) {
exceptionHandler.onManageFailed(BrooklynObjectType.ENTITY, entity, e);
}
@@ -816,7 +836,7 @@ public class RebindManagerImpl implements RebindManager {
exceptionHandler.onDone();
- if (!isEmpty) {
+ if (!isEmpty && mode!=ManagementNodeState.HOT_STANDBY) {
LOG.info("Rebind complete: {} app{}, {} entit{}, {} location{}, {} polic{}, {} enricher{}, {} feed{}, {} catalog item{}", new Object[]{
apps.size(), Strings.s(apps),
rebindContext.getEntities().size(), Strings.ies(rebindContext.getEntities()),
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/3a8a33a8/core/src/main/java/brooklyn/entity/rebind/persister/BrooklynMementoPersisterToObjectStore.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/entity/rebind/persister/BrooklynMementoPersisterToObjectStore.java b/core/src/main/java/brooklyn/entity/rebind/persister/BrooklynMementoPersisterToObjectStore.java
index e02de72..f3a2882 100644
--- a/core/src/main/java/brooklyn/entity/rebind/persister/BrooklynMementoPersisterToObjectStore.java
+++ b/core/src/main/java/brooklyn/entity/rebind/persister/BrooklynMementoPersisterToObjectStore.java
@@ -67,6 +67,7 @@ import brooklyn.util.xstream.XmlUtil;
import com.google.common.annotations.Beta;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Stopwatch;
+import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Lists;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
@@ -612,12 +613,17 @@ public class BrooklynMementoPersisterToObjectStore implements BrooklynMementoPer
public void waitForWritesCompleted(Duration timeout) throws InterruptedException, TimeoutException {
boolean locked = lock.readLock().tryLock(timeout.toMillisecondsRoundingUp(), TimeUnit.MILLISECONDS);
if (locked) {
+ ImmutableSet<StoreObjectAccessorWithLock> wc;
+ synchronized (writers) {
+ wc = ImmutableSet.copyOf(writers.values());
+ }
lock.readLock().unlock();
// Belt-and-braces: the lock above should be enough to ensure no outstanding writes, because
// each writer is now synchronous.
- for (StoreObjectAccessorWithLock writer : writers.values())
+ for (StoreObjectAccessorWithLock writer : wc) {
writer.waitForCurrentWrites(timeout);
+ }
} else {
throw new TimeoutException("Timeout waiting for writes to "+objectStore);
}
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/3a8a33a8/core/src/main/java/brooklyn/event/basic/BasicSensorEvent.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/event/basic/BasicSensorEvent.java b/core/src/main/java/brooklyn/event/basic/BasicSensorEvent.java
index 33134f4..3f09ffe 100644
--- a/core/src/main/java/brooklyn/event/basic/BasicSensorEvent.java
+++ b/core/src/main/java/brooklyn/event/basic/BasicSensorEvent.java
@@ -18,6 +18,11 @@
*/
package brooklyn.event.basic;
+import java.util.ConcurrentModificationException;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
import brooklyn.entity.Entity;
import brooklyn.event.Sensor;
import brooklyn.event.SensorEvent;
@@ -28,6 +33,9 @@ import com.google.common.base.Objects;
* A {@link SensorEvent} containing data from a {@link Sensor} generated by an {@link Entity}.
*/
public class BasicSensorEvent<T> implements SensorEvent<T> {
+
+ private static final Logger log = LoggerFactory.getLogger(BasicSensorEvent.class);
+
private final Sensor<T> sensor;
private final Entity source;
private final T value;
@@ -77,6 +85,16 @@ public class BasicSensorEvent<T> implements SensorEvent<T> {
@Override
public String toString() {
- return source+"."+sensor+"="+value+" @ "+timestamp;
+ try {
+ return source+"."+sensor+"="+value+" @ "+timestamp;
+ } catch (ConcurrentModificationException e) {
+ // TODO occasional CME observed on shutdown, wrt map, e.g. in UrlMappingTest
+ // transformations should set a copy of the map; see e.g. in ServiceStateLogic.updateMapSensor
+ String result = getClass()+":"+source+"."+sensor+"@"+timestamp;
+ log.warn("Error creating string for " + result + " (ignoring): " + e);
+ if (log.isDebugEnabled())
+ log.debug("Trace for error creating string for " + result + " (ignoring): " + e, e);
+ return result;
+ }
}
}
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/3a8a33a8/core/src/main/java/brooklyn/management/internal/EntityManagementSupport.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/management/internal/EntityManagementSupport.java b/core/src/main/java/brooklyn/management/internal/EntityManagementSupport.java
index 0b31289..1e2779a 100644
--- a/core/src/main/java/brooklyn/management/internal/EntityManagementSupport.java
+++ b/core/src/main/java/brooklyn/management/internal/EntityManagementSupport.java
@@ -89,6 +89,7 @@ public class EntityManagementSupport {
protected final AtomicBoolean managementContextUsable = new AtomicBoolean(false);
protected final AtomicBoolean currentlyDeployed = new AtomicBoolean(false);
protected final AtomicBoolean everDeployed = new AtomicBoolean(false);
+ protected Boolean readOnly = null;
protected final AtomicBoolean managementFailed = new AtomicBoolean(false);
private volatile EntityChangeListener entityChangeListener = EntityChangeListener.NOOP;
@@ -101,7 +102,28 @@ public class EntityManagementSupport {
public boolean isNoLongerManaged() {
return wasDeployed() && !isDeployed();
}
+ /** whether entity has ever been deployed (managed) */
public boolean wasDeployed() { return everDeployed.get(); }
+
+ @Beta
+ public void setReadOnly() {
+ if (isDeployed())
+ throw new IllegalStateException("Cannot set read only after deployment");
+ readOnly = true;
+ }
+
+ /** as {@link EntityManagerInternal#isReadOnly(Entity)} */
+ @Beta
+ public Boolean isReadOnly() {
+ if (readOnly==null) {
+ // readOnly is set in two places, by rebindManagerImpl when the entity is created,
+ // and again when the entity manager is told we should be read only;
+ // TODO twice setting read-only is redundant; possibly doesn't even need to be on managementContext?
+ // however we must check here to discover canonically when it IS fully managed and it is NOT read only
+ readOnly = ((EntityManagerInternal)managementContext.getEntityManager()).isReadOnly(entity);
+ }
+ return readOnly;
+ }
/**
* Whether the entity's management lifecycle is complete (i.e. both "onManagementStarting" and "onManagementStarted" have
@@ -421,4 +443,5 @@ public class EntityManagementSupport {
getManagementContext().getRebindManager().getChangeListener().onChanged(entity);
}
}
+
}
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/3a8a33a8/core/src/main/java/brooklyn/management/internal/EntityManagerInternal.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/management/internal/EntityManagerInternal.java b/core/src/main/java/brooklyn/management/internal/EntityManagerInternal.java
index fc7485e..0dc7c13 100644
--- a/core/src/main/java/brooklyn/management/internal/EntityManagerInternal.java
+++ b/core/src/main/java/brooklyn/management/internal/EntityManagerInternal.java
@@ -27,4 +27,15 @@ public interface EntityManagerInternal extends EntityManager {
/** gets all entities currently known to the application, including entities that are not yet managed */
Iterable<Entity> getAllEntitiesInApplication(Application application);
+ /**
+ * Indicates that the given entity should be treated as read only
+ * when it is subsequently managed.
+ * @throws IllegalStateException if the entity is already managed.
+ */
+ void setReadOnly(Entity e);
+
+ /** Whether the entity and its adjuncts should be treated as read-only;
+ * may be null when initializing if RO status is unknown. */
+ Boolean isReadOnly(Entity item);
+
}
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/3a8a33a8/core/src/main/java/brooklyn/management/internal/LocalEntityManager.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/management/internal/LocalEntityManager.java b/core/src/main/java/brooklyn/management/internal/LocalEntityManager.java
index 9f9e5e3..69fe77b 100644
--- a/core/src/main/java/brooklyn/management/internal/LocalEntityManager.java
+++ b/core/src/main/java/brooklyn/management/internal/LocalEntityManager.java
@@ -84,7 +84,9 @@ public class LocalEntityManager implements EntityManagerInternal {
/** Real managed entities */
protected final Map<String,Entity> entitiesById = Maps.newLinkedHashMap();
-
+
+ protected final Set<String> readOnlyEntityIds = Sets.newLinkedHashSet();
+
/** Proxies of the managed entities */
protected final ObservableList entities = new ObservableList();
@@ -220,6 +222,16 @@ public class LocalEntityManager implements EntityManagerInternal {
preRegisteredEntitiesById.put(entity.getId(), entity);
}
+ public void setReadOnly(Entity e) {
+ readOnlyEntityIds.add(e.getId());
+ ((EntityInternal)e).getManagementSupport().setReadOnly();
+ }
+
+ @Override
+ public Boolean isReadOnly(Entity item) {
+ return readOnlyEntityIds.contains(item.getId());
+ }
+
// TODO synchronization issues here. We guard with isManaged(), but if another thread executing
// concurrently then the managed'ness could be set after our check but before we do
// onManagementStarting etc. However, we can't just synchronize because we're calling alien code
@@ -265,24 +277,28 @@ public class LocalEntityManager implements EntityManagerInternal {
}
@Override
- public void unmanage(Entity e) {
+ public void unmanage(final Entity e) {
if (shouldSkipUnmanagement(e)) return;
- // Need to store all child entities as onManagementStopping removes a child from the parent entity
- final List<EntityInternal> allEntities = Lists.newArrayList();
- final ManagementTransitionInfo info = new ManagementTransitionInfo(managementContext, ManagementTransitionMode.NORMAL);
- recursively(e, new Predicate<EntityInternal>() { public boolean apply(EntityInternal it) {
- if (shouldSkipUnmanagement(it)) return false;
- allEntities.add(it);
- it.getManagementSupport().onManagementStopping(info);
- return true;
- } });
-
- for (EntityInternal it : allEntities) {
- if (shouldSkipUnmanagement(it)) continue;
- unmanageNonRecursive(it);
- it.getManagementSupport().onManagementStopped(info);
- managementContext.getRebindManager().getChangeListener().onUnmanaged(it);
- if (managementContext.gc != null) managementContext.gc.onUnmanaged(it);
+ if (isReadOnly(e)) {
+ unmanageNonRecursive(e);
+ } else {
+ // Need to store all child entities as onManagementStopping removes a child from the parent entity
+ final List<EntityInternal> allEntities = Lists.newArrayList();
+ final ManagementTransitionInfo info = new ManagementTransitionInfo(managementContext, ManagementTransitionMode.NORMAL);
+ recursively(e, new Predicate<EntityInternal>() { public boolean apply(EntityInternal it) {
+ if (shouldSkipUnmanagement(it)) return false;
+ allEntities.add(it);
+ it.getManagementSupport().onManagementStopping(info);
+ return true;
+ } });
+
+ for (EntityInternal it : allEntities) {
+ if (shouldSkipUnmanagement(it)) continue;
+ unmanageNonRecursive(it);
+ it.getManagementSupport().onManagementStopped(info);
+ managementContext.getRebindManager().getChangeListener().onUnmanaged(it);
+ if (managementContext.gc != null) managementContext.gc.onUnmanaged(it);
+ }
}
}
@@ -299,6 +315,8 @@ public class LocalEntityManager implements EntityManagerInternal {
return;
} else if (isPreManaged(entity)) {
return;
+ } else if (Boolean.TRUE.equals(((EntityInternal)entity).getManagementSupport().isReadOnly())) {
+ return;
} else {
Entity rootUnmanaged = entity;
while (true) {
@@ -415,16 +433,20 @@ public class LocalEntityManager implements EntityManagerInternal {
* from its groups?
*/
- Collection<Group> groups = e.getGroups();
- e.clearParent();
- for (Group group : groups) {
- group.removeMember(e);
- }
- if (e instanceof Group) {
- Collection<Entity> members = ((Group)e).getMembers();
- for (Entity member : members) {
- member.removeGroup((Group)e);
+ if (!isReadOnly(e)) {
+ Collection<Group> groups = e.getGroups();
+ e.clearParent();
+ for (Group group : groups) {
+ group.removeMember(e);
}
+ if (e instanceof Group) {
+ Collection<Entity> members = ((Group)e).getMembers();
+ for (Entity member : members) {
+ member.removeGroup((Group)e);
+ }
+ }
+ } else {
+ log.debug("No relations being updated on unmanage of read only {}", e);
}
synchronized (this) {
@@ -436,6 +458,7 @@ public class LocalEntityManager implements EntityManagerInternal {
entities.remove(proxyE);
entityProxiesById.remove(e.getId());
+ readOnlyEntityIds.remove(e.getId());
Object old = entitiesById.remove(e.getId());
entityTypes.remove(e.getId());
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/3a8a33a8/core/src/main/java/brooklyn/management/internal/NonDeploymentEntityManager.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/management/internal/NonDeploymentEntityManager.java b/core/src/main/java/brooklyn/management/internal/NonDeploymentEntityManager.java
index 67dbe97..f6cc2ef 100644
--- a/core/src/main/java/brooklyn/management/internal/NonDeploymentEntityManager.java
+++ b/core/src/main/java/brooklyn/management/internal/NonDeploymentEntityManager.java
@@ -155,4 +155,15 @@ public class NonDeploymentEntityManager implements EntityManagerInternal {
throw new IllegalStateException("Non-deployment context "+this+" (with no initial management context supplied) is not valid for this operation.");
}
}
+
+ @Override
+ public void setReadOnly(Entity e) {
+ throw new IllegalStateException("Non-deployment context "+this+" is not valid for this operation: cannot manage "+e);
+ }
+
+ @Override
+ public Boolean isReadOnly(Entity item) {
+ // NB: must return null here, otherwise callers may cache this value
+ return null;
+ }
}
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/3a8a33a8/core/src/main/java/brooklyn/management/internal/NonDeploymentManagementContext.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/management/internal/NonDeploymentManagementContext.java b/core/src/main/java/brooklyn/management/internal/NonDeploymentManagementContext.java
index 21f1968..1738a9e 100644
--- a/core/src/main/java/brooklyn/management/internal/NonDeploymentManagementContext.java
+++ b/core/src/main/java/brooklyn/management/internal/NonDeploymentManagementContext.java
@@ -31,6 +31,9 @@ import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
import brooklyn.catalog.BrooklynCatalog;
import brooklyn.config.BrooklynProperties;
import brooklyn.config.StringConfigMap;
@@ -74,6 +77,8 @@ import com.google.common.base.Objects;
public class NonDeploymentManagementContext implements ManagementContextInternal {
+ private static final Logger log = LoggerFactory.getLogger(NonDeploymentManagementContext.class);
+
public enum NonDeploymentManagementContextMode {
PRE_MANAGEMENT,
MANAGEMENT_REBINDING,
@@ -374,12 +379,14 @@ public class NonDeploymentManagementContext implements ManagementContextInternal
@Override
public void prePreManage(Entity entity) {
- // no-op
+ // should throw? but in 0.7.0-SNAPSHOT it was no-op
+ log.warn("Ignoring call to prePreManage("+entity+") on "+this);
}
@Override
public void prePreManage(Location location) {
- // no-op
+ // should throw? but in 0.7.0-SNAPSHOT it was no-op
+ log.warn("Ignoring call to prePreManage("+location+") on "+this);
}
private boolean isInitialManagementContextReal() {
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/3a8a33a8/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 cc9f065..cfb6056 100644
--- a/core/src/test/java/brooklyn/management/ha/HotStandbyTest.java
+++ b/core/src/test/java/brooklyn/management/ha/HotStandbyTest.java
@@ -26,10 +26,12 @@ import java.util.Map;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import org.testng.Assert;
import org.testng.annotations.AfterMethod;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;
+import brooklyn.entity.Application;
import brooklyn.entity.basic.Entities;
import brooklyn.entity.rebind.PersistenceExceptionHandlerImpl;
import brooklyn.entity.rebind.persister.BrooklynMementoPersisterToObjectStore;
@@ -86,6 +88,8 @@ public class HotStandbyTest {
public void tearDown() throws Exception {
if (ha != null) ha.stop();
+ if (mgmt!=null) mgmt.getRebindManager().stop();
+
if (mgmt != null) Entities.destroyAll(mgmt);
if (objectStore != null) objectStore.deleteCompletely();
}
@@ -123,8 +127,6 @@ public class HotStandbyTest {
return new InMemoryObjectStore(sharedBackingStore, sharedBackingStoreDates);
}
-
-
@Test
public void testHotStandby() throws Exception {
HaMgmtNode n1 = newNode();
@@ -142,7 +144,19 @@ public class HotStandbyTest {
assertEquals(n2.ha.getNodeState(), ManagementNodeState.HOT_STANDBY);
assertEquals(n2.mgmt.getApplications().size(), 1);
+ Application app2 = n2.mgmt.getApplications().iterator().next();
+ assertEquals(app2.getAttribute(TestEntity.SEQUENCE), (Integer)3);
+
+ try {
+ ((TestApplication)app2).setAttribute(TestEntity.SEQUENCE, 4);
+ Assert.fail("Should not have allowed sensor to be set");
+ } catch (Exception e) {
+ Assert.assertTrue(e.toString().toLowerCase().contains("read-only"), "Error message did not contain expected text: "+e);
+ }
}
-
+ @Test(groups="Integration", invocationCount=50)
+ public void testHotStandbyManyTimes() throws Exception {
+ testHotStandby();
+ }
}
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/3a8a33a8/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
new file mode 100644
index 0000000..d955d50
--- /dev/null
+++ b/core/src/test/java/brooklyn/management/ha/WarmStandbyTest.java
@@ -0,0 +1,149 @@
+/*
+ * 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.management.ha;
+
+import static org.testng.Assert.assertEquals;
+
+import java.util.Date;
+import java.util.List;
+import java.util.Map;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.testng.annotations.AfterMethod;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import brooklyn.entity.basic.Entities;
+import brooklyn.entity.rebind.PersistenceExceptionHandlerImpl;
+import brooklyn.entity.rebind.persister.BrooklynMementoPersisterToObjectStore;
+import brooklyn.entity.rebind.persister.InMemoryObjectStore;
+import brooklyn.entity.rebind.persister.ListeningObjectStore;
+import brooklyn.entity.rebind.persister.PersistMode;
+import brooklyn.entity.rebind.persister.PersistenceObjectStore;
+import brooklyn.location.Location;
+import brooklyn.management.internal.ManagementContextInternal;
+import brooklyn.test.entity.LocalManagementContextForTests;
+import brooklyn.test.entity.TestApplication;
+import brooklyn.util.collections.MutableList;
+import brooklyn.util.collections.MutableMap;
+import brooklyn.util.time.Duration;
+
+@Test
+public class WarmStandbyTest {
+
+ private static final Logger log = LoggerFactory.getLogger(WarmStandbyTest.class);
+
+ private List<HaMgmtNode> nodes = new MutableList<WarmStandbyTest.HaMgmtNode>();
+ Map<String,String> sharedBackingStore = MutableMap.of();
+ Map<String,Date> sharedBackingStoreDates = MutableMap.of();
+ private ClassLoader classLoader = getClass().getClassLoader();
+
+ public class HaMgmtNode {
+
+ private ManagementContextInternal mgmt;
+ private String ownNodeId;
+ private String nodeName;
+ private ListeningObjectStore objectStore;
+ private ManagementPlaneSyncRecordPersister persister;
+ private HighAvailabilityManagerImpl ha;
+
+ @BeforeMethod(alwaysRun=true)
+ public void setUp() throws Exception {
+ nodeName = "node "+nodes.size();
+ mgmt = newLocalManagementContext();
+ ownNodeId = mgmt.getManagementNodeId();
+ objectStore = new ListeningObjectStore(newPersistenceObjectStore());
+ objectStore.injectManagementContext(mgmt);
+ objectStore.prepareForSharedUse(PersistMode.CLEAN, HighAvailabilityMode.DISABLED);
+ persister = new ManagementPlaneSyncRecordPersisterToObjectStore(mgmt, objectStore, classLoader);
+ ((ManagementPlaneSyncRecordPersisterToObjectStore)persister).allowRemoteTimestampInMemento();
+ BrooklynMementoPersisterToObjectStore persisterObj = new BrooklynMementoPersisterToObjectStore(objectStore, mgmt.getBrooklynProperties(), classLoader);
+ mgmt.getRebindManager().setPersister(persisterObj, PersistenceExceptionHandlerImpl.builder().build());
+ ha = ((HighAvailabilityManagerImpl)mgmt.getHighAvailabilityManager())
+ .setPollPeriod(Duration.PRACTICALLY_FOREVER)
+ .setHeartbeatTimeout(Duration.THIRTY_SECONDS)
+ .setPersister(persister);
+ log.info("Created "+nodeName+" "+ownNodeId);
+ }
+
+ public void tearDown() throws Exception {
+ if (ha != null) ha.stop();
+ if (mgmt != null) Entities.destroyAll(mgmt);
+ if (objectStore != null) objectStore.deleteCompletely();
+ }
+
+ @Override
+ public String toString() {
+ return nodeName+" "+ownNodeId;
+ }
+ }
+
+ @BeforeMethod(alwaysRun=true)
+ public void setUp() throws Exception {
+ nodes.clear();
+ sharedBackingStore.clear();
+ }
+
+ public HaMgmtNode newNode() throws Exception {
+ HaMgmtNode node = new HaMgmtNode();
+ node.setUp();
+ nodes.add(node);
+ return node;
+ }
+
+ @AfterMethod(alwaysRun=true)
+ public void tearDown() throws Exception {
+ for (HaMgmtNode n: nodes)
+ n.tearDown();
+ }
+
+ protected ManagementContextInternal newLocalManagementContext() {
+ return new LocalManagementContextForTests();
+ }
+
+ protected PersistenceObjectStore newPersistenceObjectStore() {
+ return new InMemoryObjectStore(sharedBackingStore, sharedBackingStoreDates);
+ }
+
+ // TODO refactor above -- routines above this line are shared among HotStandbyTest and SplitBrainTest
+
+ @Test
+ public void testWarmStandby() throws Exception {
+ HaMgmtNode n1 = newNode();
+ n1.ha.start(HighAvailabilityMode.AUTO);
+ assertEquals(n1.ha.getNodeState(), ManagementNodeState.MASTER);
+
+ TestApplication app = TestApplication.Factory.newManagedInstanceForTests(n1.mgmt);
+ app.start(MutableList.<Location>of());
+
+ n1.mgmt.getRebindManager().forcePersistNow();
+
+ HaMgmtNode n2 = newNode();
+ n2.ha.start(HighAvailabilityMode.STANDBY);
+ assertEquals(n2.ha.getNodeState(), ManagementNodeState.STANDBY);
+
+ assertEquals(n2.mgmt.getApplications().size(), 0);
+ }
+
+ // TODO support forcible demotion, and check that a master forcibly demoted
+ // to warm standby clears its apps, policies, and locations
+
+
+}
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/3a8a33a8/software/webapp/src/main/java/brooklyn/entity/proxy/nginx/UrlMappingImpl.java
----------------------------------------------------------------------
diff --git a/software/webapp/src/main/java/brooklyn/entity/proxy/nginx/UrlMappingImpl.java b/software/webapp/src/main/java/brooklyn/entity/proxy/nginx/UrlMappingImpl.java
index 473a8d5..66b88c0 100644
--- a/software/webapp/src/main/java/brooklyn/entity/proxy/nginx/UrlMappingImpl.java
+++ b/software/webapp/src/main/java/brooklyn/entity/proxy/nginx/UrlMappingImpl.java
@@ -199,10 +199,9 @@ public class UrlMappingImpl extends AbstractGroupImpl implements UrlMapping {
}});
subscriptionHandle2 = subscribe(t, Changeable.MEMBER_REMOVED, new SensorEventListener<Entity>() {
@Override public void onEvent(SensorEvent<Entity> event) {
- boolean changed = removeMember(event.getValue());
- if (changed) {
- recomputeAddresses();
- }
+ removeMember(event.getValue());
+ // recompute, irrespective of change, because framework may have already invoked the removeMember call
+ recomputeAddresses();
}});
setMembers(t.getChildren(), EntityPredicates.attributeEqualTo(Startable.SERVICE_UP, true));
}
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/3a8a33a8/software/webapp/src/test/java/brooklyn/entity/proxy/UrlMappingTest.java
----------------------------------------------------------------------
diff --git a/software/webapp/src/test/java/brooklyn/entity/proxy/UrlMappingTest.java b/software/webapp/src/test/java/brooklyn/entity/proxy/UrlMappingTest.java
index 1d680f1..97a7409 100644
--- a/software/webapp/src/test/java/brooklyn/entity/proxy/UrlMappingTest.java
+++ b/software/webapp/src/test/java/brooklyn/entity/proxy/UrlMappingTest.java
@@ -48,6 +48,8 @@ import brooklyn.location.basic.LocalhostMachineProvisioningLocation;
import brooklyn.management.internal.LocalManagementContext;
import brooklyn.test.TestUtils;
import brooklyn.test.entity.TestApplication;
+import brooklyn.util.time.Duration;
+import brooklyn.util.time.Time;
import com.google.common.base.Function;
import com.google.common.base.Predicates;
@@ -110,6 +112,7 @@ public class UrlMappingTest {
@Test(groups = "Integration")
public void testTargetMappingsRemovesUnmanagedMember() {
Iterable<StubAppServer> members = Iterables.filter(cluster.getChildren(), StubAppServer.class);
+ assertEquals(Iterables.size(members), 2);
StubAppServer target1 = Iterables.get(members, 0);
StubAppServer target2 = Iterables.get(members, 1);
@@ -122,6 +125,11 @@ public class UrlMappingTest {
assertExpectedTargetsEventually(ImmutableSet.of(target2));
}
+ @Test(groups = "Integration", invocationCount=50)
+ public void testTargetMappingsRemovesUnmanagedMemberManyTimes() {
+ testTargetMappingsRemovesUnmanagedMember();
+ }
+
@Test(groups = "Integration")
public void testTargetMappingsRemovesDownMember() {
Iterable<StubAppServer> members = Iterables.filter(cluster.getChildren(), StubAppServer.class);
[14/15] git commit: This closes #197
Posted by he...@apache.org.
This closes #197
Project: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/commit/1f2a378b
Tree: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/tree/1f2a378b
Diff: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/diff/1f2a378b
Branch: refs/heads/master
Commit: 1f2a378bd4a0d74759d79cfd87f27c2b1738b5e8
Parents: 8a1fd77 6ba1639
Author: Alex Heneveld <al...@cloudsoftcorp.com>
Authored: Wed Oct 1 18:31:36 2014 +0100
Committer: Alex Heneveld <al...@cloudsoftcorp.com>
Committed: Wed Oct 1 18:31:36 2014 +0100
----------------------------------------------------------------------
api/src/main/java/brooklyn/entity/Entity.java | 10 +-
.../brooklyn/entity/rebind/RebindContext.java | 4 +
.../entity/rebind/RebindExceptionHandler.java | 19 +-
.../brooklyn/entity/rebind/RebindManager.java | 54 +-
.../brooklyn/entity/rebind/RebindSupport.java | 2 +-
.../brooklyn/management/ExecutionContext.java | 8 +
.../brooklyn/management/LocationManager.java | 3 -
.../brooklyn/management/ManagementContext.java | 15 +
api/src/main/java/brooklyn/management/Task.java | 8 +
.../management/ha/HighAvailabilityManager.java | 16 +-
.../management/ha/HighAvailabilityMode.java | 12 +-
.../management/ha/ManagementNodeState.java | 14 +
.../management/ha/ManagementNodeSyncRecord.java | 2 +
.../ha/ManagementPlaneSyncRecordPersister.java | 1 +
.../mementos/BrooklynMementoPersister.java | 3 +
.../brooklyn/enricher/basic/Transformer.java | 1 +
.../brooklyn/entity/basic/AbstractEntity.java | 115 ++--
.../entity/basic/AbstractGroupImpl.java | 25 +-
.../brooklyn/entity/basic/BrooklynTaskTags.java | 4 +
.../java/brooklyn/entity/basic/Entities.java | 22 +-
.../brooklyn/entity/basic/EntityInternal.java | 6 +-
.../basic/EntityTransientCopyInternal.java | 106 ++++
.../entity/basic/ServiceStateLogic.java | 6 +-
.../entity/proxying/EntityProxyImpl.java | 119 +++-
.../entity/proxying/InternalEntityFactory.java | 30 +-
.../AbstractBrooklynObjectRebindSupport.java | 3 +-
.../entity/rebind/BasicEntityRebindSupport.java | 8 +-
.../rebind/PeriodicDeltaChangeListener.java | 54 +-
.../entity/rebind/RebindContextImpl.java | 12 +
.../rebind/RebindExceptionHandlerImpl.java | 37 +-
.../entity/rebind/RebindManagerImpl.java | 324 +++++++++--
.../AbstractBrooklynMementoPersister.java | 9 +-
.../BrooklynMementoPersisterToMultiFile.java | 8 +
.../BrooklynMementoPersisterToObjectStore.java | 70 ++-
.../persister/RetryingMementoSerializer.java | 4 +
.../dto/BasicManagementNodeSyncRecord.java | 22 +-
.../brooklyn/event/basic/BasicSensorEvent.java | 20 +-
.../internal/BrooklynFeatureEnablement.java | 18 +
.../location/basic/AbstractLocation.java | 67 +--
.../location/basic/LocationInternal.java | 1 -
.../management/ha/BasicMasterChooser.java | 158 +++++-
.../ha/HighAvailabilityManagerImpl.java | 241 ++++++--
.../ha/ManagementPlaneSyncRecordDeltaImpl.java | 11 +-
...ntPlaneSyncRecordPersisterToObjectStore.java | 15 +-
.../internal/AbstractManagementContext.java | 24 +-
.../internal/BrooklynGarbageCollector.java | 1 +
.../internal/EntityManagementSupport.java | 68 ++-
.../internal/EntityManagerInternal.java | 15 +
.../management/internal/LocalAccessManager.java | 1 -
.../management/internal/LocalEntityManager.java | 246 ++++++--
.../internal/LocalLocationManager.java | 171 ++++--
.../internal/LocalManagementContext.java | 4 +-
.../internal/LocationManagerInternal.java | 40 ++
.../internal/ManagementTransitionInfo.java | 30 +-
.../internal/NonDeploymentEntityManager.java | 39 ++
.../internal/NonDeploymentLocationManager.java | 51 +-
.../NonDeploymentManagementContext.java | 67 ++-
.../util/task/AbstractExecutionContext.java | 7 +-
.../util/task/BasicExecutionContext.java | 7 +-
.../main/java/brooklyn/util/task/BasicTask.java | 2 +-
.../java/brooklyn/util/task/ScheduledTask.java | 18 +
.../src/main/java/brooklyn/util/task/Tasks.java | 51 ++
.../entity/rebind/RebindCatalogEntityTest.java | 5 +-
.../entity/rebind/RebindEntityTest.java | 7 +-
.../entity/rebind/RebindFailuresTest.java | 33 +-
.../entity/rebind/RebindTestFixture.java | 4 +-
.../brooklyn/entity/rebind/RebindTestUtils.java | 22 +-
.../rebind/RecordingRebindExceptionHandler.java | 5 -
...BrooklynMementoPersisterToMultiFileTest.java | 2 +-
.../transformer/CompoundTransformerTest.java | 1 +
.../ha/HighAvailabilityManagerInMemoryTest.java | 4 +
.../HighAvailabilityManagerSplitBrainTest.java | 51 +-
.../ha/HighAvailabilityManagerTestFixture.java | 14 +-
.../brooklyn/management/ha/HotStandbyTest.java | 564 +++++++++++++++++++
.../management/ha/MasterChooserTest.java | 71 ++-
.../brooklyn/management/ha/WarmStandbyTest.java | 150 +++++
.../entity/TestApplicationNoEnrichersImpl.java | 15 -
.../util/task/ScheduledExecutionTest.java | 159 +++++-
.../entity/proxy/nginx/UrlMappingImpl.java | 7 +-
.../brooklyn/entity/proxy/UrlMappingTest.java | 8 +
.../AbstractWebAppFixtureIntegrationTest.java | 5 +-
usage/cli/src/main/java/brooklyn/cli/Main.java | 12 +-
.../brooklyn/launcher/BrooklynLauncher.java | 7 +-
.../BrooklynLauncherHighAvailabilityTest.java | 25 +-
...lynLauncherRebindToCloudObjectStoreTest.java | 4 -
.../brooklyn/util/exceptions/Exceptions.java | 30 +-
.../util/text/NaturalOrderComparator.java | 3 +-
.../main/java/brooklyn/util/time/Duration.java | 12 +
88 files changed, 3152 insertions(+), 597 deletions(-)
----------------------------------------------------------------------
[03/15] git commit: add a hot_standby mode,
now the default when running HA (although it doesn't do anything,
it is a coherent state and the MasterChooser prefers it)
Posted by he...@apache.org.
add a hot_standby mode, now the default when running HA (although it doesn't do anything, it is a coherent state and the MasterChooser prefers it)
Project: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/commit/c2b05ff2
Tree: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/tree/c2b05ff2
Diff: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/diff/c2b05ff2
Branch: refs/heads/master
Commit: c2b05ff274fbba81e9a2fc2544b2b6587d55afae
Parents: 3ef6e25
Author: Alex Heneveld <al...@cloudsoftcorp.com>
Authored: Fri Sep 26 13:41:46 2014 +0100
Committer: Alex Heneveld <al...@cloudsoftcorp.com>
Committed: Wed Oct 1 16:40:15 2014 +0100
----------------------------------------------------------------------
.../management/ha/HighAvailabilityManager.java | 3 +-
.../management/ha/HighAvailabilityMode.java | 12 +-
.../management/ha/ManagementNodeState.java | 14 +++
.../management/ha/BasicMasterChooser.java | 120 ++++++++++++++-----
.../ha/HighAvailabilityManagerImpl.java | 77 ++++++++----
.../ha/HighAvailabilityManagerInMemoryTest.java | 4 +
.../HighAvailabilityManagerSplitBrainTest.java | 34 +++---
.../ha/HighAvailabilityManagerTestFixture.java | 10 +-
.../management/ha/MasterChooserTest.java | 37 +++++-
usage/cli/src/main/java/brooklyn/cli/Main.java | 12 +-
.../brooklyn/launcher/BrooklynLauncher.java | 1 +
.../BrooklynLauncherHighAvailabilityTest.java | 18 +++
12 files changed, 260 insertions(+), 82 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/c2b05ff2/api/src/main/java/brooklyn/management/ha/HighAvailabilityManager.java
----------------------------------------------------------------------
diff --git a/api/src/main/java/brooklyn/management/ha/HighAvailabilityManager.java b/api/src/main/java/brooklyn/management/ha/HighAvailabilityManager.java
index 937d25b..d9888f9 100644
--- a/api/src/main/java/brooklyn/management/ha/HighAvailabilityManager.java
+++ b/api/src/main/java/brooklyn/management/ha/HighAvailabilityManager.java
@@ -70,7 +70,8 @@ public interface HighAvailabilityManager {
* Starts the monitoring of other nodes (and thus potential promotion of this node from standby to master).
* <p>
* When this method returns, the status of this node will be set,
- * either {@link ManagementNodeState#MASTER} if appropriate or {@link ManagementNodeState#STANDBY}.
+ * either {@link ManagementNodeState#MASTER} if appropriate
+ * or {@link ManagementNodeState#STANDBY} / {@link ManagementNodeState#HOT_STANDBY}.
*
* @param startMode mode to start with
* @throws IllegalStateException if current state of the management-plane doesn't match that desired by {@code startMode}
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/c2b05ff2/api/src/main/java/brooklyn/management/ha/HighAvailabilityMode.java
----------------------------------------------------------------------
diff --git a/api/src/main/java/brooklyn/management/ha/HighAvailabilityMode.java b/api/src/main/java/brooklyn/management/ha/HighAvailabilityMode.java
index 9a154ca..29f8f7d 100644
--- a/api/src/main/java/brooklyn/management/ha/HighAvailabilityMode.java
+++ b/api/src/main/java/brooklyn/management/ha/HighAvailabilityMode.java
@@ -30,12 +30,22 @@ public enum HighAvailabilityMode {
AUTO,
/**
- * Means node must be standby; if there is not already a master then fail fast on startup.
+ * Means node must be lukewarm standby; if there is not already a master then fail fast on startup.
+ * See {@link ManagementNodeState#STANDBY}.
*/
STANDBY,
/**
+ * Means node must be hot standby; if there is not already a master then fail fast on startup.
+ * See {@link ManagementNodeState#HOT_STANDBY}.
+ */
+ HOT_STANDBY,
+
+ /**
* Means node must be master; if there is already a master then fail fast on startup.
+ * See {@link ManagementNodeState#MASTER}.
*/
+ // TODO when multi-master supported we will of course not fail fast on startup when there is already a master;
+ // instead the responsibility for master entities will be divided among masters
MASTER;
}
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/c2b05ff2/api/src/main/java/brooklyn/management/ha/ManagementNodeState.java
----------------------------------------------------------------------
diff --git a/api/src/main/java/brooklyn/management/ha/ManagementNodeState.java b/api/src/main/java/brooklyn/management/ha/ManagementNodeState.java
index 00a90a4..d3b0508 100644
--- a/api/src/main/java/brooklyn/management/ha/ManagementNodeState.java
+++ b/api/src/main/java/brooklyn/management/ha/ManagementNodeState.java
@@ -19,9 +19,23 @@
package brooklyn.management.ha;
public enum ManagementNodeState {
+ /** @deprecated since 0.7.0 synonym for maintenance (plus, it should have been UK english!) */
UNINITIALISED,
+ /** node is either coming online, or is in some kind of recovery/transitioning mode */
+ INITIALIZING,
+
+ /** node is in "lukewarm standby" mode, where it is available to be promoted to master,
+ * but does not have entities loaded and will require some effort to be promoted */
STANDBY,
+ /** node is acting as read-only proxy */
+ HOT_STANDBY,
+ /** node is running as primary/master, able to manage entities and create new ones */
+ // the semantics are intended to support multi-master here; we could have multiple master nodes,
+ // but we need to look up who is master for any given entity
MASTER,
+
+ /** node has failed and requires maintenance attention */
FAILED,
+ /** node has gone away; maintenance not possible */
TERMINATED;
}
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/c2b05ff2/core/src/main/java/brooklyn/management/ha/BasicMasterChooser.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/management/ha/BasicMasterChooser.java b/core/src/main/java/brooklyn/management/ha/BasicMasterChooser.java
index 2f82016..78c110a 100644
--- a/core/src/main/java/brooklyn/management/ha/BasicMasterChooser.java
+++ b/core/src/main/java/brooklyn/management/ha/BasicMasterChooser.java
@@ -18,18 +18,19 @@
*/
package brooklyn.management.ha;
+import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
-import java.util.Map;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import brooklyn.entity.trait.Identifiable;
+import brooklyn.util.collections.MutableList;
import brooklyn.util.time.Duration;
import com.google.common.annotations.Beta;
-import com.google.common.collect.Lists;
-import com.google.common.collect.Maps;
+import com.google.common.annotations.VisibleForTesting;
/**
* @since 0.7.0
@@ -41,33 +42,69 @@ public abstract class BasicMasterChooser implements MasterChooser {
private static final Logger LOG = LoggerFactory.getLogger(BasicMasterChooser.class);
- // advantage of this over taking most recent is that nodes will agree on this
- // (where heartbeat timeout is reasonably large)
+ protected static class ScoredRecord<T extends Comparable<T>> implements Identifiable, Comparable<ScoredRecord<T>> {
+ String id;
+ ManagementNodeSyncRecord record;
+ T score;
+
+ @Override
+ public String getId() {
+ return id;
+ }
+
+ @Override
+ public int compareTo(ScoredRecord<T> o) {
+ return score.compareTo(o.score);
+ }
+ }
+
+ public ManagementNodeSyncRecord choose(ManagementPlaneSyncRecord memento, Duration heartbeatTimeout, String ownNodeId) {
+ if (LOG.isDebugEnabled()) LOG.debug("Choosing new master from "+memento.getManagementNodes());
+ ManagementNodeSyncRecord me = memento.getManagementNodes().get(ownNodeId);
+ if (me==null) {
+ LOG.warn("Management node details not known when choosing new master: "+memento+" / "+ownNodeId);
+ return null;
+ }
+ Long nowIsh = me.getRemoteTimestamp();
+ if (nowIsh==null) {
+ LOG.warn("Management node for self missing timestamp when choosing new master: "+memento);
+ return null;
+ }
+
+ List<ScoredRecord<?>> contenders = filterHealthy(memento, heartbeatTimeout, nowIsh);
+
+ if (!contenders.isEmpty()) {
+ return pick(contenders);
+ } else {
+ LOG.info("No valid management node found for choosing new master: contender="+memento.getManagementNodes());
+ return null;
+ }
+ }
+
+ /** pick the best contender; argument guaranteed to be non-null and non-empty,
+ * filtered for health reasons */
+ @SuppressWarnings({ "rawtypes", "unchecked" })
+ protected ManagementNodeSyncRecord pick(List<ScoredRecord<?>> contenders) {
+ ScoredRecord min = null;
+ for (ScoredRecord x: contenders) {
+ if (min==null || x.score.compareTo(min.score)<0) min = x;
+ }
+ return min.record;
+ }
+
public static class AlphabeticMasterChooser extends BasicMasterChooser {
+ final boolean preferHot;
+ public AlphabeticMasterChooser(boolean preferHot) { this.preferHot = preferHot; }
+ public AlphabeticMasterChooser() { this.preferHot = true; }
@Override
- public ManagementNodeSyncRecord choose(ManagementPlaneSyncRecord memento, Duration heartbeatTimeout, String ownNodeId) {
- if (LOG.isDebugEnabled()) LOG.debug("Choosing new master from "+memento.getManagementNodes());
- ManagementNodeSyncRecord me = memento.getManagementNodes().get(ownNodeId);
- if (me==null) {
- LOG.warn("Management node details not known when choosing new master: "+memento+" / "+ownNodeId);
- return null;
- }
- Long nowIsh = me.getRemoteTimestamp();
- if (nowIsh==null) {
- LOG.warn("Management node for self missing timestamp when choosing new master: "+memento);
- return null;
- }
-
- Map<String, ManagementNodeSyncRecord> contenders = filterHealthy(memento, heartbeatTimeout, nowIsh);
-
- if (contenders.size() > 0) {
- List<String> contenderIds = Lists.newArrayList(contenders.keySet());
- Collections.sort(contenderIds);
- return contenders.get(contenderIds.get(0));
- } else {
- LOG.info("No valid management node found for choosing new master: contender="+memento.getManagementNodes());
- return null;
- }
+ protected String score(ManagementNodeSyncRecord contender) {
+ if (!preferHot)
+ return contender.getNodeId();
+ // simple prefix with the rating
+ String state = (contender.getStatus()==ManagementNodeState.MASTER ? "1" :
+ contender.getStatus()==ManagementNodeState.HOT_STANDBY ? "2" :
+ contender.getStatus()==ManagementNodeState.STANDBY ? "3" : "9");
+ return state + ":" + contender.getNodeId();
}
}
@@ -75,11 +112,11 @@ public abstract class BasicMasterChooser implements MasterChooser {
* Filters the {@link ManagementPlaneSyncRecord#getManagementNodes()} to only those in an appropriate state,
* and with heartbeats that have not timed out.
*/
- protected Map<String, ManagementNodeSyncRecord> filterHealthy(ManagementPlaneSyncRecord memento, Duration heartbeatTimeout, long nowIsh) {
+ protected List<ScoredRecord<?>> filterHealthy(ManagementPlaneSyncRecord memento, Duration heartbeatTimeout, long nowIsh) {
long oldestAcceptableTimestamp = nowIsh - heartbeatTimeout.toMilliseconds();
- Map<String, ManagementNodeSyncRecord> contenders = Maps.newLinkedHashMap();
+ List<ScoredRecord<?>> contenders = MutableList.of();
for (ManagementNodeSyncRecord contender : memento.getManagementNodes().values()) {
- boolean statusOk = (contender.getStatus() == ManagementNodeState.STANDBY || contender.getStatus() == ManagementNodeState.MASTER);
+ boolean statusOk = (contender.getStatus() == ManagementNodeState.STANDBY || contender.getStatus() == ManagementNodeState.HOT_STANDBY || contender.getStatus() == ManagementNodeState.MASTER);
Long remoteTimestamp = contender.getRemoteTimestamp();
boolean heartbeatOk;
if (remoteTimestamp==null) {
@@ -92,10 +129,29 @@ public abstract class BasicMasterChooser implements MasterChooser {
heartbeatOk = remoteTimestamp >= oldestAcceptableTimestamp;
}
if (statusOk && heartbeatOk) {
- contenders.put(contender.getNodeId(), contender);
+ contenders.add(newScoredRecord(contender));
}
if (LOG.isTraceEnabled()) LOG.trace("Filtering choices of new master: contender="+contender+"; statusOk="+statusOk+"; heartbeatOk="+heartbeatOk);
}
return contenders;
}
+
+ @VisibleForTesting
+ protected List<ScoredRecord<?>> sort(List<ScoredRecord<?>> input) {
+ ArrayList<ScoredRecord<?>> copy = new ArrayList<ScoredRecord<?>>(input);
+ Collections.sort(copy);
+ return copy;
+ }
+
+ @SuppressWarnings({ "unchecked", "rawtypes" })
+ protected ScoredRecord<?> newScoredRecord(ManagementNodeSyncRecord contender) {
+ ScoredRecord r = new ScoredRecord();
+ r.id = contender.getNodeId();
+ r.record = contender;
+ r.score = score(contender);
+ return r;
+ }
+
+ protected abstract Comparable<?> score(ManagementNodeSyncRecord contender);
+
}
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/c2b05ff2/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 fdfa879..2d7bf9f 100644
--- a/core/src/main/java/brooklyn/management/ha/HighAvailabilityManagerImpl.java
+++ b/core/src/main/java/brooklyn/management/ha/HighAvailabilityManagerImpl.java
@@ -123,7 +123,8 @@ public class HighAvailabilityManagerImpl implements HighAvailabilityManager {
private volatile Task<?> pollingTask;
private volatile boolean disabled;
private volatile boolean running;
- private volatile ManagementNodeState nodeState = ManagementNodeState.UNINITIALISED;
+ private volatile ManagementNodeState nodeState = ManagementNodeState.INITIALIZING;
+ private volatile boolean nodeStateTransitionComplete = false;
public HighAvailabilityManagerImpl(ManagementContextInternal managementContext) {
this.managementContext = managementContext;
@@ -202,6 +203,7 @@ public class HighAvailabilityManagerImpl implements HighAvailabilityManager {
public void start(HighAvailabilityMode startMode) {
ownNodeId = managementContext.getManagementNodeId();
nodeState = ManagementNodeState.STANDBY;
+ nodeStateTransitionComplete = true;
running = true;
// TODO Small race in that we first check, and then we'll do checkMaster() on first poll,
@@ -212,13 +214,13 @@ public class HighAvailabilityManagerImpl implements HighAvailabilityManager {
case AUTO:
// don't care; let's start and see if we promote ourselves
publishAndCheck(true);
- if (nodeState == ManagementNodeState.STANDBY) {
+ if (nodeState == ManagementNodeState.STANDBY || nodeState == ManagementNodeState.HOT_STANDBY) {
String masterNodeId = getManagementPlaneSyncState().getMasterNodeId();
ManagementNodeSyncRecord masterNodeDetails = getManagementPlaneSyncState().getManagementNodes().get(masterNodeId);
- LOG.info("Management node "+ownNodeId+" started as HA STANDBY autodetected, master is "+masterNodeId+
+ LOG.info("Management node "+ownNodeId+" started as HA " + nodeState + " autodetected, master is "+masterNodeId +
(masterNodeDetails==null || masterNodeDetails.getUri()==null ? " (no url)" : " at "+masterNodeDetails.getUri()));
} else {
- LOG.info("Management node "+ownNodeId+" started as HA MASTER autodetected");
+ LOG.info("Management node "+ownNodeId+" starting as HA MASTER autodetected");
}
break;
case MASTER:
@@ -230,9 +232,10 @@ public class HighAvailabilityManagerImpl implements HighAvailabilityManager {
}
break;
case STANDBY:
+ case HOT_STANDBY:
if (existingMaster != null) {
publishAndCheck(true);
- LOG.info("Management node "+ownNodeId+" started as HA STANDBY explicitly, status "+nodeState);
+ LOG.info("Management node "+ownNodeId+" started as "+startMode+" explicitly, status "+nodeState);
} else {
throw new IllegalStateException("No existing master; cannot start as standby");
}
@@ -241,6 +244,17 @@ public class HighAvailabilityManagerImpl implements HighAvailabilityManager {
throw new IllegalStateException("Unexpected high availability start-mode "+startMode+" for "+this);
}
+ if (nodeState==ManagementNodeState.STANDBY && startMode!=HighAvailabilityMode.STANDBY) {
+ // if standby not explicitly requested, we need promote to hot standby
+ // TODO may want a flag which enables/disables hot standby; for now, always enabled unless told to start in standby
+ nodeStateTransitionComplete = false;
+ // inform the world that we are transitioning (not eligible for promotion while going in to hot standby)
+ publishHealth();
+ attemptHotStandby();
+ publishHealth();
+ }
+
+ nodeStateTransitionComplete = true;
registerPollTask();
}
@@ -263,8 +277,23 @@ public class HighAvailabilityManagerImpl implements HighAvailabilityManager {
}
}
+ /** returns the node state this node is trying to be in */
+ public ManagementNodeState getTransitionTargetNodeState() {
+ return nodeState;
+ }
+
+ @SuppressWarnings("deprecation")
@Override
public ManagementNodeState getNodeState() {
+ if (nodeState==ManagementNodeState.FAILED) return nodeState;
+ // if target is master then we claim already being master, to prevent other nodes from taking it
+ // (we may fail subsequently of course)
+ if (nodeState==ManagementNodeState.MASTER) return nodeState;
+
+ // for backwards compatibility; remove in 0.8.0
+ if (nodeState==ManagementNodeState.UNINITIALISED) return ManagementNodeState.INITIALIZING;
+
+ if (!nodeStateTransitionComplete) return ManagementNodeState.INITIALIZING;
return nodeState;
}
@@ -366,12 +395,6 @@ public class HighAvailabilityManagerImpl implements HighAvailabilityManager {
if (LOG.isTraceEnabled()) LOG.trace("Published management-node health: {}", memento);
}
- protected ManagementNodeState toNodeStateForPersistence(ManagementNodeState nodeState) {
- // uninitialized is set as null - TODO confirm that's necessary; nicer if we don't need this method at all
- if (nodeState == ManagementNodeState.UNINITIALISED) return null;
- return nodeState;
- }
-
protected boolean isHeartbeatOk(ManagementNodeSyncRecord masterNode, ManagementNodeSyncRecord meNode) {
if (masterNode==null) return false;
if (meNode==null) {
@@ -455,7 +478,7 @@ public class HighAvailabilityManagerImpl implements HighAvailabilityManager {
if (demotingSelfInFavourOfOtherMaster) {
LOG.debug("Master-change for this node only, demoting "+ownNodeRecord.toVerboseString()+" in favour of official master "+newMasterNodeRecord.toVerboseString());
- demoteToStandby();
+ demoteToStandby(true);
return;
}
@@ -520,22 +543,29 @@ public class HighAvailabilityManagerImpl implements HighAvailabilityManager {
protected void demoteToFailed() {
nodeState = ManagementNodeState.FAILED;
- onDemotion();
+ onDemotionStopTasks();
+ nodeStateTransitionComplete = true;
publishDemotionFromMaster(true);
}
- protected void demoteToStandby() {
+ protected void demoteToStandby(boolean hot) {
if (!running) {
LOG.warn("Ignoring demote-from-master request, as HighAvailabilityManager is no longer running");
return;
}
+ nodeStateTransitionComplete = false;
nodeState = ManagementNodeState.STANDBY;
- onDemotion();
+ onDemotionStopTasks();
+ if (hot) {
+ publishDemotionFromMaster(false);
+ attemptHotStandby();
+ }
+ nodeStateTransitionComplete = true;
publishDemotionFromMaster(false);
}
- protected void onDemotion() {
+ protected void onDemotionStopTasks() {
managementContext.getRebindManager().stop();
for (Application app: managementContext.getApplications())
Entities.unmanage(app);
@@ -569,6 +599,13 @@ public class HighAvailabilityManagerImpl implements HighAvailabilityManager {
LOG.info("Cancelled "+tasks+" tasks on demotion");
}
+ /** starts hot standby, in foreground; the caller is responsible for publishing health afterwards.
+ * @return whether hot standby was possible (if not, errors should be stored elsewhere) */
+ protected boolean attemptHotStandby() {
+ nodeState = ManagementNodeState.HOT_STANDBY;
+ return true;
+ }
+
/**
* @param reportCleanedState - if true, the record for this mgmt node will be replaced with the
* actual current status known in this JVM (may be more recent than what is persisted);
@@ -580,7 +617,7 @@ public class HighAvailabilityManagerImpl implements HighAvailabilityManager {
// if HA is disabled, then we are the only node - no persistence; just load a memento to describe this node
Builder builder = ManagementPlaneSyncRecordImpl.builder()
.node(createManagementNodeSyncRecord(true));
- if (getNodeState() == ManagementNodeState.MASTER) {
+ if (getTransitionTargetNodeState() == ManagementNodeState.MASTER) {
builder.masterNodeId(ownNodeId);
}
return builder.build();
@@ -610,7 +647,7 @@ public class HighAvailabilityManagerImpl implements HighAvailabilityManager {
.masterNodeId(result.getMasterNodeId())
.nodes(allNodes);
builder.node(me);
- if (getNodeState() == ManagementNodeState.MASTER) {
+ if (getTransitionTargetNodeState() == ManagementNodeState.MASTER) {
builder.masterNodeId(ownNodeId);
}
result = builder.build();
@@ -631,7 +668,7 @@ public class HighAvailabilityManagerImpl implements HighAvailabilityManager {
brooklyn.entity.rebind.plane.dto.BasicManagementNodeSyncRecord.Builder builder = BasicManagementNodeSyncRecord.builder()
.brooklynVersion(BrooklynVersion.get())
.nodeId(ownNodeId)
- .status(toNodeStateForPersistence(getNodeState()))
+ .status(getNodeState())
.localTimestamp(timestamp)
.uri(managementContext.getManagementNodeUri().orNull());
if (useLocalTimestampAsRemoteTimestamp)
@@ -664,7 +701,7 @@ public class HighAvailabilityManagerImpl implements HighAvailabilityManager {
@Override
public ManagementNodeSyncRecord apply(@Nullable ManagementNodeSyncRecord input) {
if (input == null) return null;
- if (!(input.getStatus() == ManagementNodeState.STANDBY || input.getStatus() == ManagementNodeState.MASTER)) return input;
+ if (!(input.getStatus() == ManagementNodeState.STANDBY || input.getStatus() == ManagementNodeState.HOT_STANDBY || input.getStatus() == ManagementNodeState.MASTER)) return input;
if (isHeartbeatOk(input, referenceNode)) return input;
return BasicManagementNodeSyncRecord.builder()
.from(input)
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/c2b05ff2/core/src/test/java/brooklyn/management/ha/HighAvailabilityManagerInMemoryTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/brooklyn/management/ha/HighAvailabilityManagerInMemoryTest.java b/core/src/test/java/brooklyn/management/ha/HighAvailabilityManagerInMemoryTest.java
index 9166202..dfabd0b 100644
--- a/core/src/test/java/brooklyn/management/ha/HighAvailabilityManagerInMemoryTest.java
+++ b/core/src/test/java/brooklyn/management/ha/HighAvailabilityManagerInMemoryTest.java
@@ -29,5 +29,9 @@ public class HighAvailabilityManagerInMemoryTest extends HighAvailabilityManager
protected PersistenceObjectStore newPersistenceObjectStore() {
return new InMemoryObjectStore();
}
+
+ public void testGetManagementPlaneStatus() throws Exception {
+ super.testGetManagementPlaneStatus();
+ }
}
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/c2b05ff2/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 f83816e..dbdf3d3 100644
--- a/core/src/test/java/brooklyn/management/ha/HighAvailabilityManagerSplitBrainTest.java
+++ b/core/src/test/java/brooklyn/management/ha/HighAvailabilityManagerSplitBrainTest.java
@@ -20,6 +20,7 @@ package brooklyn.management.ha;
import static org.testng.Assert.assertEquals;
+import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.Map;
@@ -201,7 +202,7 @@ public class HighAvailabilityManagerSplitBrainTest {
log.info(n2+" HA: "+memento2);
assertEquals(memento2.getMasterNodeId(), n1.ownNodeId);
assertEquals(memento2.getManagementNodes().get(n1.ownNodeId).getStatus(), ManagementNodeState.MASTER);
- assertEquals(memento2.getManagementNodes().get(n2.ownNodeId).getStatus(), ManagementNodeState.STANDBY);
+ assertEquals(memento2.getManagementNodes().get(n2.ownNodeId).getStatus(), ManagementNodeState.HOT_STANDBY);
assertEquals(memento2.getManagementNodes().get(n1.ownNodeId).getRemoteTimestamp(), time0);
assertEquals(memento2.getManagementNodes().get(n2.ownNodeId).getRemoteTimestamp(), time0);
@@ -254,7 +255,7 @@ public class HighAvailabilityManagerSplitBrainTest {
log.info(n1+" HA now: "+memento1b);
// n1 comes back and demotes himself
- assertEquals(memento1b.getManagementNodes().get(n1.ownNodeId).getStatus(), ManagementNodeState.STANDBY);
+ assertEquals(memento1b.getManagementNodes().get(n1.ownNodeId).getStatus(), ManagementNodeState.HOT_STANDBY);
assertEquals(memento1b.getManagementNodes().get(n2.ownNodeId).getStatus(), ManagementNodeState.MASTER);
assertEquals(memento1b.getMasterNodeId(), n2.ownNodeId);
assertEquals(memento1b.getManagementNodes().get(n1.ownNodeId).getRemoteTimestamp(), time2);
@@ -263,7 +264,7 @@ public class HighAvailabilityManagerSplitBrainTest {
// n2 now sees itself as master, with n1 in standby again
ManagementPlaneSyncRecord memento2c = n2.ha.getManagementPlaneSyncState();
log.info(n2+" HA now: "+memento2c);
- assertEquals(memento2c.getManagementNodes().get(n1.ownNodeId).getStatus(), ManagementNodeState.STANDBY);
+ assertEquals(memento2c.getManagementNodes().get(n1.ownNodeId).getStatus(), ManagementNodeState.HOT_STANDBY);
assertEquals(memento2c.getManagementNodes().get(n2.ownNodeId).getStatus(), ManagementNodeState.MASTER);
assertEquals(memento2c.getMasterNodeId(), n2.ownNodeId);
assertEquals(memento2c.getManagementNodes().get(n1.ownNodeId).getRemoteTimestamp(), time2);
@@ -314,28 +315,33 @@ public class HighAvailabilityManagerSplitBrainTest {
Asserts.succeedsEventually(new Runnable() {
@Override public void run() {
ManagementPlaneSyncRecord memento = nodes.get(0).ha.getManagementPlaneSyncState();
- int masters=0, standbys=0, savedMasters=0, savedStandbys=0;
+ List<ManagementNodeState> counts = MutableList.of(), savedCounts = MutableList.of();
for (HaMgmtNode n: nodes) {
- if (n.ha.getNodeState()==ManagementNodeState.MASTER) masters++;
- if (n.ha.getNodeState()==ManagementNodeState.STANDBY) standbys++;
+ counts.add(n.ha.getNodeState());
ManagementNodeSyncRecord m = memento.getManagementNodes().get(n.ownNodeId);
if (m!=null) {
- if (m.getStatus()==ManagementNodeState.MASTER) savedMasters++;
- if (m.getStatus()==ManagementNodeState.STANDBY) savedStandbys++;
+ savedCounts.add(m.getStatus());
}
}
- log.info("while starting "+nodes.size()+" nodes: "+masters+" M + "+standbys+" zzz; "
+ log.info("while starting "+nodes.size()+" nodes: "
+ +Collections.frequency(counts, ManagementNodeState.MASTER)+" M + "
+ +Collections.frequency(counts, ManagementNodeState.HOT_STANDBY)+" hot + "
+ +Collections.frequency(counts, ManagementNodeState.STANDBY)+" warm + "
+ +Collections.frequency(counts, ManagementNodeState.INITIALIZING)+" init; "
+ memento.getManagementNodes().size()+" saved, "
- + memento.getMasterNodeId()+" master, "+savedMasters+" M + "+savedStandbys+" zzz");
+ +Collections.frequency(savedCounts, ManagementNodeState.MASTER)+" M + "
+ +Collections.frequency(savedCounts, ManagementNodeState.HOT_STANDBY)+" hot + "
+ +Collections.frequency(savedCounts, ManagementNodeState.STANDBY)+" warm + "
+ +Collections.frequency(savedCounts, ManagementNodeState.INITIALIZING)+" init");
if (timer.isRunning() && Duration.of(timer).compareTo(Duration.TEN_SECONDS)>0) {
log.warn("we seem to have a problem stabilizing"); //handy place to set a suspend-VM breakpoint!
timer.stop();
}
- assertEquals(masters, 1);
- assertEquals(standbys, nodes.size()-1);
- assertEquals(savedMasters, 1);
- assertEquals(savedStandbys, nodes.size()-1);
+ assertEquals(Collections.frequency(counts, ManagementNodeState.MASTER), 1);
+ assertEquals(Collections.frequency(counts, ManagementNodeState.HOT_STANDBY), nodes.size()-1);
+ assertEquals(Collections.frequency(savedCounts, ManagementNodeState.MASTER), 1);
+ assertEquals(Collections.frequency(savedCounts, ManagementNodeState.HOT_STANDBY), nodes.size()-1);
}});
} catch (Throwable t) {
log.warn("Failed to stabilize (rethrowing): "+t, t);
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/c2b05ff2/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 e0288e3..a657636 100644
--- a/core/src/test/java/brooklyn/management/ha/HighAvailabilityManagerTestFixture.java
+++ b/core/src/test/java/brooklyn/management/ha/HighAvailabilityManagerTestFixture.java
@@ -99,7 +99,7 @@ public abstract class HighAvailabilityManagerTestFixture {
.setRemoteTicker(getRemoteTicker())
.setPersister(persister);
persister.delta(ManagementPlaneSyncRecordDeltaImpl.builder()
- .node(newManagerMemento(ownNodeId, ManagementNodeState.STANDBY))
+ .node(newManagerMemento(ownNodeId, ManagementNodeState.HOT_STANDBY))
.build());
}
@@ -142,7 +142,7 @@ public abstract class HighAvailabilityManagerTestFixture {
// next poll fixes it.
public void testPromotes() throws Exception {
persister.delta(ManagementPlaneSyncRecordDeltaImpl.builder()
- .node(newManagerMemento(ownNodeId, ManagementNodeState.STANDBY))
+ .node(newManagerMemento(ownNodeId, ManagementNodeState.HOT_STANDBY))
.node(newManagerMemento("node1", ManagementNodeState.MASTER))
.setMaster("node1")
.build());
@@ -160,7 +160,7 @@ public abstract class HighAvailabilityManagerTestFixture {
@Test(groups="Integration") // because one second wait in succeedsContinually
public void testDoesNotPromoteIfMasterTimeoutNotExpired() throws Exception {
persister.delta(ManagementPlaneSyncRecordDeltaImpl.builder()
- .node(newManagerMemento(ownNodeId, ManagementNodeState.STANDBY))
+ .node(newManagerMemento(ownNodeId, ManagementNodeState.HOT_STANDBY))
.node(newManagerMemento("node1", ManagementNodeState.MASTER))
.setMaster("node1")
.build());
@@ -211,7 +211,7 @@ public abstract class HighAvailabilityManagerTestFixture {
@Test
public void testGetManagementPlaneSyncStateInfersTimedOutNodeAsFailed() throws Exception {
persister.delta(ManagementPlaneSyncRecordDeltaImpl.builder()
- .node(newManagerMemento(ownNodeId, ManagementNodeState.STANDBY))
+ .node(newManagerMemento(ownNodeId, ManagementNodeState.HOT_STANDBY))
.node(newManagerMemento("node1", ManagementNodeState.MASTER))
.setMaster("node1")
.build());
@@ -220,7 +220,7 @@ public abstract class HighAvailabilityManagerTestFixture {
ManagementPlaneSyncRecord state = manager.getManagementPlaneSyncState();
assertEquals(state.getManagementNodes().get("node1").getStatus(), ManagementNodeState.MASTER);
- assertEquals(state.getManagementNodes().get(ownNodeId).getStatus(), ManagementNodeState.STANDBY);
+ assertEquals(state.getManagementNodes().get(ownNodeId).getStatus(), ManagementNodeState.HOT_STANDBY);
// Simulate passage of time; ticker used by this HA-manager so it will "correctly" publish
// its own heartbeat with the new time; but node1's record is now out-of-date.
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/c2b05ff2/core/src/test/java/brooklyn/management/ha/MasterChooserTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/brooklyn/management/ha/MasterChooserTest.java b/core/src/test/java/brooklyn/management/ha/MasterChooserTest.java
index 23acc5c..6f8ba48 100644
--- a/core/src/test/java/brooklyn/management/ha/MasterChooserTest.java
+++ b/core/src/test/java/brooklyn/management/ha/MasterChooserTest.java
@@ -21,15 +21,20 @@ package brooklyn.management.ha;
import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertNull;
+import java.util.List;
+
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;
import brooklyn.BrooklynVersion;
+import brooklyn.entity.basic.EntityFunctions;
import brooklyn.entity.rebind.plane.dto.BasicManagementNodeSyncRecord;
import brooklyn.management.ha.BasicMasterChooser.AlphabeticMasterChooser;
+import brooklyn.management.ha.BasicMasterChooser.ScoredRecord;
import brooklyn.util.time.Duration;
-import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Iterables;
public class MasterChooserTest {
@@ -67,18 +72,42 @@ public class MasterChooserTest {
memento.addNode(newManagerMemento("node2", ManagementNodeState.STANDBY, now - 20*1000));
memento.addNode(newManagerMemento("node3", ManagementNodeState.STANDBY, now));
Duration heartbeatTimeout = Duration.THIRTY_SECONDS;
- assertEquals(chooser.filterHealthy(memento, heartbeatTimeout, now).keySet(), ImmutableSet.of("node2", "node3"));
+ assertEquals(getIds(chooser.sort(chooser.filterHealthy(memento, heartbeatTimeout, now))), ImmutableList.of("node2", "node3"));
+ }
+
+ protected static List<String> getIds(List<ScoredRecord<?>> filterHealthy) {
+ return ImmutableList.copyOf(Iterables.transform(filterHealthy, EntityFunctions.id()));
+ }
+
+ @Test
+ public void testFiltersOutByStatusNotPreferringMaster() throws Exception {
+ assertEquals(doTestFiltersOutByStatus(false, false), ImmutableList.of("node4", "node5"));
+ }
+ @Test
+ public void testFiltersOutByStatusPreferringMaster() throws Exception {
+ assertEquals(doTestFiltersOutByStatus(true, false), ImmutableList.of("node5", "node4"));
}
@Test
- public void testFiltersOutByStatus() throws Exception {
+ public void testFiltersOutByStatusNotPreferringHot() throws Exception {
+ assertEquals(doTestFiltersOutByStatus(false, true), ImmutableList.of("node4", "node5", "node6"));
+ }
+ @Test
+ public void testFiltersOutByStatusPreferringHot() throws Exception {
+ assertEquals(doTestFiltersOutByStatus(true, true), ImmutableList.of("node5", "node6", "node4"));
+ }
+
+ protected List<String> doTestFiltersOutByStatus(boolean preferHot, boolean includeHot) throws Exception {
+ chooser = new AlphabeticMasterChooser(preferHot);
memento.addNode(newManagerMemento("node1", ManagementNodeState.FAILED, now));
memento.addNode(newManagerMemento("node2", ManagementNodeState.TERMINATED, now));
memento.addNode(newManagerMemento("node3", null, now));
memento.addNode(newManagerMemento("node4", ManagementNodeState.STANDBY, now));
memento.addNode(newManagerMemento("node5", ManagementNodeState.MASTER, now));
+ if (includeHot)
+ memento.addNode(newManagerMemento("node6", ManagementNodeState.HOT_STANDBY, now));
Duration heartbeatTimeout = Duration.THIRTY_SECONDS;
- assertEquals(chooser.filterHealthy(memento, heartbeatTimeout, now).keySet(), ImmutableSet.of("node4", "node5"));
+ return getIds(chooser.sort(chooser.filterHealthy(memento, heartbeatTimeout, now)));
}
private ManagementNodeSyncRecord newManagerMemento(String nodeId, ManagementNodeState status, long timestamp) {
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/c2b05ff2/usage/cli/src/main/java/brooklyn/cli/Main.java
----------------------------------------------------------------------
diff --git a/usage/cli/src/main/java/brooklyn/cli/Main.java b/usage/cli/src/main/java/brooklyn/cli/Main.java
index 093f307..681e7a0 100644
--- a/usage/cli/src/main/java/brooklyn/cli/Main.java
+++ b/usage/cli/src/main/java/brooklyn/cli/Main.java
@@ -298,16 +298,18 @@ public class Main extends AbstractMain {
protected final static String HA_OPTION_AUTO = "auto";
protected final static String HA_OPTION_MASTER = "master";
protected final static String HA_OPTION_STANDBY = "standby";
- static { Enums.checkAllEnumeratedIgnoreCase(HighAvailabilityMode.class, HA_OPTION_AUTO, HA_OPTION_DISABLED, HA_OPTION_MASTER, HA_OPTION_STANDBY); }
+ protected final static String HA_OPTION_HOT_STANDBY = "hot_standby";
+ static { Enums.checkAllEnumeratedIgnoreCase(HighAvailabilityMode.class, HA_OPTION_AUTO, HA_OPTION_DISABLED, HA_OPTION_MASTER, HA_OPTION_STANDBY, HA_OPTION_HOT_STANDBY); }
- @Option(name = { HA_OPTION }, allowedValues = { HA_OPTION_DISABLED, HA_OPTION_AUTO, HA_OPTION_MASTER, HA_OPTION_STANDBY },
+ @Option(name = { HA_OPTION }, allowedValues = { HA_OPTION_DISABLED, HA_OPTION_AUTO, HA_OPTION_MASTER, HA_OPTION_STANDBY, HA_OPTION_HOT_STANDBY },
title = "high availability mode",
description =
"The high availability mode. Possible values are: \n"+
"disabled: management node works in isolation - will not cooperate with any other standby/master nodes in management plane; \n"+
"auto: will look for other management nodes, and will allocate itself as standby or master based on other nodes' states; \n"+
"master: will startup as master - if there is already a master then fails immediately; \n"+
- "standby: will start up as standby - if there is not already a master then fails immediately")
+ "standby: will start up as lukewarm standby - if there is not already a master then fails immediately; \n"+
+ "hot_standby: will start up as hot standby - if there is not already a master then fails immediately")
public String highAvailability = HA_OPTION_AUTO;
@VisibleForTesting
@@ -456,8 +458,8 @@ public class Main extends AbstractMain {
if (highAvailabilityMode.get() == HighAvailabilityMode.AUTO)
return HighAvailabilityMode.DISABLED;
throw new FatalConfigurationRuntimeException("Cannot specify highAvailability when persistence is disabled");
- } else if (persistMode == PersistMode.CLEAN && highAvailabilityMode.get() == HighAvailabilityMode.STANDBY) {
- throw new FatalConfigurationRuntimeException("Cannot specify highAvailability STANDBY when persistence is CLEAN");
+ } else if (persistMode == PersistMode.CLEAN && (highAvailabilityMode.get() == HighAvailabilityMode.STANDBY || highAvailabilityMode.get() == HighAvailabilityMode.HOT_STANDBY)) {
+ throw new FatalConfigurationRuntimeException("Cannot specify highAvailability "+highAvailabilityMode.get()+" when persistence is CLEAN");
}
}
return highAvailabilityMode.get();
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/c2b05ff2/usage/launcher/src/main/java/brooklyn/launcher/BrooklynLauncher.java
----------------------------------------------------------------------
diff --git a/usage/launcher/src/main/java/brooklyn/launcher/BrooklynLauncher.java b/usage/launcher/src/main/java/brooklyn/launcher/BrooklynLauncher.java
index dd5db0e..47d80a4 100644
--- a/usage/launcher/src/main/java/brooklyn/launcher/BrooklynLauncher.java
+++ b/usage/launcher/src/main/java/brooklyn/launcher/BrooklynLauncher.java
@@ -758,6 +758,7 @@ public class BrooklynLauncher {
case AUTO:
case MASTER:
case STANDBY:
+ case HOT_STANDBY:
startMode = highAvailabilityMode;
break;
case DISABLED:
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/c2b05ff2/usage/launcher/src/test/java/brooklyn/launcher/BrooklynLauncherHighAvailabilityTest.java
----------------------------------------------------------------------
diff --git a/usage/launcher/src/test/java/brooklyn/launcher/BrooklynLauncherHighAvailabilityTest.java b/usage/launcher/src/test/java/brooklyn/launcher/BrooklynLauncherHighAvailabilityTest.java
index 949638f..419124d 100644
--- a/usage/launcher/src/test/java/brooklyn/launcher/BrooklynLauncherHighAvailabilityTest.java
+++ b/usage/launcher/src/test/java/brooklyn/launcher/BrooklynLauncherHighAvailabilityTest.java
@@ -216,6 +216,24 @@ public class BrooklynLauncherHighAvailabilityTest {
}
}
+ @Test
+ public void testHighAvailabilityHotStandbyModeFailsIfNoExistingMaster() throws Exception {
+ try {
+ primary = BrooklynLauncher.newInstance();
+ primary.webconsole(false)
+ .brooklynProperties(LocalManagementContextForTests.setEmptyCatalogAsDefault(BrooklynProperties.Factory.newEmpty()))
+ .highAvailabilityMode(HighAvailabilityMode.HOT_STANDBY)
+ .persistMode(PersistMode.AUTO)
+ .persistenceDir(persistenceDir)
+ .persistPeriod(Duration.millis(10))
+ .application(EntitySpec.create(TestApplication.class))
+ .start();
+ fail();
+ } catch (IllegalStateException e) {
+ // success
+ }
+ }
+
private void assertOnlyApp(ManagementContext managementContext, Class<? extends Application> expectedType) {
assertEquals(managementContext.getApplications().size(), 1, "apps="+managementContext.getApplications());
assertNotNull(Iterables.find(managementContext.getApplications(), Predicates.instanceOf(TestApplication.class), null), "apps="+managementContext.getApplications());
[10/15] git commit: Ensure BrooklynObject management, remanagement,
and unmanagement, by replacing Location instances, refreshing the catalog,
and swapping out the delegate but keeping the entity proxy.
Posted by he...@apache.org.
Ensure BrooklynObject management, remanagement, and unmanagement, by replacing Location instances, refreshing the catalog, and swapping out the delegate but keeping the entity proxy.
Project: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/commit/4c866cb8
Tree: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/tree/4c866cb8
Diff: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/diff/4c866cb8
Branch: refs/heads/master
Commit: 4c866cb8767b28752a1e871bd2cd123f7e5a776a
Parents: a1d83ee
Author: Alex Heneveld <al...@cloudsoftcorp.com>
Authored: Tue Sep 30 23:03:22 2014 +0100
Committer: Alex Heneveld <al...@cloudsoftcorp.com>
Committed: Wed Oct 1 16:40:16 2014 +0100
----------------------------------------------------------------------
api/src/main/java/brooklyn/entity/Entity.java | 3 +-
.../brooklyn/entity/rebind/RebindManager.java | 14 +-
.../brooklyn/management/LocationManager.java | 3 -
.../brooklyn/entity/basic/AbstractEntity.java | 106 ++++++-----
.../entity/basic/AbstractGroupImpl.java | 25 +--
.../entity/proxying/EntityProxyImpl.java | 70 ++++---
.../entity/rebind/BasicEntityRebindSupport.java | 8 +-
.../rebind/RebindExceptionHandlerImpl.java | 2 +-
.../entity/rebind/RebindManagerImpl.java | 82 ++++++--
.../internal/BrooklynFeatureEnablement.java | 11 ++
.../location/basic/AbstractLocation.java | 67 +++----
.../location/basic/LocationInternal.java | 1 -
.../internal/EntityManagementSupport.java | 65 ++++---
.../internal/EntityManagerInternal.java | 22 ++-
.../management/internal/LocalAccessManager.java | 1 -
.../management/internal/LocalEntityManager.java | 190 +++++++++++++++----
.../internal/LocalLocationManager.java | 165 ++++++++++++----
.../internal/LocalManagementContext.java | 4 +-
.../internal/LocationManagerInternal.java | 40 ++++
.../internal/ManagementTransitionInfo.java | 30 ++-
.../internal/NonDeploymentEntityManager.java | 48 ++++-
.../internal/NonDeploymentLocationManager.java | 51 ++++-
.../NonDeploymentManagementContext.java | 2 +-
.../brooklyn/management/ha/HotStandbyTest.java | 50 +++--
24 files changed, 771 insertions(+), 289 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/4c866cb8/api/src/main/java/brooklyn/entity/Entity.java
----------------------------------------------------------------------
diff --git a/api/src/main/java/brooklyn/entity/Entity.java b/api/src/main/java/brooklyn/entity/Entity.java
index 9ff131e..8e32dcd 100644
--- a/api/src/main/java/brooklyn/entity/Entity.java
+++ b/api/src/main/java/brooklyn/entity/Entity.java
@@ -127,7 +127,8 @@ public interface Entity extends BrooklynObject {
/**
* Creates an {@link Entity} from the given spec and adds it, setting this entity as the parent,
- * returning the added child. */
+ * returning the added child. The added child is not yet managed, even if the parent is managed,
+ * so if adding post-management an explicit call to manage the child will be needed. */
<T extends Entity> T addChild(EntitySpec<T> spec);
/**
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/4c866cb8/api/src/main/java/brooklyn/entity/rebind/RebindManager.java
----------------------------------------------------------------------
diff --git a/api/src/main/java/brooklyn/entity/rebind/RebindManager.java b/api/src/main/java/brooklyn/entity/rebind/RebindManager.java
index 6f15599..9e62fa4 100644
--- a/api/src/main/java/brooklyn/entity/rebind/RebindManager.java
+++ b/api/src/main/java/brooklyn/entity/rebind/RebindManager.java
@@ -29,6 +29,7 @@ import brooklyn.mementos.BrooklynMementoPersister;
import brooklyn.mementos.BrooklynMementoRawData;
import brooklyn.util.time.Duration;
+import com.google.common.annotations.Beta;
import com.google.common.annotations.VisibleForTesting;
/**
@@ -58,13 +59,18 @@ public interface RebindManager {
public BrooklynMementoPersister getPersister();
/** @deprecated since 0.7; use {@link #rebind(ClassLoader, RebindExceptionHandler, ManagementNodeState)} */ @Deprecated
- public List<Application> rebind() throws IOException;
+ public List<Application> rebind();
/** @deprecated since 0.7; use {@link #rebind(ClassLoader, RebindExceptionHandler, ManagementNodeState)} */ @Deprecated
- public List<Application> rebind(ClassLoader classLoader) throws IOException;
+ public List<Application> rebind(ClassLoader classLoader);
/** @deprecated since 0.7; use {@link #rebind(ClassLoader, RebindExceptionHandler, ManagementNodeState)} */ @Deprecated
- public List<Application> rebind(ClassLoader classLoader, RebindExceptionHandler exceptionHandler) throws IOException;
- public List<Application> rebind(ClassLoader classLoader, RebindExceptionHandler exceptionHandler, ManagementNodeState mode) throws IOException;
+ public List<Application> rebind(ClassLoader classLoader, RebindExceptionHandler exceptionHandler);
+ /** causes this management context to rebind, loading data from the given backing store.
+ * use wisely, as this can cause local entities to be completely lost, or will throw in many other situations.
+ * in general it may be invoked for a new node becoming {@link ManagementNodeState#MASTER}
+ * or periodically for a node in {@link ManagementNodeState#HOT_STANDBY}. */
+ @Beta
+ public List<Application> rebind(ClassLoader classLoader, RebindExceptionHandler exceptionHandler, ManagementNodeState mode);
public BrooklynMementoRawData retrieveMementoRawData() throws IOException;
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/4c866cb8/api/src/main/java/brooklyn/management/LocationManager.java
----------------------------------------------------------------------
diff --git a/api/src/main/java/brooklyn/management/LocationManager.java b/api/src/main/java/brooklyn/management/LocationManager.java
index 4708474..c871aab 100644
--- a/api/src/main/java/brooklyn/management/LocationManager.java
+++ b/api/src/main/java/brooklyn/management/LocationManager.java
@@ -21,10 +21,7 @@ package brooklyn.management;
import java.util.Collection;
import java.util.Map;
-import javax.annotation.Nullable;
-
import brooklyn.location.Location;
-import brooklyn.location.LocationDefinition;
import brooklyn.location.LocationSpec;
/**
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/4c866cb8/core/src/main/java/brooklyn/entity/basic/AbstractEntity.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/entity/basic/AbstractEntity.java b/core/src/main/java/brooklyn/entity/basic/AbstractEntity.java
index 3c3c65f..de00c8a 100644
--- a/core/src/main/java/brooklyn/entity/basic/AbstractEntity.java
+++ b/core/src/main/java/brooklyn/entity/basic/AbstractEntity.java
@@ -53,6 +53,7 @@ import brooklyn.event.basic.AttributeSensorAndConfigKey;
import brooklyn.event.basic.BasicNotificationSensor;
import brooklyn.event.feed.AbstractFeed;
import brooklyn.event.feed.ConfigToAttributes;
+import brooklyn.internal.BrooklynFeatureEnablement;
import brooklyn.internal.storage.BrooklynStorage;
import brooklyn.internal.storage.Reference;
import brooklyn.internal.storage.impl.BasicReference;
@@ -359,8 +360,14 @@ public abstract class AbstractEntity extends AbstractBrooklynObject implements E
(o instanceof Entity && Objects.equal(getId(), ((Entity)o).getId()));
}
+ /** internal use only */ @Beta
public void setProxy(Entity proxy) {
- if (selfProxy != null) throw new IllegalStateException("Proxy is already set; cannot reset proxy for "+toString());
+ if (selfProxy != null)
+ throw new IllegalStateException("Proxy is already set; cannot reset proxy for "+toString());
+ resetProxy(proxy);
+ }
+ /** internal use only */ @Beta
+ public void resetProxy(Entity proxy) {
selfProxy = checkNotNull(proxy, "proxy");
}
@@ -403,53 +410,55 @@ public abstract class AbstractEntity extends AbstractBrooklynObject implements E
getManagementSupport().setManagementContext(managementContext);
entityType.setName(getEntityTypeName());
if (displayNameAutoGenerated) displayName.set(getEntityType().getSimpleName()+":"+Strings.maxlen(getId(), 4));
-
- Entity oldParent = parent.get();
- Set<Group> oldGroups = groups;
- Set<Entity> oldChildren = children;
- List<Location> oldLocations = locations.get();
- EntityConfigMap oldConfig = configsInternal;
- AttributeMap oldAttribs = attributesInternal;
- long oldCreationTimeUtc = creationTimeUtc.get();
- String oldDisplayName = displayName.get();
- String oldIconUrl = iconUrl.get();
-
- parent = managementContext.getStorage().getReference(getId()+"-parent");
- groups = SetFromLiveMap.create(managementContext.getStorage().<Group,Boolean>getMap(getId()+"-groups"));
- children = SetFromLiveMap.create(managementContext.getStorage().<Entity,Boolean>getMap(getId()+"-children"));
- locations = managementContext.getStorage().getNonConcurrentList(getId()+"-locations");
- creationTimeUtc = managementContext.getStorage().getReference(getId()+"-creationTime");
- displayName = managementContext.getStorage().getReference(getId()+"-displayName");
- iconUrl = managementContext.getStorage().getReference(getId()+"-iconUrl");
-
- // Only override stored defaults if we have actual values. We might be in setManagementContext
- // because we are reconstituting an existing entity in a new brooklyn management-node (in which
- // case believe what is already in the storage), or we might be in the middle of creating a new
- // entity. Normally for a new entity (using EntitySpec creation approach), this will get called
- // before setting the parent etc. However, for backwards compatibility we still support some
- // things calling the entity's constructor directly.
- if (oldParent != null) parent.set(oldParent);
- if (oldGroups.size() > 0) groups.addAll(oldGroups);
- if (oldChildren.size() > 0) children.addAll(oldChildren);
- if (oldLocations.size() > 0) locations.set(ImmutableList.copyOf(oldLocations));
- if (creationTimeUtc.isNull()) creationTimeUtc.set(oldCreationTimeUtc);
- if (displayName.isNull()) {
- displayName.set(oldDisplayName);
- } else {
- displayNameAutoGenerated = false;
- }
- if (iconUrl.isNull()) iconUrl.set(oldIconUrl);
-
- configsInternal = new EntityConfigMap(this, managementContext.getStorage().<ConfigKey<?>, Object>getMap(getId()+"-config"));
- if (oldConfig.getLocalConfig().size() > 0) {
- configsInternal.setLocalConfig(oldConfig.getLocalConfig());
- }
- refreshInheritedConfig();
-
- attributesInternal = new AttributeMap(this, managementContext.getStorage().<Collection<String>, Object>getMap(getId()+"-attributes"));
- if (oldAttribs.asRawMap().size() > 0) {
- for (Map.Entry<Collection<String>,Object> entry : oldAttribs.asRawMap().entrySet()) {
- attributesInternal.update(entry.getKey(), entry.getValue());
+
+ if (BrooklynFeatureEnablement.isEnabled(BrooklynFeatureEnablement.FEATURE_USE_BROOKLYN_LIVE_OBJECTS_DATAGRID_STORAGE)) {
+ Entity oldParent = parent.get();
+ Set<Group> oldGroups = groups;
+ Set<Entity> oldChildren = children;
+ List<Location> oldLocations = locations.get();
+ EntityConfigMap oldConfig = configsInternal;
+ AttributeMap oldAttribs = attributesInternal;
+ long oldCreationTimeUtc = creationTimeUtc.get();
+ String oldDisplayName = displayName.get();
+ String oldIconUrl = iconUrl.get();
+
+ parent = managementContext.getStorage().getReference(getId()+"-parent");
+ groups = SetFromLiveMap.create(managementContext.getStorage().<Group,Boolean>getMap(getId()+"-groups"));
+ children = SetFromLiveMap.create(managementContext.getStorage().<Entity,Boolean>getMap(getId()+"-children"));
+ locations = managementContext.getStorage().getNonConcurrentList(getId()+"-locations");
+ creationTimeUtc = managementContext.getStorage().getReference(getId()+"-creationTime");
+ displayName = managementContext.getStorage().getReference(getId()+"-displayName");
+ iconUrl = managementContext.getStorage().getReference(getId()+"-iconUrl");
+
+ // Only override stored defaults if we have actual values. We might be in setManagementContext
+ // because we are reconstituting an existing entity in a new brooklyn management-node (in which
+ // case believe what is already in the storage), or we might be in the middle of creating a new
+ // entity. Normally for a new entity (using EntitySpec creation approach), this will get called
+ // before setting the parent etc. However, for backwards compatibility we still support some
+ // things calling the entity's constructor directly.
+ if (oldParent != null) parent.set(oldParent);
+ if (oldGroups.size() > 0) groups.addAll(oldGroups);
+ if (oldChildren.size() > 0) children.addAll(oldChildren);
+ if (oldLocations.size() > 0) locations.set(ImmutableList.copyOf(oldLocations));
+ if (creationTimeUtc.isNull()) creationTimeUtc.set(oldCreationTimeUtc);
+ if (displayName.isNull()) {
+ displayName.set(oldDisplayName);
+ } else {
+ displayNameAutoGenerated = false;
+ }
+ if (iconUrl.isNull()) iconUrl.set(oldIconUrl);
+
+ configsInternal = new EntityConfigMap(this, managementContext.getStorage().<ConfigKey<?>, Object>getMap(getId()+"-config"));
+ if (oldConfig.getLocalConfig().size() > 0) {
+ configsInternal.setLocalConfig(oldConfig.getLocalConfig());
+ }
+ refreshInheritedConfig();
+
+ attributesInternal = new AttributeMap(this, managementContext.getStorage().<Collection<String>, Object>getMap(getId()+"-attributes"));
+ if (oldAttribs.asRawMap().size() > 0) {
+ for (Map.Entry<Collection<String>,Object> entry : oldAttribs.asRawMap().entrySet()) {
+ attributesInternal.update(entry.getKey(), entry.getValue());
+ }
}
}
}
@@ -533,7 +542,6 @@ public abstract class AbstractEntity extends AbstractBrooklynObject implements E
throw new IllegalStateException("loop detected trying to set parent of "+this+" as "+entity+", which is already a descendent");
parent.set(entity);
- //previously tested entity!=null but that should be guaranteed?
entity.addChild(getProxyIfAvailable());
refreshInheritedConfig();
previouslyOwned = true;
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/4c866cb8/core/src/main/java/brooklyn/entity/basic/AbstractGroupImpl.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/entity/basic/AbstractGroupImpl.java b/core/src/main/java/brooklyn/entity/basic/AbstractGroupImpl.java
index a8ace7f..bff6e2c 100644
--- a/core/src/main/java/brooklyn/entity/basic/AbstractGroupImpl.java
+++ b/core/src/main/java/brooklyn/entity/basic/AbstractGroupImpl.java
@@ -30,6 +30,7 @@ import org.slf4j.LoggerFactory;
import brooklyn.entity.Entity;
import brooklyn.entity.Group;
import brooklyn.entity.proxying.EntitySpec;
+import brooklyn.internal.BrooklynFeatureEnablement;
import brooklyn.management.internal.ManagementContextInternal;
import brooklyn.util.collections.SetFromLiveMap;
@@ -69,17 +70,19 @@ public abstract class AbstractGroupImpl extends AbstractEntity implements Abstra
public void setManagementContext(ManagementContextInternal managementContext) {
super.setManagementContext(managementContext);
- Set<Entity> oldMembers = members;
-
- members = SetFromLiveMap.create(managementContext.getStorage().<Entity,Boolean>getMap(getId()+"-members"));
-
- // Only override stored defaults if we have actual values. We might be in setManagementContext
- // because we are reconstituting an existing entity in a new brooklyn management-node (in which
- // case believe what is already in the storage), or we might be in the middle of creating a new
- // entity. Normally for a new entity (using EntitySpec creation approach), this will get called
- // before setting the parent etc. However, for backwards compatibility we still support some
- // things calling the entity's constructor directly.
- if (oldMembers.size() > 0) members.addAll(oldMembers);
+ if (BrooklynFeatureEnablement.isEnabled(BrooklynFeatureEnablement.FEATURE_USE_BROOKLYN_LIVE_OBJECTS_DATAGRID_STORAGE)) {
+ Set<Entity> oldMembers = members;
+
+ members = SetFromLiveMap.create(managementContext.getStorage().<Entity,Boolean>getMap(getId()+"-members"));
+
+ // Only override stored defaults if we have actual values. We might be in setManagementContext
+ // because we are reconstituting an existing entity in a new brooklyn management-node (in which
+ // case believe what is already in the storage), or we might be in the middle of creating a new
+ // entity. Normally for a new entity (using EntitySpec creation approach), this will get called
+ // before setting the parent etc. However, for backwards compatibility we still support some
+ // things calling the entity's constructor directly.
+ if (oldMembers.size() > 0) members.addAll(oldMembers);
+ }
}
@Override
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/4c866cb8/core/src/main/java/brooklyn/entity/proxying/EntityProxyImpl.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/entity/proxying/EntityProxyImpl.java b/core/src/main/java/brooklyn/entity/proxying/EntityProxyImpl.java
index 45364ba..094c24c 100644
--- a/core/src/main/java/brooklyn/entity/proxying/EntityProxyImpl.java
+++ b/core/src/main/java/brooklyn/entity/proxying/EntityProxyImpl.java
@@ -34,9 +34,11 @@ import brooklyn.entity.basic.AbstractEntity;
import brooklyn.entity.basic.EntityInternal;
import brooklyn.entity.basic.EntityLocal;
import brooklyn.entity.basic.EntityTransientCopyInternal;
+import brooklyn.entity.rebind.RebindManagerImpl.RebindTracker;
import brooklyn.management.ManagementContext;
import brooklyn.management.internal.EffectorUtils;
import brooklyn.management.internal.EntityManagerInternal;
+import brooklyn.management.internal.ManagementTransitionInfo.ManagementTransitionMode;
import com.google.common.base.Objects;
import com.google.common.collect.Sets;
@@ -53,10 +55,9 @@ public class EntityProxyImpl implements java.lang.reflect.InvocationHandler {
// TODO Currently the proxy references the real entity and invokes methods on it directly.
// As we work on remoting/distribution, this will be replaced by RPC.
- @SuppressWarnings("unused")
private static final Logger LOG = LoggerFactory.getLogger(EntityProxyImpl.class);
- private final Entity delegate;
+ private Entity delegate;
private Boolean isMaster;
private static final Set<MethodSignature> OBJECT_METHODS = Sets.newLinkedHashSet();
@@ -96,6 +97,15 @@ public class EntityProxyImpl implements java.lang.reflect.InvocationHandler {
this.delegate = checkNotNull(entity, "entity");
}
+ /** invoked to specify that a different underlying delegate should be used,
+ * e.g. because we are switching copy impls or switching primary/copy*/
+ // TODO XXX because the delegate has a backpointer to the proxy, there is potentially
+ // a nasty problem wherein an old proxy is kept, with a pointer to an old delegate.
+ public synchronized void resetDelegate(Entity delegate) {
+ this.delegate = delegate;
+ this.isMaster = null;
+ }
+
@Override
public String toString() {
return delegate.toString();
@@ -103,11 +113,23 @@ public class EntityProxyImpl implements java.lang.reflect.InvocationHandler {
protected boolean isMaster() {
if (isMaster!=null) return isMaster;
+
ManagementContext mgmt = ((EntityInternal)delegate).getManagementContext();
- Boolean isReadOnlyNow = ((EntityManagerInternal)mgmt.getEntityManager()).isReadOnly(delegate);
- if (isReadOnlyNow==null) return false;
- isMaster = !isReadOnlyNow;
- return isMaster;
+ ManagementTransitionMode mode = ((EntityManagerInternal)mgmt.getEntityManager()).getLastManagementTransitionMode(delegate.getId());
+ Boolean ro = ((EntityInternal)delegate).getManagementSupport().isReadOnlyRaw();
+
+ if (mode==null || ro==null) {
+ // not configured yet
+ return false;
+ }
+ boolean isMasterX = !mode.isReadOnly();
+ if (isMasterX != !ro) {
+ LOG.warn("Inconsistent read-only state for "+delegate+" (possibly rebinding); "
+ + "management thinks "+isMasterX+" but entity thinks "+!ro);
+ return false;
+ }
+ isMaster = isMasterX;
+ return isMasterX;
}
public Object invoke(Object proxy, final Method m, final Object[] args) throws Throwable {
@@ -119,25 +141,29 @@ public class EntityProxyImpl implements java.lang.reflect.InvocationHandler {
Object result;
if (OBJECT_METHODS.contains(sig)) {
- result = m.invoke(this, args);
- } else if (!isMaster()) {
- if (ENTITY_PERMITTED_READ_ONLY_METHODS.contains(sig)) {
- result = m.invoke(delegate, args);
- } else if (isMaster==null) {
- // rebinding or caller manipulating before management; permit all access
- result = m.invoke(delegate, args);
- } else {
- throw new UnsupportedOperationException("Call to '"+sig+"' not permitted on read-only entity "+delegate);
- }
- } else if (ENTITY_NON_EFFECTOR_METHODS.contains(sig)) {
+ result = m.invoke(delegate, args);
+ } else if (ENTITY_PERMITTED_READ_ONLY_METHODS.contains(sig)) {
result = m.invoke(delegate, args);
} else {
- Object[] nonNullArgs = (args == null) ? new Object[0] : args;
- Effector<?> eff = findEffector(m, nonNullArgs);
- if (eff != null) {
- result = EffectorUtils.invokeMethodEffector(delegate, eff, nonNullArgs);
+ if (!isMaster()) {
+ if (isMaster==null || RebindTracker.isRebinding()) {
+ // rebinding or caller manipulating before management; permit all access
+ // (as of this writing, things seem to work fine without the isRebinding check;
+ // but including in it may allow us to tighten the methods in EntityTransientCopyInternal)
+ result = m.invoke(delegate, args);
+ } else {
+ throw new UnsupportedOperationException("Call to '"+sig+"' not permitted on read-only entity "+delegate);
+ }
+ } else if (ENTITY_NON_EFFECTOR_METHODS.contains(sig)) {
+ result = m.invoke(delegate, args);
} else {
- result = m.invoke(delegate, nonNullArgs);
+ Object[] nonNullArgs = (args == null) ? new Object[0] : args;
+ Effector<?> eff = findEffector(m, nonNullArgs);
+ if (eff != null) {
+ result = EffectorUtils.invokeMethodEffector(delegate, eff, nonNullArgs);
+ } else {
+ result = m.invoke(delegate, nonNullArgs);
+ }
}
}
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/4c866cb8/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 624d3dd..849ef05 100644
--- a/core/src/main/java/brooklyn/entity/rebind/BasicEntityRebindSupport.java
+++ b/core/src/main/java/brooklyn/entity/rebind/BasicEntityRebindSupport.java
@@ -192,11 +192,15 @@ public class BasicEntityRebindSupport extends AbstractBrooklynObjectRebindSuppor
}
}
+ protected Entity proxy(Entity target) {
+ return target instanceof AbstractEntity ? ((AbstractEntity)target).getProxyIfAvailable() : target;
+ }
+
protected void addChildren(RebindContext rebindContext, EntityMemento memento) {
for (String childId : memento.getChildren()) {
Entity child = rebindContext.getEntity(childId);
if (child != null) {
- entity.addChild(child);
+ entity.addChild(proxy(child));
} else {
LOG.warn("Entity not found; discarding child {} of entity {}({})",
new Object[] {childId, memento.getType(), memento.getId()});
@@ -207,7 +211,7 @@ public class BasicEntityRebindSupport extends AbstractBrooklynObjectRebindSuppor
protected void setParent(RebindContext rebindContext, EntityMemento memento) {
Entity parent = (memento.getParent() != null) ? rebindContext.getEntity(memento.getParent()) : null;
if (parent != null) {
- entity.setParent(parent);
+ entity.setParent(proxy(parent));
} else if (memento.getParent() != null){
LOG.warn("Entity not found; discarding parent {} of entity {}({}), so entity will be orphaned and unmanaged",
new Object[] {memento.getParent(), memento.getType(), memento.getId()});
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/4c866cb8/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 954d95b..870a031 100644
--- a/core/src/main/java/brooklyn/entity/rebind/RebindExceptionHandlerImpl.java
+++ b/core/src/main/java/brooklyn/entity/rebind/RebindExceptionHandlerImpl.java
@@ -356,7 +356,7 @@ public class RebindExceptionHandlerImpl implements RebindExceptionHandler {
}
protected void onDoneImpl(Exception e) {
- if (e != null) Exceptions.propagateIfFatal(e);
+ Exceptions.propagateIfFatal(e);
List<Exception> allExceptions = Lists.newArrayList();
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/4c866cb8/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 cddb43f..8c067f1 100644
--- a/core/src/main/java/brooklyn/entity/rebind/RebindManagerImpl.java
+++ b/core/src/main/java/brooklyn/entity/rebind/RebindManagerImpl.java
@@ -24,7 +24,9 @@ import java.io.IOException;
import java.util.Collection;
import java.util.List;
import java.util.Map;
+import java.util.Set;
import java.util.concurrent.Callable;
+import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
@@ -61,7 +63,9 @@ import brooklyn.management.Task;
import brooklyn.management.ha.HighAvailabilityManagerImpl;
import brooklyn.management.ha.ManagementNodeState;
import brooklyn.management.internal.EntityManagerInternal;
+import brooklyn.management.internal.LocationManagerInternal;
import brooklyn.management.internal.ManagementContextInternal;
+import brooklyn.management.internal.ManagementTransitionInfo.ManagementTransitionMode;
import brooklyn.mementos.BrooklynMemento;
import brooklyn.mementos.BrooklynMementoManifest;
import brooklyn.mementos.BrooklynMementoPersister;
@@ -95,6 +99,7 @@ import com.google.common.base.Optional;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
+import com.google.common.collect.Sets;
/** Manages the persistence/rebind process.
* <p>
@@ -132,6 +137,7 @@ public class RebindManagerImpl implements RebindManager {
private volatile boolean readOnlyRunning = false;
private volatile ScheduledTask readOnlyTask = null;
+ private transient Semaphore rebindActive = new Semaphore(1);
private volatile BrooklynMementoPersister persistenceStoreAccess;
@@ -369,22 +375,22 @@ public class RebindManagerImpl implements RebindManager {
}
@Override
- public List<Application> rebind() throws IOException {
+ public List<Application> rebind() {
return rebind(null, null, null);
}
@Override
- public List<Application> rebind(final ClassLoader classLoader) throws IOException {
+ public List<Application> rebind(final ClassLoader classLoader) {
return rebind(classLoader, null, null);
}
@Override
- public List<Application> rebind(final ClassLoader classLoader, final RebindExceptionHandler exceptionHandler) throws IOException {
+ 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) throws IOException {
+ public List<Application> rebind(ClassLoader classLoaderO, RebindExceptionHandler exceptionHandlerO, ManagementNodeState modeO) {
final ClassLoader classLoader = classLoaderO!=null ? classLoaderO :
managementContext.getCatalog().getRootClassLoader();
final RebindExceptionHandler exceptionHandler = exceptionHandlerO!=null ? exceptionHandlerO :
@@ -446,9 +452,12 @@ public class RebindManagerImpl implements RebindManager {
}
}
- protected List<Application> rebindImpl(final ClassLoader classLoader, final RebindExceptionHandler exceptionHandler, ManagementNodeState mode) throws IOException {
+ protected List<Application> rebindImpl(final ClassLoader classLoader, final RebindExceptionHandler exceptionHandler, ManagementNodeState mode) {
checkNotNull(classLoader, "classLoader");
+ try {
+ rebindActive.acquire();
+ } catch (InterruptedException e1) { Exceptions.propagate(e1); }
RebindTracker.setRebinding();
try {
exceptionHandler.onStart();
@@ -526,9 +535,6 @@ public class RebindManagerImpl implements RebindManager {
try {
Entity entity = newEntity(entityId, entityType, reflections);
- if (rebindContext.isReadOnly(entity)) {
- ((EntityInternal)entity).getManagementSupport().setReadOnly();
- }
rebindContext.registerEntity(entityId, entity);
} catch (Exception e) {
exceptionHandler.onCreateFailed(BrooklynObjectType.ENTITY, entityId, entityType, e);
@@ -770,20 +776,41 @@ public class RebindManagerImpl implements RebindManager {
//
LOG.debug("RebindManager managing locations");
+ LocationManagerInternal locationManager = (LocationManagerInternal)managementContext.getLocationManager();
+ Set<String> oldLocations = Sets.newLinkedHashSet(locationManager.getLocationIds());
+ for (Location location: rebindContext.getLocations()) {
+ ManagementTransitionMode oldMode = locationManager.getLastManagementTransitionMode(location.getId());
+ locationManager.setManagementTransitionMode(location, computeMode(location, oldMode, rebindContext.isReadOnly(location)) );
+ if (oldMode!=null)
+ oldLocations.remove(location.getId());
+ }
for (Location location: rebindContext.getLocations()) {
if (location.getParent()==null) {
// manage all root locations
- // LocationManager.manage perhaps should not be deprecated, as we need to do this I think?
try {
- managementContext.getLocationManager().manage(location);
+ ((LocationManagerInternal)managementContext.getLocationManager()).manageRebindedRoot(location);
} catch (Exception e) {
exceptionHandler.onManageFailed(BrooklynObjectType.LOCATION, location, e);
}
}
}
+ // destroy old
+ for (String oldLocationId: oldLocations) {
+ locationManager.unmanage(locationManager.getLocation(oldLocationId), ManagementTransitionMode.REBINDING_DESTROYED);
+ }
// Manage the top-level apps (causing everything under them to become managed)
LOG.debug("RebindManager managing entities");
+ EntityManagerInternal entityManager = (EntityManagerInternal)managementContext.getEntityManager();
+ Set<String> oldEntities = Sets.newLinkedHashSet(entityManager.getEntityIds());
+ for (Entity entity: rebindContext.getEntities()) {
+ ((EntityInternal)entity).getManagementSupport().setReadOnly( rebindContext.isReadOnly(entity) );
+
+ ManagementTransitionMode oldMode = entityManager.getLastManagementTransitionMode(entity.getId());
+ entityManager.setManagementTransitionMode(entity, computeMode(entity, oldMode, rebindContext.isReadOnly(entity)) );
+ if (oldMode!=null)
+ oldEntities.remove(entity.getId());
+ }
List<Application> apps = Lists.newArrayList();
for (String appId : memento.getApplicationIds()) {
Entity entity = rebindContext.getEntity(appId);
@@ -792,16 +819,17 @@ public class RebindManagerImpl implements RebindManager {
exceptionHandler.onNotFound(BrooklynObjectType.ENTITY, appId);
} else {
try {
- if (rebindContext.isReadOnly(entity)) {
- ((EntityManagerInternal)managementContext.getEntityManager()).setReadOnly(entity);
- }
- managementContext.getEntityManager().manage(entity);
+ entityManager.manageRebindedRoot(entity);
} catch (Exception e) {
exceptionHandler.onManageFailed(BrooklynObjectType.ENTITY, entity, e);
}
apps.add((Application)entity);
}
}
+ // destroy old
+ for (String oldEntityId: oldEntities) {
+ entityManager.unmanage(entityManager.getEntity(oldEntityId), ManagementTransitionMode.REBINDING_DESTROYED);
+ }
// Register catalogue items with the management context.
CatalogLoadMode catalogLoadMode = managementContext.getConfig().getConfig(BrooklynServerConfig.CATALOG_LOAD_MODE);
@@ -851,14 +879,36 @@ public class RebindManagerImpl implements RebindManager {
// Return the top-level applications
LOG.debug("RebindManager complete; return apps: {}", memento.getApplicationIds());
return apps;
-
- } catch (RuntimeException e) {
+
+ } catch (Exception e) {
throw exceptionHandler.onFailed(e);
} finally {
+ rebindActive.release();
RebindTracker.reset();
}
}
+ static ManagementTransitionMode computeMode(BrooklynObject item, ManagementTransitionMode oldMode, boolean isNowReadOnly) {
+ return computeMode(item, oldMode==null ? null : oldMode.wasReadOnly(), isNowReadOnly);
+ }
+
+ static ManagementTransitionMode computeMode(BrooklynObject item, Boolean wasReadOnly, boolean isNowReadOnly) {
+ if (wasReadOnly==null) {
+ // not known
+ if (Boolean.TRUE.equals(isNowReadOnly)) return ManagementTransitionMode.REBINDING_READONLY;
+ else return ManagementTransitionMode.CREATING;
+ } else {
+ if (wasReadOnly && isNowReadOnly)
+ return ManagementTransitionMode.REBINDING_READONLY;
+ else if (wasReadOnly)
+ return ManagementTransitionMode.REBINDING_NO_LONGER_PRIMARY;
+ else if (isNowReadOnly)
+ return ManagementTransitionMode.REBINDING_BECOMING_PRIMARY;
+ else
+ throw new IllegalStateException("Rebinding master not supported: "+item);
+ }
+ }
+
/**
* Sorts the map of nodes, so that a node's parent is guaranteed to come before that node
* (unless the parent is missing).
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/4c866cb8/core/src/main/java/brooklyn/internal/BrooklynFeatureEnablement.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/internal/BrooklynFeatureEnablement.java b/core/src/main/java/brooklyn/internal/BrooklynFeatureEnablement.java
index eb98144..fc938c4 100644
--- a/core/src/main/java/brooklyn/internal/BrooklynFeatureEnablement.java
+++ b/core/src/main/java/brooklyn/internal/BrooklynFeatureEnablement.java
@@ -23,8 +23,10 @@ import java.util.Map;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import brooklyn.internal.storage.BrooklynStorage;
import brooklyn.management.ha.HighAvailabilityMode;
+import com.google.common.annotations.Beta;
import com.google.common.collect.Maps;
/**
@@ -35,6 +37,7 @@ import com.google.common.collect.Maps;
*
* @author aled
*/
+@Beta
public class BrooklynFeatureEnablement {
private static final Logger LOG = LoggerFactory.getLogger(BrooklynFeatureEnablement.class);
@@ -50,6 +53,13 @@ public class BrooklynFeatureEnablement {
/** whether the default standby mode is {@link HighAvailabilityMode#HOT_STANDBY} or falling back to the traditional
* {@link HighAvailabilityMode#STANDBY} */
public static final String FEATURE_DEFAULT_STANDBY_IS_HOT_PROPERTY = "brooklyn.experimental.feature.defaultStandbyIsHot";
+
+ /** whether to attempt to use {@link BrooklynStorage} (datagrid) as a backing store for data;
+ * note this is <b>not</b> compatible with {@link #FEATURE_DEFAULT_STANDBY_IS_HOT_PROPERTY}
+ * which uses a blob/file store and a larger-granularity rebind process than was intended with the datagrid */
+ /* not sure if we still even need this? now the rebind/read-only feature reloads on demand from the persistence store;
+ * the data-grid backing */
+ public static final String FEATURE_USE_BROOKLYN_LIVE_OBJECTS_DATAGRID_STORAGE = "brooklyn.experimental.feature.useBrooklynLiveObjectsDatagridStorage";
private static final Map<String, Boolean> FEATURE_ENABLEMENTS = Maps.newLinkedHashMap();
@@ -65,6 +75,7 @@ public class BrooklynFeatureEnablement {
setDefault(FEATURE_FEED_PERSISTENCE_PROPERTY, true);
setDefault(FEATURE_CATALOG_PERSISTENCE_PROPERTY, true);
setDefault(FEATURE_DEFAULT_STANDBY_IS_HOT_PROPERTY, false);
+ setDefault(FEATURE_USE_BROOKLYN_LIVE_OBJECTS_DATAGRID_STORAGE, false);
}
static {
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/4c866cb8/core/src/main/java/brooklyn/location/basic/AbstractLocation.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/location/basic/AbstractLocation.java b/core/src/main/java/brooklyn/location/basic/AbstractLocation.java
index 7535564..473ce78 100644
--- a/core/src/main/java/brooklyn/location/basic/AbstractLocation.java
+++ b/core/src/main/java/brooklyn/location/basic/AbstractLocation.java
@@ -39,6 +39,7 @@ import brooklyn.entity.rebind.BasicLocationRebindSupport;
import brooklyn.entity.rebind.RebindSupport;
import brooklyn.entity.trait.Configurable;
import brooklyn.event.basic.BasicConfigKey;
+import brooklyn.internal.BrooklynFeatureEnablement;
import brooklyn.internal.storage.BrooklynStorage;
import brooklyn.internal.storage.Reference;
import brooklyn.internal.storage.impl.BasicReference;
@@ -166,38 +167,40 @@ public abstract class AbstractLocation extends AbstractBrooklynObject implements
super.setManagementContext(managementContext);
if (displayNameAutoGenerated && getId() != null) name.set(getClass().getSimpleName()+":"+getId().substring(0, Math.min(getId().length(),4)));
- Location oldParent = parent.get();
- Set<Location> oldChildren = children;
- Map<String, Object> oldConfig = configBag.getAllConfig();
- Long oldCreationTimeUtc = creationTimeUtc.get();
- String oldDisplayName = name.get();
- HostGeoInfo oldHostGeoInfo = hostGeoInfo.get();
-
- parent = managementContext.getStorage().getReference(getId()+"-parent");
- children = SetFromLiveMap.create(managementContext.getStorage().<Location,Boolean>getMap(getId()+"-children"));
- creationTimeUtc = managementContext.getStorage().getReference(getId()+"-creationTime");
- hostGeoInfo = managementContext.getStorage().getReference(getId()+"-hostGeoInfo");
- name = managementContext.getStorage().getReference(getId()+"-displayName");
-
- // Only override stored defaults if we have actual values. We might be in setManagementContext
- // because we are reconstituting an existing entity in a new brooklyn management-node (in which
- // case believe what is already in the storage), or we might be in the middle of creating a new
- // entity. Normally for a new entity (using EntitySpec creation approach), this will get called
- // before setting the parent etc. However, for backwards compatibility we still support some
- // things calling the entity's constructor directly.
- if (oldParent != null) parent.set(oldParent);
- if (oldChildren.size() > 0) children.addAll(oldChildren);
- if (creationTimeUtc.isNull()) creationTimeUtc.set(oldCreationTimeUtc);
- if (hostGeoInfo.isNull()) hostGeoInfo.set(oldHostGeoInfo);
- if (name.isNull()) {
- name.set(oldDisplayName);
- } else {
- displayNameAutoGenerated = false;
- }
-
- configBag = ConfigBag.newLiveInstance(managementContext.getStorage().<String,Object>getMap(getId()+"-config"));
- if (oldConfig.size() > 0) {
- configBag.putAll(oldConfig);
+ if (BrooklynFeatureEnablement.isEnabled(BrooklynFeatureEnablement.FEATURE_USE_BROOKLYN_LIVE_OBJECTS_DATAGRID_STORAGE)) {
+ Location oldParent = parent.get();
+ Set<Location> oldChildren = children;
+ Map<String, Object> oldConfig = configBag.getAllConfig();
+ Long oldCreationTimeUtc = creationTimeUtc.get();
+ String oldDisplayName = name.get();
+ HostGeoInfo oldHostGeoInfo = hostGeoInfo.get();
+
+ parent = managementContext.getStorage().getReference(getId()+"-parent");
+ children = SetFromLiveMap.create(managementContext.getStorage().<Location,Boolean>getMap(getId()+"-children"));
+ creationTimeUtc = managementContext.getStorage().getReference(getId()+"-creationTime");
+ hostGeoInfo = managementContext.getStorage().getReference(getId()+"-hostGeoInfo");
+ name = managementContext.getStorage().getReference(getId()+"-displayName");
+
+ // Only override stored defaults if we have actual values. We might be in setManagementContext
+ // because we are reconstituting an existing entity in a new brooklyn management-node (in which
+ // case believe what is already in the storage), or we might be in the middle of creating a new
+ // entity. Normally for a new entity (using EntitySpec creation approach), this will get called
+ // before setting the parent etc. However, for backwards compatibility we still support some
+ // things calling the entity's constructor directly.
+ if (oldParent != null) parent.set(oldParent);
+ if (oldChildren.size() > 0) children.addAll(oldChildren);
+ if (creationTimeUtc.isNull()) creationTimeUtc.set(oldCreationTimeUtc);
+ if (hostGeoInfo.isNull()) hostGeoInfo.set(oldHostGeoInfo);
+ if (name.isNull()) {
+ name.set(oldDisplayName);
+ } else {
+ displayNameAutoGenerated = false;
+ }
+
+ configBag = ConfigBag.newLiveInstance(managementContext.getStorage().<String,Object>getMap(getId()+"-config"));
+ if (oldConfig.size() > 0) {
+ configBag.putAll(oldConfig);
+ }
}
}
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/4c866cb8/core/src/main/java/brooklyn/location/basic/LocationInternal.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/location/basic/LocationInternal.java b/core/src/main/java/brooklyn/location/basic/LocationInternal.java
index 8c8f38c..a31b0c8 100644
--- a/core/src/main/java/brooklyn/location/basic/LocationInternal.java
+++ b/core/src/main/java/brooklyn/location/basic/LocationInternal.java
@@ -24,7 +24,6 @@ import brooklyn.basic.BrooklynObjectInternal;
import brooklyn.config.ConfigKey;
import brooklyn.entity.basic.ConfigKeys;
import brooklyn.entity.rebind.RebindSupport;
-import brooklyn.entity.rebind.Rebindable;
import brooklyn.location.Location;
import brooklyn.management.ManagementContext;
import brooklyn.mementos.LocationMemento;
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/4c866cb8/core/src/main/java/brooklyn/management/internal/EntityManagementSupport.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/management/internal/EntityManagementSupport.java b/core/src/main/java/brooklyn/management/internal/EntityManagementSupport.java
index 1e2779a..61ff168 100644
--- a/core/src/main/java/brooklyn/management/internal/EntityManagementSupport.java
+++ b/core/src/main/java/brooklyn/management/internal/EntityManagementSupport.java
@@ -48,6 +48,7 @@ import brooklyn.util.exceptions.Exceptions;
import com.google.common.annotations.Beta;
import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Preconditions;
import com.google.common.base.Stopwatch;
/**
@@ -85,6 +86,7 @@ public class EntityManagementSupport {
protected transient ExecutionContext executionContext;
// TODO the application
+ // (elaborate or remove ^^^ ? -AH, Sept 2014)
protected final AtomicBoolean managementContextUsable = new AtomicBoolean(false);
protected final AtomicBoolean currentlyDeployed = new AtomicBoolean(false);
@@ -106,22 +108,24 @@ public class EntityManagementSupport {
public boolean wasDeployed() { return everDeployed.get(); }
@Beta
- public void setReadOnly() {
+ public void setReadOnly(boolean isReadOnly) {
if (isDeployed())
throw new IllegalStateException("Cannot set read only after deployment");
- readOnly = true;
+ this.readOnly = isReadOnly;
}
- /** as {@link EntityManagerInternal#isReadOnly(Entity)} */
+ /** Whether the entity and its adjuncts should be treated as read-only;
+ * may be null briefly when initializing if RO status is unknown. */
@Beta
- public Boolean isReadOnly() {
- if (readOnly==null) {
- // readOnly is set in two places, by rebindManagerImpl when the entity is created,
- // and again when the entity manager is told we should be read only;
- // TODO twice setting read-only is redundant; possibly doesn't even need to be on managementContext?
- // however we must check here to discover canonically when it IS fully managed and it is NOT read only
- readOnly = ((EntityManagerInternal)managementContext.getEntityManager()).isReadOnly(entity);
- }
+ public Boolean isReadOnlyRaw() {
+ return readOnly;
+ }
+
+ /** Whether the entity and its adjuncts should be treated as read-only;
+ * error if initializing and RO status is unknown. */
+ @Beta
+ public boolean isReadOnly() {
+ Preconditions.checkNotNull(readOnly, "Read-only status of %s not yet known", entity);
return readOnly;
}
@@ -174,8 +178,10 @@ public class EntityManagementSupport {
this.managementContext = info.getManagementContext();
nonDeploymentManagementContext.setMode(NonDeploymentManagementContext.NonDeploymentManagementContextMode.MANAGEMENT_STARTING);
- nonDeploymentManagementContext.getSubscriptionManager().setDelegate((AbstractSubscriptionManager) managementContext.getSubscriptionManager());
- nonDeploymentManagementContext.getSubscriptionManager().startDelegatingForSubscribing();
+ if (!isReadOnly()) {
+ nonDeploymentManagementContext.getSubscriptionManager().setDelegate((AbstractSubscriptionManager) managementContext.getSubscriptionManager());
+ nonDeploymentManagementContext.getSubscriptionManager().startDelegatingForSubscribing();
+ }
managementContextUsable.set(true);
currentlyDeployed.set(true);
@@ -196,13 +202,16 @@ public class EntityManagementSupport {
* then: set the management context and the entity is "managed" from the perspective of external viewers (ManagementContext.isManaged(entity) returns true)
*/
- entity.onManagementStarting();
+ if (!isReadOnly()) {
+ entity.onManagementStarting();
+ }
} catch (Throwable t) {
managementFailed.set(true);
throw Exceptions.propagate(t);
}
}
+ @SuppressWarnings("deprecation")
public void onManagementStarted(ManagementTransitionInfo info) {
try {
synchronized (this) {
@@ -231,14 +240,19 @@ public class EntityManagementSupport {
* subsequent sensor events and executions occur directly (no queueing)
*/
- nonDeploymentManagementContext.getSubscriptionManager().startDelegatingForPublishing();
- //TODO more of the above
-
+ if (!isReadOnly()) {
+ nonDeploymentManagementContext.getSubscriptionManager().startDelegatingForPublishing();
+ }
+
+ // TODO more of the above
// TODO custom started activities
+ // (elaborate or remove ^^^ ? -AH, Sept 2014)
}
- entity.onManagementBecomingMaster();
- entity.onManagementStarted();
+ if (!isReadOnly()) {
+ entity.onManagementBecomingMaster();
+ entity.onManagementStarted();
+ }
synchronized (this) {
nonDeploymentManagementContext = null;
@@ -249,6 +263,7 @@ public class EntityManagementSupport {
}
}
+ @SuppressWarnings("deprecation")
public void onManagementStopping(ManagementTransitionInfo info) {
synchronized (this) {
if (managementContext != info.getManagementContext()) {
@@ -278,15 +293,21 @@ public class EntityManagementSupport {
}
// TODO custom stopping activities
// TODO framework stopping events - no more sensors, executions, etc
+ // (elaborate or remove ^^^ ? -AH, Sept 2014)
- if (entity.getParent()!=null) entity.getParent().removeChild(entity.getProxyIfAvailable());
+ if (!isReadOnly()) {
+ // if we support remote parent of local child, the following call will need to be properly remoted
+ if (entity.getParent()!=null) entity.getParent().removeChild(entity.getProxyIfAvailable());
+ }
// new subscriptions will be queued / not allowed
nonDeploymentManagementContext.getSubscriptionManager().stopDelegatingForSubscribing();
// new publications will be queued / not allowed
nonDeploymentManagementContext.getSubscriptionManager().stopDelegatingForPublishing();
- entity.onManagementNoLongerMaster();
- entity.onManagementStopped();
+ if (!isReadOnly()) {
+ entity.onManagementNoLongerMaster();
+ entity.onManagementStopped();
+ }
}
public void onManagementStopped(ManagementTransitionInfo info) {
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/4c866cb8/core/src/main/java/brooklyn/management/internal/EntityManagerInternal.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/management/internal/EntityManagerInternal.java b/core/src/main/java/brooklyn/management/internal/EntityManagerInternal.java
index 0dc7c13..e134b23 100644
--- a/core/src/main/java/brooklyn/management/internal/EntityManagerInternal.java
+++ b/core/src/main/java/brooklyn/management/internal/EntityManagerInternal.java
@@ -20,22 +20,26 @@ package brooklyn.management.internal;
import brooklyn.entity.Application;
import brooklyn.entity.Entity;
+import brooklyn.location.Location;
import brooklyn.management.EntityManager;
+import brooklyn.management.internal.ManagementTransitionInfo.ManagementTransitionMode;
public interface EntityManagerInternal extends EntityManager {
/** gets all entities currently known to the application, including entities that are not yet managed */
Iterable<Entity> getAllEntitiesInApplication(Application application);
- /**
- * Indicates that the given entity should be treated as read only
- * when it is subsequently managed.
- * @throws IllegalStateException if the entity is already managed.
- */
- void setReadOnly(Entity e);
+ public Iterable<String> getEntityIds();
+
+ ManagementTransitionMode getLastManagementTransitionMode(String itemId);
+ void setManagementTransitionMode(Entity item, ManagementTransitionMode mode);
- /** Whether the entity and its adjuncts should be treated as read-only;
- * may be null when initializing if RO status is unknown. */
- Boolean isReadOnly(Entity item);
+ /**
+ * Begins management for the given rebinded root, recursively;
+ * if rebinding as a read-only copy, {@link #setReadOnly(Location, boolean)} should be called prior to this.
+ */
+ void manageRebindedRoot(Entity item);
+
+ void unmanage(final Entity e, final ManagementTransitionMode info);
}
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/4c866cb8/core/src/main/java/brooklyn/management/internal/LocalAccessManager.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/management/internal/LocalAccessManager.java b/core/src/main/java/brooklyn/management/internal/LocalAccessManager.java
index fb20963..077e56f 100644
--- a/core/src/main/java/brooklyn/management/internal/LocalAccessManager.java
+++ b/core/src/main/java/brooklyn/management/internal/LocalAccessManager.java
@@ -23,7 +23,6 @@ import java.util.concurrent.atomic.AtomicReference;
import brooklyn.entity.Entity;
import brooklyn.location.Location;
import brooklyn.management.AccessController;
-import brooklyn.management.AccessController.Response;
import com.google.common.annotations.Beta;
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/4c866cb8/core/src/main/java/brooklyn/management/internal/LocalEntityManager.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/management/internal/LocalEntityManager.java b/core/src/main/java/brooklyn/management/internal/LocalEntityManager.java
index 69fe77b..52050a4 100644
--- a/core/src/main/java/brooklyn/management/internal/LocalEntityManager.java
+++ b/core/src/main/java/brooklyn/management/internal/LocalEntityManager.java
@@ -39,6 +39,7 @@ import brooklyn.entity.basic.EntityInternal;
import brooklyn.entity.basic.EntityPredicates;
import brooklyn.entity.proxying.BasicEntityTypeRegistry;
import brooklyn.entity.proxying.EntityProxy;
+import brooklyn.entity.proxying.EntityProxyImpl;
import brooklyn.entity.proxying.EntitySpec;
import brooklyn.entity.proxying.EntityTypeRegistry;
import brooklyn.entity.proxying.InternalEntityFactory;
@@ -54,6 +55,7 @@ import brooklyn.policy.PolicySpec;
import brooklyn.util.collections.SetFromLiveMap;
import brooklyn.util.exceptions.Exceptions;
+import com.google.api.client.repackaged.com.google.common.base.Preconditions;
import com.google.common.base.Function;
import com.google.common.base.Predicate;
import com.google.common.base.Predicates;
@@ -84,8 +86,9 @@ public class LocalEntityManager implements EntityManagerInternal {
/** Real managed entities */
protected final Map<String,Entity> entitiesById = Maps.newLinkedHashMap();
-
- protected final Set<String> readOnlyEntityIds = Sets.newLinkedHashSet();
+
+ /** Management mode for each entity */
+ protected final Map<String,ManagementTransitionMode> entityModesById = Maps.newLinkedHashMap();
/** Proxies of the managed entities */
protected final ObservableList entities = new ObservableList();
@@ -168,6 +171,11 @@ public class LocalEntityManager implements EntityManagerInternal {
}
@Override
+ public synchronized Collection<String> getEntityIds() {
+ return ImmutableList.copyOf(entityProxiesById.keySet());
+ }
+
+ @Override
public synchronized Collection<Entity> getEntitiesInApplication(Application application) {
Predicate<Entity> predicate = EntityPredicates.applicationIdEqualTo(application.getId());
return ImmutableList.copyOf(Iterables.filter(entityProxiesById.values(), predicate));
@@ -221,15 +229,15 @@ public class LocalEntityManager implements EntityManagerInternal {
}
preRegisteredEntitiesById.put(entity.getId(), entity);
}
-
- public void setReadOnly(Entity e) {
- readOnlyEntityIds.add(e.getId());
- ((EntityInternal)e).getManagementSupport().setReadOnly();
+
+ @Override
+ public ManagementTransitionMode getLastManagementTransitionMode(String itemId) {
+ return entityModesById.get(itemId);
}
@Override
- public Boolean isReadOnly(Entity item) {
- return readOnlyEntityIds.contains(item.getId());
+ public void setManagementTransitionMode(Entity item, ManagementTransitionMode mode) {
+ entityModesById.put(item.getId(), mode);
}
// TODO synchronization issues here. We guard with isManaged(), but if another thread executing
@@ -249,27 +257,64 @@ public class LocalEntityManager implements EntityManagerInternal {
new Exception("source of duplicate management of "+e));
return;
}
+ manageRecursive(e, ManagementTransitionMode.CREATING);
+ }
+ @Override
+ public void manageRebindedRoot(Entity item) {
+ ManagementTransitionMode mode = getLastManagementTransitionMode(item.getId());
+ Preconditions.checkNotNull(mode, "Mode not set for rebinding %s", item);
+ manageRecursive(item, mode);
+ }
+
+ protected void manageRecursive(Entity e, final ManagementTransitionMode initialMode) {
AccessController.Response access = managementContext.getAccessController().canManageEntity(e);
if (!access.isAllowed()) {
throw new IllegalStateException("Access controller forbids management of "+e+": "+access.getMsg());
}
- final ManagementTransitionInfo info = new ManagementTransitionInfo(managementContext, ManagementTransitionMode.NORMAL);
final List<EntityInternal> allEntities = Lists.newArrayList();
recursively(e, new Predicate<EntityInternal>() { public boolean apply(EntityInternal it) {
+ ManagementTransitionMode mode = getLastManagementTransitionMode(it.getId());
+ if (mode==null) {
+ setManagementTransitionMode(it, mode = initialMode);
+ }
+ if (it.getManagementSupport().isReadOnlyRaw()==null) {
+ if (mode.isReadOnly()) {
+ // should have been marked by rebinder
+ log.warn("Read-only entity "+it+" not marked as such on call to manage");
+ }
+ it.getManagementSupport().setReadOnly(mode.isReadOnly());
+ }
+
if (it.getManagementSupport().isDeployed()) {
- return false;
- } else {
- allEntities.add(it);
- preManageNonRecursive(it);
- it.getManagementSupport().onManagementStarting(info);
- return manageNonRecursive(it);
+ if (mode==ManagementTransitionMode.CREATING) {
+ // silently bail out
+ return false;
+ } else {
+ // on rebind, should not have any deployed instances
+ log.warn("Already deployed "+it+" when managing "+mode+"/"+initialMode+"; ignoring this and all descendants");
+ return false;
+ }
+ }
+
+ // check RO status is consistent
+ boolean isNowReadOnly = Boolean.TRUE.equals( ((EntityInternal)it).getManagementSupport().isReadOnly() );
+ if (mode.isReadOnly()!=isNowReadOnly) {
+ throw new IllegalStateException("Read-only status mismatch for "+it+": "+mode+" / RO="+isNowReadOnly);
}
+
+ allEntities.add(it);
+ preManageNonRecursive(it, mode);
+ it.getManagementSupport().onManagementStarting( new ManagementTransitionInfo(managementContext, mode) );
+ return manageNonRecursive(it, mode);
} });
for (EntityInternal it : allEntities) {
if (!it.getManagementSupport().isFullyManaged()) {
+ ManagementTransitionMode mode = getLastManagementTransitionMode(it.getId());
+ ManagementTransitionInfo info = new ManagementTransitionInfo(managementContext, mode);
+
it.getManagementSupport().onManagementStarted(info);
managementContext.getRebindManager().getChangeListener().onManaged(it);
}
@@ -278,13 +323,47 @@ public class LocalEntityManager implements EntityManagerInternal {
@Override
public void unmanage(final Entity e) {
+ unmanage(e, ManagementTransitionMode.DESTROYING);
+ }
+
+ public void unmanage(final Entity e, final ManagementTransitionMode mode) {
+ unmanage(e, mode, false);
+ }
+
+ private void unmanage(final Entity e, ManagementTransitionMode mode, boolean hasBeenReplaced) {
if (shouldSkipUnmanagement(e)) return;
- if (isReadOnly(e)) {
+ final ManagementTransitionInfo info = new ManagementTransitionInfo(managementContext, mode);
+
+ if (hasBeenReplaced) {
+ // we are unmanaging an old instance after having replaced it
+
+ if (mode==ManagementTransitionMode.REBINDING_NO_LONGER_PRIMARY) {
+ // when migrating away, these all need to be called
+ ((EntityInternal)e).getManagementSupport().onManagementStopping(info);
+ ((EntityInternal)e).getManagementSupport().onManagementStopped(info);
+ managementContext.getRebindManager().getChangeListener().onUnmanaged(e);
+ if (managementContext.gc != null) managementContext.gc.onUnmanaged(e);
+ } else {
+ // should be coming *from* read only; nothing needed
+ if (!mode.wasReadOnly())
+ log.warn("Should not be unmanaging "+e+" in mode "+mode);
+ }
+
+ } else if (mode==ManagementTransitionMode.REBINDING_DESTROYED) {
+ // we are unmanaging an instance (secondary) for which the primary has been destroyed elsewhere
+ ((EntityInternal)e).getManagementSupport().onManagementStopping(info);
+ ((EntityInternal)e).getManagementSupport().onManagementStopped(info);
unmanageNonRecursive(e);
- } else {
+ managementContext.getRebindManager().getChangeListener().onUnmanaged(e);
+ if (managementContext.gc != null) managementContext.gc.onUnmanaged(e);
+
+ } else if (mode==ManagementTransitionMode.DESTROYING) {
+ // we are unmanaging an instance either because it is being destroyed (primary),
+ // or due to an explicit call (shutting down all things, read-only and primary);
+ // in either case, should be recursive
+
// Need to store all child entities as onManagementStopping removes a child from the parent entity
final List<EntityInternal> allEntities = Lists.newArrayList();
- final ManagementTransitionInfo info = new ManagementTransitionInfo(managementContext, ManagementTransitionMode.NORMAL);
recursively(e, new Predicate<EntityInternal>() { public boolean apply(EntityInternal it) {
if (shouldSkipUnmanagement(it)) return false;
allEntities.add(it);
@@ -299,6 +378,9 @@ public class LocalEntityManager implements EntityManagerInternal {
managementContext.getRebindManager().getChangeListener().onUnmanaged(it);
if (managementContext.gc != null) managementContext.gc.onUnmanaged(it);
}
+
+ } else {
+ log.warn("Invalid mode for unmanage: "+mode+" on "+e+" (ignoring)");
}
}
@@ -357,14 +439,15 @@ public class LocalEntityManager implements EntityManagerInternal {
* Note that refs to the given entity are stored in a a weak hashmap so if the subsequent management
* attempt fails then this reference to the entity will eventually be discarded (if no-one else holds
* a reference).
+ * @param isOrWasReadOnly
*/
- private synchronized boolean preManageNonRecursive(Entity e) {
+ private synchronized boolean preManageNonRecursive(Entity e, ManagementTransitionMode mode) {
Entity realE = toRealEntity(e);
Object old = preManagedEntitiesById.put(e.getId(), realE);
preRegisteredEntitiesById.remove(e.getId());
- if (old!=null) {
+ if (old!=null && mode==ManagementTransitionMode.CREATING) {
if (old.equals(e)) {
log.warn("{} redundant call to pre-start management of entity {}", this, e);
} else {
@@ -372,7 +455,8 @@ public class LocalEntityManager implements EntityManagerInternal {
}
return false;
} else {
- if (log.isTraceEnabled()) log.trace("{} pre-start management of entity {}", this, e);
+ if (log.isTraceEnabled()) log.trace("{} pre-start management of entity {}, mode {}",
+ new Object[] { this, e, mode });
return true;
}
}
@@ -380,35 +464,53 @@ public class LocalEntityManager implements EntityManagerInternal {
/**
* Should ensure that the entity is now managed somewhere, and known about in all the lists.
* Returns true if the entity has now become managed; false if it was already managed (anything else throws exception)
+ * @param isOrWasReadOnly
*/
- private synchronized boolean manageNonRecursive(Entity e) {
- Entity realE = toRealEntity(e);
- Entity proxyE = toProxyEntityIfAvailable(e);
-
- // If we don't already know about the proxy, then use the real thing; presumably it's
- // the legacy way of creating the entity so didn't get a preManage() call
- entityProxiesById.put(e.getId(), proxyE);
+ private synchronized boolean manageNonRecursive(Entity e, ManagementTransitionMode mode) {
+ Entity old = entitiesById.get(e.getId());
- entityTypes.put(e.getId(), realE.getClass().getName());
-
- Object old = entitiesById.put(e.getId(), realE);
- if (old!=null) {
+ if (old!=null && mode==ManagementTransitionMode.CREATING) {
if (old.equals(e)) {
log.warn("{} redundant call to start management of entity {}", this, e);
} else {
throw new IllegalStateException("call to manage entity "+e+" but different entity "+old+" already known under that id at "+this);
}
return false;
- } else {
- if (log.isDebugEnabled()) log.debug("{} starting management of entity {}", this, e);
- preManagedEntitiesById.remove(e.getId());
- if ((e instanceof Application) && (e.getParent()==null)) {
- applications.add((Application)proxyE);
- applicationIds.add(e.getId());
+ }
+
+ if (log.isDebugEnabled()) log.debug("{} starting management of entity {}", this, e);
+ Entity realE = toRealEntity(e);
+
+ Entity oldProxy = entityProxiesById.get(e.getId());
+ Entity proxyE;
+ if (oldProxy!=null) {
+ if (mode==ManagementTransitionMode.CREATING) {
+ throw new IllegalStateException("call to manage entity "+e+" but already had proxy "+oldProxy+" already known under that id at "+this);
}
- entities.add(proxyE);
- return true;
+ // set the new entity to the old proxy
+ ((AbstractEntity)realE).resetProxy(oldProxy);
+ proxyE = oldProxy;
+ ((EntityProxyImpl)(Proxy.getInvocationHandler(proxyE))).resetDelegate(realE);
+ } else {
+ proxyE = toProxyEntityIfAvailable(e);
+ }
+ entityProxiesById.put(e.getId(), proxyE);
+ entityTypes.put(e.getId(), realE.getClass().getName());
+ entitiesById.put(e.getId(), realE);
+
+ preManagedEntitiesById.remove(e.getId());
+ if ((e instanceof Application) && (e.getParent()==null)) {
+ applications.add((Application)proxyE);
+ applicationIds.add(e.getId());
+ }
+ entities.add(proxyE);
+
+ if (old!=null && old!=e) {
+ // passing the transition info will ensure the right shutdown steps invoked for old instance
+ unmanage(old, mode, true);
}
+
+ return true;
}
/**
@@ -433,7 +535,7 @@ public class LocalEntityManager implements EntityManagerInternal {
* from its groups?
*/
- if (!isReadOnly(e)) {
+ if (!getLastManagementTransitionMode(e.getId()).isReadOnly()) {
Collection<Group> groups = e.getGroups();
e.clearParent();
for (Group group : groups) {
@@ -458,7 +560,7 @@ public class LocalEntityManager implements EntityManagerInternal {
entities.remove(proxyE);
entityProxiesById.remove(e.getId());
- readOnlyEntityIds.remove(e.getId());
+ entityModesById.remove(e.getId());
Object old = entitiesById.remove(e.getId());
entityTypes.remove(e.getId());
@@ -509,6 +611,9 @@ public class LocalEntityManager implements EntityManagerInternal {
Entity result = ((AbstractEntity)e).getProxy();
return (result == null) ? e : result;
} else {
+ // If we don't already know about the proxy, then use the real thing; presumably it's
+ // the legacy way of creating the entity so didn't get a preManage() call
+
return e;
}
}
@@ -545,4 +650,5 @@ public class LocalEntityManager implements EntityManagerInternal {
private boolean isRunning() {
return managementContext.isRunning();
}
+
}
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/4c866cb8/core/src/main/java/brooklyn/management/internal/LocalLocationManager.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/management/internal/LocalLocationManager.java b/core/src/main/java/brooklyn/management/internal/LocalLocationManager.java
index 8ce4dbe..47087f9 100644
--- a/core/src/main/java/brooklyn/management/internal/LocalLocationManager.java
+++ b/core/src/main/java/brooklyn/management/internal/LocalLocationManager.java
@@ -36,17 +36,18 @@ import brooklyn.location.LocationSpec;
import brooklyn.location.basic.AbstractLocation;
import brooklyn.location.basic.LocationInternal;
import brooklyn.management.AccessController;
-import brooklyn.management.LocationManager;
+import brooklyn.management.internal.ManagementTransitionInfo.ManagementTransitionMode;
import brooklyn.util.config.ConfigBag;
import brooklyn.util.exceptions.Exceptions;
import brooklyn.util.exceptions.RuntimeInterruptedException;
+import com.google.api.client.repackaged.com.google.common.base.Preconditions;
import com.google.common.annotations.Beta;
import com.google.common.base.Predicate;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Maps;
-public class LocalLocationManager implements LocationManager {
+public class LocalLocationManager implements LocationManagerInternal {
@Beta /* expect to remove when API returns LocationSpec or similar */
public static final ConfigKey<Boolean> CREATE_UNMANAGED = ConfigKeys.newBooleanConfigKey("brooklyn.internal.location.createUnmanaged",
@@ -60,6 +61,9 @@ public class LocalLocationManager implements LocationManager {
protected final Map<String,Location> locationsById = Maps.newLinkedHashMap();
private final Map<String, Location> preRegisteredLocationsById = Maps.newLinkedHashMap();
+ /** Management mode for each location */
+ protected final Map<String,ManagementTransitionMode> locationModesById = Maps.newLinkedHashMap();
+
private final BrooklynStorage storage;
private Map<String, String> locationTypes;
@@ -115,6 +119,11 @@ public class LocalLocationManager implements LocationManager {
}
@Override
+ public Collection<String> getLocationIds() {
+ return ImmutableList.copyOf(locationsById.keySet());
+ }
+
+ @Override
public synchronized Location getLocation(String id) {
return locationsById.get(id);
}
@@ -149,7 +158,18 @@ public class LocalLocationManager implements LocationManager {
preRegisteredLocationsById.put(loc.getId(), loc);
}
+ @Override
+ public ManagementTransitionMode getLastManagementTransitionMode(String itemId) {
+ return locationModesById.get(itemId);
+ }
+
+ @Override
+ public void setManagementTransitionMode(Location item, ManagementTransitionMode mode) {
+ locationModesById.put(item.getId(), mode);
+ }
+
// TODO synchronization issues here: see comment in LocalEntityManager.manage(Entity)
+ /** management on creation */
@Override
public Location manage(Location loc) {
if (isManaged(loc)) {
@@ -158,50 +178,115 @@ public class LocalLocationManager implements LocationManager {
return loc;
}
- AccessController.Response access = managementContext.getAccessManager().getAccessController().canManageLocation(loc);
- if (!access.isAllowed()) {
- throw new IllegalStateException("Access controller forbids management of "+loc+": "+access.getMsg());
- }
-
Location parent = loc.getParent();
if (parent != null && !managementContext.getLocationManager().isManaged(parent)) {
-// throw new IllegalStateException("Can't manage "+e+" because its parent is not yet managed ("+parent+")");
log.warn("Parent location "+parent+" of "+loc+" is not managed; attempting to manage it (in future this may be disallowed)");
- manage(parent);
+ return manage(parent);
+ } else {
+ return manageRecursive(loc, ManagementTransitionMode.CREATING);
}
-
+ }
+
+ @Override
+ public void manageRebindedRoot(Location item) {
+ ManagementTransitionMode mode = getLastManagementTransitionMode(item.getId());
+ Preconditions.checkNotNull(mode, "Mode not set for rebinding %s", item);
+ manageRecursive(item, mode);
+ }
+
+ protected Location manageRecursive(Location loc, final ManagementTransitionMode initialMode) {
+ AccessController.Response access = managementContext.getAccessController().canManageLocation(loc);
+ if (!access.isAllowed()) {
+ throw new IllegalStateException("Access controller forbids management of "+loc+": "+access.getMsg());
+ }
+
recursively(loc, new Predicate<AbstractLocation>() { public boolean apply(AbstractLocation it) {
+ ManagementTransitionMode mode = getLastManagementTransitionMode(it.getId());
+ if (mode==null) {
+ setManagementTransitionMode(it, mode = initialMode);
+ }
+
if (it.isManaged()) {
- return false;
- } else {
- boolean result = manageNonRecursive(it);
- if (result) {
- it.setManagementContext(managementContext);
+ if (mode==ManagementTransitionMode.CREATING) {
+ // silently bail out
+ return false;
+ } else {
+ // on rebind, we just replace, fall through to below
+ }
+ }
+
+ boolean result = manageNonRecursive(it, null);
+ if (result) {
+ it.setManagementContext(managementContext);
+ if (!mode.isReadOnly()) {
it.onManagementStarted();
recordLocationEvent(it, Lifecycle.CREATED);
- managementContext.getRebindManager().getChangeListener().onManaged(it);
}
- return result;
+ managementContext.getRebindManager().getChangeListener().onManaged(it);
}
+ return result;
} });
return loc;
}
@Override
- public void unmanage(Location loc) {
+ public void unmanage(final Location loc) {
+ unmanage(loc, ManagementTransitionMode.DESTROYING);
+ }
+
+ public void unmanage(final Location loc, final ManagementTransitionMode mode) {
+ unmanage(loc, mode, false);
+ }
+
+ private void unmanage(final Location loc, ManagementTransitionMode mode, boolean hasBeenReplaced) {
if (shouldSkipUnmanagement(loc)) return;
-
- recursively(loc, new Predicate<AbstractLocation>() { public boolean apply(AbstractLocation it) {
- if (shouldSkipUnmanagement(it)) return false;
- boolean result = unmanageNonRecursive(it);
- if (result) {
- it.onManagementStopped();
- managementContext.getRebindManager().getChangeListener().onUnmanaged(it);
- recordLocationEvent(it, Lifecycle.DESTROYED);
- if (managementContext.gc != null) managementContext.gc.onUnmanaged(it);
+
+ if (hasBeenReplaced) {
+ // we are unmanaging an old instance after having replaced it
+
+ if (mode==ManagementTransitionMode.REBINDING_NO_LONGER_PRIMARY) {
+ // when migrating away, these all need to be called
+ managementContext.getRebindManager().getChangeListener().onUnmanaged(loc);
+ if (managementContext.gc != null) managementContext.gc.onUnmanaged(loc);
+ } else {
+ // should be coming *from* read only; nothing needed
+ if (!mode.wasReadOnly())
+ log.warn("Should not be unmanaging "+loc+" in mode "+mode);
}
- return result;
- } });
+
+ } else if (mode==ManagementTransitionMode.REBINDING_DESTROYED) {
+ // we are unmanaging an instance (secondary) for which the primary has been destroyed elsewhere
+ unmanageNonRecursive(loc);
+ managementContext.getRebindManager().getChangeListener().onUnmanaged(loc);
+ if (managementContext.gc != null) managementContext.gc.onUnmanaged(loc);
+
+ } else if (mode==ManagementTransitionMode.DESTROYING) {
+ // we are unmanaging an instance either because it is being destroyed (primary),
+ // or due to an explicit call (shutting down all things, read-only and primary);
+ // in either case, should be recursive
+
+ // Need to store all child entities as onManagementStopping removes a child from the parent entity
+ recursively(loc, new Predicate<AbstractLocation>() { public boolean apply(AbstractLocation it) {
+ if (shouldSkipUnmanagement(it)) return false;
+ boolean result = unmanageNonRecursive(it);
+ if (result) {
+ ManagementTransitionMode mode = getLastManagementTransitionMode(it.getId());
+ if (mode==null) {
+ log.warn("Missing transition mode for "+it+" when unmanaging; assuming primary");
+ mode = ManagementTransitionMode.DESTROYING;
+ }
+ if (!mode.isReadOnly()) it.onManagementStopped();
+ managementContext.getRebindManager().getChangeListener().onUnmanaged(it);
+ if (!mode.isReadOnly()) recordLocationEvent(it, Lifecycle.DESTROYED);
+ if (managementContext.gc != null) managementContext.gc.onUnmanaged(it);
+ }
+ return result;
+ } });
+
+ } else {
+ log.warn("Invalid mode for unmanage: "+mode+" on "+loc+" (ignoring)");
+ }
+
}
/**
@@ -231,33 +316,40 @@ public class LocalLocationManager implements LocationManager {
/**
* Should ensure that the location is now managed somewhere, and known about in all the lists.
* Returns true if the location has now become managed; false if it was already managed (anything else throws exception)
+ * @param rebindPrimary true if rebinding primary, false if rebinding as copy, null if creating (not rebinding)
*/
- private synchronized boolean manageNonRecursive(Location loc) {
- Object old = locationsById.put(loc.getId(), loc);
+ private synchronized boolean manageNonRecursive(Location loc, ManagementTransitionMode mode) {
+ Location old = locationsById.put(loc.getId(), loc);
preRegisteredLocationsById.remove(loc.getId());
locationTypes.put(loc.getId(), loc.getClass().getName());
- if (old!=null) {
+ if (old!=null && mode==ManagementTransitionMode.CREATING) {
if (old.equals(loc)) {
log.warn("{} redundant call to start management of location {}", this, loc);
} else {
throw new IllegalStateException("call to manage location "+loc+" but different location "+old+" already known under that id at "+this);
}
return false;
- } else {
- return true;
}
- }
+ if (old!=null && old!=loc) {
+ // passing the transition info will ensure the right shutdown steps invoked for old instance
+ unmanage(old, mode, true);
+ }
+
+ return true;
+ }
+
/**
* Should ensure that the location is no longer managed anywhere, remove from all lists.
* Returns true if the location has been removed from management; if it was not previously managed (anything else throws exception)
*/
- private synchronized boolean unmanageNonRecursive(AbstractLocation loc) {
+ private synchronized boolean unmanageNonRecursive(Location loc) {
loc.setParent(null);
Object old = locationsById.remove(loc.getId());
locationTypes.remove(loc.getId());
+ locationModesById.remove(loc.getId());
if (old==null) {
log.warn("{} call to stop management of unknown location (already unmanaged?) {}", this, loc);
@@ -288,4 +380,5 @@ public class LocalLocationManager implements LocationManager {
private boolean isRunning() {
return managementContext.isRunning();
}
+
}
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/4c866cb8/core/src/main/java/brooklyn/management/internal/LocalManagementContext.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/management/internal/LocalManagementContext.java b/core/src/main/java/brooklyn/management/internal/LocalManagementContext.java
index bf4a559..96de11d 100644
--- a/core/src/main/java/brooklyn/management/internal/LocalManagementContext.java
+++ b/core/src/main/java/brooklyn/management/internal/LocalManagementContext.java
@@ -39,7 +39,6 @@ import brooklyn.config.BrooklynProperties.Factory.Builder;
import brooklyn.entity.Application;
import brooklyn.entity.Effector;
import brooklyn.entity.Entity;
-import brooklyn.entity.basic.BrooklynTaskTags;
import brooklyn.entity.drivers.downloads.BasicDownloadsManager;
import brooklyn.entity.effector.Effectors;
import brooklyn.entity.proxying.InternalEntityFactory;
@@ -55,7 +54,6 @@ import brooklyn.management.SubscriptionManager;
import brooklyn.management.Task;
import brooklyn.management.TaskAdaptable;
import brooklyn.management.ha.OsgiManager;
-import brooklyn.util.collections.MutableMap;
import brooklyn.util.exceptions.Exceptions;
import brooklyn.util.guava.Maybe;
import brooklyn.util.task.BasicExecutionContext;
@@ -135,7 +133,7 @@ public class LocalManagementContext extends AbstractManagementContext {
* Creates a LocalManagement with default BrooklynProperties.
*/
public LocalManagementContext() {
- this(new Builder());
+ this(BrooklynProperties.Factory.builderDefault());
}
public LocalManagementContext(BrooklynProperties brooklynProperties) {
[13/15] git commit: address the rest of @sjcorbett's comments
Posted by he...@apache.org.
address the rest of @sjcorbett's comments
Project: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/commit/6ba1639e
Tree: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/tree/6ba1639e
Diff: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/diff/6ba1639e
Branch: refs/heads/master
Commit: 6ba1639e3af23f6cae31f2062647b7900b3ccd5e
Parents: 6d58882
Author: Alex Heneveld <al...@cloudsoftcorp.com>
Authored: Wed Oct 1 18:28:00 2014 +0100
Committer: Alex Heneveld <al...@cloudsoftcorp.com>
Committed: Wed Oct 1 18:28:00 2014 +0100
----------------------------------------------------------------------
.../management/ha/HighAvailabilityManagerImpl.java | 4 +---
.../management/internal/LocalEntityManager.java | 17 +++++++++--------
.../management/internal/LocalLocationManager.java | 10 +++++-----
.../internal/NonDeploymentLocationManager.java | 2 +-
.../ha/HighAvailabilityManagerSplitBrainTest.java | 5 +++--
.../brooklyn/management/ha/HotStandbyTest.java | 8 +++++---
.../brooklyn/management/ha/WarmStandbyTest.java | 1 +
7 files changed, 25 insertions(+), 22 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/6ba1639e/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 200a902..c71d352 100644
--- a/core/src/main/java/brooklyn/management/ha/HighAvailabilityManagerImpl.java
+++ b/core/src/main/java/brooklyn/management/ha/HighAvailabilityManagerImpl.java
@@ -53,10 +53,10 @@ import brooklyn.util.task.ScheduledTask;
import brooklyn.util.text.Strings;
import brooklyn.util.time.Duration;
-import com.google.api.client.repackaged.com.google.common.base.Preconditions;
import com.google.common.annotations.Beta;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Function;
+import com.google.common.base.Preconditions;
import com.google.common.base.Ticker;
import com.google.common.collect.Iterables;
@@ -718,8 +718,6 @@ public class HighAvailabilityManagerImpl implements HighAvailabilityManager {
try {
Preconditions.checkState(nodeStateTransitionComplete==false, "Must be in transitioning state to go into hot standby");
nodeState = ManagementNodeState.HOT_STANDBY;
-
- // XXX
managementContext.getRebindManager().startReadOnly();
return true;
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/6ba1639e/core/src/main/java/brooklyn/management/internal/LocalEntityManager.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/management/internal/LocalEntityManager.java b/core/src/main/java/brooklyn/management/internal/LocalEntityManager.java
index 0eae80b..adc9839 100644
--- a/core/src/main/java/brooklyn/management/internal/LocalEntityManager.java
+++ b/core/src/main/java/brooklyn/management/internal/LocalEntityManager.java
@@ -23,6 +23,7 @@ import groovy.util.ObservableList;
import java.lang.reflect.Proxy;
import java.util.Collection;
+import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;
@@ -88,7 +89,7 @@ public class LocalEntityManager implements EntityManagerInternal {
protected final Map<String,Entity> entitiesById = Maps.newLinkedHashMap();
/** Management mode for each entity */
- protected final Map<String,ManagementTransitionMode> entityModesById = Maps.newLinkedHashMap();
+ protected final Map<String,ManagementTransitionMode> entityModesById = Collections.synchronizedMap(Maps.<String,ManagementTransitionMode>newLinkedHashMap());
/** Proxies of the managed entities */
protected final ObservableList entities = new ObservableList();
@@ -282,7 +283,7 @@ public class LocalEntityManager implements EntityManagerInternal {
if (it.getManagementSupport().isReadOnlyRaw()==null) {
if (mode.isReadOnly()) {
// should have been marked by rebinder
- log.warn("Read-only entity "+it+" not marked as such on call to manage");
+ log.warn("Read-only entity "+it+" not marked as such on call to manage; marking and continuing");
}
it.getManagementSupport().setReadOnly(mode.isReadOnly());
}
@@ -345,8 +346,9 @@ public class LocalEntityManager implements EntityManagerInternal {
if (managementContext.gc != null) managementContext.gc.onUnmanaged(e);
} else {
// should be coming *from* read only; nothing needed
- if (!mode.wasReadOnly())
- log.warn("Should not be unmanaging "+e+" in mode "+mode);
+ if (!mode.wasReadOnly()) {
+ log.warn("Should not be unmanaging "+e+" in mode "+mode+"; ignoring");
+ }
}
} else if (mode==ManagementTransitionMode.REBINDING_DESTROYED) {
@@ -439,7 +441,6 @@ public class LocalEntityManager implements EntityManagerInternal {
* Note that refs to the given entity are stored in a a weak hashmap so if the subsequent management
* attempt fails then this reference to the entity will eventually be discarded (if no-one else holds
* a reference).
- * @param isOrWasReadOnly
*/
private synchronized boolean preManageNonRecursive(Entity e, ManagementTransitionMode mode) {
Entity realE = toRealEntity(e);
@@ -449,7 +450,7 @@ public class LocalEntityManager implements EntityManagerInternal {
if (old!=null && mode==ManagementTransitionMode.CREATING) {
if (old.equals(e)) {
- log.warn("{} redundant call to pre-start management of entity {}", this, e);
+ log.warn("{} redundant call to pre-start management of entity {}; ignoring", this, e);
} else {
throw new IllegalStateException("call to pre-manage entity "+e+" but different entity "+old+" already known under that id at "+this);
}
@@ -471,7 +472,7 @@ public class LocalEntityManager implements EntityManagerInternal {
if (old!=null && mode==ManagementTransitionMode.CREATING) {
if (old.equals(e)) {
- log.warn("{} redundant call to start management of entity {}", this, e);
+ log.warn("{} redundant call to start management of entity {}; ignoring", this, e);
} else {
throw new IllegalStateException("call to manage entity "+e+" but different entity "+old+" already known under that id at "+this);
}
@@ -565,7 +566,7 @@ public class LocalEntityManager implements EntityManagerInternal {
entityTypes.remove(e.getId());
if (old==null) {
- log.warn("{} call to stop management of unknown entity (already unmanaged?) {}", this, e);
+ log.warn("{} call to stop management of unknown entity (already unmanaged?) {}; ignoring", this, e);
return false;
} else if (!old.equals(e)) {
// shouldn't happen...
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/6ba1639e/core/src/main/java/brooklyn/management/internal/LocalLocationManager.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/management/internal/LocalLocationManager.java b/core/src/main/java/brooklyn/management/internal/LocalLocationManager.java
index 47087f9..b70d4f3 100644
--- a/core/src/main/java/brooklyn/management/internal/LocalLocationManager.java
+++ b/core/src/main/java/brooklyn/management/internal/LocalLocationManager.java
@@ -41,8 +41,8 @@ import brooklyn.util.config.ConfigBag;
import brooklyn.util.exceptions.Exceptions;
import brooklyn.util.exceptions.RuntimeInterruptedException;
-import com.google.api.client.repackaged.com.google.common.base.Preconditions;
import com.google.common.annotations.Beta;
+import com.google.common.base.Preconditions;
import com.google.common.base.Predicate;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Maps;
@@ -251,7 +251,7 @@ public class LocalLocationManager implements LocationManagerInternal {
} else {
// should be coming *from* read only; nothing needed
if (!mode.wasReadOnly())
- log.warn("Should not be unmanaging "+loc+" in mode "+mode);
+ log.warn("Should not be unmanaging "+loc+" in mode "+mode+"; ignoring");
}
} else if (mode==ManagementTransitionMode.REBINDING_DESTROYED) {
@@ -299,7 +299,7 @@ public class LocalLocationManager implements LocationManagerInternal {
} catch (RuntimeInterruptedException e) {
throw e;
} catch (RuntimeException e) {
- log.warn("Failed to store location lifecycle event for "+loc, e);
+ log.warn("Failed to store location lifecycle event for "+loc+" (ignoring)", e);
}
}
@@ -352,11 +352,11 @@ public class LocalLocationManager implements LocationManagerInternal {
locationModesById.remove(loc.getId());
if (old==null) {
- log.warn("{} call to stop management of unknown location (already unmanaged?) {}", this, loc);
+ log.warn("{} call to stop management of unknown location (already unmanaged?) {}; ignoring", this, loc);
return false;
} else if (!old.equals(loc)) {
// shouldn't happen...
- log.error("{} call to stop management of location {} removed different location {}", new Object[] { this, loc, old });
+ log.error("{} call to stop management of location {} removed different location {}; ignoring", new Object[] { this, loc, old });
return true;
} else {
if (log.isDebugEnabled()) log.debug("{} stopped management of location {}", this, loc);
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/6ba1639e/core/src/main/java/brooklyn/management/internal/NonDeploymentLocationManager.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/management/internal/NonDeploymentLocationManager.java b/core/src/main/java/brooklyn/management/internal/NonDeploymentLocationManager.java
index 3628231..a11d876 100644
--- a/core/src/main/java/brooklyn/management/internal/NonDeploymentLocationManager.java
+++ b/core/src/main/java/brooklyn/management/internal/NonDeploymentLocationManager.java
@@ -76,7 +76,7 @@ public class NonDeploymentLocationManager implements LocationManagerInternal {
if (isInitialManagementContextReal()) {
return ((LocationManagerInternal)initialManagementContext.getLocationManager()).getLocationIds();
} else {
- return null;
+ return Collections.emptyList();
}
}
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/6ba1639e/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 a0b7feb..48bdfa5 100644
--- a/core/src/test/java/brooklyn/management/ha/HighAvailabilityManagerSplitBrainTest.java
+++ b/core/src/test/java/brooklyn/management/ha/HighAvailabilityManagerSplitBrainTest.java
@@ -69,7 +69,8 @@ public class HighAvailabilityManagerSplitBrainTest {
private ClassLoader classLoader = getClass().getClassLoader();
public class HaMgmtNode {
-
+ // TODO share with HotStandbyTest and WarmStandbyTest and a few others (minor differences but worth it ultimately)
+
private ManagementContextInternal mgmt;
private String ownNodeId;
private String nodeName;
@@ -278,7 +279,7 @@ public class HighAvailabilityManagerSplitBrainTest {
// right number of entities at n2; n1 may or may not depending whether hot standby is default
assertEquals(n2.mgmt.getApplications().size(), 1);
-// assertEquals(n1.mgmt.getApplications().size(), 0);
+ assertEquals(n1.mgmt.getApplications().size(), BrooklynFeatureEnablement.isEnabled(BrooklynFeatureEnablement.FEATURE_DEFAULT_STANDBY_IS_HOT_PROPERTY) ? 1 : 0);
}
@Test(invocationCount=50, groups="Integration")
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/6ba1639e/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 1a41c19..a34f26b 100644
--- a/core/src/test/java/brooklyn/management/ha/HotStandbyTest.java
+++ b/core/src/test/java/brooklyn/management/ha/HotStandbyTest.java
@@ -70,7 +70,8 @@ public class HotStandbyTest {
private ClassLoader classLoader = getClass().getClassLoader();
public class HaMgmtNode {
-
+ // TODO share with WarmStandbyTest and SplitBrainTest and a few others (minor differences but worth it ultimately)
+
private ManagementContextInternal mgmt;
private String ownNodeId;
private String nodeName;
@@ -204,7 +205,7 @@ public class HotStandbyTest {
}
@Test
- public void testHotStandbySeesChangedNameConfigAndSensorValueButDoesntAllowChange() throws Exception {
+ public void testHotStandbySeesInitialCustomNameConfigAndSensorValueButDoesntAllowChange() throws Exception {
HaMgmtNode n1 = createMaster(Duration.PRACTICALLY_FOREVER);
TestApplication app = createFirstAppAndPersist(n1);
HaMgmtNode n2 = createHotStandby(Duration.PRACTICALLY_FOREVER);
@@ -223,10 +224,11 @@ public class HotStandbyTest {
} catch (Exception e) {
Assert.assertTrue(e.toString().toLowerCase().contains("read-only"), "Error message did not contain expected text: "+e);
}
+ assertEquals(appRO.getAttribute(TestEntity.SEQUENCE), (Integer)3);
}
@Test
- public void testHotStandbySeesChangedNameConfigAndSensorValue() throws Exception {
+ public void testHotStandbySeesChangesToNameConfigAndSensorValue() throws Exception {
HaMgmtNode n1 = createMaster(Duration.PRACTICALLY_FOREVER);
TestApplication app = createFirstAppAndPersist(n1);
HaMgmtNode n2 = createHotStandby(Duration.PRACTICALLY_FOREVER);
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/6ba1639e/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 d955d50..8c40947 100644
--- a/core/src/test/java/brooklyn/management/ha/WarmStandbyTest.java
+++ b/core/src/test/java/brooklyn/management/ha/WarmStandbyTest.java
@@ -56,6 +56,7 @@ public class WarmStandbyTest {
private ClassLoader classLoader = getClass().getClassLoader();
public class HaMgmtNode {
+ // TODO share with HotStandbyTest and SplitBrainTest and a few others (minor differences but worth it ultimately)
private ManagementContextInternal mgmt;
private String ownNodeId;
[12/15] git commit: Wait for the actual result of the shutdown call.
Posted by he...@apache.org.
Wait for the actual result of the shutdown call.
Wait indefinitely for the shutdown result, otherwise we get "invalid" success, not aware if there was indeed a shutdown.
Project: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/commit/69718095
Tree: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/tree/69718095
Diff: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/diff/69718095
Branch: refs/heads/master
Commit: 69718095f319fa087215d4f54e0f01d3b1582c17
Parents: c49d7d3
Author: Svetoslav Neykov <sv...@cloudsoftcorp.com>
Authored: Wed Oct 1 19:14:45 2014 +0300
Committer: Svetoslav Neykov <sv...@cloudsoftcorp.com>
Committed: Wed Oct 1 19:14:45 2014 +0300
----------------------------------------------------------------------
usage/jsgui/src/main/webapp/assets/tpl/help/page.html | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/69718095/usage/jsgui/src/main/webapp/assets/tpl/help/page.html
----------------------------------------------------------------------
diff --git a/usage/jsgui/src/main/webapp/assets/tpl/help/page.html b/usage/jsgui/src/main/webapp/assets/tpl/help/page.html
index 9f4d21a..7d3ef0a 100644
--- a/usage/jsgui/src/main/webapp/assets/tpl/help/page.html
+++ b/usage/jsgui/src/main/webapp/assets/tpl/help/page.html
@@ -50,7 +50,7 @@ under the License.
$.ajax({
type:"POST",
url:"/v1/server/shutdown",
- data: { stopAppsFirst: stopAppsFirst },
+ data: { stopAppsFirst: stopAppsFirst, shutdownTimeout: 0, requestTimeout: 0 },
success:function (data) {
$('#help-page').fadeTo(500,0.1);
},
[06/15] git commit: improve support for interrupting scheduled tasks
and a "wait for interrupted tasks to really complete" method on Tasks;
see discussion there, and test case in ScheduledExecutionTest.java
Posted by he...@apache.org.
improve support for interrupting scheduled tasks and a "wait for interrupted tasks to really complete" method on Tasks; see discussion there, and test case in ScheduledExecutionTest.java
Project: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/commit/ea4b8121
Tree: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/tree/ea4b8121
Diff: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/diff/ea4b8121
Branch: refs/heads/master
Commit: ea4b8121688c0eea3a69d6917c8e873359ec5b32
Parents: ea4b4cc
Author: Alex Heneveld <al...@cloudsoftcorp.com>
Authored: Mon Sep 29 13:36:18 2014 +0100
Committer: Alex Heneveld <al...@cloudsoftcorp.com>
Committed: Wed Oct 1 16:40:15 2014 +0100
----------------------------------------------------------------------
api/src/main/java/brooklyn/management/Task.java | 8 +
.../main/java/brooklyn/util/task/BasicTask.java | 2 +-
.../java/brooklyn/util/task/ScheduledTask.java | 18 +++
.../src/main/java/brooklyn/util/task/Tasks.java | 51 ++++++
.../util/task/ScheduledExecutionTest.java | 159 ++++++++++++++++---
5 files changed, 213 insertions(+), 25 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/ea4b8121/api/src/main/java/brooklyn/management/Task.java
----------------------------------------------------------------------
diff --git a/api/src/main/java/brooklyn/management/Task.java b/api/src/main/java/brooklyn/management/Task.java
index fa3bd40..d6dc401 100644
--- a/api/src/main/java/brooklyn/management/Task.java
+++ b/api/src/main/java/brooklyn/management/Task.java
@@ -22,6 +22,7 @@ import java.util.Set;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
+import java.util.concurrent.FutureTask;
import java.util.concurrent.TimeoutException;
import com.google.common.util.concurrent.ListenableFuture;
@@ -117,4 +118,11 @@ public interface Task<T> extends ListenableFuture<T>, TaskAdaptable<T> {
* (including a {@link TimeoutException} if the duration expires) */
public T getUnchecked(Duration duration);
+ /** As {@link Future#cancel(boolean)}. Note that {@link #isDone()} and {@link #blockUntilEnded(Duration)} return immediately
+ * once a task is cancelled, consistent with the underlying {@link FutureTask} behaviour.
+ * TODO Fine-grained control over underlying jobs, e.g. to ensure anything represented by this task is actually completed,
+ * is not (yet) publicly exposed. See the convenience method blockUntilInternalTasksEnded in the Tasks set of helpers
+ * for more discussion. */
+ public boolean cancel(boolean mayInterruptIfRunning);
+
}
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/ea4b8121/core/src/main/java/brooklyn/util/task/BasicTask.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/util/task/BasicTask.java b/core/src/main/java/brooklyn/util/task/BasicTask.java
index 64c768c..a3847a6 100644
--- a/core/src/main/java/brooklyn/util/task/BasicTask.java
+++ b/core/src/main/java/brooklyn/util/task/BasicTask.java
@@ -276,7 +276,7 @@ public class BasicTask<T> implements TaskInternal<T> {
}
@Override
- public synchronized boolean cancel() { return cancel(true); }
+ public final synchronized boolean cancel() { return cancel(true); }
/** doesn't resume it, just means if something was cancelled but not submitted it could now be submitted;
* probably going to be removed and perhaps some mechanism for running again made available
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/ea4b8121/core/src/main/java/brooklyn/util/task/ScheduledTask.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/util/task/ScheduledTask.java b/core/src/main/java/brooklyn/util/task/ScheduledTask.java
index b6b3d9d..27aae30 100644
--- a/core/src/main/java/brooklyn/util/task/ScheduledTask.java
+++ b/core/src/main/java/brooklyn/util/task/ScheduledTask.java
@@ -32,6 +32,7 @@ import brooklyn.management.Task;
import brooklyn.util.collections.MutableMap;
import brooklyn.util.time.Duration;
+import com.google.common.annotations.Beta;
import com.google.common.base.Throwables;
/**
@@ -162,4 +163,21 @@ public class ScheduledTask extends BasicTask {
blockUntilFirstScheduleStarted();
return (truth(recentRun)) ? recentRun.get() : internalFuture.get();
}
+
+ @Override
+ public synchronized boolean cancel(boolean mayInterrupt) {
+ boolean result = super.cancel(mayInterrupt);
+ if (nextRun!=null) {
+ nextRun.cancel(mayInterrupt);
+ notifyAll();
+ }
+ return result;
+ }
+
+ /** internal method used to allow callers to wait for underlying tasks to finished in the case of cancellation
+ * @param duration */
+ @Beta
+ public boolean blockUntilNextRunFinished(Duration timeout) {
+ return Tasks.blockUntilInternalTasksEnded(nextRun, timeout);
+ }
}
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/ea4b8121/core/src/main/java/brooklyn/util/task/Tasks.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/util/task/Tasks.java b/core/src/main/java/brooklyn/util/task/Tasks.java
index 2afd539..b4bebee 100644
--- a/core/src/main/java/brooklyn/util/task/Tasks.java
+++ b/core/src/main/java/brooklyn/util/task/Tasks.java
@@ -23,6 +23,7 @@ import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.CancellationException;
import java.util.concurrent.ExecutionException;
+import java.util.concurrent.FutureTask;
import javax.annotation.Nullable;
@@ -36,6 +37,9 @@ import brooklyn.management.TaskAdaptable;
import brooklyn.management.TaskFactory;
import brooklyn.management.TaskQueueingContext;
import brooklyn.util.exceptions.Exceptions;
+import brooklyn.util.time.CountdownTimer;
+import brooklyn.util.time.Duration;
+import brooklyn.util.time.Time;
import com.google.common.annotations.Beta;
import com.google.common.base.Function;
@@ -371,4 +375,51 @@ public class Tasks {
if (t!=null) TaskTags.addTagDynamically(t, tag);
}
+ /**
+ * Workaround for limitation described at {@link Task#cancel(boolean)};
+ * internal method used to allow callers to wait for underlying tasks to finished in the case of cancellation.
+ * <p>
+ * It is irritating that {@link FutureTask} sync's object clears the runner thread,
+ * so even if {@link BasicTask#getInternalFuture()} is used, there is no means of determining if the underlying object is done.
+ * The {@link Task#getEndTimeUtc()} seems the only way.
+ *
+ * @return true if tasks ended; false if timed out
+ **/
+ @Beta
+ public static boolean blockUntilInternalTasksEnded(Task<?> t, Duration timeout) {
+ CountdownTimer timer = timeout.countdownTimer();
+
+ if (t==null)
+ return true;
+
+ if (t instanceof ScheduledTask) {
+ boolean result = ((ScheduledTask)t).blockUntilNextRunFinished(timer.getDurationRemaining());
+ if (!result) return false;
+ }
+
+ t.blockUntilEnded(timer.getDurationRemaining());
+
+ // TODO when the below is confirmed, delete the code below
+// Future<?> f = ((BasicTask<?>)t).getInternalFuture();
+// if (f==null) return;
+// try {
+// f.isDone();
+// } catch (Exception e) {
+// Exceptions.propagateIfFatal(e);
+// // ignore
+// }
+ while (true) {
+ if (t.getEndTimeUtc()>=0) return true;
+ // above should be sufficient; but just in case, trying the below
+ Thread tt = t.getThread();
+ if (tt==null || !tt.isAlive()) {
+ log.warn("Internal task thread is dead or null ("+tt+") but task not ended: "+t.getEndTimeUtc()+" ("+t+")");
+ return true;
+ }
+ if (timer.isExpired())
+ return false;
+ Time.sleep(Duration.millis(10));
+ }
+ }
+
}
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/ea4b8121/core/src/test/java/brooklyn/util/task/ScheduledExecutionTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/brooklyn/util/task/ScheduledExecutionTest.java b/core/src/test/java/brooklyn/util/task/ScheduledExecutionTest.java
index 654168e..31dd5e0 100644
--- a/core/src/test/java/brooklyn/util/task/ScheduledExecutionTest.java
+++ b/core/src/test/java/brooklyn/util/task/ScheduledExecutionTest.java
@@ -25,17 +25,25 @@ import static org.testng.Assert.fail;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.concurrent.Semaphore;
+import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import org.testng.Assert;
import org.testng.annotations.Test;
import brooklyn.management.Task;
import brooklyn.test.Asserts;
import brooklyn.util.collections.MutableMap;
import brooklyn.util.exceptions.Exceptions;
+import brooklyn.util.exceptions.RuntimeInterruptedException;
+import brooklyn.util.javalang.JavaClassNames;
+import brooklyn.util.time.Duration;
+import brooklyn.util.time.Time;
+import com.google.common.base.Stopwatch;
import com.google.common.collect.Lists;
public class ScheduledExecutionTest {
@@ -99,35 +107,138 @@ public class ScheduledExecutionTest {
assertEquals(i.get(), 5);
}
- @Test
- public void testScheduledTaskCancelEnding() throws Exception {
- int PERIOD = 20;
- BasicExecutionManager m = new BasicExecutionManager("mycontextid");
- final AtomicInteger i = new AtomicInteger();
- ScheduledTask t = new ScheduledTask(MutableMap.of("delay", 2*PERIOD, "period", PERIOD), new Callable<Task<?>>() {
+ @Test
+ public void testScheduledTaskCancelEnding() throws Exception {
+ Duration PERIOD = Duration.millis(20);
+ BasicExecutionManager m = new BasicExecutionManager("mycontextid");
+ final AtomicInteger i = new AtomicInteger();
+ ScheduledTask t = new ScheduledTask(MutableMap.of("delay", PERIOD.times(2), "period", PERIOD), new Callable<Task<?>>() {
public Task<?> call() throws Exception {
return new BasicTask<Integer>(new Callable<Integer>() {
public Integer call() {
- log.info("task running ("+i+"): "+Tasks.current()+" "+Tasks.current().getStatusDetail(false));
- ScheduledTask submitter = (ScheduledTask) ((BasicTask)Tasks.current()).getSubmittedByTask();
- i.incrementAndGet();
- if (i.get() >= 5) submitter.cancel();
- return i.get();
+ log.info("task running ("+i+"): "+Tasks.current()+" "+Tasks.current().getStatusDetail(false));
+ ScheduledTask submitter = (ScheduledTask) ((BasicTask)Tasks.current()).getSubmittedByTask();
+ i.incrementAndGet();
+ if (i.get() >= 5) submitter.cancel();
+ return i.get();
}});
}});
-
- log.info("submitting {} {}", t, t.getStatusDetail(false));
- m.submit(t);
- log.info("submitted {} {}", t, t.getStatusDetail(false));
- Integer interimResult = (Integer) t.get();
- log.info("done one ({}) {} {}", new Object[] {interimResult, t, t.getStatusDetail(false)});
- assertTrue(i.get() > 0);
- t.blockUntilEnded();
-// int finalResult = t.get()
- log.info("ended ({}) {} {}", new Object[] {i, t, t.getStatusDetail(false)});
-// assertEquals(finalResult, 5)
- assertEquals(i.get(), 5);
- }
+
+ log.info(JavaClassNames.niceClassAndMethod()+" - submitting {} {}", t, t.getStatusDetail(false));
+ m.submit(t);
+ log.info("submitted {} {}", t, t.getStatusDetail(false));
+ Integer interimResult = (Integer) t.get();
+ log.info("done one ({}) {} {}", new Object[] {interimResult, t, t.getStatusDetail(false)});
+ assertTrue(i.get() > 0);
+ t.blockUntilEnded();
+// int finalResult = t.get()
+ log.info("ended ({}) {} {}", new Object[] {i, t, t.getStatusDetail(false)});
+// assertEquals(finalResult, 5)
+ assertEquals(i.get(), 5);
+ }
+
+ @Test(groups="Integration")
+ public void testScheduledTaskCancelOuter() throws Exception {
+ final Duration PERIOD = Duration.millis(20);
+ final Duration CYCLE_DELAY = Duration.ONE_SECOND;
+ // this should be enough to start the next cycle, but not so much that the cycle ends;
+ // and enough that when a task is interrupted it terminates within this period
+ final Duration SMALL_FRACTION_OF_CYCLE_DELAY = PERIOD.add(CYCLE_DELAY.multiply(0.1));
+
+ BasicExecutionManager m = new BasicExecutionManager("mycontextid");
+ final AtomicInteger i = new AtomicInteger();
+ ScheduledTask t = new ScheduledTask(MutableMap.of("delay", PERIOD.times(2), "period", PERIOD), new Callable<Task<?>>() {
+ public Task<?> call() throws Exception {
+ return new BasicTask<Integer>(new Callable<Integer>() {
+ public Integer call() {
+ log.info("task running ("+i+"): "+Tasks.current()+" "+Tasks.current().getStatusDetail(false));
+ Time.sleep(CYCLE_DELAY);
+ i.incrementAndGet();
+ return i.get();
+ }});
+ }});
+
+ log.info(JavaClassNames.niceClassAndMethod()+" - submitting {} {}", t, t.getStatusDetail(false));
+ m.submit(t);
+ log.info("submitted {} {}", t, t.getStatusDetail(false));
+ Integer interimResult = (Integer) t.get();
+ log.info("done one ({}) {} {}", new Object[] {interimResult, t, t.getStatusDetail(false)});
+ assertEquals(i.get(), 1);
+
+ Time.sleep(SMALL_FRACTION_OF_CYCLE_DELAY);
+ assertEquals(t.get(), 2);
+
+ Time.sleep(SMALL_FRACTION_OF_CYCLE_DELAY);
+ Stopwatch timer = Stopwatch.createUnstarted();
+ t.cancel(true);
+ t.blockUntilEnded();
+// int finalResult = t.get()
+ log.info("blocked until ended ({}) {} {}, in {}", new Object[] {i, t, t.getStatusDetail(false), Duration.of(timer)});
+ try {
+ t.get();
+ Assert.fail("Should have failed getting result of cancelled "+t);
+ } catch (Exception e) {
+ /* expected */
+ }
+ assertEquals(i.get(), 2);
+ log.info("ended ({}) {} {}, in {}", new Object[] {i, t, t.getStatusDetail(false), Duration.of(timer)});
+ Assert.assertTrue(Duration.of(timer).isShorterThan(SMALL_FRACTION_OF_CYCLE_DELAY));
+ }
+
+ @Test(groups="Integration")
+ public void testScheduledTaskCancelInterrupts() throws Exception {
+ final Duration PERIOD = Duration.millis(20);
+ final Duration CYCLE_DELAY = Duration.ONE_SECOND;
+ // this should be enough to start the next cycle, but not so much that the cycle ends;
+ // and enough that when a task is interrupted it terminates within this period
+ final Duration SMALL_FRACTION_OF_CYCLE_DELAY = PERIOD.add(CYCLE_DELAY.multiply(0.1));
+
+ BasicExecutionManager m = new BasicExecutionManager("mycontextid");
+ final Semaphore interruptedSemaphore = new Semaphore(0);
+ final AtomicInteger i = new AtomicInteger();
+ ScheduledTask t = new ScheduledTask(MutableMap.of("delay", PERIOD.times(2), "period", PERIOD), new Callable<Task<?>>() {
+ public Task<?> call() throws Exception {
+ return new BasicTask<Integer>(new Callable<Integer>() {
+ public Integer call() {
+ try {
+ log.info("task running ("+i+"): "+Tasks.current()+" "+Tasks.current().getStatusDetail(false));
+ Time.sleep(CYCLE_DELAY);
+ i.incrementAndGet();
+ return i.get();
+ } catch (RuntimeInterruptedException e) {
+ interruptedSemaphore.release();
+ throw Exceptions.propagate(e);
+ }
+ }});
+ }});
+
+ log.info(JavaClassNames.niceClassAndMethod()+" - submitting {} {}", t, t.getStatusDetail(false));
+ m.submit(t);
+ log.info("submitted {} {}", t, t.getStatusDetail(false));
+ Integer interimResult = (Integer) t.get();
+ log.info("done one ({}) {} {}", new Object[] {interimResult, t, t.getStatusDetail(false)});
+ assertEquals(i.get(), 1);
+
+ Time.sleep(SMALL_FRACTION_OF_CYCLE_DELAY);
+ assertEquals(t.get(), 2);
+
+ Time.sleep(SMALL_FRACTION_OF_CYCLE_DELAY);
+ Stopwatch timer = Stopwatch.createUnstarted();
+ t.cancel(true);
+ t.blockUntilEnded();
+// int finalResult = t.get()
+ log.info("blocked until ended ({}) {} {}, in {}", new Object[] {i, t, t.getStatusDetail(false), Duration.of(timer)});
+ try {
+ t.get();
+ Assert.fail("Should have failed getting result of cancelled "+t);
+ } catch (Exception e) {
+ /* expected */
+ }
+ assertEquals(i.get(), 2);
+ Assert.assertTrue(interruptedSemaphore.tryAcquire(1, SMALL_FRACTION_OF_CYCLE_DELAY.toMilliseconds(), TimeUnit.MILLISECONDS), "child thread was not interrupted");
+ log.info("ended ({}) {} {}, in {}", new Object[] {i, t, t.getStatusDetail(false), Duration.of(timer)});
+ Assert.assertTrue(Duration.of(timer).isShorterThan(SMALL_FRACTION_OF_CYCLE_DELAY));
+ }
@Test(groups="Integration")
public void testScheduledTaskTakesLongerThanPeriod() throws Exception {
[04/15] git commit: RebindManager is can run in persistence mode or
in read-only mode
Posted by he...@apache.org.
RebindManager is can run in persistence mode or in read-only mode
Project: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/commit/5188c137
Tree: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/tree/5188c137
Diff: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/diff/5188c137
Branch: refs/heads/master
Commit: 5188c137dd0ad62b478ce356820ed847c9bb02b1
Parents: c2b05ff
Author: Alex Heneveld <al...@cloudsoftcorp.com>
Authored: Fri Sep 26 22:29:00 2014 +0100
Committer: Alex Heneveld <al...@cloudsoftcorp.com>
Committed: Wed Oct 1 16:40:15 2014 +0100
----------------------------------------------------------------------
.../brooklyn/entity/rebind/RebindManager.java | 31 +++-
.../rebind/PeriodicDeltaChangeListener.java | 12 +-
.../rebind/RebindExceptionHandlerImpl.java | 5 +-
.../entity/rebind/RebindManagerImpl.java | 180 +++++++++++++++----
.../BrooklynMementoPersisterToObjectStore.java | 38 ++--
.../ha/HighAvailabilityManagerImpl.java | 19 +-
.../NonDeploymentManagementContext.java | 20 +++
.../entity/rebind/RebindCatalogEntityTest.java | 2 +-
.../entity/rebind/RebindEntityTest.java | 2 +-
.../entity/rebind/RebindFailuresTest.java | 2 +-
.../brooklyn/entity/rebind/RebindTestUtils.java | 6 +-
...BrooklynMementoPersisterToMultiFileTest.java | 2 +-
.../brooklyn/management/ha/HotStandbyTest.java | 148 +++++++++++++++
.../AbstractWebAppFixtureIntegrationTest.java | 2 +-
.../brooklyn/launcher/BrooklynLauncher.java | 2 +-
.../brooklyn/util/exceptions/Exceptions.java | 30 ++--
16 files changed, 419 insertions(+), 82 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/5188c137/api/src/main/java/brooklyn/entity/rebind/RebindManager.java
----------------------------------------------------------------------
diff --git a/api/src/main/java/brooklyn/entity/rebind/RebindManager.java b/api/src/main/java/brooklyn/entity/rebind/RebindManager.java
index 5c5d972..8bbdcaa 100644
--- a/api/src/main/java/brooklyn/entity/rebind/RebindManager.java
+++ b/api/src/main/java/brooklyn/entity/rebind/RebindManager.java
@@ -24,7 +24,7 @@ import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import brooklyn.entity.Application;
-import brooklyn.mementos.BrooklynMemento;
+import brooklyn.management.ha.ManagementNodeState;
import brooklyn.mementos.BrooklynMementoPersister;
import brooklyn.mementos.BrooklynMementoRawData;
import brooklyn.util.time.Duration;
@@ -72,15 +72,32 @@ public interface RebindManager {
public ChangeListener getChangeListener();
/**
- * Starts the persisting of state (if persister is set; otherwise will start persisting as soon as
- * persister is set). Until {@link #start()} is called, no data will be persisted but entities can
- * rebind.
+ * Starts the background persisting of state
+ * (if persister is set; otherwise will start persisting as soon as persister is set).
+ * Until this is called, no data will be persisted although entities can be rebinded.
*/
- public void start();
+ public void startPersistence();
- /** Stops persisting. Waits for any current persistence to complete. */
- public void stop();
+ /** Stops the background persistence of state.
+ * Waits for any current persistence to complete. */
+ public void stopPersistence();
+ /**
+ * Perform an initial load of state read-only and starts a background process
+ * reading (mirroring) state periodically.
+ */
+ public void startReadOnly();
+ /** Stops the background reading (mirroring) of state.
+ * Interrupts any current activity and waits for it to cease. */
+ public void stopReadOnly();
+
+ /** Starts the appropriate background processes, {@link #startPersistence()} if {@link ManagementNodeState#MASTER},
+ * {@link #startReadOnly()} if {@link ManagementNodeState#HOT_STANDBY} */
+ public void start();
+ /** Stops the appropriate background processes, {@link #stopPersistence()} or {@link #stopReadOnly()},
+ * waiting for activity there to cease (interrupting in the case of {@link #stopReadOnly()}). */
+ public void stop();
+
/** @deprecated since 0.7.0; use {@link #waitForPendingComplete(Duration)} */
@VisibleForTesting
@Deprecated
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/5188c137/core/src/main/java/brooklyn/entity/rebind/PeriodicDeltaChangeListener.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/entity/rebind/PeriodicDeltaChangeListener.java b/core/src/main/java/brooklyn/entity/rebind/PeriodicDeltaChangeListener.java
index a20a158..0d708f4 100644
--- a/core/src/main/java/brooklyn/entity/rebind/PeriodicDeltaChangeListener.java
+++ b/core/src/main/java/brooklyn/entity/rebind/PeriodicDeltaChangeListener.java
@@ -117,11 +117,17 @@ public class PeriodicDeltaChangeListener implements ChangeListener {
private final Semaphore persistingMutex = new Semaphore(1);
+ /** @deprecated since 0.7.0 pass in a {@link Duration} */
+ @Deprecated
public PeriodicDeltaChangeListener(ExecutionManager executionManager, BrooklynMementoPersister persister, PersistenceExceptionHandler exceptionHandler, long periodMillis) {
+ this(executionManager, persister, exceptionHandler, Duration.of(periodMillis, TimeUnit.MILLISECONDS));
+ }
+
+ public PeriodicDeltaChangeListener(ExecutionManager executionManager, BrooklynMementoPersister persister, PersistenceExceptionHandler exceptionHandler, Duration period) {
this.executionManager = executionManager;
this.persister = persister;
this.exceptionHandler = exceptionHandler;
- this.period = Duration.of(periodMillis, TimeUnit.MILLISECONDS);
+ this.period = period;
this.persistPoliciesEnabled = BrooklynFeatureEnablement.isEnabled(BrooklynFeatureEnablement.FEATURE_POLICY_PERSISTENCE_PROPERTY);
this.persistEnrichersEnabled = BrooklynFeatureEnablement.isEnabled(BrooklynFeatureEnablement.FEATURE_ENRICHER_PERSISTENCE_PROPERTY);
@@ -130,6 +136,10 @@ public class PeriodicDeltaChangeListener implements ChangeListener {
@SuppressWarnings("unchecked")
public void start() {
+ if (running || (scheduledTask!=null && !scheduledTask.isDone())) {
+ LOG.warn("Request to start "+this+" when already running - "+scheduledTask+"; ignoring");
+ return;
+ }
running = true;
Callable<Task<?>> taskFactory = new Callable<Task<?>>() {
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/5188c137/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 ad96961..25523c6 100644
--- a/core/src/main/java/brooklyn/entity/rebind/RebindExceptionHandlerImpl.java
+++ b/core/src/main/java/brooklyn/entity/rebind/RebindExceptionHandlerImpl.java
@@ -36,7 +36,6 @@ import brooklyn.entity.rebind.RebindManager.RebindFailureMode;
import brooklyn.location.Location;
import brooklyn.policy.Enricher;
import brooklyn.policy.Policy;
-import brooklyn.util.exceptions.CompoundRuntimeException;
import brooklyn.util.exceptions.Exceptions;
import brooklyn.util.text.Strings;
@@ -375,8 +374,8 @@ public class RebindExceptionHandlerImpl implements RebindExceptionHandler {
if (allExceptions.isEmpty()) {
return; // no errors
} else {
- CompoundRuntimeException compoundException = new CompoundRuntimeException("Problem"+(allExceptions.size() == 1 ? "" : "s")+" rebinding", allExceptions);
- LOG.info("RebindManager failed (throwing): "+compoundException.toString());
+ RuntimeException compoundException = Exceptions.create("Failure rebinding", allExceptions);
+ LOG.debug(compoundException.getMessage()+" (rethrowing)");
throw compoundException;
}
}
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/5188c137/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 f24612c..70a2ba7 100644
--- a/core/src/main/java/brooklyn/entity/rebind/RebindManagerImpl.java
+++ b/core/src/main/java/brooklyn/entity/rebind/RebindManagerImpl.java
@@ -58,6 +58,8 @@ import brooklyn.location.Location;
import brooklyn.location.basic.AbstractLocation;
import brooklyn.location.basic.LocationInternal;
import brooklyn.management.Task;
+import brooklyn.management.ha.HighAvailabilityManagerImpl;
+import brooklyn.management.ha.ManagementNodeState;
import brooklyn.management.internal.ManagementContextInternal;
import brooklyn.mementos.BrooklynMemento;
import brooklyn.mementos.BrooklynMementoManifest;
@@ -76,12 +78,16 @@ import brooklyn.policy.Policy;
import brooklyn.policy.basic.AbstractPolicy;
import brooklyn.util.collections.MutableMap;
import brooklyn.util.exceptions.Exceptions;
+import brooklyn.util.exceptions.RuntimeInterruptedException;
import brooklyn.util.flags.FlagUtils;
import brooklyn.util.javalang.Reflections;
import brooklyn.util.task.BasicExecutionContext;
+import brooklyn.util.task.BasicTask;
+import brooklyn.util.task.ScheduledTask;
import brooklyn.util.text.Strings;
import brooklyn.util.time.Duration;
+import com.google.api.client.repackaged.com.google.common.base.Preconditions;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Optional;
import com.google.common.collect.ImmutableMap;
@@ -114,16 +120,17 @@ public class RebindManagerImpl implements RebindManager {
public static final Logger LOG = LoggerFactory.getLogger(RebindManagerImpl.class);
- private volatile Duration periodicPersistPeriod = Duration.ONE_SECOND;
-
- private volatile boolean running = false;
-
private final ManagementContextInternal managementContext;
-
- private volatile PeriodicDeltaChangeListener realChangeListener;
- private volatile ChangeListener changeListener;
+ private volatile Duration periodicPersistPeriod = Duration.ONE_SECOND;
+
+ private volatile boolean persistenceRunning = false;
+ private volatile PeriodicDeltaChangeListener persistenceRealChangeListener;
+ private volatile ChangeListener persistencePublicChangeListener;
private volatile BrooklynMementoPersister persister;
+
+ private volatile boolean readOnlyRunning = false;
+ private volatile ScheduledTask readOnlyTask = null;
private final boolean persistPoliciesEnabled;
private final boolean persistEnrichersEnabled;
@@ -160,7 +167,7 @@ public class RebindManagerImpl implements RebindManager {
public RebindManagerImpl(ManagementContextInternal managementContext) {
this.managementContext = managementContext;
- this.changeListener = ChangeListener.NOOP;
+ this.persistencePublicChangeListener = ChangeListener.NOOP;
this.persistPoliciesEnabled = BrooklynFeatureEnablement.isEnabled(BrooklynFeatureEnablement.FEATURE_POLICY_PERSISTENCE_PROPERTY);
this.persistEnrichersEnabled = BrooklynFeatureEnablement.isEnabled(BrooklynFeatureEnablement.FEATURE_ENRICHER_PERSISTENCE_PROPERTY);
@@ -205,11 +212,11 @@ public class RebindManagerImpl implements RebindManager {
}
this.persister = checkNotNull(val, "persister");
- this.realChangeListener = new PeriodicDeltaChangeListener(managementContext.getExecutionManager(), persister, exceptionHandler, periodicPersistPeriod.toMilliseconds());
- this.changeListener = new SafeChangeListener(realChangeListener);
+ this.persistenceRealChangeListener = new PeriodicDeltaChangeListener(managementContext.getExecutionManager(), persister, exceptionHandler, periodicPersistPeriod);
+ this.persistencePublicChangeListener = new SafeChangeListener(persistenceRealChangeListener);
- if (running) {
- realChangeListener.start();
+ if (persistenceRunning) {
+ persistenceRealChangeListener.start();
}
}
@@ -220,16 +227,113 @@ public class RebindManagerImpl implements RebindManager {
}
@Override
- public void start() {
- running = true;
- if (realChangeListener != null) realChangeListener.start();
+ public void startPersistence() {
+ if (readOnlyRunning) {
+ throw new IllegalStateException("Cannot start read-only when already running with persistence");
+ }
+ LOG.debug("Starting persistence, mgmt "+managementContext.getManagementNodeId());
+ persistenceRunning = true;
+ if (persistenceRealChangeListener != null) persistenceRealChangeListener.start();
}
@Override
- public void stop() {
- running = false;
- if (realChangeListener != null) realChangeListener.stop();
+ public void stopPersistence() {
+ LOG.debug("Stopping persistence, mgmt "+managementContext.getManagementNodeId());
+ persistenceRunning = false;
+ if (persistenceRealChangeListener != null) persistenceRealChangeListener.stop();
if (persister != null) persister.stop(true);
+ LOG.debug("Stopped persistence, mgmt "+managementContext.getManagementNodeId());
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ public void startReadOnly() {
+ 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, mgmt "+managementContext.getManagementNodeId());
+ readOnlyRunning = true;
+
+ try {
+ rebind(null, null, ManagementNodeState.HOT_STANDBY);
+ } catch (Exception e) {
+ Exceptions.propagate(e);
+ }
+
+ Callable<Task<?>> taskFactory = new Callable<Task<?>>() {
+ @Override public Task<Void> call() {
+ return new BasicTask<Void>(new Callable<Void>() {
+ public Void call() {
+ try {
+ rebind(null, null, ManagementNodeState.HOT_STANDBY);
+ return null;
+ } catch (RuntimeInterruptedException e) {
+ LOG.debug("Interrupted rebinding (re-interrupting): "+e);
+ if (LOG.isTraceEnabled())
+ LOG.trace("Interrupted rebinding (re-interrupting): "+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) {
+ if (LOG.isTraceEnabled())
+ LOG.trace("Problem rebinding (read-only running turned off): "+e, e);
+ } else {
+ LOG.warn("Problem rebinding: "+Exceptions.collapseText(e), e);
+ }
+ return null;
+ } catch (Throwable t) {
+ LOG.warn("Problem rebinding (rethrowing)", t);
+ throw Exceptions.propagate(t);
+ }
+ }});
+ }
+ };
+ readOnlyTask = (ScheduledTask) managementContext.getExecutionManager().submit(
+ new ScheduledTask(taskFactory).period(periodicPersistPeriod));
+ }
+
+ @Override
+ public void stopReadOnly() {
+ LOG.debug("Stopping read-only rebinding, mgmt "+managementContext.getManagementNodeId());
+ readOnlyRunning = false;
+ if (readOnlyTask!=null) {
+ readOnlyTask.cancel(true);
+ readOnlyTask.blockUntilEnded();
+ readOnlyTask = null;
+ }
+ LOG.debug("Stopped read-only rebinding, mgmt "+managementContext.getManagementNodeId());
+ }
+
+ @Override
+ public void start() {
+ ManagementNodeState target = getRebindMode();
+ if (target==ManagementNodeState.HOT_STANDBY) {
+ startReadOnly();
+ } 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();
+ }
+
+ 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
@@ -240,45 +344,54 @@ public class RebindManagerImpl implements RebindManager {
@Override
@VisibleForTesting
public void waitForPendingComplete(Duration timeout) throws InterruptedException, TimeoutException {
- if (persister == null || !running) return;
- realChangeListener.waitForPendingComplete(timeout);
+ if (persister == null || !persistenceRunning) return;
+ persistenceRealChangeListener.waitForPendingComplete(timeout);
persister.waitForWritesCompleted(timeout);
}
@Override
@VisibleForTesting
public void forcePersistNow() {
- realChangeListener.persistNow();
+ persistenceRealChangeListener.persistNow();
}
@Override
public ChangeListener getChangeListener() {
- return changeListener;
+ return persistencePublicChangeListener;
}
@Override
public List<Application> rebind() throws IOException {
- return rebind(managementContext.getCatalog().getRootClassLoader());
+ return rebind(null, null, null);
}
@Override
public List<Application> rebind(final ClassLoader classLoader) throws IOException {
- RebindExceptionHandler exceptionHandler = RebindExceptionHandlerImpl.builder()
+ return rebind(classLoader, null);
+ }
+
+ @Override
+ public List<Application> rebind(final ClassLoader classLoader, final RebindExceptionHandler exceptionHandler) throws IOException {
+ return rebind(classLoader, exceptionHandler, null);
+ }
+
+ protected List<Application> rebind(ClassLoader classLoaderO, RebindExceptionHandler exceptionHandlerO, ManagementNodeState modeO) throws IOException {
+ final ClassLoader classLoader = classLoaderO!=null ? classLoaderO :
+ managementContext.getCatalog().getRootClassLoader();
+ final RebindExceptionHandler exceptionHandler = exceptionHandlerO!=null ? exceptionHandlerO :
+ RebindExceptionHandlerImpl.builder()
.danglingRefFailureMode(danglingRefFailureMode)
.rebindFailureMode(rebindFailureMode)
.addPolicyFailureMode(addPolicyFailureMode)
.loadPolicyFailureMode(loadPolicyFailureMode)
.build();
- return rebind(classLoader, exceptionHandler);
- }
-
- @Override
- public List<Application> rebind(final ClassLoader classLoader, final RebindExceptionHandler exceptionHandler) throws IOException {
+ final ManagementNodeState mode = modeO!=null ? modeO : getRebindMode();
+
BasicExecutionContext ec = BasicExecutionContext.getCurrentExecutionContext();
if (ec == null) {
ec = new BasicExecutionContext(managementContext.getExecutionManager());
Task<List<Application>> task = ec.submit(new Callable<List<Application>>() {
@Override public List<Application> call() throws Exception {
- return rebindImpl(classLoader, exceptionHandler);
+ return rebindImpl(classLoader, exceptionHandler, mode);
}});
try {
return task.get();
@@ -286,7 +399,7 @@ public class RebindManagerImpl implements RebindManager {
throw Exceptions.propagate(e);
}
} else {
- return rebindImpl(classLoader, exceptionHandler);
+ return rebindImpl(classLoader, exceptionHandler, mode);
}
}
@@ -320,8 +433,10 @@ public class RebindManagerImpl implements RebindManager {
}
}
- protected List<Application> rebindImpl(final ClassLoader classLoader, final RebindExceptionHandler exceptionHandler) throws IOException {
+ protected List<Application> rebindImpl(final ClassLoader classLoader, final RebindExceptionHandler exceptionHandler, ManagementNodeState mode) throws IOException {
checkNotNull(classLoader, "classLoader");
+ boolean isReadOnly = mode==ManagementNodeState.HOT_STANDBY;
+ if (!isReadOnly) Preconditions.checkState(mode==ManagementNodeState.MASTER, "Must be either master or read only to rebind (mode "+mode+")");
RebindTracker.setRebinding();
try {
@@ -649,6 +764,7 @@ public class RebindManagerImpl implements RebindManager {
exceptionHandler.onNotFound(BrooklynObjectType.ENTITY, appId);
} else {
try {
+ // TODO if read only we must do something special here!
Entities.startManagement((Application)entity, managementContext);
} catch (Exception e) {
exceptionHandler.onManageFailed(BrooklynObjectType.ENTITY, entity, e);
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/5188c137/core/src/main/java/brooklyn/entity/rebind/persister/BrooklynMementoPersisterToObjectStore.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/entity/rebind/persister/BrooklynMementoPersisterToObjectStore.java b/core/src/main/java/brooklyn/entity/rebind/persister/BrooklynMementoPersisterToObjectStore.java
index 0b95e47..5b224b1 100644
--- a/core/src/main/java/brooklyn/entity/rebind/persister/BrooklynMementoPersisterToObjectStore.java
+++ b/core/src/main/java/brooklyn/entity/rebind/persister/BrooklynMementoPersisterToObjectStore.java
@@ -102,6 +102,7 @@ public class BrooklynMementoPersisterToObjectStore implements BrooklynMementoPer
private final ListeningExecutorService executor;
private volatile boolean running = true;
+ private volatile boolean shuttingDown = false;
/**
* Lock used on writes (checkpoint + delta) so that {@link #waitForWritesCompleted(Duration)} can block
@@ -136,20 +137,25 @@ public class BrooklynMementoPersisterToObjectStore implements BrooklynMementoPer
@Override
public void stop(boolean graceful) {
- running = false;
- if (executor != null) {
- if (graceful) {
- // a very long timeout to ensure we don't lose state.
- // If persisting thousands of entities over slow network to Object Store, could take minutes.
- executor.shutdown();
- try {
- executor.awaitTermination(1, TimeUnit.HOURS);
- } catch (InterruptedException e) {
- throw Exceptions.propagate(e);
+ shuttingDown = true;
+ try {
+ running = false;
+ if (executor != null) {
+ if (graceful) {
+ // a very long timeout to ensure we don't lose state.
+ // If persisting thousands of entities over slow network to Object Store, could take minutes.
+ executor.shutdown();
+ try {
+ executor.awaitTermination(1, TimeUnit.HOURS);
+ } catch (InterruptedException e) {
+ throw Exceptions.propagate(e);
+ }
+ } else {
+ executor.shutdownNow();
}
- } else {
- executor.shutdownNow();
}
+ } finally {
+ shuttingDown = false;
}
}
@@ -325,7 +331,7 @@ public class BrooklynMementoPersisterToObjectStore implements BrooklynMementoPer
public void visit(String contents, BrooklynObjectType type, String subPath) throws Exception;
}
protected void visitMemento(final Visitor visitor, final RebindExceptionHandler exceptionHandler) throws IOException {
- if (!running) {
+ if (!running && !shuttingDown) {
throw new IllegalStateException("Persister not running; cannot load memento manifest from " + objectStore.getSummaryName());
}
@@ -425,7 +431,7 @@ public class BrooklynMementoPersisterToObjectStore implements BrooklynMementoPer
@Beta
public void checkpoint(BrooklynMementoRawData newMemento, PersistenceExceptionHandler exceptionHandler) {
- if (!running) {
+ if (!running && !shuttingDown) {
if (LOG.isDebugEnabled()) LOG.debug("Ignoring checkpointing entire memento, because not running");
return;
}
@@ -480,7 +486,7 @@ public class BrooklynMementoPersisterToObjectStore implements BrooklynMementoPer
@Override
public void checkpoint(BrooklynMemento newMemento, PersistenceExceptionHandler exceptionHandler) {
- if (!running) {
+ if (!running && !shuttingDown) {
if (LOG.isDebugEnabled()) LOG.debug("Ignoring checkpointing entire memento, because not running");
return;
}
@@ -500,7 +506,7 @@ public class BrooklynMementoPersisterToObjectStore implements BrooklynMementoPer
@Override
public void delta(Delta delta, PersistenceExceptionHandler exceptionHandler) {
- if (!running) {
+ if (!running && !shuttingDown) {
if (LOG.isDebugEnabled()) LOG.debug("Ignoring checkpointed delta of memento, because not running");
return;
}
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/5188c137/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 2d7bf9f..d63aa99 100644
--- a/core/src/main/java/brooklyn/management/ha/HighAvailabilityManagerImpl.java
+++ b/core/src/main/java/brooklyn/management/ha/HighAvailabilityManagerImpl.java
@@ -51,6 +51,7 @@ import brooklyn.util.task.BasicTask;
import brooklyn.util.task.ScheduledTask;
import brooklyn.util.time.Duration;
+import com.google.api.client.repackaged.com.google.common.base.Preconditions;
import com.google.common.annotations.Beta;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Function;
@@ -98,6 +99,7 @@ public class HighAvailabilityManagerImpl implements HighAvailabilityManager {
// TODO Should we pass in a classloader on construction, so it can be passed to {@link RebindManager#rebind(ClassLoader)}
+ @VisibleForTesting /* only used in tests currently */
public static interface PromotionListener {
public void promotingToMaster();
}
@@ -602,8 +604,21 @@ public class HighAvailabilityManagerImpl implements HighAvailabilityManager {
/** starts hot standby, in foreground; the caller is responsible for publishing health afterwards.
* @return whether hot standby was possible (if not, errors should be stored elsewhere) */
protected boolean attemptHotStandby() {
- nodeState = ManagementNodeState.HOT_STANDBY;
- return true;
+ try {
+ Preconditions.checkState(nodeStateTransitionComplete==false, "Must be in transitioning state to go into hot standby");
+ nodeState = ManagementNodeState.HOT_STANDBY;
+
+ // XXX
+ managementContext.getRebindManager().startReadOnly();
+
+ return true;
+ } catch (Exception e) {
+ Exceptions.propagateIfFatal(e);
+ // TODO maybe move to failure state? and record problems somewhere else for investigation subsequently.
+ nodeState = ManagementNodeState.STANDBY;
+ LOG.warn("Unable to promote "+ownNodeId+" to hot standby: "+e, e);
+ return false;
+ }
}
/**
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/5188c137/core/src/main/java/brooklyn/management/internal/NonDeploymentManagementContext.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/management/internal/NonDeploymentManagementContext.java b/core/src/main/java/brooklyn/management/internal/NonDeploymentManagementContext.java
index cc2c916..a404bb8 100644
--- a/core/src/main/java/brooklyn/management/internal/NonDeploymentManagementContext.java
+++ b/core/src/main/java/brooklyn/management/internal/NonDeploymentManagementContext.java
@@ -447,6 +447,26 @@ public class NonDeploymentManagementContext implements ManagementContextInternal
}
@Override
+ public void startPersistence() {
+ throw new IllegalStateException("Non-deployment context "+NonDeploymentManagementContext.this+" is not valid for this operation.");
+ }
+
+ @Override
+ public void stopPersistence() {
+ throw new IllegalStateException("Non-deployment context "+NonDeploymentManagementContext.this+" is not valid for this operation.");
+ }
+
+ @Override
+ public void startReadOnly() {
+ throw new IllegalStateException("Non-deployment context "+NonDeploymentManagementContext.this+" is not valid for this operation.");
+ }
+
+ @Override
+ public void stopReadOnly() {
+ throw new IllegalStateException("Non-deployment context "+NonDeploymentManagementContext.this+" is not valid for this operation.");
+ }
+
+ @Override
public void start() {
throw new IllegalStateException("Non-deployment context "+NonDeploymentManagementContext.this+" is not valid for this operation.");
}
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/5188c137/core/src/test/java/brooklyn/entity/rebind/RebindCatalogEntityTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/brooklyn/entity/rebind/RebindCatalogEntityTest.java b/core/src/test/java/brooklyn/entity/rebind/RebindCatalogEntityTest.java
index ad484b3..e482577 100644
--- a/core/src/test/java/brooklyn/entity/rebind/RebindCatalogEntityTest.java
+++ b/core/src/test/java/brooklyn/entity/rebind/RebindCatalogEntityTest.java
@@ -129,7 +129,7 @@ public class RebindCatalogEntityTest extends RebindTestFixture<StartableApplicat
ClassLoader classLoader = newManagementContext.getCatalog().getRootClassLoader();
List<Application> newApps = newManagementContext.getRebindManager().rebind(classLoader);
- newManagementContext.getRebindManager().start();
+ newManagementContext.getRebindManager().startPersistence();
return (StartableApplication) newApps.get(0);
}
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/5188c137/core/src/test/java/brooklyn/entity/rebind/RebindEntityTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/brooklyn/entity/rebind/RebindEntityTest.java b/core/src/test/java/brooklyn/entity/rebind/RebindEntityTest.java
index 1886619..fed6dd9 100644
--- a/core/src/test/java/brooklyn/entity/rebind/RebindEntityTest.java
+++ b/core/src/test/java/brooklyn/entity/rebind/RebindEntityTest.java
@@ -579,7 +579,7 @@ public class RebindEntityTest extends RebindTestFixtureWithApp {
RebindTestUtils.waitForPersisted(origManagementContext);
newManagementContext = RebindTestUtils.newPersistingManagementContextUnstarted(mementoDir, classLoader);
List<Application> newApps = newManagementContext.getRebindManager().rebind(classLoader);
- newManagementContext.getRebindManager().start();
+ newManagementContext.getRebindManager().startPersistence();
assertEquals(newApps.size(), 0, "apps="+newApps);
assertEquals(newManagementContext.getApplications().size(), 0, "apps="+newManagementContext.getApplications());
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/5188c137/core/src/test/java/brooklyn/entity/rebind/RebindFailuresTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/brooklyn/entity/rebind/RebindFailuresTest.java b/core/src/test/java/brooklyn/entity/rebind/RebindFailuresTest.java
index 1fe4007..9714dc9 100644
--- a/core/src/test/java/brooklyn/entity/rebind/RebindFailuresTest.java
+++ b/core/src/test/java/brooklyn/entity/rebind/RebindFailuresTest.java
@@ -160,7 +160,7 @@ public class RebindFailuresTest extends RebindTestFixtureWithApp {
RebindFailureMode danglingRefFailureMode = RebindManager.RebindFailureMode.CONTINUE;
RebindFailureMode rebindFailureMode = RebindManager.RebindFailureMode.FAIL_AT_END;
- origManagementContext.getRebindManager().stop();
+ origManagementContext.getRebindManager().stopPersistence();
if (mementoDir != null) RebindTestUtils.deleteMementoDir(mementoDir);
File entitiesDir = Os.mkdirs(new File(mementoDir, "entities"));
Files.write("invalid text", new File(entitiesDir, "mycorruptfile"), Charsets.UTF_8);
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/5188c137/core/src/test/java/brooklyn/entity/rebind/RebindTestUtils.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/brooklyn/entity/rebind/RebindTestUtils.java b/core/src/test/java/brooklyn/entity/rebind/RebindTestUtils.java
index 7993c33..65738a7 100644
--- a/core/src/test/java/brooklyn/entity/rebind/RebindTestUtils.java
+++ b/core/src/test/java/brooklyn/entity/rebind/RebindTestUtils.java
@@ -211,7 +211,7 @@ public class RebindTestUtils {
public LocalManagementContext buildStarted() {
LocalManagementContext unstarted = buildUnstarted();
unstarted.getHighAvailabilityManager().disabled();
- unstarted.getRebindManager().start();
+ unstarted.getRebindManager().startPersistence();
return unstarted;
}
@@ -256,7 +256,7 @@ public class RebindTestUtils {
newApps = newManagementContext.getRebindManager().rebind(classLoader, exceptionHandler);
}
if (newApps.isEmpty()) throw new IllegalStateException("Application could not be rebinded; serialization probably failed");
- newManagementContext.getRebindManager().start();
+ newManagementContext.getRebindManager().startPersistence();
return newApps;
}
@@ -292,7 +292,7 @@ public class RebindTestUtils {
} else {
newApps = newManagementContext.getRebindManager().rebind(classLoader, exceptionHandler);
}
- newManagementContext.getRebindManager().start();
+ newManagementContext.getRebindManager().startPersistence();
return newApps;
}
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/5188c137/core/src/test/java/brooklyn/entity/rebind/persister/BrooklynMementoPersisterToMultiFileTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/brooklyn/entity/rebind/persister/BrooklynMementoPersisterToMultiFileTest.java b/core/src/test/java/brooklyn/entity/rebind/persister/BrooklynMementoPersisterToMultiFileTest.java
index d4edd41..5449791 100644
--- a/core/src/test/java/brooklyn/entity/rebind/persister/BrooklynMementoPersisterToMultiFileTest.java
+++ b/core/src/test/java/brooklyn/entity/rebind/persister/BrooklynMementoPersisterToMultiFileTest.java
@@ -48,7 +48,7 @@ public class BrooklynMementoPersisterToMultiFileTest extends BrooklynMementoPers
persister = new BrooklynMementoPersisterToMultiFile(mementoDir, BrooklynMementoPersisterToMultiFileTest.class.getClassLoader());
mgmt.getRebindManager().setPersister(persister, PersistenceExceptionHandlerImpl.builder().build());
mgmt.getHighAvailabilityManager().disabled();
- mgmt.getRebindManager().start();
+ mgmt.getRebindManager().startPersistence();
return mgmt;
}
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/5188c137/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
new file mode 100644
index 0000000..e4219f7
--- /dev/null
+++ b/core/src/test/java/brooklyn/management/ha/HotStandbyTest.java
@@ -0,0 +1,148 @@
+/*
+ * 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.management.ha;
+
+import static org.testng.Assert.assertEquals;
+
+import java.util.Date;
+import java.util.List;
+import java.util.Map;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.testng.annotations.AfterMethod;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import brooklyn.entity.basic.Entities;
+import brooklyn.entity.rebind.PersistenceExceptionHandlerImpl;
+import brooklyn.entity.rebind.persister.BrooklynMementoPersisterToObjectStore;
+import brooklyn.entity.rebind.persister.InMemoryObjectStore;
+import brooklyn.entity.rebind.persister.ListeningObjectStore;
+import brooklyn.entity.rebind.persister.PersistMode;
+import brooklyn.entity.rebind.persister.PersistenceObjectStore;
+import brooklyn.location.Location;
+import brooklyn.management.internal.ManagementContextInternal;
+import brooklyn.test.entity.LocalManagementContextForTests;
+import brooklyn.test.entity.TestApplication;
+import brooklyn.test.entity.TestEntity;
+import brooklyn.util.collections.MutableList;
+import brooklyn.util.collections.MutableMap;
+import brooklyn.util.time.Duration;
+
+@Test
+public class HotStandbyTest {
+
+ private static final Logger log = LoggerFactory.getLogger(HotStandbyTest.class);
+
+ private List<HaMgmtNode> nodes = new MutableList<HotStandbyTest.HaMgmtNode>();
+ Map<String,String> sharedBackingStore = MutableMap.of();
+ Map<String,Date> sharedBackingStoreDates = MutableMap.of();
+ private ClassLoader classLoader = getClass().getClassLoader();
+
+ public class HaMgmtNode {
+
+ private ManagementContextInternal mgmt;
+ private String ownNodeId;
+ private String nodeName;
+ private ListeningObjectStore objectStore;
+ private ManagementPlaneSyncRecordPersister persister;
+ private HighAvailabilityManagerImpl ha;
+
+ @BeforeMethod(alwaysRun=true)
+ public void setUp() throws Exception {
+ nodeName = "node "+nodes.size();
+ mgmt = newLocalManagementContext();
+ ownNodeId = mgmt.getManagementNodeId();
+ objectStore = new ListeningObjectStore(newPersistenceObjectStore());
+ objectStore.injectManagementContext(mgmt);
+ objectStore.prepareForSharedUse(PersistMode.CLEAN, HighAvailabilityMode.DISABLED);
+ persister = new ManagementPlaneSyncRecordPersisterToObjectStore(mgmt, objectStore, classLoader);
+ ((ManagementPlaneSyncRecordPersisterToObjectStore)persister).allowRemoteTimestampInMemento();
+ BrooklynMementoPersisterToObjectStore persisterObj = new BrooklynMementoPersisterToObjectStore(objectStore, mgmt.getBrooklynProperties(), classLoader);
+ mgmt.getRebindManager().setPersister(persisterObj, PersistenceExceptionHandlerImpl.builder().build());
+ ha = ((HighAvailabilityManagerImpl)mgmt.getHighAvailabilityManager())
+ .setPollPeriod(Duration.PRACTICALLY_FOREVER)
+ .setHeartbeatTimeout(Duration.THIRTY_SECONDS)
+ .setPersister(persister);
+ log.info("Created "+nodeName+" "+ownNodeId+" - RM "+mgmt.getRebindManager());
+ }
+
+ public void tearDown() throws Exception {
+ if (ha != null) ha.stop();
+ if (mgmt != null) Entities.destroyAll(mgmt);
+ if (objectStore != null) objectStore.deleteCompletely();
+ }
+
+ @Override
+ public String toString() {
+ return nodeName+" "+ownNodeId;
+ }
+ }
+
+ @BeforeMethod(alwaysRun=true)
+ public void setUp() throws Exception {
+ nodes.clear();
+ sharedBackingStore.clear();
+ }
+
+ public HaMgmtNode newNode() throws Exception {
+ HaMgmtNode node = new HaMgmtNode();
+ node.setUp();
+ nodes.add(node);
+ return node;
+ }
+
+ @AfterMethod(alwaysRun=true)
+ public void tearDown() throws Exception {
+ for (HaMgmtNode n: nodes)
+ n.tearDown();
+ }
+
+ protected ManagementContextInternal newLocalManagementContext() {
+ return new LocalManagementContextForTests();
+ }
+
+ protected PersistenceObjectStore newPersistenceObjectStore() {
+ return new InMemoryObjectStore(sharedBackingStore, sharedBackingStoreDates);
+ }
+
+
+
+ @Test
+ public void testHotStandby() throws Exception {
+ HaMgmtNode n1 = newNode();
+ n1.ha.start(HighAvailabilityMode.AUTO);
+ assertEquals(n1.ha.getNodeState(), ManagementNodeState.MASTER);
+
+ TestApplication app = TestApplication.Factory.newManagedInstanceForTests(n1.mgmt);
+ app.start(MutableList.<Location>of());
+ app.setAttribute(TestEntity.SEQUENCE, 3);
+
+ n1.mgmt.getRebindManager().forcePersistNow();
+
+ HaMgmtNode n2 = newNode();
+ n2.ha.start(HighAvailabilityMode.AUTO);
+ assertEquals(n2.ha.getNodeState(), ManagementNodeState.HOT_STANDBY);
+
+ assertEquals(n2.mgmt.getApplications().size(), 1);
+ }
+
+
+}
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/5188c137/software/webapp/src/test/java/brooklyn/entity/webapp/AbstractWebAppFixtureIntegrationTest.java
----------------------------------------------------------------------
diff --git a/software/webapp/src/test/java/brooklyn/entity/webapp/AbstractWebAppFixtureIntegrationTest.java b/software/webapp/src/test/java/brooklyn/entity/webapp/AbstractWebAppFixtureIntegrationTest.java
index 2b358d0..b8c14d7 100644
--- a/software/webapp/src/test/java/brooklyn/entity/webapp/AbstractWebAppFixtureIntegrationTest.java
+++ b/software/webapp/src/test/java/brooklyn/entity/webapp/AbstractWebAppFixtureIntegrationTest.java
@@ -227,7 +227,7 @@ public abstract class AbstractWebAppFixtureIntegrationTest {
newManagementContext = new LocalManagementContextForTests();
newManagementContext.getRebindManager().setPersister(newPersister, PersistenceExceptionHandlerImpl.builder().build());
newManagementContext.getRebindManager().rebind(getClass().getClassLoader());
- newManagementContext.getRebindManager().start();
+ newManagementContext.getRebindManager().startPersistence();
SoftwareProcess entity2 = (SoftwareProcess) newManagementContext.getEntityManager().getEntity(tokill.getId());
entity2.stop();
} finally {
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/5188c137/usage/launcher/src/main/java/brooklyn/launcher/BrooklynLauncher.java
----------------------------------------------------------------------
diff --git a/usage/launcher/src/main/java/brooklyn/launcher/BrooklynLauncher.java b/usage/launcher/src/main/java/brooklyn/launcher/BrooklynLauncher.java
index 47d80a4..bea6c2e 100644
--- a/usage/launcher/src/main/java/brooklyn/launcher/BrooklynLauncher.java
+++ b/usage/launcher/src/main/java/brooklyn/launcher/BrooklynLauncher.java
@@ -790,7 +790,7 @@ public class BrooklynLauncher {
throw new FatalRuntimeException("Error rebinding to persisted state: "+
Exceptions.collapseText(e), e);
}
- rebindManager.start();
+ rebindManager.startPersistence();
}
protected void createApps() {
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/5188c137/utils/common/src/main/java/brooklyn/util/exceptions/Exceptions.java
----------------------------------------------------------------------
diff --git a/utils/common/src/main/java/brooklyn/util/exceptions/Exceptions.java b/utils/common/src/main/java/brooklyn/util/exceptions/Exceptions.java
index 53fb816..81831d9 100644
--- a/utils/common/src/main/java/brooklyn/util/exceptions/Exceptions.java
+++ b/utils/common/src/main/java/brooklyn/util/exceptions/Exceptions.java
@@ -196,25 +196,31 @@ public class Exceptions {
return t2.toString();
}
- public static RuntimeException propagate(String prefix, Collection<? extends Throwable> exceptions) {
- if (exceptions.size()==1)
- throw new CompoundRuntimeException(prefix + ": " + Exceptions.collapseText(exceptions.iterator().next()), exceptions);
- if (exceptions.isEmpty())
- throw new CompoundRuntimeException(prefix, exceptions);
- throw new CompoundRuntimeException(prefix + ", including: " + Exceptions.collapseText(exceptions.iterator().next()), exceptions);
- }
public static RuntimeException propagate(Collection<? extends Throwable> exceptions) {
throw propagate(create(exceptions));
}
+ public static RuntimeException propagate(String prefix, Collection<? extends Throwable> exceptions) {
+ throw propagate(create(prefix, exceptions));
+ }
/** creates the given exception, but without propagating it, for use when caller will be wrapping */
public static Throwable create(Collection<? extends Throwable> exceptions) {
- if (exceptions.size()==1)
- return exceptions.iterator().next();
- if (exceptions.isEmpty())
- return new CompoundRuntimeException("(empty compound exception)", exceptions);
- return new CompoundRuntimeException(exceptions.size()+" errors, including: " + Exceptions.collapseText(exceptions.iterator().next()), exceptions);
+ return create(null, exceptions);
+ }
+ /** creates the given exception, but without propagating it, for use when caller will be wrapping */
+ public static RuntimeException create(String prefix, Collection<? extends Throwable> exceptions) {
+ if (exceptions.size()==1) {
+ Throwable e = exceptions.iterator().next();
+ if (Strings.isBlank(prefix)) return new PropagatedRuntimeException(e);
+ return new PropagatedRuntimeException(prefix + ": " + Exceptions.collapseText(e), e);
+ }
+ if (exceptions.isEmpty()) {
+ if (Strings.isBlank(prefix)) return new CompoundRuntimeException("(empty compound exception)", exceptions);
+ return new CompoundRuntimeException(prefix+": (empty compound exception)", exceptions);
+ }
+ if (Strings.isBlank(prefix)) return new CompoundRuntimeException(exceptions.size()+" errors, including: " + Exceptions.collapseText(exceptions.iterator().next()), exceptions);
+ return new CompoundRuntimeException(prefix+", "+exceptions.size()+" errors including: " + Exceptions.collapseText(exceptions.iterator().next()), exceptions);
}
}
[09/15] Ensure BrooklynObject management, remanagement,
and unmanagement, by replacing Location instances, refreshing the catalog,
and swapping out the delegate but keeping the entity proxy.
Posted by he...@apache.org.
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/4c866cb8/core/src/main/java/brooklyn/management/internal/LocationManagerInternal.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/management/internal/LocationManagerInternal.java b/core/src/main/java/brooklyn/management/internal/LocationManagerInternal.java
new file mode 100644
index 0000000..96e2ff3
--- /dev/null
+++ b/core/src/main/java/brooklyn/management/internal/LocationManagerInternal.java
@@ -0,0 +1,40 @@
+/*
+ * 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.management.internal;
+
+import brooklyn.location.Location;
+import brooklyn.management.LocationManager;
+import brooklyn.management.internal.ManagementTransitionInfo.ManagementTransitionMode;
+
+public interface LocationManagerInternal extends LocationManager {
+
+ public Iterable<String> getLocationIds();
+
+ ManagementTransitionMode getLastManagementTransitionMode(String itemId);
+ void setManagementTransitionMode(Location item, ManagementTransitionMode mode);
+
+ /**
+ * Begins management for the given rebinded root, recursively;
+ * if rebinding as a read-only copy, {@link #setReadOnly(Location, boolean)} should be called prior to this.
+ */
+ void manageRebindedRoot(Location item);
+
+ void unmanage(final Location item, final ManagementTransitionMode info);
+
+}
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/4c866cb8/core/src/main/java/brooklyn/management/internal/ManagementTransitionInfo.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/management/internal/ManagementTransitionInfo.java b/core/src/main/java/brooklyn/management/internal/ManagementTransitionInfo.java
index 170652f..06cd2e4 100644
--- a/core/src/main/java/brooklyn/management/internal/ManagementTransitionInfo.java
+++ b/core/src/main/java/brooklyn/management/internal/ManagementTransitionInfo.java
@@ -31,12 +31,30 @@ public class ManagementTransitionInfo {
*/
public enum ManagementTransitionMode {
- /** Entity is being created fresh, for the first time, or stopping permanently */
- NORMAL,
- /** Entity management is moving from one location to another (ie stopping at one location / starting at another) */
- MIGRATORY,
- /** Entity is being created, from a serialized/specified state */
- REBIND
+ /** Item is being created fresh, for the first time */
+ CREATING(false, false),
+ /** Item is being destroyed / stopping permanently */
+ DESTROYING(false, false),
+
+ /** Item is being mirrored (or refreshed) here from a serialized/specified state */
+ REBINDING_READONLY(true, true),
+ /** Item management is stopping here, going elsewhere */
+ REBINDING_NO_LONGER_PRIMARY(false, true),
+ /** Item management is starting here, having previously been running elsewhere */
+ REBINDING_BECOMING_PRIMARY(true, false),
+ /** Item was being mirrored but has now been destroyed */
+ REBINDING_DESTROYED(true, true);
+
+ private boolean wasReadOnly;
+ private boolean isReadOnly;
+
+ ManagementTransitionMode(boolean wasReadOnly, boolean isReadOnly) {
+ this.wasReadOnly = wasReadOnly;
+ this.isReadOnly = isReadOnly;
+ }
+
+ public boolean wasReadOnly() { return wasReadOnly; }
+ public boolean isReadOnly() { return isReadOnly; }
}
public ManagementTransitionInfo(ManagementContext mgmtContext, ManagementTransitionMode mode) {
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/4c866cb8/core/src/main/java/brooklyn/management/internal/NonDeploymentEntityManager.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/management/internal/NonDeploymentEntityManager.java b/core/src/main/java/brooklyn/management/internal/NonDeploymentEntityManager.java
index f6cc2ef..c3720b1 100644
--- a/core/src/main/java/brooklyn/management/internal/NonDeploymentEntityManager.java
+++ b/core/src/main/java/brooklyn/management/internal/NonDeploymentEntityManager.java
@@ -27,6 +27,7 @@ import brooklyn.entity.Entity;
import brooklyn.entity.proxying.EntitySpec;
import brooklyn.entity.proxying.EntityTypeRegistry;
import brooklyn.management.ManagementContext;
+import brooklyn.management.internal.ManagementTransitionInfo.ManagementTransitionMode;
import brooklyn.policy.Enricher;
import brooklyn.policy.EnricherSpec;
import brooklyn.policy.Policy;
@@ -129,6 +130,33 @@ public class NonDeploymentEntityManager implements EntityManagerInternal {
}
@Override
+ public Iterable<String> getEntityIds() {
+ if (isInitialManagementContextReal()) {
+ return ((EntityManagerInternal)initialManagementContext.getEntityManager()).getEntityIds();
+ } else {
+ return Collections.emptyList();
+ }
+ }
+
+ @Override
+ public ManagementTransitionMode getLastManagementTransitionMode(String itemId) {
+ if (isInitialManagementContextReal()) {
+ return ((EntityManagerInternal)initialManagementContext.getEntityManager()).getLastManagementTransitionMode(itemId);
+ } else {
+ return null;
+ }
+ }
+
+ @Override
+ public void setManagementTransitionMode(Entity item, ManagementTransitionMode mode) {
+ if (isInitialManagementContextReal()) {
+ ((EntityManagerInternal)initialManagementContext.getEntityManager()).setManagementTransitionMode(item, mode);
+ } else {
+ throw new IllegalStateException("Non-deployment context "+this+" is not valid for this operation");
+ }
+ }
+
+ @Override
public boolean isManaged(Entity entity) {
return false;
}
@@ -139,6 +167,16 @@ public class NonDeploymentEntityManager implements EntityManagerInternal {
}
@Override
+ public void unmanage(Entity e, ManagementTransitionMode info) {
+ throw new IllegalStateException("Non-deployment context "+this+" is not valid for this operation");
+ }
+
+ @Override
+ public void manageRebindedRoot(Entity item) {
+ throw new IllegalStateException("Non-deployment context "+this+" is not valid for this operation");
+ }
+
+ @Override
public void unmanage(Entity e) {
throw new IllegalStateException("Non-deployment context "+this+" is not valid for this operation: cannot unmanage "+e);
}
@@ -156,14 +194,4 @@ public class NonDeploymentEntityManager implements EntityManagerInternal {
}
}
- @Override
- public void setReadOnly(Entity e) {
- throw new IllegalStateException("Non-deployment context "+this+" is not valid for this operation: cannot manage "+e);
- }
-
- @Override
- public Boolean isReadOnly(Entity item) {
- // NB: must return null here, otherwise callers may cache this value
- return null;
- }
}
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/4c866cb8/core/src/main/java/brooklyn/management/internal/NonDeploymentLocationManager.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/management/internal/NonDeploymentLocationManager.java b/core/src/main/java/brooklyn/management/internal/NonDeploymentLocationManager.java
index 1aa3c32..3628231 100644
--- a/core/src/main/java/brooklyn/management/internal/NonDeploymentLocationManager.java
+++ b/core/src/main/java/brooklyn/management/internal/NonDeploymentLocationManager.java
@@ -24,10 +24,10 @@ import java.util.Map;
import brooklyn.location.Location;
import brooklyn.location.LocationSpec;
-import brooklyn.management.LocationManager;
import brooklyn.management.ManagementContext;
+import brooklyn.management.internal.ManagementTransitionInfo.ManagementTransitionMode;
-public class NonDeploymentLocationManager implements LocationManager {
+public class NonDeploymentLocationManager implements LocationManagerInternal {
private final ManagementContext initialManagementContext;
@@ -72,11 +72,30 @@ public class NonDeploymentLocationManager implements LocationManager {
}
@Override
+ public Iterable<String> getLocationIds() {
+ if (isInitialManagementContextReal()) {
+ return ((LocationManagerInternal)initialManagementContext.getLocationManager()).getLocationIds();
+ } else {
+ return null;
+ }
+ }
+
+ @Override
public boolean isManaged(Location loc) {
return false;
}
@Override
+ public void manageRebindedRoot(Location loc) {
+ if (isInitialManagementContextReal()) {
+ ((LocationManagerInternal)initialManagementContext.getLocationManager()).manageRebindedRoot(loc);
+ } else {
+ throw new IllegalStateException("Non-deployment context "+this+" is not valid for this operation: cannot manage "+loc);
+ }
+ }
+
+ @Override
+ @Deprecated
public Location manage(Location loc) {
if (isInitialManagementContextReal()) {
return initialManagementContext.getLocationManager().manage(loc);
@@ -85,6 +104,34 @@ public class NonDeploymentLocationManager implements LocationManager {
}
}
+
+ @Override
+ public ManagementTransitionMode getLastManagementTransitionMode(String itemId) {
+ if (isInitialManagementContextReal()) {
+ return ((LocationManagerInternal)initialManagementContext.getLocationManager()).getLastManagementTransitionMode(itemId);
+ } else {
+ throw new IllegalStateException("Non-deployment context "+this+" is not valid for this operation");
+ }
+ }
+
+ @Override
+ public void setManagementTransitionMode(Location item, ManagementTransitionMode mode) {
+ if (isInitialManagementContextReal()) {
+ ((LocationManagerInternal)initialManagementContext.getLocationManager()).setManagementTransitionMode(item, mode);
+ } else {
+ throw new IllegalStateException("Non-deployment context "+this+" is not valid for this operation");
+ }
+ }
+
+ @Override
+ public void unmanage(Location item, ManagementTransitionMode info) {
+ if (isInitialManagementContextReal()) {
+ ((LocationManagerInternal)initialManagementContext.getLocationManager()).unmanage(item, info);
+ } else {
+ throw new IllegalStateException("Non-deployment context "+this+" is not valid for this operation");
+ }
+ }
+
@Override
public void unmanage(Location loc) {
if (isInitialManagementContextReal()) {
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/4c866cb8/core/src/main/java/brooklyn/management/internal/NonDeploymentManagementContext.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/management/internal/NonDeploymentManagementContext.java b/core/src/main/java/brooklyn/management/internal/NonDeploymentManagementContext.java
index dbe50d9..afe42df 100644
--- a/core/src/main/java/brooklyn/management/internal/NonDeploymentManagementContext.java
+++ b/core/src/main/java/brooklyn/management/internal/NonDeploymentManagementContext.java
@@ -473,7 +473,7 @@ public class NonDeploymentManagementContext implements ManagementContextInternal
}
@Override
- public List<Application> rebind(ClassLoader classLoader, RebindExceptionHandler exceptionHandler, ManagementNodeState mode) throws IOException {
+ public List<Application> rebind(ClassLoader classLoader, RebindExceptionHandler exceptionHandler, ManagementNodeState mode) {
throw new IllegalStateException("Non-deployment context "+NonDeploymentManagementContext.this+" is not valid for this operation.");
}
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/4c866cb8/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 03eec67..befdae4 100644
--- a/core/src/test/java/brooklyn/management/ha/HotStandbyTest.java
+++ b/core/src/test/java/brooklyn/management/ha/HotStandbyTest.java
@@ -50,11 +50,9 @@ import brooklyn.test.entity.TestEntity;
import brooklyn.util.collections.MutableList;
import brooklyn.util.collections.MutableMap;
import brooklyn.util.time.Duration;
-import brooklyn.util.time.Time;
import com.google.common.collect.Iterables;
-@Test
public class HotStandbyTest {
private static final Logger log = LoggerFactory.getLogger(HotStandbyTest.class);
@@ -158,12 +156,23 @@ public class HotStandbyTest {
return app;
}
- private Application waitForRebindSequenceNumber(HaMgmtNode master, HaMgmtNode hotStandby, Application app, int expectedSensorSequenceValue) {
- Time.sleep(Duration.FIVE_SECONDS);
+ private Application expectRebindSequenceNumber(HaMgmtNode master, HaMgmtNode hotStandby, Application app, int expectedSensorSequenceValue, boolean immediate) {
Application appRO = hotStandby.mgmt.lookup(app.getId(), Application.class);
- // TODO drop the sleep and the new lookup, when the implementation can do in-place replace
+
+ if (immediate) {
+ master.mgmt.getRebindManager().forcePersistNow();
+ hotStandby.mgmt.getRebindManager().rebind(null, null, ManagementNodeState.HOT_STANDBY);
+ EntityTestUtils.assertAttributeEquals(appRO, TestEntity.SEQUENCE, expectedSensorSequenceValue);
+ } else {
+ EntityTestUtils.assertAttributeEqualsEventually(appRO, TestEntity.SEQUENCE, expectedSensorSequenceValue);
+ }
+
+ log.info("got sequence number "+expectedSensorSequenceValue+" from "+appRO);
+
+ // make sure the instance (proxy) is unchanged
+ Application appRO2 = hotStandby.mgmt.lookup(app.getId(), Application.class);
+ Assert.assertTrue(appRO2==appRO);
- EntityTestUtils.assertAttributeEqualsEventually(appRO, TestEntity.SEQUENCE, expectedSensorSequenceValue);
return appRO;
}
@@ -189,6 +198,7 @@ public class HotStandbyTest {
}
}
+ @Test
public void testHotStandbySeesChangedNameConfigAndSensorValue() throws Exception {
HaMgmtNode n1 = createMaster();
TestApplication app = createFirstAppAndPersist(n1);
@@ -205,7 +215,7 @@ public class HotStandbyTest {
app.setConfig(TestEntity.CONF_NAME, "first-app-renamed");
app.setAttribute(TestEntity.SEQUENCE, 4);
- appRO = waitForRebindSequenceNumber(n1, n2, app, 4);
+ appRO = expectRebindSequenceNumber(n1, n2, app, 4, true);
assertEquals(n2.mgmt.getEntityManager().getEntities().size(), 1);
assertEquals(appRO.getDisplayName(), "First App Renamed");
assertEquals(appRO.getConfig(TestEntity.CONF_NAME), "first-app-renamed");
@@ -216,20 +226,23 @@ public class HotStandbyTest {
app.setConfig(TestEntity.CONF_NAME, "first-app-restored");
app.setAttribute(TestEntity.SEQUENCE, 5);
- appRO = waitForRebindSequenceNumber(n1, n2, app, 4);
+ appRO = expectRebindSequenceNumber(n1, n2, app, 5, true);
assertEquals(n2.mgmt.getEntityManager().getEntities().size(), 1);
assertEquals(appRO.getDisplayName(), "First App");
assertEquals(appRO.getConfig(TestEntity.CONF_NAME), "first-app-restored");
}
- @Test(groups="Integration", invocationCount=50)
- public void testHotStandbySeesAdditionAndRemovalManyTimes() throws Exception {
- testHotStandbySeesStructuralChangesIncludingRemoval();
+ public void testHotStandbySeesStructuralChangesIncludingRemoval() throws Exception {
+ doTestHotStandbySeesStructuralChangesIncludingRemoval(true);
}
- @Test(groups="Integration") // due to time (could speed up by forcing persist and rebind)
- public void testHotStandbySeesStructuralChangesIncludingRemoval() throws Exception {
+ @Test(groups="Integration") // due to time (it waits for background persistence)
+ public void testHotStandbyUnforcedSeesStructuralChangesIncludingRemoval() throws Exception {
+ doTestHotStandbySeesStructuralChangesIncludingRemoval(false);
+ }
+
+ public void doTestHotStandbySeesStructuralChangesIncludingRemoval(boolean immediate) throws Exception {
HaMgmtNode n1 = createMaster();
TestApplication app = createFirstAppAndPersist(n1);
HaMgmtNode n2 = createHotStandby();
@@ -242,10 +255,12 @@ public class HotStandbyTest {
// test additions - new child, new app
TestEntity child = app.addChild(EntitySpec.create(TestEntity.class).configure(TestEntity.CONF_NAME, "first-child"));
+ Entities.manage(child);
TestApplication app2 = TestApplication.Factory.newManagedInstanceForTests(n1.mgmt);
+ app2.setConfig(TestEntity.CONF_NAME, "second-app");
app.setAttribute(TestEntity.SEQUENCE, 4);
- appRO = waitForRebindSequenceNumber(n1, n2, app, 4);
+ appRO = expectRebindSequenceNumber(n1, n2, app, 4, immediate);
assertEquals(appRO.getChildren().size(), 1);
Entity childRO = Iterables.getOnlyElement(appRO.getChildren());
@@ -265,7 +280,7 @@ public class HotStandbyTest {
Entities.unmanage(app2);
app.setAttribute(TestEntity.SEQUENCE, 5);
- appRO = waitForRebindSequenceNumber(n1, n2, app, 5);
+ appRO = expectRebindSequenceNumber(n1, n2, app, 5, immediate);
EntityTestUtils.assertAttributeEqualsEventually(appRO, TestEntity.SEQUENCE, 5);
assertEquals(n2.mgmt.getEntityManager().getEntities().size(), 1);
@@ -274,5 +289,10 @@ public class HotStandbyTest {
Assert.assertNull(n2.mgmt.lookup(app2.getId(), Application.class));
Assert.assertNull(n2.mgmt.lookup(child.getId(), Application.class));
}
+
+ @Test(groups="Integration", invocationCount=50)
+ public void testHotStandbySeesStructuralChangesIncludingRemovalManyTimes() throws Exception {
+ doTestHotStandbySeesStructuralChangesIncludingRemoval(true);
+ }
}
[02/15] git commit: set up a new
ManagementContext.getServerExecutionContext() and use that for node-level
tasks, with a tag that can be tracked;
improve lifecycle and configuration for rebind exception handler and for the
persister itself
Posted by he...@apache.org.
set up a new ManagementContext.getServerExecutionContext() and use that for node-level tasks, with a tag that can be tracked; improve lifecycle and configuration for rebind exception handler and for the persister itself
Project: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/commit/ea4b4ccb
Tree: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/tree/ea4b4ccb
Diff: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/diff/ea4b4ccb
Branch: refs/heads/master
Commit: ea4b4ccb4c26f2c6e7547853e91ff619f2db6365
Parents: 5188c13
Author: Alex Heneveld <al...@cloudsoftcorp.com>
Authored: Fri Sep 26 23:27:54 2014 +0100
Committer: Alex Heneveld <al...@cloudsoftcorp.com>
Committed: Wed Oct 1 16:40:15 2014 +0100
----------------------------------------------------------------------
.../entity/rebind/RebindExceptionHandler.java | 19 ++++-
.../brooklyn/entity/rebind/RebindManager.java | 9 +--
.../brooklyn/management/ExecutionContext.java | 8 ++
.../brooklyn/management/ManagementContext.java | 6 ++
.../mementos/BrooklynMementoPersister.java | 3 +
.../brooklyn/entity/basic/BrooklynTaskTags.java | 4 +
.../java/brooklyn/entity/basic/Entities.java | 2 +-
.../rebind/PeriodicDeltaChangeListener.java | 16 ++--
.../rebind/RebindExceptionHandlerImpl.java | 25 ++++++
.../entity/rebind/RebindManagerImpl.java | 58 +++++++++-----
.../AbstractBrooklynMementoPersister.java | 9 +--
.../BrooklynMementoPersisterToMultiFile.java | 8 ++
.../BrooklynMementoPersisterToObjectStore.java | 81 +++++++++++---------
.../ha/HighAvailabilityManagerImpl.java | 16 +++-
.../internal/AbstractManagementContext.java | 8 +-
.../internal/LocalManagementContext.java | 4 +-
.../NonDeploymentManagementContext.java | 11 +++
.../util/task/AbstractExecutionContext.java | 7 +-
.../util/task/BasicExecutionContext.java | 7 +-
.../entity/rebind/RebindCatalogEntityTest.java | 3 +-
.../entity/rebind/RebindEntityTest.java | 5 +-
.../entity/rebind/RebindFailuresTest.java | 31 +++++---
.../entity/rebind/RebindTestFixture.java | 4 +-
.../brooklyn/entity/rebind/RebindTestUtils.java | 16 +---
.../rebind/RecordingRebindExceptionHandler.java | 5 --
.../transformer/CompoundTransformerTest.java | 1 +
.../HighAvailabilityManagerSplitBrainTest.java | 6 +-
.../ha/HighAvailabilityManagerTestFixture.java | 2 +-
.../brooklyn/management/ha/HotStandbyTest.java | 2 +-
.../AbstractWebAppFixtureIntegrationTest.java | 3 +-
.../brooklyn/launcher/BrooklynLauncher.java | 4 +-
.../BrooklynLauncherHighAvailabilityTest.java | 7 +-
32 files changed, 261 insertions(+), 129 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/ea4b4ccb/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 6c80f8b..34c4f05 100644
--- a/api/src/main/java/brooklyn/entity/rebind/RebindExceptionHandler.java
+++ b/api/src/main/java/brooklyn/entity/rebind/RebindExceptionHandler.java
@@ -31,6 +31,14 @@ import com.google.common.annotations.Beta;
/**
* Handler called on all exceptions to do with rebind.
+ * A handler instance is linked to a single rebind pass;
+ * it should not be invoked after {@link #onDone()}.
+ * <p>
+ * {@link #onStart()} must be invoked before the run.
+ * {@link #onDone()} must be invoked after a successful run, and it may throw.
+ * <p>
+ * Implementations may propagate errors or may catch them until {@link #onDone()} is invoked,
+ * and that may throw or report elsewhere, as appropriate.
*
* @author aled
*/
@@ -59,7 +67,6 @@ public interface RebindExceptionHandler {
*/
Enricher onDanglingEnricherRef(String id);
- //XXX
/**
* @return the feed to use in place of the missing one, or null (if hasn't thrown an exception)
*/
@@ -84,7 +91,13 @@ public interface RebindExceptionHandler {
void onManageFailed(BrooklynObjectType type, BrooklynObject instance, Exception e);
- void onDone();
-
+ /** invoked for any high-level, unexpected, or otherwise uncaught failure;
+ * may be invoked on catching above errors */
RuntimeException onFailed(Exception e);
+
+ /** invoked before the rebind pass */
+ void onStart();
+
+ /** 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/ea4b4ccb/api/src/main/java/brooklyn/entity/rebind/RebindManager.java
----------------------------------------------------------------------
diff --git a/api/src/main/java/brooklyn/entity/rebind/RebindManager.java b/api/src/main/java/brooklyn/entity/rebind/RebindManager.java
index 8bbdcaa..6f15599 100644
--- a/api/src/main/java/brooklyn/entity/rebind/RebindManager.java
+++ b/api/src/main/java/brooklyn/entity/rebind/RebindManager.java
@@ -57,15 +57,14 @@ public interface RebindManager {
@VisibleForTesting
public BrooklynMementoPersister getPersister();
- /**
- * @deprecated since 0.7; use {@link #rebind(ClassLoader)}
- */
- @Deprecated
+ /** @deprecated since 0.7; use {@link #rebind(ClassLoader, RebindExceptionHandler, ManagementNodeState)} */ @Deprecated
public List<Application> rebind() throws IOException;
+ /** @deprecated since 0.7; use {@link #rebind(ClassLoader, RebindExceptionHandler, ManagementNodeState)} */ @Deprecated
public List<Application> rebind(ClassLoader classLoader) throws IOException;
-
+ /** @deprecated since 0.7; use {@link #rebind(ClassLoader, RebindExceptionHandler, ManagementNodeState)} */ @Deprecated
public List<Application> rebind(ClassLoader classLoader, RebindExceptionHandler exceptionHandler) throws IOException;
+ public List<Application> rebind(ClassLoader classLoader, RebindExceptionHandler exceptionHandler, ManagementNodeState mode) throws IOException;
public BrooklynMementoRawData retrieveMementoRawData() throws IOException;
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/ea4b4ccb/api/src/main/java/brooklyn/management/ExecutionContext.java
----------------------------------------------------------------------
diff --git a/api/src/main/java/brooklyn/management/ExecutionContext.java b/api/src/main/java/brooklyn/management/ExecutionContext.java
index 0758ecb..2e43a91 100644
--- a/api/src/main/java/brooklyn/management/ExecutionContext.java
+++ b/api/src/main/java/brooklyn/management/ExecutionContext.java
@@ -48,6 +48,12 @@ public interface ExecutionContext extends Executor {
*/
<T> Task<T> submit(Map<?,?> properties, Callable<T> callable);
+ /** {@link ExecutionManager#submit(Runnable) */
+ Task<?> submit(Runnable runnable);
+
+ /** {@link ExecutionManager#submit(Callable) */
+ <T> Task<T> submit(Callable<T> callable);
+
/** See {@link ExecutionManager#submit(Map, TaskAdaptable)}. */
<T> Task<T> submit(TaskAdaptable<T> task);
@@ -56,4 +62,6 @@ public interface ExecutionContext extends Executor {
*/
<T> Task<T> submit(Map<?,?> properties, TaskAdaptable<T> task);
+ boolean isShutdown();
+
}
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/ea4b4ccb/api/src/main/java/brooklyn/management/ManagementContext.java
----------------------------------------------------------------------
diff --git a/api/src/main/java/brooklyn/management/ManagementContext.java b/api/src/main/java/brooklyn/management/ManagementContext.java
index 68722d9..29cb20e 100644
--- a/api/src/main/java/brooklyn/management/ManagementContext.java
+++ b/api/src/main/java/brooklyn/management/ManagementContext.java
@@ -98,6 +98,12 @@ public interface ManagementContext {
* to submit tasks and to observe what tasks are occurring
*/
ExecutionManager getExecutionManager();
+
+ /**
+ * Returns an {@link ExecutionContext} within the {@link ExecutionManager} for tasks
+ * associated to the Brooklyn node's operation (not any entities).
+ **/
+ ExecutionContext getServerExecutionContext();
/**
* Returns the {@link EntityDriverManager} entities can use to create drivers. This
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/ea4b4ccb/api/src/main/java/brooklyn/mementos/BrooklynMementoPersister.java
----------------------------------------------------------------------
diff --git a/api/src/main/java/brooklyn/mementos/BrooklynMementoPersister.java b/api/src/main/java/brooklyn/mementos/BrooklynMementoPersister.java
index 878a211..0317059 100644
--- a/api/src/main/java/brooklyn/mementos/BrooklynMementoPersister.java
+++ b/api/src/main/java/brooklyn/mementos/BrooklynMementoPersister.java
@@ -64,6 +64,9 @@ public interface BrooklynMementoPersister {
void delta(Delta delta, PersistenceExceptionHandler exceptionHandler);
+ void enableWriteAccess();
+ void disableWriteAccess(boolean graceful);
+ /** permanently shuts down all access to the remote store */
void stop(boolean graceful);
@VisibleForTesting
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/ea4b4ccb/core/src/main/java/brooklyn/entity/basic/BrooklynTaskTags.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/entity/basic/BrooklynTaskTags.java b/core/src/main/java/brooklyn/entity/basic/BrooklynTaskTags.java
index e7a8c70..db16c4b 100644
--- a/core/src/main/java/brooklyn/entity/basic/BrooklynTaskTags.java
+++ b/core/src/main/java/brooklyn/entity/basic/BrooklynTaskTags.java
@@ -61,7 +61,11 @@ public class BrooklynTaskTags extends TaskTags {
private static final Logger log = LoggerFactory.getLogger(BrooklynTaskTags.WrappedEntity.class);
+ /** Tag for tasks which are running on behalf of the management server, rather than any entity */
+ public static final String BROOKLYN_SERVER_TASK_TAG = "BROOKLYN-SERVER";
+ /** Tag for a task which represents an effector */
public static final String EFFECTOR_TAG = "EFFECTOR";
+ /** Tag for a task which *is* interesting, in contrast to {@link #TRANSIENT_TASK_TAG} */
public static final String NON_TRANSIENT_TASK_TAG = "NON-TRANSIENT";
/** indicates a task is transient, roughly that is to say it is uninteresting --
* specifically this means it can be GC'd as soon as it is completed,
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/ea4b4ccb/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 516fbfa..9a75ab2 100644
--- a/core/src/main/java/brooklyn/entity/basic/Entities.java
+++ b/core/src/main/java/brooklyn/entity/basic/Entities.java
@@ -1007,7 +1007,7 @@ public class Entities {
@Beta
public static ProcessTaskWrapper<Integer> shell(ManagementContext mgmt, String command) {
ProcessTaskWrapper<Integer> t = SystemTasks.exec(command).newTask();
- mgmt.getExecutionManager().submit(t).getUnchecked();
+ mgmt.getServerExecutionContext().submit(t).getUnchecked();
System.out.println(t.getStdout());
System.err.println(t.getStderr());
return t;
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/ea4b4ccb/core/src/main/java/brooklyn/entity/rebind/PeriodicDeltaChangeListener.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/entity/rebind/PeriodicDeltaChangeListener.java b/core/src/main/java/brooklyn/entity/rebind/PeriodicDeltaChangeListener.java
index 0d708f4..e9cef0c 100644
--- a/core/src/main/java/brooklyn/entity/rebind/PeriodicDeltaChangeListener.java
+++ b/core/src/main/java/brooklyn/entity/rebind/PeriodicDeltaChangeListener.java
@@ -37,6 +37,7 @@ import brooklyn.entity.basic.EntityInternal;
import brooklyn.internal.BrooklynFeatureEnablement;
import brooklyn.location.Location;
import brooklyn.location.basic.LocationInternal;
+import brooklyn.management.ExecutionContext;
import brooklyn.management.ExecutionManager;
import brooklyn.management.Task;
import brooklyn.mementos.BrooklynMementoPersister;
@@ -44,6 +45,7 @@ import brooklyn.policy.Enricher;
import brooklyn.policy.Policy;
import brooklyn.util.exceptions.Exceptions;
import brooklyn.util.exceptions.RuntimeInterruptedException;
+import brooklyn.util.task.BasicExecutionContext;
import brooklyn.util.task.BasicTask;
import brooklyn.util.task.ScheduledTask;
import brooklyn.util.time.Duration;
@@ -93,7 +95,7 @@ public class PeriodicDeltaChangeListener implements ChangeListener {
}
}
- private final ExecutionManager executionManager;
+ private final ExecutionContext executionContext;
private final BrooklynMementoPersister persister;
@@ -117,14 +119,14 @@ public class PeriodicDeltaChangeListener implements ChangeListener {
private final Semaphore persistingMutex = new Semaphore(1);
- /** @deprecated since 0.7.0 pass in a {@link Duration} */
+ /** @deprecated since 0.7.0 pass in an {@link ExecutionContext} and a {@link Duration} */
@Deprecated
public PeriodicDeltaChangeListener(ExecutionManager executionManager, BrooklynMementoPersister persister, PersistenceExceptionHandler exceptionHandler, long periodMillis) {
- this(executionManager, persister, exceptionHandler, Duration.of(periodMillis, TimeUnit.MILLISECONDS));
+ this(new BasicExecutionContext(executionManager), persister, exceptionHandler, Duration.of(periodMillis, TimeUnit.MILLISECONDS));
}
- public PeriodicDeltaChangeListener(ExecutionManager executionManager, BrooklynMementoPersister persister, PersistenceExceptionHandler exceptionHandler, Duration period) {
- this.executionManager = executionManager;
+ public PeriodicDeltaChangeListener(ExecutionContext executionContext, BrooklynMementoPersister persister, PersistenceExceptionHandler exceptionHandler, Duration period) {
+ this.executionContext = executionContext;
this.persister = persister;
this.exceptionHandler = exceptionHandler;
this.period = period;
@@ -165,7 +167,7 @@ public class PeriodicDeltaChangeListener implements ChangeListener {
}});
}
};
- scheduledTask = (ScheduledTask) executionManager.submit(new ScheduledTask(taskFactory).period(period));
+ scheduledTask = (ScheduledTask) executionContext.submit(new ScheduledTask(taskFactory).period(period));
}
void stop() {
@@ -223,7 +225,7 @@ public class PeriodicDeltaChangeListener implements ChangeListener {
* Whether we have been stopped, in which case will not persist or store anything.
*/
private boolean isStopped() {
- return stopped || executionManager.isShutdown();
+ return stopped || executionContext.isShutdown();
}
private void addReferencedObjects(DeltaCollector deltaCollector) {
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/ea4b4ccb/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 25523c6..de37f42 100644
--- a/core/src/main/java/brooklyn/entity/rebind/RebindExceptionHandlerImpl.java
+++ b/core/src/main/java/brooklyn/entity/rebind/RebindExceptionHandlerImpl.java
@@ -42,6 +42,7 @@ import brooklyn.util.text.Strings;
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);
@@ -62,6 +63,9 @@ public class RebindExceptionHandlerImpl implements RebindExceptionHandler {
protected final Set<Exception> loadPolicyFailures = Sets.newConcurrentHashSet();
protected final List<Exception> exceptions = Collections.synchronizedList(Lists.<Exception>newArrayList());
+ protected boolean started = false;
+ protected boolean done = false;
+
public static Builder builder() {
return new Builder();
}
@@ -99,6 +103,16 @@ public class RebindExceptionHandlerImpl implements RebindExceptionHandler {
this.addPolicyFailureMode = checkNotNull(builder.addPolicyFailureMode, "addPolicyFailureMode");
this.loadPolicyFailureMode = checkNotNull(builder.deserializePolicyFailureMode, "deserializePolicyFailureMode");
}
+
+ public void onStart() {
+ 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");
+ }
+ started = true;
+ }
@Override
public void onLoadMementoFailed(BrooklynObjectType type, String msg, Exception e) {
@@ -329,6 +343,9 @@ public class RebindExceptionHandlerImpl implements RebindExceptionHandler {
@Override
public RuntimeException onFailed(Exception e) {
+ if (done)
+ throw Exceptions.propagate(e);
+
onDoneImpl(e);
throw new IllegalStateException("Rebind failed", e); // should have thrown exception above
}
@@ -338,9 +355,17 @@ public class RebindExceptionHandlerImpl implements RebindExceptionHandler {
List<Exception> allExceptions = Lists.newArrayList();
+ if (done) {
+ allExceptions.add(new IllegalStateException(this+" has already been informed of rebind done"));
+ }
+ done = true;
+ if (!started) {
+ allExceptions.add(new IllegalStateException(this+" was not informed of start of rebind run"));
+ }
if (e != null) {
allExceptions.add(e);
}
+
if (addPolicyFailureMode != RebindManager.RebindFailureMode.CONTINUE) {
allExceptions.addAll(addPolicyFailures);
}
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/ea4b4ccb/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 70a2ba7..47f3770 100644
--- a/core/src/main/java/brooklyn/entity/rebind/RebindManagerImpl.java
+++ b/core/src/main/java/brooklyn/entity/rebind/RebindManagerImpl.java
@@ -57,6 +57,7 @@ import brooklyn.internal.BrooklynFeatureEnablement;
import brooklyn.location.Location;
import brooklyn.location.basic.AbstractLocation;
import brooklyn.location.basic.LocationInternal;
+import brooklyn.management.ExecutionContext;
import brooklyn.management.Task;
import brooklyn.management.ha.HighAvailabilityManagerImpl;
import brooklyn.management.ha.ManagementNodeState;
@@ -127,10 +128,11 @@ public class RebindManagerImpl implements RebindManager {
private volatile boolean persistenceRunning = false;
private volatile PeriodicDeltaChangeListener persistenceRealChangeListener;
private volatile ChangeListener persistencePublicChangeListener;
- private volatile BrooklynMementoPersister persister;
private volatile boolean readOnlyRunning = false;
private volatile ScheduledTask readOnlyTask = null;
+
+ private volatile BrooklynMementoPersister persistenceStoreAccess;
private final boolean persistPoliciesEnabled;
private final boolean persistEnrichersEnabled;
@@ -187,7 +189,7 @@ public class RebindManagerImpl implements RebindManager {
* Must be called before setPerister()
*/
public void setPeriodicPersistPeriod(Duration period) {
- if (persister!=null) throw new IllegalStateException("Cannot set period after persister is generated.");
+ if (persistenceStoreAccess!=null) throw new IllegalStateException("Cannot set period after persister is generated.");
this.periodicPersistPeriod = period;
}
@@ -207,12 +209,12 @@ public class RebindManagerImpl implements RebindManager {
@Override
public void setPersister(BrooklynMementoPersister val, PersistenceExceptionHandler exceptionHandler) {
- if (persister != null && persister != val) {
- throw new IllegalStateException("Dynamically changing persister is not supported: old="+persister+"; new="+val);
+ if (persistenceStoreAccess != null && persistenceStoreAccess != val) {
+ throw new IllegalStateException("Dynamically changing persister is not supported: old="+persistenceStoreAccess+"; new="+val);
}
- this.persister = checkNotNull(val, "persister");
+ this.persistenceStoreAccess = checkNotNull(val, "persister");
- this.persistenceRealChangeListener = new PeriodicDeltaChangeListener(managementContext.getExecutionManager(), persister, exceptionHandler, periodicPersistPeriod);
+ this.persistenceRealChangeListener = new PeriodicDeltaChangeListener(managementContext.getServerExecutionContext(), persistenceStoreAccess, exceptionHandler, periodicPersistPeriod);
this.persistencePublicChangeListener = new SafeChangeListener(persistenceRealChangeListener);
if (persistenceRunning) {
@@ -223,7 +225,7 @@ public class RebindManagerImpl implements RebindManager {
@Override
@VisibleForTesting
public BrooklynMementoPersister getPersister() {
- return persister;
+ return persistenceStoreAccess;
}
@Override
@@ -233,6 +235,7 @@ public class RebindManagerImpl implements RebindManager {
}
LOG.debug("Starting persistence, mgmt "+managementContext.getManagementNodeId());
persistenceRunning = true;
+ persistenceStoreAccess.enableWriteAccess();
if (persistenceRealChangeListener != null) persistenceRealChangeListener.start();
}
@@ -241,7 +244,7 @@ public class RebindManagerImpl implements RebindManager {
LOG.debug("Stopping persistence, mgmt "+managementContext.getManagementNodeId());
persistenceRunning = false;
if (persistenceRealChangeListener != null) persistenceRealChangeListener.stop();
- if (persister != null) persister.stop(true);
+ if (persistenceStoreAccess != null) persistenceStoreAccess.disableWriteAccess(true);
LOG.debug("Stopped persistence, mgmt "+managementContext.getManagementNodeId());
}
@@ -294,8 +297,8 @@ public class RebindManagerImpl implements RebindManager {
}});
}
};
- readOnlyTask = (ScheduledTask) managementContext.getExecutionManager().submit(
- new ScheduledTask(taskFactory).period(periodicPersistPeriod));
+ readOnlyTask = (ScheduledTask) managementContext.getServerExecutionContext().submit(
+ new ScheduledTask(MutableMap.of("displayName", "Periodic read-only rebind"), taskFactory).period(periodicPersistPeriod));
}
@Override
@@ -326,6 +329,7 @@ public class RebindManagerImpl implements RebindManager {
public void stop() {
stopReadOnly();
stopPersistence();
+ if (persistenceStoreAccess != null) persistenceStoreAccess.stop(true);
}
protected ManagementNodeState getRebindMode() {
@@ -344,9 +348,9 @@ public class RebindManagerImpl implements RebindManager {
@Override
@VisibleForTesting
public void waitForPendingComplete(Duration timeout) throws InterruptedException, TimeoutException {
- if (persister == null || !persistenceRunning) return;
+ if (persistenceStoreAccess == null || !persistenceRunning) return;
persistenceRealChangeListener.waitForPendingComplete(timeout);
- persister.waitForWritesCompleted(timeout);
+ persistenceStoreAccess.waitForWritesCompleted(timeout);
}
@Override
@VisibleForTesting
@@ -366,7 +370,7 @@ public class RebindManagerImpl implements RebindManager {
@Override
public List<Application> rebind(final ClassLoader classLoader) throws IOException {
- return rebind(classLoader, null);
+ return rebind(classLoader, null, null);
}
@Override
@@ -374,7 +378,8 @@ public class RebindManagerImpl implements RebindManager {
return rebind(classLoader, exceptionHandler, null);
}
- protected List<Application> rebind(ClassLoader classLoaderO, RebindExceptionHandler exceptionHandlerO, ManagementNodeState modeO) throws IOException {
+ @Override
+ public List<Application> rebind(ClassLoader classLoaderO, RebindExceptionHandler exceptionHandlerO, ManagementNodeState modeO) throws IOException {
final ClassLoader classLoader = classLoaderO!=null ? classLoaderO :
managementContext.getCatalog().getRootClassLoader();
final RebindExceptionHandler exceptionHandler = exceptionHandlerO!=null ? exceptionHandlerO :
@@ -386,9 +391,12 @@ public class RebindManagerImpl implements RebindManager {
.build();
final ManagementNodeState mode = modeO!=null ? modeO : getRebindMode();
- BasicExecutionContext ec = BasicExecutionContext.getCurrentExecutionContext();
+ if (mode!=ManagementNodeState.HOT_STANDBY && mode!=ManagementNodeState.MASTER)
+ throw new IllegalStateException("Must be either master or read only to rebind (mode "+mode+")");
+
+ ExecutionContext ec = BasicExecutionContext.getCurrentExecutionContext();
if (ec == null) {
- ec = new BasicExecutionContext(managementContext.getExecutionManager());
+ 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);
@@ -422,11 +430,11 @@ public class RebindManagerImpl implements RebindManager {
*/
protected BrooklynMementoRawData loadMementoRawData(final RebindExceptionHandler exceptionHandler) throws IOException {
try {
- if (!(persister instanceof BrooklynMementoPersisterToObjectStore)) {
- throw new IllegalStateException("Cannot load raw memento with persister "+persister);
+ if (!(persistenceStoreAccess instanceof BrooklynMementoPersisterToObjectStore)) {
+ throw new IllegalStateException("Cannot load raw memento with persister "+persistenceStoreAccess);
}
- return ((BrooklynMementoPersisterToObjectStore)persister).loadMementoRawData(exceptionHandler);
+ return ((BrooklynMementoPersisterToObjectStore)persistenceStoreAccess).loadMementoRawData(exceptionHandler);
} catch (RuntimeException e) {
throw exceptionHandler.onFailed(e);
@@ -440,6 +448,8 @@ public class RebindManagerImpl implements RebindManager {
RebindTracker.setRebinding();
try {
+ exceptionHandler.onStart();
+
Reflections reflections = new Reflections(classLoader);
RebindContextImpl rebindContext = new RebindContextImpl(exceptionHandler, classLoader);
LookupContext realLookupContext = new RebindContextLookupContext(managementContext, rebindContext, exceptionHandler);
@@ -463,7 +473,7 @@ public class RebindManagerImpl implements RebindManager {
// PHASE ONE
//
- BrooklynMementoManifest mementoManifest = persister.loadMementoManifest(exceptionHandler);
+ BrooklynMementoManifest mementoManifest = persistenceStoreAccess.loadMementoManifest(exceptionHandler);
boolean isEmpty = mementoManifest.isEmpty();
if (!isEmpty) {
@@ -512,7 +522,7 @@ public class RebindManagerImpl implements RebindManager {
// PHASE THREE
//
- BrooklynMemento memento = persister.loadMemento(realLookupContext, exceptionHandler);
+ BrooklynMemento memento = persistenceStoreAccess.loadMemento(realLookupContext, exceptionHandler);
//
@@ -786,6 +796,7 @@ public class RebindManagerImpl implements RebindManager {
managementContext.getCatalog().reset(rebindContext.getCatalogItems());
} else if (shouldLoadDefaultCatalog) {
// Load catalogue as normal
+ // TODO in read-only mode, should do this less frequently than entities etc
LOG.debug("RebindManager loading default catalog");
((BasicBrooklynCatalog) managementContext.getCatalog()).resetCatalogToContentsAtConfiguredUrl();
} else {
@@ -1080,4 +1091,9 @@ public class RebindManagerImpl implements RebindManager {
}
}
}
+
+ @Override
+ public String toString() {
+ return super.toString()+"[mgmt="+managementContext.getManagementNodeId()+"]";
+ }
}
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/ea4b4ccb/core/src/main/java/brooklyn/entity/rebind/persister/AbstractBrooklynMementoPersister.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/entity/rebind/persister/AbstractBrooklynMementoPersister.java b/core/src/main/java/brooklyn/entity/rebind/persister/AbstractBrooklynMementoPersister.java
index 32bfcc3..c230e9e 100644
--- a/core/src/main/java/brooklyn/entity/rebind/persister/AbstractBrooklynMementoPersister.java
+++ b/core/src/main/java/brooklyn/entity/rebind/persister/AbstractBrooklynMementoPersister.java
@@ -67,11 +67,10 @@ public abstract class AbstractBrooklynMementoPersister implements BrooklynMement
}
return builder.build();
}
-
- @Override
- public void stop(boolean graceful) {
- // no-op
- }
+
+ @Override public void enableWriteAccess() {}
+ @Override public void disableWriteAccess(boolean graceful) {}
+ @Override public void stop(boolean graceful) {}
@Override
public void checkpoint(BrooklynMemento newMemento, PersistenceExceptionHandler exceptionHandler) {
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/ea4b4ccb/core/src/main/java/brooklyn/entity/rebind/persister/BrooklynMementoPersisterToMultiFile.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/entity/rebind/persister/BrooklynMementoPersisterToMultiFile.java b/core/src/main/java/brooklyn/entity/rebind/persister/BrooklynMementoPersisterToMultiFile.java
index ab0f831..aeedf61 100644
--- a/core/src/main/java/brooklyn/entity/rebind/persister/BrooklynMementoPersisterToMultiFile.java
+++ b/core/src/main/java/brooklyn/entity/rebind/persister/BrooklynMementoPersisterToMultiFile.java
@@ -128,6 +128,14 @@ public class BrooklynMementoPersisterToMultiFile implements BrooklynMementoPersi
LOG.info("Memento-persister will use directory {}", dir);
}
+ @Override public void enableWriteAccess() {
+ // no-op -- means subsequent writes will not be enabled
+ }
+
+ @Override public void disableWriteAccess(boolean graceful) {
+ stop(graceful);
+ }
+
@Override
public void stop(boolean graceful) {
running = false;
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/ea4b4ccb/core/src/main/java/brooklyn/entity/rebind/persister/BrooklynMementoPersisterToObjectStore.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/entity/rebind/persister/BrooklynMementoPersisterToObjectStore.java b/core/src/main/java/brooklyn/entity/rebind/persister/BrooklynMementoPersisterToObjectStore.java
index 5b224b1..e02de72 100644
--- a/core/src/main/java/brooklyn/entity/rebind/persister/BrooklynMementoPersisterToObjectStore.java
+++ b/core/src/main/java/brooklyn/entity/rebind/persister/BrooklynMementoPersisterToObjectStore.java
@@ -101,8 +101,8 @@ public class BrooklynMementoPersisterToObjectStore implements BrooklynMementoPer
private final ListeningExecutorService executor;
- private volatile boolean running = true;
- private volatile boolean shuttingDown = false;
+ private volatile boolean writesAllowed = false;
+ private volatile boolean writesShuttingDown = false;
/**
* Lock used on writes (checkpoint + delta) so that {@link #waitForWritesCompleted(Duration)} can block
@@ -135,30 +135,45 @@ public class BrooklynMementoPersisterToObjectStore implements BrooklynMementoPer
}}));
}
+ @Override public void enableWriteAccess() {
+ writesAllowed = true;
+ }
+
@Override
- public void stop(boolean graceful) {
- shuttingDown = true;
+ public void disableWriteAccess(boolean graceful) {
+ writesShuttingDown = true;
try {
- running = false;
- if (executor != null) {
- if (graceful) {
- // a very long timeout to ensure we don't lose state.
- // If persisting thousands of entities over slow network to Object Store, could take minutes.
- executor.shutdown();
- try {
- executor.awaitTermination(1, TimeUnit.HOURS);
- } catch (InterruptedException e) {
- throw Exceptions.propagate(e);
- }
- } else {
- executor.shutdownNow();
- }
- }
+ writesAllowed = false;
+ // a very long timeout to ensure we don't lose state.
+ // If persisting thousands of entities over slow network to Object Store, could take minutes.
+ waitForWritesCompleted(Duration.ONE_HOUR);
+
+ } catch (Exception e) {
+ throw Exceptions.propagate(e);
} finally {
- shuttingDown = false;
+ writesShuttingDown = false;
}
}
+ @Override
+ public void stop(boolean graceful) {
+ disableWriteAccess(graceful);
+
+ if (executor != null) {
+ if (graceful) {
+ executor.shutdown();
+ try {
+ // should be quick because we've just turned off writes, waiting for their completion
+ executor.awaitTermination(1, TimeUnit.MINUTES);
+ } catch (InterruptedException e) {
+ throw Exceptions.propagate(e);
+ }
+ } else {
+ executor.shutdownNow();
+ }
+ }
+ }
+
public PersistenceObjectStore getObjectStore() {
return objectStore;
}
@@ -331,10 +346,6 @@ public class BrooklynMementoPersisterToObjectStore implements BrooklynMementoPer
public void visit(String contents, BrooklynObjectType type, String subPath) throws Exception;
}
protected void visitMemento(final Visitor visitor, final RebindExceptionHandler exceptionHandler) throws IOException {
- if (!running && !shuttingDown) {
- throw new IllegalStateException("Persister not running; cannot load memento manifest from " + objectStore.getSummaryName());
- }
-
List<String> entitySubPathList;
List<String> locationSubPathList;
List<String> policySubPathList;
@@ -429,12 +440,15 @@ public class BrooklynMementoPersisterToObjectStore implements BrooklynMementoPer
}
}
+ protected void checkWritesAllowed() {
+ if (!writesAllowed && !writesShuttingDown) {
+ throw new IllegalStateException("Writes not allowed in "+this);
+ }
+ }
+
@Beta
public void checkpoint(BrooklynMementoRawData newMemento, PersistenceExceptionHandler exceptionHandler) {
- if (!running && !shuttingDown) {
- if (LOG.isDebugEnabled()) LOG.debug("Ignoring checkpointing entire memento, because not running");
- return;
- }
+ checkWritesAllowed();
try {
lock.writeLock().lockInterruptibly();
@@ -486,10 +500,7 @@ public class BrooklynMementoPersisterToObjectStore implements BrooklynMementoPer
@Override
public void checkpoint(BrooklynMemento newMemento, PersistenceExceptionHandler exceptionHandler) {
- if (!running && !shuttingDown) {
- if (LOG.isDebugEnabled()) LOG.debug("Ignoring checkpointing entire memento, because not running");
- return;
- }
+ checkWritesAllowed();
Delta delta = PersisterDeltaImpl.builder()
.entities(newMemento.getEntityMementos().values())
@@ -506,10 +517,8 @@ public class BrooklynMementoPersisterToObjectStore implements BrooklynMementoPer
@Override
public void delta(Delta delta, PersistenceExceptionHandler exceptionHandler) {
- if (!running && !shuttingDown) {
- if (LOG.isDebugEnabled()) LOG.debug("Ignoring checkpointed delta of memento, because not running");
- return;
- }
+ checkWritesAllowed();
+
Stopwatch stopwatch = deltaImpl(delta, exceptionHandler);
if (LOG.isDebugEnabled()) LOG.debug("Checkpointed delta of memento in {}; updated {} entities, {} locations, " +
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/ea4b4ccb/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 d63aa99..52fffdc 100644
--- a/core/src/main/java/brooklyn/management/ha/HighAvailabilityManagerImpl.java
+++ b/core/src/main/java/brooklyn/management/ha/HighAvailabilityManagerImpl.java
@@ -74,7 +74,7 @@ import com.google.common.collect.Iterables;
* Promotion to master involves:
* <ol>
* <li>notifying the other management-nodes that it is now master
- * <li>calling {@link RebindManager#rebind()} to read all persisted entity state, and thus reconstitute the entities.
+ * <li>calling {@link RebindManager#rebind(ClassLoader, brooklyn.entity.rebind.RebindExceptionHandler, ManagementNodeState)} to read all persisted entity state, and thus reconstitute the entities.
* </ol>
* <p>
* Future improvements in this area will include brooklyn-managing-brooklyn to decide + promote
@@ -212,6 +212,10 @@ public class HighAvailabilityManagerImpl implements HighAvailabilityManager {
// so another node could have already become master or terminated in that window.
ManagementNodeSyncRecord existingMaster = hasHealthyMaster();
+ // catch error in some tests where mgmt context has a different mgmt context
+ if (managementContext.getHighAvailabilityManager()!=this)
+ throw new IllegalStateException("Cannot start an HA manager on a management context with a different HA manager!");
+
switch (startMode) {
case AUTO:
// don't care; let's start and see if we promote ourselves
@@ -531,10 +535,16 @@ public class HighAvailabilityManagerImpl implements HighAvailabilityManager {
LOG.warn("Problem in promption-listener (continuing)", e);
}
}
+ boolean wasHotStandby = nodeState==ManagementNodeState.HOT_STANDBY;
nodeState = ManagementNodeState.MASTER;
publishPromotionToMaster();
try {
- managementContext.getRebindManager().rebind(managementContext.getCatalog().getRootClassLoader());
+ if (wasHotStandby) {
+ // could just promote the standby items; but for now we stop the old read-only and re-load them, to make sure nothing has been missed
+ // TODO ideally there'd be an incremental rebind as well as an incremental persist
+ managementContext.getRebindManager().stopReadOnly();
+ }
+ managementContext.getRebindManager().rebind(managementContext.getCatalog().getRootClassLoader(), null, nodeState);
} catch (Exception e) {
LOG.error("Management node enountered problem during rebind when promoting self to master; demoting to FAILED and rethrowing: "+e);
demoteToFailed();
@@ -568,7 +578,7 @@ public class HighAvailabilityManagerImpl implements HighAvailabilityManager {
}
protected void onDemotionStopTasks() {
- managementContext.getRebindManager().stop();
+ managementContext.getRebindManager().stopPersistence();
for (Application app: managementContext.getApplications())
Entities.unmanage(app);
// let's try forcibly interrupting tasks on managed entities
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/ea4b4ccb/core/src/main/java/brooklyn/management/internal/AbstractManagementContext.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/management/internal/AbstractManagementContext.java b/core/src/main/java/brooklyn/management/internal/AbstractManagementContext.java
index 44a77ae..b91e99b 100644
--- a/core/src/main/java/brooklyn/management/internal/AbstractManagementContext.java
+++ b/core/src/main/java/brooklyn/management/internal/AbstractManagementContext.java
@@ -212,7 +212,13 @@ public abstract class AbstractManagementContext implements ManagementContextInte
// BEC is a thin wrapper around EM so fine to create a new one here
return new BasicExecutionContext(MutableMap.of("tag", BrooklynTaskTags.tagForContextEntity(e)), getExecutionManager());
}
-
+
+ @Override
+ public ExecutionContext getServerExecutionContext() {
+ // BEC is a thin wrapper around EM so fine to create a new one here
+ return new BasicExecutionContext(MutableMap.of("tag", BrooklynTaskTags.BROOKLYN_SERVER_TASK_TAG), getExecutionManager());
+ }
+
@Override
public SubscriptionContext getSubscriptionContext(Entity e) {
// BSC is a thin wrapper around SM so fine to create a new one here
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/ea4b4ccb/core/src/main/java/brooklyn/management/internal/LocalManagementContext.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/management/internal/LocalManagementContext.java b/core/src/main/java/brooklyn/management/internal/LocalManagementContext.java
index 2c3d43a..bf4a559 100644
--- a/core/src/main/java/brooklyn/management/internal/LocalManagementContext.java
+++ b/core/src/main/java/brooklyn/management/internal/LocalManagementContext.java
@@ -39,6 +39,7 @@ import brooklyn.config.BrooklynProperties.Factory.Builder;
import brooklyn.entity.Application;
import brooklyn.entity.Effector;
import brooklyn.entity.Entity;
+import brooklyn.entity.basic.BrooklynTaskTags;
import brooklyn.entity.drivers.downloads.BasicDownloadsManager;
import brooklyn.entity.effector.Effectors;
import brooklyn.entity.proxying.InternalEntityFactory;
@@ -54,6 +55,7 @@ import brooklyn.management.SubscriptionManager;
import brooklyn.management.Task;
import brooklyn.management.TaskAdaptable;
import brooklyn.management.ha.OsgiManager;
+import brooklyn.util.collections.MutableMap;
import brooklyn.util.exceptions.Exceptions;
import brooklyn.util.guava.Maybe;
import brooklyn.util.task.BasicExecutionContext;
@@ -309,7 +311,7 @@ public class LocalManagementContext extends AbstractManagementContext {
}
return execution;
}
-
+
@Override
public void terminate() {
INSTANCES.remove(this);
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/ea4b4ccb/core/src/main/java/brooklyn/management/internal/NonDeploymentManagementContext.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/management/internal/NonDeploymentManagementContext.java b/core/src/main/java/brooklyn/management/internal/NonDeploymentManagementContext.java
index a404bb8..21f1968 100644
--- a/core/src/main/java/brooklyn/management/internal/NonDeploymentManagementContext.java
+++ b/core/src/main/java/brooklyn/management/internal/NonDeploymentManagementContext.java
@@ -20,6 +20,7 @@ package brooklyn.management.internal;
import static com.google.common.base.Preconditions.checkNotNull;
+import java.io.IOException;
import java.net.URI;
import java.net.URL;
import java.util.Collection;
@@ -231,6 +232,11 @@ public class NonDeploymentManagementContext implements ManagementContextInternal
return initialManagementContext.getExecutionContext(entity);
}
+ @Override
+ public ExecutionContext getServerExecutionContext() {
+ return initialManagementContext.getServerExecutionContext();
+ }
+
// TODO the methods below should delegate to the application?
@Override
public EntityDriverManager getEntityDriverManager() {
@@ -445,6 +451,11 @@ public class NonDeploymentManagementContext implements ManagementContextInternal
public List<Application> rebind(ClassLoader classLoader, RebindExceptionHandler exceptionHandler) {
throw new IllegalStateException("Non-deployment context "+NonDeploymentManagementContext.this+" is not valid for this operation.");
}
+
+ @Override
+ public List<Application> rebind(ClassLoader classLoader, RebindExceptionHandler exceptionHandler, ManagementNodeState mode) throws IOException {
+ throw new IllegalStateException("Non-deployment context "+NonDeploymentManagementContext.this+" is not valid for this operation.");
+ }
@Override
public void startPersistence() {
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/ea4b4ccb/core/src/main/java/brooklyn/util/task/AbstractExecutionContext.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/util/task/AbstractExecutionContext.java b/core/src/main/java/brooklyn/util/task/AbstractExecutionContext.java
index cd94915..3002922 100644
--- a/core/src/main/java/brooklyn/util/task/AbstractExecutionContext.java
+++ b/core/src/main/java/brooklyn/util/task/AbstractExecutionContext.java
@@ -37,21 +37,27 @@ public abstract class AbstractExecutionContext implements ExecutionContext {
*
* @see ExecutionManager#submit(Map, Task)
*/
+ @Override
public Task<?> submit(Map<?,?> properties, Runnable runnable) { return submitInternal(properties, runnable); }
/** @see #submit(Map, Runnable) */
+ @Override
public Task<?> submit(Runnable runnable) { return submitInternal(Maps.newLinkedHashMap(), runnable); }
/** @see #submit(Map, Runnable) */
+ @Override
public <T> Task<T> submit(Callable<T> callable) { return submitInternal(Maps.newLinkedHashMap(), callable); }
/** @see #submit(Map, Runnable) */
+ @Override
public <T> Task<T> submit(Map<?,?> properties, Callable<T> callable) { return submitInternal(properties, callable); }
/** @see #submit(Map, Runnable) */
+ @Override
public <T> Task<T> submit(TaskAdaptable<T> task) { return submitInternal(Maps.newLinkedHashMap(), task.asTask()); }
/** @see #submit(Map, Runnable) */
+ @Override
public <T> Task<T> submit(Map<?,?> properties, TaskAdaptable<T> task) { return submitInternal(properties, task.asTask()); }
/**
@@ -66,5 +72,4 @@ public abstract class AbstractExecutionContext implements ExecutionContext {
/** does the work internally of submitting the task; note that the return value may be a wrapper task even if a task is passed in,
* if the execution context where the target should run is different (e.g. submitting an effector task cross-context) */
protected abstract <T> Task<T> submitInternal(Map<?,?> properties, Object task);
-
}
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/ea4b4ccb/core/src/main/java/brooklyn/util/task/BasicExecutionContext.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/util/task/BasicExecutionContext.java b/core/src/main/java/brooklyn/util/task/BasicExecutionContext.java
index a7bcbbd..ee1818e 100644
--- a/core/src/main/java/brooklyn/util/task/BasicExecutionContext.java
+++ b/core/src/main/java/brooklyn/util/task/BasicExecutionContext.java
@@ -190,7 +190,12 @@ public class BasicExecutionContext extends AbstractExecutionContext {
private void registerPerThreadExecutionContext() { perThreadExecutionContext.set(this); }
private void clearPerThreadExecutionContext() { perThreadExecutionContext.remove(); }
-
+
+ @Override
+ public boolean isShutdown() {
+ return getExecutionManager().isShutdown();
+ }
+
@Override
public String toString() {
return super.toString()+"("+tags+")";
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/ea4b4ccb/core/src/test/java/brooklyn/entity/rebind/RebindCatalogEntityTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/brooklyn/entity/rebind/RebindCatalogEntityTest.java b/core/src/test/java/brooklyn/entity/rebind/RebindCatalogEntityTest.java
index e482577..eac2e35 100644
--- a/core/src/test/java/brooklyn/entity/rebind/RebindCatalogEntityTest.java
+++ b/core/src/test/java/brooklyn/entity/rebind/RebindCatalogEntityTest.java
@@ -39,6 +39,7 @@ import brooklyn.entity.basic.EntityInternal;
import brooklyn.entity.basic.StartableApplication;
import brooklyn.entity.proxying.EntitySpec;
import brooklyn.event.basic.Sensors;
+import brooklyn.management.ha.ManagementNodeState;
import brooklyn.management.internal.LocalManagementContext;
import brooklyn.util.javalang.UrlClassLoader;
@@ -128,7 +129,7 @@ public class RebindCatalogEntityTest extends RebindTestFixture<StartableApplicat
newManagementContext.getCatalog().addItem(appClazz);
ClassLoader classLoader = newManagementContext.getCatalog().getRootClassLoader();
- List<Application> newApps = newManagementContext.getRebindManager().rebind(classLoader);
+ List<Application> newApps = newManagementContext.getRebindManager().rebind(classLoader, null, ManagementNodeState.MASTER);
newManagementContext.getRebindManager().startPersistence();
return (StartableApplication) newApps.get(0);
}
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/ea4b4ccb/core/src/test/java/brooklyn/entity/rebind/RebindEntityTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/brooklyn/entity/rebind/RebindEntityTest.java b/core/src/test/java/brooklyn/entity/rebind/RebindEntityTest.java
index fed6dd9..3450c40 100644
--- a/core/src/test/java/brooklyn/entity/rebind/RebindEntityTest.java
+++ b/core/src/test/java/brooklyn/entity/rebind/RebindEntityTest.java
@@ -61,6 +61,7 @@ import brooklyn.event.basic.DependentConfiguration;
import brooklyn.event.basic.Sensors;
import brooklyn.location.Location;
import brooklyn.location.basic.LocationConfigTest.MyLocation;
+import brooklyn.management.ha.ManagementNodeState;
import brooklyn.management.internal.LocalManagementContext;
import brooklyn.mementos.EntityMemento;
import brooklyn.test.Asserts;
@@ -338,7 +339,7 @@ public class RebindEntityTest extends RebindTestFixtureWithApp {
Thread thread = new Thread() {
public void run() {
try {
- newManagementContext.getRebindManager().rebind(classLoader);
+ newManagementContext.getRebindManager().rebind(classLoader, null, ManagementNodeState.MASTER);
} catch (Exception e) {
throw Throwables.propagate(e);
}
@@ -578,7 +579,7 @@ public class RebindEntityTest extends RebindTestFixtureWithApp {
RebindTestUtils.waitForPersisted(origManagementContext);
newManagementContext = RebindTestUtils.newPersistingManagementContextUnstarted(mementoDir, classLoader);
- List<Application> newApps = newManagementContext.getRebindManager().rebind(classLoader);
+ List<Application> newApps = newManagementContext.getRebindManager().rebind(classLoader, null, ManagementNodeState.MASTER);
newManagementContext.getRebindManager().startPersistence();
assertEquals(newApps.size(), 0, "apps="+newApps);
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/ea4b4ccb/core/src/test/java/brooklyn/entity/rebind/RebindFailuresTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/brooklyn/entity/rebind/RebindFailuresTest.java b/core/src/test/java/brooklyn/entity/rebind/RebindFailuresTest.java
index 9714dc9..e070bd0 100644
--- a/core/src/test/java/brooklyn/entity/rebind/RebindFailuresTest.java
+++ b/core/src/test/java/brooklyn/entity/rebind/RebindFailuresTest.java
@@ -27,6 +27,7 @@ import java.io.File;
import java.util.Map;
import java.util.Set;
+import org.testng.Assert;
import org.testng.annotations.Test;
import brooklyn.config.ConfigKey;
@@ -43,12 +44,13 @@ import brooklyn.entity.rebind.RebindManager.RebindFailureMode;
import brooklyn.entity.trait.Identifiable;
import brooklyn.event.AttributeSensor;
import brooklyn.management.EntityManager;
-import brooklyn.management.internal.LocalManagementContext;
import brooklyn.policy.Enricher;
import brooklyn.policy.EnricherSpec;
import brooklyn.policy.Policy;
import brooklyn.policy.PolicySpec;
import brooklyn.policy.basic.AbstractPolicy;
+import brooklyn.test.entity.LocalManagementContextForTests;
+import brooklyn.util.exceptions.Exceptions;
import brooklyn.util.flags.SetFromFlag;
import brooklyn.util.os.Os;
@@ -91,14 +93,15 @@ public class RebindFailuresTest extends RebindTestFixtureWithApp {
.impl(MyEntityFailingImpl.class)
.configure(MyEntityFailingImpl.FAIL_ON_REBIND, true));
- newManagementContext = new LocalManagementContext();
+ newManagementContext = LocalManagementContextForTests.newInstance();
EntityManager newEntityManager = newManagementContext.getEntityManager();
RecordingRebindExceptionHandler exceptionHandler = new RecordingRebindExceptionHandler(danglingRefFailureMode, rebindFailureMode);
try {
newApp = rebind(newManagementContext, exceptionHandler);
fail();
} catch (Exception e) {
- if (!e.toString().contains("Problems rebinding")) throw e; // expected
+ assertFailureRebindingError(e);
+ Assert.assertTrue(e.toString().toLowerCase().contains("rebinding entity"), "Wrong error: "+e);
}
// exception handler should have been told about failure
@@ -117,14 +120,15 @@ public class RebindFailuresTest extends RebindTestFixtureWithApp {
.impl(MyEntityFailingImpl.class)
.configure(MyEntityFailingImpl.FAIL_ON_REBIND, true));
- newManagementContext = new LocalManagementContext();
+ newManagementContext = LocalManagementContextForTests.newInstance();
EntityManager newEntityManager = newManagementContext.getEntityManager();
RecordingRebindExceptionHandler exceptionHandler = new RecordingRebindExceptionHandler(danglingRefFailureMode, rebindFailureMode);
try {
newApp = rebind(newManagementContext, exceptionHandler);
fail();
} catch (Exception e) {
- if (!e.toString().contains("Problems rebinding")) throw e; // expected
+ assertFailureRebindingError(e);
+ Assert.assertTrue(e.toString().toLowerCase().contains("rebinding entity"), "Wrong error: "+e);
}
// exception handler should have been told about failure
@@ -143,7 +147,7 @@ public class RebindFailuresTest extends RebindTestFixtureWithApp {
.impl(MyEntityFailingImpl.class)
.configure(MyEntityFailingImpl.FAIL_ON_REBIND, true));
- newManagementContext = new LocalManagementContext();
+ newManagementContext = LocalManagementContextForTests.newInstance();
EntityManager newEntityManager = newManagementContext.getEntityManager();
RecordingRebindExceptionHandler exceptionHandler = new RecordingRebindExceptionHandler(danglingRefFailureMode, rebindFailureMode);
newApp = rebind(newManagementContext, exceptionHandler);
@@ -165,16 +169,13 @@ public class RebindFailuresTest extends RebindTestFixtureWithApp {
File entitiesDir = Os.mkdirs(new File(mementoDir, "entities"));
Files.write("invalid text", new File(entitiesDir, "mycorruptfile"), Charsets.UTF_8);
- newManagementContext = new LocalManagementContext();
+ newManagementContext = LocalManagementContextForTests.newInstance();
RecordingRebindExceptionHandler exceptionHandler = new RecordingRebindExceptionHandler(danglingRefFailureMode, rebindFailureMode);
try {
newApp = rebind(newManagementContext, exceptionHandler);
fail();
} catch (Exception e) {
- if (e.toString().contains("Problem rebinding") || e.toString().contains("Problems rebinding"))
- /* expected */ ;
- else
- throw e;
+ assertFailureRebindingError(e);
}
// exception handler should have been told about failure
@@ -182,6 +183,14 @@ public class RebindFailuresTest extends RebindTestFixtureWithApp {
assertEquals(exceptionHandler.loadMementoFailures.size(), 2, "exceptions="+exceptionHandler.loadMementoFailures);
}
+ protected void assertFailureRebindingError(Exception e) {
+ if (e.toString().toLowerCase().matches(".*(problem|failure)(s?) rebinding.*")) {
+ // expected
+ } else {
+ throw Exceptions.propagate(e);
+ }
+ }
+
@Test
public void testRebindWithFailingPolicyContinuesWithoutPolicy() throws Exception {
origApp.addPolicy(PolicySpec.create(MyPolicyFailingImpl.class)
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/ea4b4ccb/core/src/test/java/brooklyn/entity/rebind/RebindTestFixture.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/brooklyn/entity/rebind/RebindTestFixture.java b/core/src/test/java/brooklyn/entity/rebind/RebindTestFixture.java
index f487252..f2314f7 100644
--- a/core/src/test/java/brooklyn/entity/rebind/RebindTestFixture.java
+++ b/core/src/test/java/brooklyn/entity/rebind/RebindTestFixture.java
@@ -94,7 +94,7 @@ public abstract class RebindTestFixture<T extends StartableApplication> {
}
protected boolean useEmptyCatalog() {
- return false;
+ return true;
}
protected int getPersistPeriodMillis() {
@@ -158,7 +158,7 @@ public abstract class RebindTestFixture<T extends StartableApplication> {
}
protected BrooklynMementoManifest loadMementoManifest() throws Exception {
- newManagementContext = new LocalManagementContextForTests();
+ newManagementContext = createNewManagementContext();
FileBasedObjectStore objectStore = new FileBasedObjectStore(mementoDir);
objectStore.injectManagementContext(newManagementContext);
objectStore.prepareForSharedUse(PersistMode.AUTO, HighAvailabilityMode.DISABLED);
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/ea4b4ccb/core/src/test/java/brooklyn/entity/rebind/RebindTestUtils.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/brooklyn/entity/rebind/RebindTestUtils.java b/core/src/test/java/brooklyn/entity/rebind/RebindTestUtils.java
index 65738a7..ee34e54 100644
--- a/core/src/test/java/brooklyn/entity/rebind/RebindTestUtils.java
+++ b/core/src/test/java/brooklyn/entity/rebind/RebindTestUtils.java
@@ -45,6 +45,7 @@ import brooklyn.entity.trait.Identifiable;
import brooklyn.location.Location;
import brooklyn.management.ManagementContext;
import brooklyn.management.ha.HighAvailabilityMode;
+import brooklyn.management.ha.ManagementNodeState;
import brooklyn.management.internal.LocalManagementContext;
import brooklyn.management.internal.ManagementContextInternal;
import brooklyn.mementos.BrooklynMemento;
@@ -249,12 +250,8 @@ public class RebindTestUtils {
public static Collection<Application> rebindAll(LocalManagementContext newManagementContext, ClassLoader classLoader, RebindExceptionHandler exceptionHandler) throws Exception {
LOG.info("Rebinding app");
- List<Application> newApps;
- if (exceptionHandler == null) {
- newApps = newManagementContext.getRebindManager().rebind(classLoader);
- } else {
- newApps = newManagementContext.getRebindManager().rebind(classLoader, exceptionHandler);
- }
+ List<Application> newApps = newManagementContext.getRebindManager().rebind(classLoader, exceptionHandler, ManagementNodeState.MASTER);
+
if (newApps.isEmpty()) throw new IllegalStateException("Application could not be rebinded; serialization probably failed");
newManagementContext.getRebindManager().startPersistence();
return newApps;
@@ -286,12 +283,7 @@ public class RebindTestUtils {
((ManagementContextInternal)newManagementContext).getBrooklynProperties(),
classLoader);
newManagementContext.getRebindManager().setPersister(newPersister, PersistenceExceptionHandlerImpl.builder().build());
- List<Application> newApps;
- if (exceptionHandler == null) {
- newApps = newManagementContext.getRebindManager().rebind(classLoader);
- } else {
- newApps = newManagementContext.getRebindManager().rebind(classLoader, exceptionHandler);
- }
+ List<Application> newApps = newManagementContext.getRebindManager().rebind(classLoader, exceptionHandler, ManagementNodeState.MASTER);
newManagementContext.getRebindManager().startPersistence();
return newApps;
}
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/ea4b4ccb/core/src/test/java/brooklyn/entity/rebind/RecordingRebindExceptionHandler.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/brooklyn/entity/rebind/RecordingRebindExceptionHandler.java b/core/src/test/java/brooklyn/entity/rebind/RecordingRebindExceptionHandler.java
index 2cec642..d2c8b8c 100644
--- a/core/src/test/java/brooklyn/entity/rebind/RecordingRebindExceptionHandler.java
+++ b/core/src/test/java/brooklyn/entity/rebind/RecordingRebindExceptionHandler.java
@@ -82,11 +82,6 @@ public class RecordingRebindExceptionHandler extends RebindExceptionHandlerImpl
}
@Override
- public void onDone() {
- super.onDone();
- }
-
- @Override
public RuntimeException onFailed(Exception e) {
failed = e;
return super.onFailed(e);
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/ea4b4ccb/core/src/test/java/brooklyn/entity/rebind/transformer/CompoundTransformerTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/brooklyn/entity/rebind/transformer/CompoundTransformerTest.java b/core/src/test/java/brooklyn/entity/rebind/transformer/CompoundTransformerTest.java
index 6e9ab65..9474cd8 100644
--- a/core/src/test/java/brooklyn/entity/rebind/transformer/CompoundTransformerTest.java
+++ b/core/src/test/java/brooklyn/entity/rebind/transformer/CompoundTransformerTest.java
@@ -141,6 +141,7 @@ public class CompoundTransformerTest extends RebindTestFixtureWithApp {
objectStore,
((ManagementContextInternal)origManagementContext).getBrooklynProperties(),
origManagementContext.getCatalog().getRootClassLoader());
+ persister.enableWriteAccess();
PersistenceExceptionHandler exceptionHandler = PersistenceExceptionHandlerImpl.builder().build();
persister.checkpoint(rawData, exceptionHandler);
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/ea4b4ccb/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 dbdf3d3..f7399d4 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 {
((ManagementPlaneSyncRecordPersisterToObjectStore)persister).allowRemoteTimestampInMemento();
BrooklynMementoPersisterToObjectStore persisterObj = new BrooklynMementoPersisterToObjectStore(objectStore, mgmt.getBrooklynProperties(), classLoader);
mgmt.getRebindManager().setPersister(persisterObj, PersistenceExceptionHandlerImpl.builder().build());
- ha = new HighAvailabilityManagerImpl(mgmt)
+ ha = ((HighAvailabilityManagerImpl)mgmt.getHighAvailabilityManager())
.setPollPeriod(Duration.PRACTICALLY_FOREVER)
.setHeartbeatTimeout(Duration.THIRTY_SECONDS)
.setLocalTicker(ticker)
@@ -270,9 +270,9 @@ public class HighAvailabilityManagerSplitBrainTest {
assertEquals(memento2c.getManagementNodes().get(n1.ownNodeId).getRemoteTimestamp(), time2);
assertEquals(memento2c.getManagementNodes().get(n2.ownNodeId).getRemoteTimestamp(), time2);
- // and no entities at n1
- assertEquals(n1.mgmt.getApplications().size(), 0);
+ // right number of entities at n2; n1 may or may not depending whether hot standby is default
assertEquals(n2.mgmt.getApplications().size(), 1);
+// assertEquals(n1.mgmt.getApplications().size(), 0);
}
@Test(invocationCount=50, groups="Integration")
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/ea4b4ccb/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 a657636..c646f69 100644
--- a/core/src/test/java/brooklyn/management/ha/HighAvailabilityManagerTestFixture.java
+++ b/core/src/test/java/brooklyn/management/ha/HighAvailabilityManagerTestFixture.java
@@ -91,7 +91,7 @@ public abstract class HighAvailabilityManagerTestFixture {
managementContext.getBrooklynProperties(),
classLoader);
managementContext.getRebindManager().setPersister(persisterObj, PersistenceExceptionHandlerImpl.builder().build());
- manager = new HighAvailabilityManagerImpl(managementContext)
+ manager = ((HighAvailabilityManagerImpl)managementContext.getHighAvailabilityManager())
.setPollPeriod(getPollPeriod())
.setHeartbeatTimeout(Duration.THIRTY_SECONDS)
.setPromotionListener(promotionListener)
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/ea4b4ccb/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 e4219f7..cc9f065 100644
--- a/core/src/test/java/brooklyn/management/ha/HotStandbyTest.java
+++ b/core/src/test/java/brooklyn/management/ha/HotStandbyTest.java
@@ -81,7 +81,7 @@ public class HotStandbyTest {
.setPollPeriod(Duration.PRACTICALLY_FOREVER)
.setHeartbeatTimeout(Duration.THIRTY_SECONDS)
.setPersister(persister);
- log.info("Created "+nodeName+" "+ownNodeId+" - RM "+mgmt.getRebindManager());
+ log.info("Created "+nodeName+" "+ownNodeId);
}
public void tearDown() throws Exception {
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/ea4b4ccb/software/webapp/src/test/java/brooklyn/entity/webapp/AbstractWebAppFixtureIntegrationTest.java
----------------------------------------------------------------------
diff --git a/software/webapp/src/test/java/brooklyn/entity/webapp/AbstractWebAppFixtureIntegrationTest.java b/software/webapp/src/test/java/brooklyn/entity/webapp/AbstractWebAppFixtureIntegrationTest.java
index b8c14d7..2767444 100644
--- a/software/webapp/src/test/java/brooklyn/entity/webapp/AbstractWebAppFixtureIntegrationTest.java
+++ b/software/webapp/src/test/java/brooklyn/entity/webapp/AbstractWebAppFixtureIntegrationTest.java
@@ -59,6 +59,7 @@ import brooklyn.location.basic.LocalhostMachineProvisioningLocation;
import brooklyn.management.ManagementContext;
import brooklyn.management.SubscriptionContext;
import brooklyn.management.SubscriptionHandle;
+import brooklyn.management.ha.ManagementNodeState;
import brooklyn.management.internal.LocalManagementContext;
import brooklyn.management.internal.ManagementContextInternal;
import brooklyn.mementos.BrooklynMemento;
@@ -226,7 +227,7 @@ public abstract class AbstractWebAppFixtureIntegrationTest {
BrooklynMementoPersisterToMultiFile newPersister = new BrooklynMementoPersisterToMultiFile(tempDir , getClass().getClassLoader());
newManagementContext = new LocalManagementContextForTests();
newManagementContext.getRebindManager().setPersister(newPersister, PersistenceExceptionHandlerImpl.builder().build());
- newManagementContext.getRebindManager().rebind(getClass().getClassLoader());
+ newManagementContext.getRebindManager().rebind(getClass().getClassLoader(), null, ManagementNodeState.MASTER);
newManagementContext.getRebindManager().startPersistence();
SoftwareProcess entity2 = (SoftwareProcess) newManagementContext.getEntityManager().getEntity(tokill.getId());
entity2.stop();
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/ea4b4ccb/usage/launcher/src/main/java/brooklyn/launcher/BrooklynLauncher.java
----------------------------------------------------------------------
diff --git a/usage/launcher/src/main/java/brooklyn/launcher/BrooklynLauncher.java b/usage/launcher/src/main/java/brooklyn/launcher/BrooklynLauncher.java
index bea6c2e..df190d1 100644
--- a/usage/launcher/src/main/java/brooklyn/launcher/BrooklynLauncher.java
+++ b/usage/launcher/src/main/java/brooklyn/launcher/BrooklynLauncher.java
@@ -19,7 +19,6 @@
package brooklyn.launcher;
import static com.google.common.base.Preconditions.checkNotNull;
-
import brooklyn.catalog.CatalogLoadMode;
import brooklyn.internal.BrooklynFeatureEnablement;
import io.brooklyn.camp.CampPlatform;
@@ -73,6 +72,7 @@ import brooklyn.management.ManagementContext;
import brooklyn.management.ha.HighAvailabilityManager;
import brooklyn.management.ha.HighAvailabilityManagerImpl;
import brooklyn.management.ha.HighAvailabilityMode;
+import brooklyn.management.ha.ManagementNodeState;
import brooklyn.management.ha.ManagementPlaneSyncRecordPersister;
import brooklyn.management.ha.ManagementPlaneSyncRecordPersisterToObjectStore;
import brooklyn.management.internal.LocalManagementContext;
@@ -783,7 +783,7 @@ public class BrooklynLauncher {
ClassLoader classLoader = managementContext.getCatalog().getRootClassLoader();
try {
- rebindManager.rebind(classLoader);
+ rebindManager.rebind(classLoader, null, ManagementNodeState.MASTER);
} catch (Exception e) {
Exceptions.propagateIfFatal(e);
LOG.debug("Error rebinding to persisted state (rethrowing): "+e, e);
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/ea4b4ccb/usage/launcher/src/test/java/brooklyn/launcher/BrooklynLauncherHighAvailabilityTest.java
----------------------------------------------------------------------
diff --git a/usage/launcher/src/test/java/brooklyn/launcher/BrooklynLauncherHighAvailabilityTest.java b/usage/launcher/src/test/java/brooklyn/launcher/BrooklynLauncherHighAvailabilityTest.java
index 419124d..f151d92 100644
--- a/usage/launcher/src/test/java/brooklyn/launcher/BrooklynLauncherHighAvailabilityTest.java
+++ b/usage/launcher/src/test/java/brooklyn/launcher/BrooklynLauncherHighAvailabilityTest.java
@@ -130,7 +130,8 @@ public class BrooklynLauncherHighAvailabilityTest {
ManagementContext secondaryManagementContext = secondary.getServerDetails().getManagementContext();
log.info("started mgmt secondary "+secondaryManagementContext);
- assertNoApps(secondary.getServerDetails().getManagementContext());
+ // TODO can assert it sees the apps read only
+// assertNoApps(secondary.getServerDetails().getManagementContext());
// Terminate primary; expect secondary to take over
if (stopGracefully) {
@@ -143,11 +144,11 @@ public class BrooklynLauncherHighAvailabilityTest {
assertOnlyAppEventually(secondaryManagementContext, TestApplication.class);
- // Start tertiary (will come up as standby)
+ // Start tertiary (force up as standby)
tertiary = BrooklynLauncher.newInstance();
tertiary.webconsole(false)
.brooklynProperties(LocalManagementContextForTests.setEmptyCatalogAsDefault(BrooklynProperties.Factory.newEmpty()))
- .highAvailabilityMode(HighAvailabilityMode.AUTO)
+ .highAvailabilityMode(HighAvailabilityMode.STANDBY)
.persistMode(PersistMode.AUTO)
.persistenceDir(persistenceDir)
.persistPeriod(Duration.millis(10))
[11/15] git commit: address @sjcorbett review comments
Posted by he...@apache.org.
address @sjcorbett review comments
Project: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/commit/6d58882f
Tree: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/tree/6d58882f
Diff: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/diff/6d58882f
Branch: refs/heads/master
Commit: 6d58882f9e6a33b870609017abc1433ae7611884
Parents: 32c926b
Author: Alex Heneveld <al...@cloudsoftcorp.com>
Authored: Wed Oct 1 16:17:33 2014 +0100
Committer: Alex Heneveld <al...@cloudsoftcorp.com>
Committed: Wed Oct 1 17:01:52 2014 +0100
----------------------------------------------------------------------
api/src/main/java/brooklyn/entity/Entity.java | 11 +++++++++--
.../java/brooklyn/entity/rebind/RebindManager.java | 4 ++--
.../java/brooklyn/management/ManagementContext.java | 7 ++++---
.../brooklyn/management/ha/ManagementNodeState.java | 14 +++++++-------
.../java/brooklyn/entity/basic/AbstractEntity.java | 4 ++--
.../src/main/java/brooklyn/entity/basic/Entities.java | 11 +++++++----
.../internal/AbstractManagementContext.java | 2 +-
.../internal/NonDeploymentManagementContext.java | 2 +-
8 files changed, 33 insertions(+), 22 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/6d58882f/api/src/main/java/brooklyn/entity/Entity.java
----------------------------------------------------------------------
diff --git a/api/src/main/java/brooklyn/entity/Entity.java b/api/src/main/java/brooklyn/entity/Entity.java
index 8e32dcd..dba9a28 100644
--- a/api/src/main/java/brooklyn/entity/Entity.java
+++ b/api/src/main/java/brooklyn/entity/Entity.java
@@ -122,13 +122,20 @@ public interface Entity extends BrooklynObject {
/**
* Add a child {@link Entity}, and set this entity as its parent,
* returning the added child.
+ * <p>
+ * As with {@link #addChild(EntitySpec)} the child is <b>not</b> brought under management
+ * as part of this call. It should not be managed prior to this call either.
*/
<T extends Entity> T addChild(T child);
/**
* Creates an {@link Entity} from the given spec and adds it, setting this entity as the parent,
- * returning the added child. The added child is not yet managed, even if the parent is managed,
- * so if adding post-management an explicit call to manage the child will be needed. */
+ * returning the added child.
+ * <p>
+ * The added child is <b>not</b> managed as part of this call, even if the parent is managed,
+ * so if adding post-management an explicit call to manage the child will be needed;
+ * see the convenience method <code>Entities.manage(...)</code>.
+ * */
<T extends Entity> T addChild(EntitySpec<T> spec);
/**
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/6d58882f/api/src/main/java/brooklyn/entity/rebind/RebindManager.java
----------------------------------------------------------------------
diff --git a/api/src/main/java/brooklyn/entity/rebind/RebindManager.java b/api/src/main/java/brooklyn/entity/rebind/RebindManager.java
index 9e62fa4..5f9ccf8 100644
--- a/api/src/main/java/brooklyn/entity/rebind/RebindManager.java
+++ b/api/src/main/java/brooklyn/entity/rebind/RebindManager.java
@@ -65,7 +65,7 @@ public interface RebindManager {
public List<Application> rebind(ClassLoader classLoader);
/** @deprecated since 0.7; use {@link #rebind(ClassLoader, RebindExceptionHandler, ManagementNodeState)} */ @Deprecated
public List<Application> rebind(ClassLoader classLoader, RebindExceptionHandler exceptionHandler);
- /** causes this management context to rebind, loading data from the given backing store.
+ /** Causes this management context to rebind, loading data from the given backing store.
* use wisely, as this can cause local entities to be completely lost, or will throw in many other situations.
* in general it may be invoked for a new node becoming {@link ManagementNodeState#MASTER}
* or periodically for a node in {@link ManagementNodeState#HOT_STANDBY}. */
@@ -110,7 +110,7 @@ public interface RebindManager {
/** waits for any needed or pending writes to complete */
@VisibleForTesting
public void waitForPendingComplete(Duration duration) throws InterruptedException, TimeoutException;
- /** forcibly performs persistence, in the foreground */
+ /** Forcibly performs persistence, in the foreground */
@VisibleForTesting
public void forcePersistNow();
}
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/6d58882f/api/src/main/java/brooklyn/management/ManagementContext.java
----------------------------------------------------------------------
diff --git a/api/src/main/java/brooklyn/management/ManagementContext.java b/api/src/main/java/brooklyn/management/ManagementContext.java
index 6e3a3b1..26024b0 100644
--- a/api/src/main/java/brooklyn/management/ManagementContext.java
+++ b/api/src/main/java/brooklyn/management/ManagementContext.java
@@ -214,10 +214,11 @@ public interface ManagementContext {
EntitlementManager getEntitlementManager();
- /** as {@link #lookup(String, Class)} but not constraining the return type */
+ /** As {@link #lookup(String, Class)} but not constraining the return type */
public BrooklynObject lookup(String id);
- /** finds an entity with the given ID known at this management context */
+
+ /** Finds an entity with the given ID known at this management context */
// TODO in future support policies etc
- public <T> T lookup(String id, Class<T> type);
+ public <T extends BrooklynObject> T lookup(String id, Class<T> type);
}
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/6d58882f/api/src/main/java/brooklyn/management/ha/ManagementNodeState.java
----------------------------------------------------------------------
diff --git a/api/src/main/java/brooklyn/management/ha/ManagementNodeState.java b/api/src/main/java/brooklyn/management/ha/ManagementNodeState.java
index d3b0508..c8a90f1 100644
--- a/api/src/main/java/brooklyn/management/ha/ManagementNodeState.java
+++ b/api/src/main/java/brooklyn/management/ha/ManagementNodeState.java
@@ -19,23 +19,23 @@
package brooklyn.management.ha;
public enum ManagementNodeState {
- /** @deprecated since 0.7.0 synonym for maintenance (plus, it should have been UK english!) */
+ /** @deprecated since 0.7.0 synonym for maintenance (plus, it should have been US English!) */
UNINITIALISED,
- /** node is either coming online, or is in some kind of recovery/transitioning mode */
+ /** Node is either coming online, or is in some kind of recovery/transitioning mode */
INITIALIZING,
- /** node is in "lukewarm standby" mode, where it is available to be promoted to master,
+ /** Node is in "lukewarm standby" mode, where it is available to be promoted to master,
* but does not have entities loaded and will require some effort to be promoted */
STANDBY,
- /** node is acting as read-only proxy */
+ /** Node is acting as read-only proxy */
HOT_STANDBY,
- /** node is running as primary/master, able to manage entities and create new ones */
+ /** Node is running as primary/master, able to manage entities and create new ones */
// the semantics are intended to support multi-master here; we could have multiple master nodes,
// but we need to look up who is master for any given entity
MASTER,
- /** node has failed and requires maintenance attention */
+ /** Node has failed and requires maintenance attention */
FAILED,
- /** node has gone away; maintenance not possible */
+ /** Node has gone away; maintenance not possible */
TERMINATED;
}
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/6d58882f/core/src/main/java/brooklyn/entity/basic/AbstractEntity.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/entity/basic/AbstractEntity.java b/core/src/main/java/brooklyn/entity/basic/AbstractEntity.java
index de00c8a..e1429e9 100644
--- a/core/src/main/java/brooklyn/entity/basic/AbstractEntity.java
+++ b/core/src/main/java/brooklyn/entity/basic/AbstractEntity.java
@@ -686,8 +686,8 @@ public abstract class AbstractEntity extends AbstractBrooklynObject implements E
@Override
public ManagementContext getManagementContext() {
// NB Sept 2014 - removed synch keyword above due to deadlock;
- // it also synchs in ManagementSupport..getManagementContext();
- // no apparent reason why it was here also. @aledsage can you review?
+ // it also synchs in ManagementSupport.getManagementContext();
+ // no apparent reason why it was here also
return getManagementSupport().getManagementContext();
}
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/6d58882f/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 aa669db..d32afa6 100644
--- a/core/src/main/java/brooklyn/entity/basic/Entities.java
+++ b/core/src/main/java/brooklyn/entity/basic/Entities.java
@@ -658,13 +658,16 @@ public class Entities {
*/
public static void destroy(Entity e) {
if (isManaged(e)) {
- if (!isReadOnly(e)) {
+ if (isReadOnly(e)) {
+ unmanage(e);
+ log.debug("destroyed and unmanaged read-only copy of "+e);
+ } else {
if (e instanceof Startable) Entities.invokeEffector((EntityLocal)e, e, Startable.STOP).getUnchecked();
if (e instanceof EntityInternal) ((EntityInternal)e).destroy();
- }
- unmanage(e);
- log.debug("destroyed and unmanaged "+e+"; mgmt now "+
+ unmanage(e);
+ log.debug("destroyed and unmanaged "+e+"; mgmt now "+
(e.getApplicationId()==null ? "(no app)" : e.getApplication().getManagementContext())+" - managed? "+isManaged(e));
+ }
} else {
log.debug("skipping destroy of "+e+": not managed");
}
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/6d58882f/core/src/main/java/brooklyn/management/internal/AbstractManagementContext.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/management/internal/AbstractManagementContext.java b/core/src/main/java/brooklyn/management/internal/AbstractManagementContext.java
index 4c6d7d9..ee4e518 100644
--- a/core/src/main/java/brooklyn/management/internal/AbstractManagementContext.java
+++ b/core/src/main/java/brooklyn/management/internal/AbstractManagementContext.java
@@ -413,7 +413,7 @@ public abstract class AbstractManagementContext implements ManagementContextInte
}
@SuppressWarnings("unchecked")
- public <T> T lookup(String id, Class<T> type) {
+ public <T extends BrooklynObject> T lookup(String id, Class<T> type) {
Object result;
result = getEntityManager().getEntity(id);
if (result!=null && type.isInstance(result)) return (T)result;
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/6d58882f/core/src/main/java/brooklyn/management/internal/NonDeploymentManagementContext.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/management/internal/NonDeploymentManagementContext.java b/core/src/main/java/brooklyn/management/internal/NonDeploymentManagementContext.java
index 46e3f97..7f07f14 100644
--- a/core/src/main/java/brooklyn/management/internal/NonDeploymentManagementContext.java
+++ b/core/src/main/java/brooklyn/management/internal/NonDeploymentManagementContext.java
@@ -424,7 +424,7 @@ public class NonDeploymentManagementContext implements ManagementContextInternal
}
@Override
- public <T> T lookup(String id, Class<T> type) {
+ public <T extends BrooklynObject> T lookup(String id, Class<T> type) {
checkInitialManagementContextReal();
return initialManagementContext.lookup(id, type);
}
[07/15] git commit: Better memory leak testing, fixing some leaks,
including escaped proxy/reference chains,
and an issue with subscriptions not being cleared when children are removed
(which it turned out was there previously).
Posted by he...@apache.org.
Better memory leak testing, fixing some leaks, including escaped proxy/reference chains,
and an issue with subscriptions not being cleared when children are removed
(which it turned out was there previously).
Project: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/commit/c3aac5e3
Tree: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/tree/c3aac5e3
Diff: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/diff/c3aac5e3
Branch: refs/heads/master
Commit: c3aac5e3ec69032ea51b71639549a22956f03e95
Parents: 4c866cb
Author: Alex Heneveld <al...@cloudsoftcorp.com>
Authored: Wed Oct 1 03:21:52 2014 +0100
Committer: Alex Heneveld <al...@cloudsoftcorp.com>
Committed: Wed Oct 1 16:40:16 2014 +0100
----------------------------------------------------------------------
.../entity/proxying/EntityProxyImpl.java | 42 +++-
.../rebind/PeriodicDeltaChangeListener.java | 27 ++-
.../entity/rebind/RebindManagerImpl.java | 12 +-
.../BrooklynMementoPersisterToObjectStore.java | 3 +
.../persister/RetryingMementoSerializer.java | 4 +
.../ha/HighAvailabilityManagerImpl.java | 2 +-
.../internal/BrooklynGarbageCollector.java | 1 +
.../internal/EntityManagementSupport.java | 2 +-
.../management/internal/LocalEntityManager.java | 6 +-
.../brooklyn/management/ha/HotStandbyTest.java | 224 +++++++++++++++++--
.../entity/TestApplicationNoEnrichersImpl.java | 15 --
.../main/java/brooklyn/util/time/Duration.java | 12 +
12 files changed, 300 insertions(+), 50 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/c3aac5e3/core/src/main/java/brooklyn/entity/proxying/EntityProxyImpl.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/entity/proxying/EntityProxyImpl.java b/core/src/main/java/brooklyn/entity/proxying/EntityProxyImpl.java
index 094c24c..04b5d44 100644
--- a/core/src/main/java/brooklyn/entity/proxying/EntityProxyImpl.java
+++ b/core/src/main/java/brooklyn/entity/proxying/EntityProxyImpl.java
@@ -21,9 +21,11 @@ package brooklyn.entity.proxying;
import static com.google.common.base.Preconditions.checkNotNull;
import java.lang.reflect.Method;
+import java.lang.reflect.Proxy;
import java.util.Arrays;
import java.util.LinkedHashSet;
import java.util.Set;
+import java.util.WeakHashMap;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -59,6 +61,8 @@ public class EntityProxyImpl implements java.lang.reflect.InvocationHandler {
private Entity delegate;
private Boolean isMaster;
+
+ private WeakHashMap<Entity,Void> temporaryProxies = new WeakHashMap<Entity, Void>();
private static final Set<MethodSignature> OBJECT_METHODS = Sets.newLinkedHashSet();
static {
@@ -92,18 +96,46 @@ public class EntityProxyImpl implements java.lang.reflect.InvocationHandler {
extras);
}
}
-
+
public EntityProxyImpl(Entity entity) {
this.delegate = checkNotNull(entity, "entity");
}
/** invoked to specify that a different underlying delegate should be used,
* e.g. because we are switching copy impls or switching primary/copy*/
- // TODO XXX because the delegate has a backpointer to the proxy, there is potentially
- // a nasty problem wherein an old proxy is kept, with a pointer to an old delegate.
- public synchronized void resetDelegate(Entity delegate) {
- this.delegate = delegate;
+ public synchronized void resetDelegate(Entity thisProxy, Entity preferredProxy, Entity newDelegate) {
+ if (LOG.isTraceEnabled()) {
+ LOG.trace("updating "+Integer.toHexString(System.identityHashCode(thisProxy))
+ +" to be the same as "+Integer.toHexString(System.identityHashCode(preferredProxy))
+ +" pointing at "+Integer.toHexString(System.identityHashCode(newDelegate))
+ +" ("+temporaryProxies.size()+" temporary proxies)");
+ }
+
+ Entity oldDelegate = delegate;
+ this.delegate = newDelegate;
this.isMaster = null;
+
+ if (newDelegate==oldDelegate)
+ return;
+
+ /* we have to make sure that any newly created proxy of the newDelegate
+ * which have leaked (eg by being set as a child) also get repointed to this new delegate */
+ if (oldDelegate!=null) {
+ Entity temporaryProxy = ((AbstractEntity)oldDelegate).getProxy();
+ if (temporaryProxy!=null) temporaryProxies.put(temporaryProxy, null);
+ ((AbstractEntity)oldDelegate).resetProxy(preferredProxy);
+ }
+ if (newDelegate!=null) {
+ Entity temporaryProxy = ((AbstractEntity)newDelegate).getProxy();
+ if (temporaryProxy!=null) temporaryProxies.put(temporaryProxy, null);
+ ((AbstractEntity)newDelegate).resetProxy(preferredProxy);
+ }
+
+ // update any proxies which might be in use
+ for (Entity tp: temporaryProxies.keySet()) {
+ if (tp==thisProxy || tp==preferredProxy) continue;
+ ((EntityProxyImpl)(Proxy.getInvocationHandler(tp))).resetDelegate(tp, preferredProxy, newDelegate);
+ }
}
@Override
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/c3aac5e3/core/src/main/java/brooklyn/entity/rebind/PeriodicDeltaChangeListener.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/entity/rebind/PeriodicDeltaChangeListener.java b/core/src/main/java/brooklyn/entity/rebind/PeriodicDeltaChangeListener.java
index e9cef0c..acf9de7 100644
--- a/core/src/main/java/brooklyn/entity/rebind/PeriodicDeltaChangeListener.java
+++ b/core/src/main/java/brooklyn/entity/rebind/PeriodicDeltaChangeListener.java
@@ -48,6 +48,8 @@ import brooklyn.util.exceptions.RuntimeInterruptedException;
import brooklyn.util.task.BasicExecutionContext;
import brooklyn.util.task.BasicTask;
import brooklyn.util.task.ScheduledTask;
+import brooklyn.util.task.Tasks;
+import brooklyn.util.time.CountdownTimer;
import brooklyn.util.time.Duration;
import brooklyn.util.time.Time;
@@ -169,11 +171,32 @@ public class PeriodicDeltaChangeListener implements ChangeListener {
};
scheduledTask = (ScheduledTask) executionContext.submit(new ScheduledTask(taskFactory).period(period));
}
-
+
+ /** stops persistence, waiting for it to complete */
void stop() {
+ stop(Duration.TEN_SECONDS, Duration.ONE_SECOND);
+ }
+ void stop(Duration timeout, Duration graceTimeoutForSubsequentOperations) {
stopped = true;
running = false;
- if (scheduledTask != null) scheduledTask.cancel();
+
+ if (scheduledTask != null) {
+ CountdownTimer expiry = timeout.countdownTimer();
+ scheduledTask.cancel(false);
+ try {
+ waitForPendingComplete(expiry.getDurationRemaining().minimum(Duration.ZERO).add(graceTimeoutForSubsequentOperations));
+ } catch (Exception e) {
+ throw Exceptions.propagate(e);
+ }
+ scheduledTask.blockUntilEnded(expiry.getDurationRemaining().minimum(Duration.ZERO).add(graceTimeoutForSubsequentOperations));
+ scheduledTask.cancel(true);
+ boolean reallyEnded = Tasks.blockUntilInternalTasksEnded(scheduledTask, expiry.getDurationRemaining().minimum(Duration.ZERO).add(graceTimeoutForSubsequentOperations));
+ if (!reallyEnded) {
+ LOG.warn("Persistence tasks took too long to complete when stopping persistence (ignoring): "+scheduledTask);
+ }
+ scheduledTask = null;
+ }
+
// Discard all state that was waiting to be persisted
synchronized (this) {
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/c3aac5e3/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 8c067f1..2f85db6 100644
--- a/core/src/main/java/brooklyn/entity/rebind/RebindManagerImpl.java
+++ b/core/src/main/java/brooklyn/entity/rebind/RebindManagerImpl.java
@@ -87,7 +87,6 @@ import brooklyn.util.exceptions.RuntimeInterruptedException;
import brooklyn.util.flags.FlagUtils;
import brooklyn.util.javalang.Reflections;
import brooklyn.util.task.BasicExecutionContext;
-import brooklyn.util.task.BasicTask;
import brooklyn.util.task.ScheduledTask;
import brooklyn.util.task.Tasks;
import brooklyn.util.text.Strings;
@@ -266,8 +265,12 @@ public class RebindManagerImpl implements RebindManager {
return;
}
LOG.debug("Starting read-only rebinding, mgmt "+managementContext.getManagementNodeId());
- readOnlyRunning = true;
+ if (persistenceRealChangeListener != null) persistenceRealChangeListener.stop();
+ if (persistenceStoreAccess != null) persistenceStoreAccess.disableWriteAccess(true);
+
+ readOnlyRunning = true;
+
try {
rebind(null, null, ManagementNodeState.HOT_STANDBY);
} catch (Exception e) {
@@ -276,7 +279,7 @@ public class RebindManagerImpl implements RebindManager {
Callable<Task<?>> taskFactory = new Callable<Task<?>>() {
@Override public Task<Void> call() {
- return new BasicTask<Void>(new Callable<Void>() {
+ return Tasks.<Void>builder().dynamic(false).name("rebind (periodic run").body(new Callable<Void>() {
public Void call() {
try {
rebind(null, null, ManagementNodeState.HOT_STANDBY);
@@ -301,7 +304,7 @@ public class RebindManagerImpl implements RebindManager {
LOG.warn("Problem rebinding (rethrowing)", t);
throw Exceptions.propagate(t);
}
- }});
+ }}).build();
}
};
readOnlyTask = (ScheduledTask) managementContext.getServerExecutionContext().submit(
@@ -353,6 +356,7 @@ public class RebindManagerImpl implements RebindManager {
@Override
@VisibleForTesting
+ @Deprecated /** @deprecated since 0.7.0 use Duration as argument */
public void waitForPendingComplete(long timeout, TimeUnit unit) throws InterruptedException, TimeoutException {
waitForPendingComplete(Duration.of(timeout, unit));
}
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/c3aac5e3/core/src/main/java/brooklyn/entity/rebind/persister/BrooklynMementoPersisterToObjectStore.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/entity/rebind/persister/BrooklynMementoPersisterToObjectStore.java b/core/src/main/java/brooklyn/entity/rebind/persister/BrooklynMementoPersisterToObjectStore.java
index f3a2882..a751ada 100644
--- a/core/src/main/java/brooklyn/entity/rebind/persister/BrooklynMementoPersisterToObjectStore.java
+++ b/core/src/main/java/brooklyn/entity/rebind/persister/BrooklynMementoPersisterToObjectStore.java
@@ -644,6 +644,9 @@ public class BrooklynMementoPersisterToObjectStore implements BrooklynMementoPer
private void persist(String subPath, BrooklynObjectType type, String id, String content, PersistenceExceptionHandler exceptionHandler) {
try {
+ if (content==null) {
+ LOG.warn("Null content for "+type+" "+id);
+ }
getWriter(getPath(subPath, id)).put(content);
} catch (Exception e) {
exceptionHandler.onPersistRawMementoFailed(type, id, e);
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/c3aac5e3/core/src/main/java/brooklyn/entity/rebind/persister/RetryingMementoSerializer.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/entity/rebind/persister/RetryingMementoSerializer.java b/core/src/main/java/brooklyn/entity/rebind/persister/RetryingMementoSerializer.java
index 9860571..f94fa45 100644
--- a/core/src/main/java/brooklyn/entity/rebind/persister/RetryingMementoSerializer.java
+++ b/core/src/main/java/brooklyn/entity/rebind/persister/RetryingMementoSerializer.java
@@ -58,6 +58,9 @@ public class RetryingMementoSerializer<T> implements MementoSerializer<T> {
@Override
public T fromString(String string) {
+ if (string==null)
+ return null;
+
RuntimeException lastException = null;
int attempt = 0;
do {
@@ -65,6 +68,7 @@ public class RetryingMementoSerializer<T> implements MementoSerializer<T> {
try {
return delegate.fromString(string);
} catch (RuntimeException e) {
+ // trying multiple times only makes sense for a few errors (namely ConcModExceptions); perhaps deprecate that strategy?
LOG.warn("Error deserializing memento (attempt "+attempt+" of "+maxAttempts+")", e);
lastException = e;
}
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/c3aac5e3/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 0595db5..bdd6eba 100644
--- a/core/src/main/java/brooklyn/management/ha/HighAvailabilityManagerImpl.java
+++ b/core/src/main/java/brooklyn/management/ha/HighAvailabilityManagerImpl.java
@@ -245,7 +245,7 @@ public class HighAvailabilityManagerImpl implements HighAvailabilityManager {
case HOT_STANDBY:
if (existingMaster != null) {
publishAndCheck(true);
- LOG.info("Management node "+ownNodeId+" started as "+startMode+" explicitly, status "+nodeState);
+ LOG.info("Management node "+ownNodeId+" started as "+startMode+" explicitly");
} else {
throw new IllegalStateException("No existing master; cannot start as standby");
}
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/c3aac5e3/core/src/main/java/brooklyn/management/internal/BrooklynGarbageCollector.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/management/internal/BrooklynGarbageCollector.java b/core/src/main/java/brooklyn/management/internal/BrooklynGarbageCollector.java
index b345ab3..1e10de7 100644
--- a/core/src/main/java/brooklyn/management/internal/BrooklynGarbageCollector.java
+++ b/core/src/main/java/brooklyn/management/internal/BrooklynGarbageCollector.java
@@ -180,6 +180,7 @@ public class BrooklynGarbageCollector {
TimeUnit.MILLISECONDS);
}
+ /** force a round of Brooklyn garbage collection */
public void gcIteration() {
try {
logUsage("brooklyn gc (before)");
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/c3aac5e3/core/src/main/java/brooklyn/management/internal/EntityManagementSupport.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/management/internal/EntityManagementSupport.java b/core/src/main/java/brooklyn/management/internal/EntityManagementSupport.java
index 61ff168..026cf49 100644
--- a/core/src/main/java/brooklyn/management/internal/EntityManagementSupport.java
+++ b/core/src/main/java/brooklyn/management/internal/EntityManagementSupport.java
@@ -315,7 +315,7 @@ public class EntityManagementSupport {
if (managementContext != info.getManagementContext()) {
throw new IllegalStateException("Has different management context: "+managementContext+"; expected "+info.getManagementContext());
}
- if (subscriptionContext != null) subscriptionContext.unsubscribeAll();
+ getSubscriptionContext().unsubscribeAll();
entityChangeListener = EntityChangeListener.NOOP;
managementContextUsable.set(false);
currentlyDeployed.set(false);
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/c3aac5e3/core/src/main/java/brooklyn/management/internal/LocalEntityManager.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/management/internal/LocalEntityManager.java b/core/src/main/java/brooklyn/management/internal/LocalEntityManager.java
index 52050a4..0eae80b 100644
--- a/core/src/main/java/brooklyn/management/internal/LocalEntityManager.java
+++ b/core/src/main/java/brooklyn/management/internal/LocalEntityManager.java
@@ -487,10 +487,10 @@ public class LocalEntityManager implements EntityManagerInternal {
if (mode==ManagementTransitionMode.CREATING) {
throw new IllegalStateException("call to manage entity "+e+" but already had proxy "+oldProxy+" already known under that id at "+this);
}
- // set the new entity to the old proxy
- ((AbstractEntity)realE).resetProxy(oldProxy);
+ // make the old proxy point at this new delegate
+ // (some other tricks done in the call below)
+ ((EntityProxyImpl)(Proxy.getInvocationHandler(oldProxy))).resetDelegate(oldProxy, oldProxy, realE);
proxyE = oldProxy;
- ((EntityProxyImpl)(Proxy.getInvocationHandler(proxyE))).resetDelegate(realE);
} else {
proxyE = toProxyEntityIfAvailable(e);
}
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/c3aac5e3/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 befdae4..492f2f1 100644
--- a/core/src/test/java/brooklyn/management/ha/HotStandbyTest.java
+++ b/core/src/test/java/brooklyn/management/ha/HotStandbyTest.java
@@ -20,7 +20,9 @@ package brooklyn.management.ha;
import static org.testng.Assert.assertEquals;
+import java.util.ArrayDeque;
import java.util.Date;
+import java.util.Deque;
import java.util.List;
import java.util.Map;
@@ -36,12 +38,14 @@ import brooklyn.entity.Entity;
import brooklyn.entity.basic.Entities;
import brooklyn.entity.proxying.EntitySpec;
import brooklyn.entity.rebind.PersistenceExceptionHandlerImpl;
+import brooklyn.entity.rebind.RebindManagerImpl;
import brooklyn.entity.rebind.persister.BrooklynMementoPersisterToObjectStore;
import brooklyn.entity.rebind.persister.InMemoryObjectStore;
import brooklyn.entity.rebind.persister.ListeningObjectStore;
import brooklyn.entity.rebind.persister.PersistMode;
import brooklyn.entity.rebind.persister.PersistenceObjectStore;
import brooklyn.location.Location;
+import brooklyn.management.internal.AbstractManagementContext;
import brooklyn.management.internal.ManagementContextInternal;
import brooklyn.test.EntityTestUtils;
import brooklyn.test.entity.LocalManagementContextForTests;
@@ -49,7 +53,10 @@ import brooklyn.test.entity.TestApplication;
import brooklyn.test.entity.TestEntity;
import brooklyn.util.collections.MutableList;
import brooklyn.util.collections.MutableMap;
+import brooklyn.util.javalang.JavaClassNames;
+import brooklyn.util.text.ByteSizeStrings;
import brooklyn.util.time.Duration;
+import brooklyn.util.time.Time;
import com.google.common.collect.Iterables;
@@ -70,8 +77,8 @@ public class HotStandbyTest {
private ListeningObjectStore objectStore;
private ManagementPlaneSyncRecordPersister persister;
private HighAvailabilityManagerImpl ha;
+ private Duration persistOrRebindPeriod = Duration.ONE_SECOND;
- @BeforeMethod(alwaysRun=true)
public void setUp() throws Exception {
nodeName = "node "+nodes.size();
mgmt = newLocalManagementContext();
@@ -82,6 +89,7 @@ public class HotStandbyTest {
persister = new ManagementPlaneSyncRecordPersisterToObjectStore(mgmt, objectStore, classLoader);
((ManagementPlaneSyncRecordPersisterToObjectStore)persister).allowRemoteTimestampInMemento();
BrooklynMementoPersisterToObjectStore persisterObj = new BrooklynMementoPersisterToObjectStore(objectStore, mgmt.getBrooklynProperties(), classLoader);
+ ((RebindManagerImpl)mgmt.getRebindManager()).setPeriodicPersistPeriod(persistOrRebindPeriod);
mgmt.getRebindManager().setPersister(persisterObj, PersistenceExceptionHandlerImpl.builder().build());
ha = ((HighAvailabilityManagerImpl)mgmt.getHighAvailabilityManager())
.setPollPeriod(Duration.PRACTICALLY_FOREVER)
@@ -90,11 +98,15 @@ public class HotStandbyTest {
log.info("Created "+nodeName+" "+ownNodeId);
}
- public void tearDown() throws Exception {
+ public void tearDownThisOnly() throws Exception {
if (ha != null) ha.stop();
if (mgmt!=null) mgmt.getRebindManager().stop();
-
if (mgmt != null) Entities.destroyAll(mgmt);
+ }
+
+ public void tearDownAll() throws Exception {
+ tearDownThisOnly();
+ // can't delete the object store until all being torn down
if (objectStore != null) objectStore.deleteCompletely();
}
@@ -110,8 +122,9 @@ public class HotStandbyTest {
sharedBackingStore.clear();
}
- public HaMgmtNode newNode() throws Exception {
+ public HaMgmtNode newNode(Duration persistOrRebindPeriod) throws Exception {
HaMgmtNode node = new HaMgmtNode();
+ node.persistOrRebindPeriod = persistOrRebindPeriod;
node.setUp();
nodes.add(node);
return node;
@@ -120,7 +133,7 @@ public class HotStandbyTest {
@AfterMethod(alwaysRun=true)
public void tearDown() throws Exception {
for (HaMgmtNode n: nodes)
- n.tearDown();
+ n.tearDownAll();
}
protected ManagementContextInternal newLocalManagementContext() {
@@ -131,37 +144,43 @@ public class HotStandbyTest {
return new InMemoryObjectStore(sharedBackingStore, sharedBackingStoreDates);
}
- private HaMgmtNode createMaster() throws Exception {
- HaMgmtNode n1 = newNode();
+ private HaMgmtNode createMaster(Duration persistOrRebindPeriod) throws Exception {
+ HaMgmtNode n1 = newNode(persistOrRebindPeriod);
n1.ha.start(HighAvailabilityMode.AUTO);
assertEquals(n1.ha.getNodeState(), ManagementNodeState.MASTER);
return n1;
}
- private HaMgmtNode createHotStandby() throws Exception {
- HaMgmtNode n2 = newNode();
+ private HaMgmtNode createHotStandby(Duration rebindPeriod) throws Exception {
+ HaMgmtNode n2 = newNode(rebindPeriod);
n2.ha.start(HighAvailabilityMode.HOT_STANDBY);
assertEquals(n2.ha.getNodeState(), ManagementNodeState.HOT_STANDBY);
return n2;
}
- private TestApplication createFirstAppAndPersist(HaMgmtNode n1) throws Exception {
+ private TestApplication createFirstAppAndPersist(HaMgmtNode n1) throws Exception {
TestApplication app = TestApplication.Factory.newManagedInstanceForTests(n1.mgmt);
+ // for testing without enrichers, if desired:
+// TestApplication app = ApplicationBuilder.newManagedApp(EntitySpec.create(TestApplication.class).impl(TestApplicationNoEnrichersImpl.class), n1.mgmt);
app.setDisplayName("First App");
app.start(MutableList.<Location>of());
app.setConfig(TestEntity.CONF_NAME, "first-app");
app.setAttribute(TestEntity.SEQUENCE, 3);
- n1.mgmt.getRebindManager().forcePersistNow();
+ forcePersistNow(n1);
return app;
}
+
+ protected void forcePersistNow(HaMgmtNode n1) {
+ n1.mgmt.getRebindManager().forcePersistNow();
+ }
private Application expectRebindSequenceNumber(HaMgmtNode master, HaMgmtNode hotStandby, Application app, int expectedSensorSequenceValue, boolean immediate) {
Application appRO = hotStandby.mgmt.lookup(app.getId(), Application.class);
if (immediate) {
- master.mgmt.getRebindManager().forcePersistNow();
- hotStandby.mgmt.getRebindManager().rebind(null, null, ManagementNodeState.HOT_STANDBY);
+ forcePersistNow(master);
+ forceRebindNow(hotStandby);
EntityTestUtils.assertAttributeEquals(appRO, TestEntity.SEQUENCE, expectedSensorSequenceValue);
} else {
EntityTestUtils.assertAttributeEqualsEventually(appRO, TestEntity.SEQUENCE, expectedSensorSequenceValue);
@@ -175,12 +194,16 @@ public class HotStandbyTest {
return appRO;
}
+
+ private void forceRebindNow(HaMgmtNode hotStandby) {
+ hotStandby.mgmt.getRebindManager().rebind(null, null, ManagementNodeState.HOT_STANDBY);
+ }
@Test
public void testHotStandbySeesChangedNameConfigAndSensorValueButDoesntAllowChange() throws Exception {
- HaMgmtNode n1 = createMaster();
+ HaMgmtNode n1 = createMaster(Duration.PRACTICALLY_FOREVER);
TestApplication app = createFirstAppAndPersist(n1);
- HaMgmtNode n2 = createHotStandby();
+ HaMgmtNode n2 = createHotStandby(Duration.PRACTICALLY_FOREVER);
assertEquals(n2.mgmt.getApplications().size(), 1);
Application appRO = n2.mgmt.lookup(app.getId(), Application.class);
@@ -200,9 +223,9 @@ public class HotStandbyTest {
@Test
public void testHotStandbySeesChangedNameConfigAndSensorValue() throws Exception {
- HaMgmtNode n1 = createMaster();
+ HaMgmtNode n1 = createMaster(Duration.PRACTICALLY_FOREVER);
TestApplication app = createFirstAppAndPersist(n1);
- HaMgmtNode n2 = createHotStandby();
+ HaMgmtNode n2 = createHotStandby(Duration.PRACTICALLY_FOREVER);
assertEquals(n2.mgmt.getApplications().size(), 1);
Application appRO = n2.mgmt.lookup(app.getId(), Application.class);
@@ -243,9 +266,9 @@ public class HotStandbyTest {
}
public void doTestHotStandbySeesStructuralChangesIncludingRemoval(boolean immediate) throws Exception {
- HaMgmtNode n1 = createMaster();
+ HaMgmtNode n1 = createMaster(immediate ? Duration.PRACTICALLY_FOREVER : Duration.millis(200));
TestApplication app = createFirstAppAndPersist(n1);
- HaMgmtNode n2 = createHotStandby();
+ HaMgmtNode n2 = createHotStandby(immediate ? Duration.PRACTICALLY_FOREVER : Duration.millis(200));
assertEquals(n2.mgmt.getApplications().size(), 1);
Application appRO = n2.mgmt.lookup(app.getId(), Application.class);
@@ -294,5 +317,168 @@ public class HotStandbyTest {
public void testHotStandbySeesStructuralChangesIncludingRemovalManyTimes() throws Exception {
doTestHotStandbySeesStructuralChangesIncludingRemoval(true);
}
+
+ Deque<Long> usedMemory = new ArrayDeque<Long>();
+ protected long noteUsedMemory(String message) {
+ Time.sleep(Duration.millis(200));
+ for (HaMgmtNode n: nodes) {
+ ((AbstractManagementContext)n.mgmt).getGarbageCollector().gcIteration();
+ }
+ System.gc(); System.gc();
+ Time.sleep(Duration.millis(50));
+ System.gc(); System.gc();
+ long mem = Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory();
+ usedMemory.addLast(mem);
+ log.info("Memory used - "+message+": "+ByteSizeStrings.java().apply(mem));
+ return mem;
+ }
+ public void assertUsedMemoryLessThan(String event, long max) {
+ noteUsedMemory(event);
+ long nowUsed = usedMemory.peekLast();
+ if (nowUsed > max) {
+ // aggressively try to force GC
+ Time.sleep(Duration.ONE_SECOND);
+ usedMemory.removeLast();
+ noteUsedMemory(event+" (extra GC)");
+ nowUsed = usedMemory.peekLast();
+ if (nowUsed > max) {
+ Assert.fail("Too much memory used - "+ByteSizeStrings.java().apply(nowUsed)+" > max "+ByteSizeStrings.java().apply(max));
+ }
+ }
+ }
+ public void assertUsedMemoryMaxDelta(String event, long deltaMegabytes) {
+ assertUsedMemoryLessThan(event, usedMemory.peekLast() + deltaMegabytes*1024*1024);
+ }
+
+ @Test(groups="Integration")
+ public void testHotStandbyDoesNotLeakLotsOfRebinds() throws Exception {
+ log.info("Starting test "+JavaClassNames.niceClassAndMethod());
+ final int DELTA = 2;
+
+ HaMgmtNode n1 = createMaster(Duration.PRACTICALLY_FOREVER);
+ TestApplication app = createFirstAppAndPersist(n1);
+ long initialUsed = noteUsedMemory("Created app");
+
+ HaMgmtNode n2 = createHotStandby(Duration.PRACTICALLY_FOREVER);
+ assertUsedMemoryMaxDelta("Standby created", DELTA);
+
+ forcePersistNow(n1);
+ forceRebindNow(n2);
+ assertUsedMemoryMaxDelta("Persisted and rebinded once", DELTA);
+
+ for (int i=0; i<10; i++) {
+ forcePersistNow(n1);
+ forceRebindNow(n2);
+ }
+ assertUsedMemoryMaxDelta("Persisted and rebinded 10x", DELTA);
+
+ for (int i=0; i<1000; i++) {
+ if ((i+1)%100==0) {
+ noteUsedMemory("iteration "+(i+1));
+ usedMemory.removeLast();
+ }
+ forcePersistNow(n1);
+ forceRebindNow(n2);
+ }
+ assertUsedMemoryMaxDelta("Persisted and rebinded 1000x", DELTA);
+
+ Entities.unmanage(app);
+ forcePersistNow(n1);
+ forceRebindNow(n2);
+
+ assertUsedMemoryLessThan("And now all unmanaged", initialUsed + DELTA*1000*1000);
+ }
+
+ static class BigObject {
+ public BigObject(int sizeBytes) { array = new byte[sizeBytes]; }
+ byte[] array;
+ }
+ @Test(groups="Integration")
+ public void testHotStandbyDoesNotLeakBigObjects() throws Exception {
+ log.info("Starting test "+JavaClassNames.niceClassAndMethod());
+ final int SIZE = 5;
+ final int SIZE_UP_BOUND = SIZE+2;
+ final int SIZE_DOWN_BOUND = SIZE-1;
+ final int GRACE = 2;
+ // the XML persistence uses a lot of space, we approx at between 2x and 3c
+ final int SIZE_IN_XML = 3*SIZE;
+ final int SIZE_IN_XML_DOWN = 2*SIZE;
+
+ HaMgmtNode n1 = createMaster(Duration.PRACTICALLY_FOREVER);
+ TestApplication app = createFirstAppAndPersist(n1);
+ noteUsedMemory("Finished seeding");
+ Long initialUsed = usedMemory.peekLast();
+ app.setConfig(TestEntity.CONF_OBJECT, new BigObject(SIZE*1000*1000));
+ assertUsedMemoryMaxDelta("Set a big config object", SIZE_UP_BOUND);
+ forcePersistNow(n1);
+ assertUsedMemoryMaxDelta("Persisted a big config object", SIZE_IN_XML);
+
+ HaMgmtNode n2 = createHotStandby(Duration.PRACTICALLY_FOREVER);
+ forceRebindNow(n2);
+ assertUsedMemoryMaxDelta("Rebinded", SIZE_UP_BOUND);
+
+ for (int i=0; i<10; i++)
+ forceRebindNow(n2);
+ assertUsedMemoryMaxDelta("Several more rebinds", GRACE);
+ for (int i=0; i<10; i++) {
+ forcePersistNow(n1);
+ forceRebindNow(n2);
+ }
+ assertUsedMemoryMaxDelta("And more rebinds and more persists", GRACE);
+
+ app.setConfig(TestEntity.CONF_OBJECT, "big is now small");
+ assertUsedMemoryMaxDelta("Big made small at primary", -SIZE_DOWN_BOUND);
+ forcePersistNow(n1);
+ assertUsedMemoryMaxDelta("And persisted", -SIZE_IN_XML_DOWN);
+
+ forceRebindNow(n2);
+ assertUsedMemoryMaxDelta("And at secondary", -SIZE_DOWN_BOUND);
+
+ Entities.unmanage(app);
+ forcePersistNow(n1);
+ forceRebindNow(n2);
+
+ assertUsedMemoryLessThan("And now all unmanaged", initialUsed+GRACE*1000*1000);
+ }
+
+ @Test(groups="Integration") // because it's slow
+ public void testHotStandbyDoesNotLeakLotsOfRebindsCreatingAndDestroyingAChildEntity() throws Exception {
+ log.info("Starting test "+JavaClassNames.niceClassAndMethod());
+ final int DELTA = 2;
+
+ HaMgmtNode n1 = createMaster(Duration.PRACTICALLY_FOREVER);
+ TestApplication app = createFirstAppAndPersist(n1);
+ long initialUsed = noteUsedMemory("Created app");
+
+ HaMgmtNode n2 = createHotStandby(Duration.PRACTICALLY_FOREVER);
+ assertUsedMemoryMaxDelta("Standby created", DELTA);
+
+ TestEntity lastChild = app.addChild(EntitySpec.create(TestEntity.class).configure(TestEntity.CONF_NAME, "first-child"));
+ Entities.manage(lastChild);
+ forcePersistNow(n1);
+ forceRebindNow(n2);
+ assertUsedMemoryMaxDelta("Child created and rebinded once", DELTA);
+
+ for (int i=0; i<1000; i++) {
+ if (i==9 || (i+1)%100==0) {
+ noteUsedMemory("iteration "+(i+1));
+ usedMemory.removeLast();
+ }
+ TestEntity newChild = app.addChild(EntitySpec.create(TestEntity.class).configure(TestEntity.CONF_NAME, "first-child"));
+ Entities.manage(newChild);
+ Entities.unmanage(lastChild);
+ lastChild = newChild;
+
+ forcePersistNow(n1);
+ forceRebindNow(n2);
+ }
+ assertUsedMemoryMaxDelta("Persisted and rebinded 1000x", DELTA);
+
+ Entities.unmanage(app);
+ forcePersistNow(n1);
+ forceRebindNow(n2);
+
+ assertUsedMemoryLessThan("And now all unmanaged", initialUsed + DELTA*1000*1000);
+ }
}
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/c3aac5e3/core/src/test/java/brooklyn/test/entity/TestApplicationNoEnrichersImpl.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/brooklyn/test/entity/TestApplicationNoEnrichersImpl.java b/core/src/test/java/brooklyn/test/entity/TestApplicationNoEnrichersImpl.java
index 1435443..a56fbcb 100644
--- a/core/src/test/java/brooklyn/test/entity/TestApplicationNoEnrichersImpl.java
+++ b/core/src/test/java/brooklyn/test/entity/TestApplicationNoEnrichersImpl.java
@@ -18,21 +18,6 @@
*/
package brooklyn.test.entity;
-import java.util.Map;
-
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import brooklyn.entity.Entity;
-import brooklyn.entity.Group;
-import brooklyn.entity.basic.AbstractApplication;
-import brooklyn.entity.proxying.EntitySpec;
-import brooklyn.event.Sensor;
-import brooklyn.event.SensorEventListener;
-import brooklyn.location.LocationSpec;
-import brooklyn.location.basic.LocalhostMachineProvisioningLocation;
-import brooklyn.management.SubscriptionHandle;
-import brooklyn.util.logging.LoggingSetup;
/**
* Mock application for testing.
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/c3aac5e3/utils/common/src/main/java/brooklyn/util/time/Duration.java
----------------------------------------------------------------------
diff --git a/utils/common/src/main/java/brooklyn/util/time/Duration.java b/utils/common/src/main/java/brooklyn/util/time/Duration.java
index 73cc314..c6622ba 100644
--- a/utils/common/src/main/java/brooklyn/util/time/Duration.java
+++ b/utils/common/src/main/java/brooklyn/util/time/Duration.java
@@ -281,4 +281,16 @@ public class Duration implements Comparable<Duration>, Serializable {
return compareTo(x) < 0;
}
+ /** returns the larger of this value or the argument */
+ public Duration minimum(Duration alternateMinimumValue) {
+ if (isShorterThan(alternateMinimumValue)) return alternateMinimumValue;
+ return this;
+ }
+
+ /** returns the smaller of this value or the argument */
+ public Duration maximum(Duration alternateMaximumValue) {
+ if (isLongerThan(alternateMaximumValue)) return alternateMaximumValue;
+ return this;
+ }
+
}
[08/15] git commit: support programmatic API on
HighAvailabilityManager to changeMode and setPriority;
priority persisted and used to elect master (also prefers non-snapshot and
more recent Brooklyn versions by default, unless explicit priority overrides
Posted by he...@apache.org.
support programmatic API on HighAvailabilityManager to changeMode and setPriority;
priority persisted and used to elect master (also prefers non-snapshot and more recent Brooklyn versions by default, unless explicit priority overrides).
good test for changing mode in HotStandbyTest.testChangeMode.
Project: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/commit/32c926b9
Tree: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/tree/32c926b9
Diff: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/diff/32c926b9
Branch: refs/heads/master
Commit: 32c926b95c8e89538778cd85010fa2e9a20ecf74
Parents: c3aac5e
Author: Alex Heneveld <al...@cloudsoftcorp.com>
Authored: Wed Oct 1 11:32:26 2014 +0100
Committer: Alex Heneveld <al...@cloudsoftcorp.com>
Committed: Wed Oct 1 16:40:16 2014 +0100
----------------------------------------------------------------------
.../management/ha/HighAvailabilityManager.java | 13 ++
.../management/ha/ManagementNodeSyncRecord.java | 2 +
.../ha/ManagementPlaneSyncRecordPersister.java | 1 +
.../rebind/PeriodicDeltaChangeListener.java | 5 +-
.../entity/rebind/RebindManagerImpl.java | 12 +-
.../dto/BasicManagementNodeSyncRecord.java | 22 ++-
.../management/ha/BasicMasterChooser.java | 64 ++++++--
.../ha/HighAvailabilityManagerImpl.java | 148 +++++++++++++++----
.../ha/ManagementPlaneSyncRecordDeltaImpl.java | 11 +-
...ntPlaneSyncRecordPersisterToObjectStore.java | 15 +-
.../NonDeploymentManagementContext.java | 13 +-
.../HighAvailabilityManagerSplitBrainTest.java | 8 +-
.../brooklyn/management/ha/HotStandbyTest.java | 78 ++++++++++
.../management/ha/MasterChooserTest.java | 36 ++++-
...lynLauncherRebindToCloudObjectStoreTest.java | 4 -
.../util/text/NaturalOrderComparator.java | 3 +-
16 files changed, 373 insertions(+), 62 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/32c926b9/api/src/main/java/brooklyn/management/ha/HighAvailabilityManager.java
----------------------------------------------------------------------
diff --git a/api/src/main/java/brooklyn/management/ha/HighAvailabilityManager.java b/api/src/main/java/brooklyn/management/ha/HighAvailabilityManager.java
index d9888f9..72bb70f 100644
--- a/api/src/main/java/brooklyn/management/ha/HighAvailabilityManager.java
+++ b/api/src/main/java/brooklyn/management/ha/HighAvailabilityManager.java
@@ -61,6 +61,7 @@ public interface HighAvailabilityManager {
* instead of {@link #start(HighAvailabilityMode)}. It may be an error if
* this is called after this HA Manager is started.
*/
+ @Beta
void disabled();
/** Whether HA mode is operational */
@@ -83,6 +84,18 @@ public interface HighAvailabilityManager {
*/
void stop();
+ /** changes the mode that this HA server is running in
+ * <p>
+ * note it will be an error to {@link #changeMode(HighAvailabilityMode)} to {@link ManagementNodeState#MASTER}
+ * when there is already a master; to promote a node explicitly set its priority higher than
+ * the others and invoke {@link #changeMode(HighAvailabilityMode)} to a standby mode on the existing master */
+ void changeMode(HighAvailabilityMode mode);
+
+ /** sets the priority, and publishes it synchronously so it is canonical */
+ void setPriority(long priority);
+
+ long getPriority();
+
/**
* Returns a snapshot of the management-plane's current / most-recently-known status.
* <p>
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/32c926b9/api/src/main/java/brooklyn/management/ha/ManagementNodeSyncRecord.java
----------------------------------------------------------------------
diff --git a/api/src/main/java/brooklyn/management/ha/ManagementNodeSyncRecord.java b/api/src/main/java/brooklyn/management/ha/ManagementNodeSyncRecord.java
index bbd8665..38150bf 100644
--- a/api/src/main/java/brooklyn/management/ha/ManagementNodeSyncRecord.java
+++ b/api/src/main/java/brooklyn/management/ha/ManagementNodeSyncRecord.java
@@ -46,6 +46,8 @@ public interface ManagementNodeSyncRecord {
ManagementNodeState getStatus();
+ Long getPriority();
+
/** timestamp set by the originating management machine */
long getLocalTimestamp();
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/32c926b9/api/src/main/java/brooklyn/management/ha/ManagementPlaneSyncRecordPersister.java
----------------------------------------------------------------------
diff --git a/api/src/main/java/brooklyn/management/ha/ManagementPlaneSyncRecordPersister.java b/api/src/main/java/brooklyn/management/ha/ManagementPlaneSyncRecordPersister.java
index ebacd7a..f2d42fc 100644
--- a/api/src/main/java/brooklyn/management/ha/ManagementPlaneSyncRecordPersister.java
+++ b/api/src/main/java/brooklyn/management/ha/ManagementPlaneSyncRecordPersister.java
@@ -63,5 +63,6 @@ public interface ManagementPlaneSyncRecordPersister {
Collection<String> getRemovedNodeIds();
MasterChange getMasterChange();
String getNewMasterOrNull();
+ String getExpectedMasterToClear();
}
}
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/32c926b9/core/src/main/java/brooklyn/entity/rebind/PeriodicDeltaChangeListener.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/entity/rebind/PeriodicDeltaChangeListener.java b/core/src/main/java/brooklyn/entity/rebind/PeriodicDeltaChangeListener.java
index acf9de7..48da586 100644
--- a/core/src/main/java/brooklyn/entity/rebind/PeriodicDeltaChangeListener.java
+++ b/core/src/main/java/brooklyn/entity/rebind/PeriodicDeltaChangeListener.java
@@ -144,6 +144,7 @@ public class PeriodicDeltaChangeListener implements ChangeListener {
LOG.warn("Request to start "+this+" when already running - "+scheduledTask+"; ignoring");
return;
}
+ stopped = false;
running = true;
Callable<Task<?>> taskFactory = new Callable<Task<?>>() {
@@ -298,7 +299,9 @@ public class PeriodicDeltaChangeListener implements ChangeListener {
@VisibleForTesting
public void persistNow() {
- if (!isActive()) return;
+ if (!isActive()) {
+ return;
+ }
try {
persistingMutex.acquire();
if (!isActive()) return;
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/32c926b9/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 2f85db6..745b7cb 100644
--- a/core/src/main/java/brooklyn/entity/rebind/RebindManagerImpl.java
+++ b/core/src/main/java/brooklyn/entity/rebind/RebindManagerImpl.java
@@ -206,6 +206,14 @@ public class RebindManagerImpl implements RebindManager {
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()
@@ -905,9 +913,9 @@ public class RebindManagerImpl implements RebindManager {
if (wasReadOnly && isNowReadOnly)
return ManagementTransitionMode.REBINDING_READONLY;
else if (wasReadOnly)
- return ManagementTransitionMode.REBINDING_NO_LONGER_PRIMARY;
- else if (isNowReadOnly)
return ManagementTransitionMode.REBINDING_BECOMING_PRIMARY;
+ else if (isNowReadOnly)
+ return ManagementTransitionMode.REBINDING_NO_LONGER_PRIMARY;
else
throw new IllegalStateException("Rebinding master not supported: "+item);
}
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/32c926b9/core/src/main/java/brooklyn/entity/rebind/plane/dto/BasicManagementNodeSyncRecord.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/entity/rebind/plane/dto/BasicManagementNodeSyncRecord.java b/core/src/main/java/brooklyn/entity/rebind/plane/dto/BasicManagementNodeSyncRecord.java
index c7aa008..bc1e137 100644
--- a/core/src/main/java/brooklyn/entity/rebind/plane/dto/BasicManagementNodeSyncRecord.java
+++ b/core/src/main/java/brooklyn/entity/rebind/plane/dto/BasicManagementNodeSyncRecord.java
@@ -32,7 +32,8 @@ import brooklyn.util.time.Time;
import com.google.common.base.Objects;
/**
- * Represents the state of a management node within the Brooklyn management plane.
+ * Represents the state of a management node within the Brooklyn management plane
+ * (DTO class).
*
* @author aled
*/
@@ -50,6 +51,7 @@ public class BasicManagementNodeSyncRecord implements ManagementNodeSyncRecord,
protected String nodeId;
protected URI uri;
protected ManagementNodeState status;
+ protected Long priority;
protected long localTimestamp;
protected Long remoteTimestamp;
@@ -68,6 +70,9 @@ public class BasicManagementNodeSyncRecord implements ManagementNodeSyncRecord,
public Builder status(ManagementNodeState val) {
status = val; return self();
}
+ public Builder priority(Long val) {
+ priority = val; return self();
+ }
public Builder localTimestamp(long val) {
localTimestamp = val; return self();
}
@@ -79,12 +84,13 @@ public class BasicManagementNodeSyncRecord implements ManagementNodeSyncRecord,
}
public Builder from(ManagementNodeSyncRecord other, boolean ignoreNulls) {
if (ignoreNulls && other==null) return this;
- if (other.getNodeId()!=null) nodeId = other.getNodeId();
if (other.getBrooklynVersion()!=null) brooklynVersion = other.getBrooklynVersion();
- if (other.getLocalTimestamp()>0) localTimestamp = other.getLocalTimestamp();
- if (other.getRemoteTimestamp()!=null) remoteTimestamp = other.getRemoteTimestamp();
+ if (other.getNodeId()!=null) nodeId = other.getNodeId();
if (other.getUri()!=null) uri = other.getUri();
if (other.getStatus()!=null) status = other.getStatus();
+ if (other.getPriority()!=null) priority = other.getPriority();
+ if (other.getLocalTimestamp()>0) localTimestamp = other.getLocalTimestamp();
+ if (other.getRemoteTimestamp()!=null) remoteTimestamp = other.getRemoteTimestamp();
return this;
}
public ManagementNodeSyncRecord build() {
@@ -96,6 +102,7 @@ public class BasicManagementNodeSyncRecord implements ManagementNodeSyncRecord,
private String nodeId;
private URI uri;
private ManagementNodeState status;
+ private Long priority;
private Long localTimestamp;
private Long remoteTimestamp;
@@ -116,6 +123,7 @@ public class BasicManagementNodeSyncRecord implements ManagementNodeSyncRecord,
nodeId = builder.nodeId;
uri = builder.uri;
status = builder.status;
+ priority = builder.priority;
localTimestamp = builder.localTimestamp;
remoteTimestamp = builder.remoteTimestamp;
}
@@ -141,6 +149,11 @@ public class BasicManagementNodeSyncRecord implements ManagementNodeSyncRecord,
}
@Override
+ public Long getPriority() {
+ return priority;
+ }
+
+ @Override
public long getLocalTimestamp() {
if (localTimestamp!=null) return localTimestamp;
if (timestampUtc!=null) return timestampUtc;
@@ -167,6 +180,7 @@ public class BasicManagementNodeSyncRecord implements ManagementNodeSyncRecord,
.add("nodeId", getNodeId())
.add("uri", getUri())
.add("status", getStatus())
+ .add("priority", getPriority())
.add("localTimestamp", getLocalTimestamp()+"="+Time.makeDateString(getLocalTimestamp()))
.add("remoteTimestamp", getRemoteTimestamp()+(getRemoteTimestamp()==null ? "" :
"="+Time.makeDateString(getRemoteTimestamp())))
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/32c926b9/core/src/main/java/brooklyn/management/ha/BasicMasterChooser.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/management/ha/BasicMasterChooser.java b/core/src/main/java/brooklyn/management/ha/BasicMasterChooser.java
index 78c110a..80f40d0 100644
--- a/core/src/main/java/brooklyn/management/ha/BasicMasterChooser.java
+++ b/core/src/main/java/brooklyn/management/ha/BasicMasterChooser.java
@@ -27,10 +27,13 @@ import org.slf4j.LoggerFactory;
import brooklyn.entity.trait.Identifiable;
import brooklyn.util.collections.MutableList;
+import brooklyn.util.text.NaturalOrderComparator;
import brooklyn.util.time.Duration;
import com.google.common.annotations.Beta;
import com.google.common.annotations.VisibleForTesting;
+import com.google.common.collect.ComparisonChain;
+import com.google.common.collect.Ordering;
/**
* @since 0.7.0
@@ -92,19 +95,58 @@ public abstract class BasicMasterChooser implements MasterChooser {
return min.record;
}
+ public static class AlphabeticChooserScore implements Comparable<AlphabeticChooserScore> {
+ long priority;
+ int versionBias;
+ String brooklynVersion;
+ int statePriority;
+ String nodeId;
+
+ @Override
+ public int compareTo(AlphabeticChooserScore o) {
+ if (o==null) return -1;
+ return ComparisonChain.start()
+ // invert the order where we prefer higher values
+ .compare(o.priority, this.priority)
+ .compare(o.versionBias, this.versionBias)
+ .compare(o.brooklynVersion, this.brooklynVersion,
+ Ordering.from(NaturalOrderComparator.INSTANCE).nullsFirst())
+ .compare(o.statePriority, this.statePriority)
+ .compare(this.nodeId, o.nodeId, Ordering.usingToString().nullsLast())
+ .result();
+ }
+ }
+
+ /** comparator which prefers, in order:
+ * <li> higher explicit priority
+ * <li> non-snapshot Brooklyn version, then any Brooklyn version, and lastly null version
+ * (using {@link NaturalOrderComparator} so e.g. v10 > v3.20 > v3.9 )
+ * <li> higher version (null last)
+ * <li> node which reports it's master, hot standby, then standby
+ * <li> finally (common case): lower (alphabetically) node id
+ */
public static class AlphabeticMasterChooser extends BasicMasterChooser {
- final boolean preferHot;
- public AlphabeticMasterChooser(boolean preferHot) { this.preferHot = preferHot; }
- public AlphabeticMasterChooser() { this.preferHot = true; }
+ final boolean preferHotStandby;
+ public AlphabeticMasterChooser(boolean preferHotStandby) { this.preferHotStandby = preferHotStandby; }
+ public AlphabeticMasterChooser() { this.preferHotStandby = true; }
@Override
- protected String score(ManagementNodeSyncRecord contender) {
- if (!preferHot)
- return contender.getNodeId();
- // simple prefix with the rating
- String state = (contender.getStatus()==ManagementNodeState.MASTER ? "1" :
- contender.getStatus()==ManagementNodeState.HOT_STANDBY ? "2" :
- contender.getStatus()==ManagementNodeState.STANDBY ? "3" : "9");
- return state + ":" + contender.getNodeId();
+ protected AlphabeticChooserScore score(ManagementNodeSyncRecord contender) {
+ AlphabeticChooserScore score = new AlphabeticChooserScore();
+ score.priority = contender.getPriority()!=null ? contender.getPriority() : 0;
+ score.brooklynVersion = contender.getBrooklynVersion();
+ score.versionBias = contender.getBrooklynVersion()==null ? -2 :
+ contender.getBrooklynVersion().toLowerCase().indexOf("snapshot")>=0 ? -1 :
+ 0;
+ if (preferHotStandby) {
+ // other master should be preferred before we get invoked, but including for good measure
+ score.statePriority = contender.getStatus()==ManagementNodeState.MASTER ? 3 :
+ contender.getStatus()==ManagementNodeState.HOT_STANDBY ? 2 :
+ contender.getStatus()==ManagementNodeState.STANDBY ? 1 : -1;
+ } else {
+ score.statePriority = 0;
+ }
+ score.nodeId = contender.getNodeId();
+ return score;
}
}
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/32c926b9/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 bdd6eba..200a902 100644
--- a/core/src/main/java/brooklyn/management/ha/HighAvailabilityManagerImpl.java
+++ b/core/src/main/java/brooklyn/management/ha/HighAvailabilityManagerImpl.java
@@ -50,6 +50,7 @@ import brooklyn.util.collections.MutableSet;
import brooklyn.util.exceptions.Exceptions;
import brooklyn.util.task.BasicTask;
import brooklyn.util.task.ScheduledTask;
+import brooklyn.util.text.Strings;
import brooklyn.util.time.Duration;
import com.google.api.client.repackaged.com.google.common.base.Preconditions;
@@ -128,6 +129,7 @@ public class HighAvailabilityManagerImpl implements HighAvailabilityManager {
private volatile boolean running;
private volatile ManagementNodeState nodeState = ManagementNodeState.INITIALIZING;
private volatile boolean nodeStateTransitionComplete = false;
+ private volatile long priority = 0;
public HighAvailabilityManagerImpl(ManagementContextInternal managementContext) {
this.managementContext = managementContext;
@@ -204,54 +206,112 @@ public class HighAvailabilityManagerImpl implements HighAvailabilityManager {
@Override
public void start(HighAvailabilityMode startMode) {
- ownNodeId = managementContext.getManagementNodeId();
nodeStateTransitionComplete = true;
// always start in standby; it may get promoted to master or hot_standby in this method
+ // (depending on startMode; but for startMode STANDBY or HOT_STANDBY it will not promote until the next election)
nodeState = ManagementNodeState.STANDBY;
running = true;
+ changeMode(startMode, true, true);
+ }
+
+ @Override
+ public void changeMode(HighAvailabilityMode startMode) {
+ changeMode(startMode, false, false);
+ }
+
+ @VisibleForTesting
+ @Beta
+ public void changeMode(HighAvailabilityMode startMode, boolean preventElectionOnExplicitStandbyMode, boolean failOnExplicitStandbyModeIfNoMaster) {
+ if (!running)
+ throw new IllegalStateException("Can only change mode when already running; invoke 'start' first");
+ ownNodeId = managementContext.getManagementNodeId();
// TODO Small race in that we first check, and then we'll do checkMaster() on first poll,
// so another node could have already become master or terminated in that window.
ManagementNodeSyncRecord existingMaster = hasHealthyMaster();
+ boolean weAreMaster = existingMaster!=null && ownNodeId.equals(existingMaster.getNodeId());
// catch error in some tests where mgmt context has a different mgmt context
if (managementContext.getHighAvailabilityManager()!=this)
throw new IllegalStateException("Cannot start an HA manager on a management context with a different HA manager!");
+ if (weAreMaster) {
+ // demotion may be required; do this before triggering an election
+ switch (startMode) {
+ case MASTER:
+ case AUTO:
+ // no action needed
+ break;
+ case HOT_STANDBY: demoteToStandby(true); break;
+ case STANDBY: demoteToStandby(false); break;
+ case DISABLED: demoteToFailed(); break;
+ default:
+ throw new IllegalStateException("Unexpected high availability mode "+startMode+" requested for "+this);
+ }
+ }
+
+ // now do election
switch (startMode) {
case AUTO:
// don't care; let's start and see if we promote ourselves
publishAndCheck(true);
if (nodeState == ManagementNodeState.STANDBY || nodeState == ManagementNodeState.HOT_STANDBY) {
- String masterNodeId = getManagementPlaneSyncState().getMasterNodeId();
- ManagementNodeSyncRecord masterNodeDetails = getManagementPlaneSyncState().getManagementNodes().get(masterNodeId);
- LOG.info("Management node "+ownNodeId+" started as HA " + nodeState + " autodetected, master is "+masterNodeId +
- (masterNodeDetails==null || masterNodeDetails.getUri()==null ? " (no url)" : " at "+masterNodeDetails.getUri()));
+ ManagementPlaneSyncRecord newState = getManagementPlaneSyncState();
+ String masterNodeId = newState.getMasterNodeId();
+ ManagementNodeSyncRecord masterNodeDetails = newState.getManagementNodes().get(masterNodeId);
+ LOG.info("Management node "+ownNodeId+" running as HA " + nodeState + " autodetected, " +
+ (Strings.isBlank(masterNodeId) ? "no master currently (other node should promote itself soon)" : "master "
+ + (existingMaster==null ? "(new) " : "")
+ + "is "+masterNodeId +
+ (masterNodeDetails==null || masterNodeDetails.getUri()==null ? " (no url)" : " at "+masterNodeDetails.getUri())));
} else if (nodeState == ManagementNodeState.MASTER) {
- LOG.info("Management node "+ownNodeId+" starting as HA MASTER autodetected");
+ LOG.info("Management node "+ownNodeId+" running as HA MASTER autodetected");
} else {
- throw new IllegalStateException("Management node "+ownNodeId+" started as auto in unexpected mode "+nodeState);
+ throw new IllegalStateException("Management node "+ownNodeId+" set to HA AUTO, encountered unexpected mode "+nodeState);
}
break;
case MASTER:
if (existingMaster == null) {
promoteToMaster();
- LOG.info("Management node "+ownNodeId+" started as HA MASTER explicitly");
+ LOG.info("Management node "+ownNodeId+" running as HA MASTER explicitly");
+ } else if (!weAreMaster) {
+ throw new IllegalStateException("Master already exists; cannot run as master (master "+existingMaster.toVerboseString()+"); "
+ + "to trigger a promotion, set a priority and demote the current master");
} else {
- throw new IllegalStateException("Master already exists; cannot start as master ("+existingMaster.toVerboseString()+")");
+ LOG.info("Management node "+ownNodeId+" already running as HA MASTER, when set explicitly");
}
break;
case STANDBY:
case HOT_STANDBY:
- if (existingMaster != null) {
+ if (!preventElectionOnExplicitStandbyMode)
publishAndCheck(true);
- LOG.info("Management node "+ownNodeId+" started as "+startMode+" explicitly");
+ if (failOnExplicitStandbyModeIfNoMaster && existingMaster==null) {
+ LOG.error("Management node "+ownNodeId+" detected no master when "+startMode+" requested and existing master required; failing.");
+ throw new IllegalStateException("No existing master; cannot start as "+startMode);
+ }
+
+ String message = "Management node "+ownNodeId+" running as HA "+getNodeState()+" (";
+ if (getNodeState().toString().equals(startMode.toString()))
+ message += "explicitly requested";
+ else if (startMode==HighAvailabilityMode.HOT_STANDBY && getNodeState()==ManagementNodeState.STANDBY)
+ message += "caller requested "+startMode+", will attempt rebind directly";
+ else
+ message += "caller requested "+startMode;
+
+ if (getNodeState()==ManagementNodeState.MASTER) {
+ message += " but election re-promoted this node)";
} else {
- throw new IllegalStateException("No existing master; cannot start as standby");
+ ManagementPlaneSyncRecord newState = getManagementPlaneSyncState();
+ if (Strings.isBlank(newState.getMasterNodeId())) {
+ message += "); no master currently (subsequent election may repair)";
+ } else {
+ message += "); master "+newState.getMasterNodeId();
+ }
}
+ LOG.info(message);
break;
default:
- throw new IllegalStateException("Unexpected high availability start-mode "+startMode+" for "+this);
+ throw new IllegalStateException("Unexpected high availability mode "+startMode+" requested for "+this);
}
if (startMode==HighAvailabilityMode.AUTO) {
@@ -266,15 +326,40 @@ public class HighAvailabilityManagerImpl implements HighAvailabilityManager {
nodeStateTransitionComplete = false;
// inform the world that we are transitioning (not eligible for promotion while going in to hot standby)
publishHealth();
- attemptHotStandby();
- publishHealth();
+ try {
+ attemptHotStandby();
+ nodeStateTransitionComplete = true;
+ publishHealth();
+
+ if (getNodeState()==ManagementNodeState.HOT_STANDBY) {
+ LOG.info("Management node "+ownNodeId+" now running as HA "+ManagementNodeState.HOT_STANDBY+"; "
+ + managementContext.getApplications().size()+" application"+Strings.s(managementContext.getApplications().size())+" loaded");
+ } else {
+ LOG.warn("Management node "+ownNodeId+" unable to promote to "+ManagementNodeState.HOT_STANDBY+" (currently "+getNodeState()+"); "
+ + "(see log for further details)");
+ }
+ } catch (Exception e) {
+ LOG.warn("Management node "+ownNodeId+" unable to promote to "+ManagementNodeState.HOT_STANDBY+" (currently "+getNodeState()+"); rethrowing: "+Exceptions.collapseText(e));
+ throw Exceptions.propagate(e);
+ }
+ } else {
+ nodeStateTransitionComplete = true;
}
-
- nodeStateTransitionComplete = true;
registerPollTask();
}
@Override
+ public void setPriority(long priority) {
+ this.priority = priority;
+ if (persister!=null) publishHealth();
+ }
+
+ @Override
+ public long getPriority() {
+ return priority;
+ }
+
+ @Override
public void stop() {
LOG.debug("Stopping "+this);
boolean wasRunning = running; // ensure idempotent
@@ -373,7 +458,7 @@ public class HighAvailabilityManagerImpl implements HighAvailabilityManager {
if (LOG.isTraceEnabled()) LOG.trace("Published management-node health: {}", memento);
}
- protected synchronized void publishDemotionFromMaster(boolean clearMaster) {
+ protected synchronized void publishDemotionFromMaster() {
checkState(getNodeState() != ManagementNodeState.MASTER, "node status must not be master when demoting", getNodeState());
if (persister == null) {
@@ -384,7 +469,7 @@ public class HighAvailabilityManagerImpl implements HighAvailabilityManager {
ManagementNodeSyncRecord memento = createManagementNodeSyncRecord(false);
ManagementPlaneSyncRecordDeltaImpl.Builder deltaBuilder = ManagementPlaneSyncRecordDeltaImpl.builder()
.node(memento);
- if (clearMaster) deltaBuilder.clearMaster(ownNodeId);
+ deltaBuilder.clearMaster(ownNodeId);
Delta delta = deltaBuilder.build();
persister.delta(delta);
@@ -466,7 +551,7 @@ public class HighAvailabilityManagerImpl implements HighAvailabilityManager {
return;
} else {
if (ownNodeRecord!=null && ownNodeRecord.getStatus() == ManagementNodeState.MASTER) {
- LOG.error("HA subsystem detected change of master, stolen from us ("+ownNodeId+"), deferring to "+currMasterNodeId);
+ LOG.error("Management node "+ownNodeId+" detected master change, stolen from us, deferring to "+currMasterNodeId);
newMasterNodeRecord = currMasterNodeRecord;
demotingSelfInFavourOfOtherMaster = true;
} else {
@@ -494,7 +579,7 @@ public class HighAvailabilityManagerImpl implements HighAvailabilityManager {
if (demotingSelfInFavourOfOtherMaster) {
LOG.debug("Master-change for this node only, demoting "+ownNodeRecord.toVerboseString()+" in favour of official master "+newMasterNodeRecord.toVerboseString());
- demoteToStandby(true);
+ demoteToStandby(BrooklynFeatureEnablement.isEnabled(BrooklynFeatureEnablement.FEATURE_DEFAULT_STANDBY_IS_HOT_PROPERTY));
return;
}
@@ -516,13 +601,16 @@ public class HighAvailabilityManagerImpl implements HighAvailabilityManager {
});
}
if (!initializing) {
- LOG.warn("HA subsystem detected change of master, from "
- + currMasterNodeId + " (" + (currMasterNodeRecord==null ? "?" : currMasterNodeRecord.getRemoteTimestamp()) + ")"
+ String message = "Management node "+ownNodeId+" detected ";
+ if (weAreNewMaster) message += "we should be master, changing from ";
+ else message += "master change, from ";
+ message +=currMasterNodeId + " (" + (currMasterNodeRecord==null ? "?" : currMasterNodeRecord.getRemoteTimestamp()) + ")"
+ " to "
+ (newMasterNodeId == null ? "<none>" :
(weAreNewMaster ? "us " : "")
+ newMasterNodeId + " (" + newMasterNodeRecord.getRemoteTimestamp() + ")"
- + (newMasterNodeUri!=null ? " "+newMasterNodeUri : "") ));
+ + (newMasterNodeUri!=null ? " "+newMasterNodeUri : "") );
+ LOG.warn(message);
}
// New master is ourself: promote
@@ -567,7 +655,7 @@ public class HighAvailabilityManagerImpl implements HighAvailabilityManager {
nodeState = ManagementNodeState.FAILED;
onDemotionStopTasks();
nodeStateTransitionComplete = true;
- publishDemotionFromMaster(true);
+ publishDemotionFromMaster();
}
protected void demoteToStandby(boolean hot) {
@@ -579,12 +667,15 @@ public class HighAvailabilityManagerImpl implements HighAvailabilityManager {
nodeStateTransitionComplete = false;
nodeState = ManagementNodeState.STANDBY;
onDemotionStopTasks();
+ nodeStateTransitionComplete = true;
+ publishDemotionFromMaster();
+
if (hot) {
- publishDemotionFromMaster(false);
+ nodeStateTransitionComplete = false;
attemptHotStandby();
+ nodeStateTransitionComplete = true;
+ publishHealth();
}
- nodeStateTransitionComplete = true;
- publishDemotionFromMaster(false);
}
protected void onDemotionStopTasks() {
@@ -703,6 +794,7 @@ public class HighAvailabilityManagerImpl implements HighAvailabilityManager {
.brooklynVersion(BrooklynVersion.get())
.nodeId(ownNodeId)
.status(getNodeState())
+ .priority(getPriority())
.localTimestamp(timestamp)
.uri(managementContext.getManagementNodeUri().orNull());
if (useLocalTimestampAsRemoteTimestamp)
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/32c926b9/core/src/main/java/brooklyn/management/ha/ManagementPlaneSyncRecordDeltaImpl.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/management/ha/ManagementPlaneSyncRecordDeltaImpl.java b/core/src/main/java/brooklyn/management/ha/ManagementPlaneSyncRecordDeltaImpl.java
index a9be0fd..b54b9ef 100644
--- a/core/src/main/java/brooklyn/management/ha/ManagementPlaneSyncRecordDeltaImpl.java
+++ b/core/src/main/java/brooklyn/management/ha/ManagementPlaneSyncRecordDeltaImpl.java
@@ -45,6 +45,7 @@ public class ManagementPlaneSyncRecordDeltaImpl implements Delta {
private Collection <String> removedNodeIds = Sets.newLinkedHashSet();
private MasterChange masterChange = MasterChange.NO_CHANGE;
private String master;
+ private String expectedOldMaster;
public Builder node(ManagementNodeSyncRecord node) {
nodes.add(checkNotNull(node, "node")); return this;
@@ -57,8 +58,9 @@ public class ManagementPlaneSyncRecordDeltaImpl implements Delta {
master = checkNotNull(nodeId, "masterId");
return this;
}
- public Builder clearMaster(String nodeId) {
+ public Builder clearMaster(String optionalExpectedNodeId) {
masterChange = MasterChange.CLEAR_MASTER;
+ this.expectedOldMaster = optionalExpectedNodeId;
return this;
}
public Delta build() {
@@ -70,12 +72,14 @@ public class ManagementPlaneSyncRecordDeltaImpl implements Delta {
private final Collection <String> removedNodeIds;
private final MasterChange masterChange;
private String masterId;
+ private String expectedOldMaster;
ManagementPlaneSyncRecordDeltaImpl(Builder builder) {
nodes = builder.nodes;
removedNodeIds = builder.removedNodeIds;
masterChange = builder.masterChange;
masterId = builder.master;
+ this.expectedOldMaster = builder.expectedOldMaster;
checkState((masterChange == MasterChange.SET_MASTER) ? (masterId != null) : (masterId == null),
"invalid combination: change=%s; masterId=%s", masterChange, masterId);
}
@@ -99,4 +103,9 @@ public class ManagementPlaneSyncRecordDeltaImpl implements Delta {
public String getNewMasterOrNull() {
return masterId;
}
+
+ @Override
+ public String getExpectedMasterToClear() {
+ return expectedOldMaster;
+ }
}
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/32c926b9/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 3a74e9a..a00f197 100644
--- a/core/src/main/java/brooklyn/management/ha/ManagementPlaneSyncRecordPersisterToObjectStore.java
+++ b/core/src/main/java/brooklyn/management/ha/ManagementPlaneSyncRecordPersisterToObjectStore.java
@@ -248,17 +248,26 @@ public class ManagementPlaneSyncRecordPersisterToObjectStore implements Manageme
case NO_CHANGE:
break; // no-op
case SET_MASTER:
- persistMaster(checkNotNull(delta.getNewMasterOrNull()));
+ persistMaster(checkNotNull(delta.getNewMasterOrNull()), null);
break;
case CLEAR_MASTER:
- persistMaster("");
+ persistMaster("", delta.getExpectedMasterToClear());
break; // no-op
default:
throw new IllegalStateException("Unknown state for master-change: "+delta.getMasterChange());
}
}
- private void persistMaster(String nodeId) {
+ private void persistMaster(String nodeId, String optionalExpectedId) {
+ if (optionalExpectedId!=null) {
+ String currentRemoteMaster = masterWriter.get();
+ if (currentRemoteMaster==null) {
+ // nothing at remote is okay
+ } else if (!currentRemoteMaster.trim().equals(optionalExpectedId.trim())) {
+ LOG.warn("Master at server is "+currentRemoteMaster+"; expected "+optionalExpectedId+" in order to set as "+nodeId+", so not applying (yet)");
+ return;
+ }
+ }
masterWriter.put(nodeId);
try {
masterWriter.waitForCurrentWrites(SYNC_WRITE_TIMEOUT);
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/32c926b9/core/src/main/java/brooklyn/management/internal/NonDeploymentManagementContext.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/management/internal/NonDeploymentManagementContext.java b/core/src/main/java/brooklyn/management/internal/NonDeploymentManagementContext.java
index afe42df..46e3f97 100644
--- a/core/src/main/java/brooklyn/management/internal/NonDeploymentManagementContext.java
+++ b/core/src/main/java/brooklyn/management/internal/NonDeploymentManagementContext.java
@@ -20,7 +20,6 @@ package brooklyn.management.internal;
import static com.google.common.base.Preconditions.checkNotNull;
-import java.io.IOException;
import java.net.URI;
import java.net.URL;
import java.util.Collection;
@@ -563,6 +562,18 @@ public class NonDeploymentManagementContext implements ManagementContextInternal
public ManagementPlaneSyncRecord getManagementPlaneSyncState() {
throw new IllegalStateException("Non-deployment context "+NonDeploymentManagementContext.this+" is not valid for this operation.");
}
+ @Override
+ public void changeMode(HighAvailabilityMode startMode) {
+ throw new IllegalStateException("Non-deployment context "+NonDeploymentManagementContext.this+" is not valid for this operation.");
+ }
+ @Override
+ public void setPriority(long priority) {
+ throw new IllegalStateException("Non-deployment context "+NonDeploymentManagementContext.this+" is not valid for this operation.");
+ }
+ @Override
+ public long getPriority() {
+ throw new IllegalStateException("Non-deployment context "+NonDeploymentManagementContext.this+" is not valid for this operation.");
+ }
}
}
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/32c926b9/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 da1fd0a..a0b7feb 100644
--- a/core/src/test/java/brooklyn/management/ha/HighAvailabilityManagerSplitBrainTest.java
+++ b/core/src/test/java/brooklyn/management/ha/HighAvailabilityManagerSplitBrainTest.java
@@ -41,6 +41,7 @@ import brooklyn.entity.rebind.persister.InMemoryObjectStore;
import brooklyn.entity.rebind.persister.ListeningObjectStore;
import brooklyn.entity.rebind.persister.PersistMode;
import brooklyn.entity.rebind.persister.PersistenceObjectStore;
+import brooklyn.internal.BrooklynFeatureEnablement;
import brooklyn.location.Location;
import brooklyn.management.internal.ManagementContextInternal;
import brooklyn.test.Asserts;
@@ -256,8 +257,11 @@ public class HighAvailabilityManagerSplitBrainTest {
ManagementPlaneSyncRecord memento1b = n1.ha.getManagementPlaneSyncState();
log.info(n1+" HA now: "+memento1b);
+ ManagementNodeState expectedStateAfterDemotion = BrooklynFeatureEnablement.isEnabled(BrooklynFeatureEnablement.FEATURE_DEFAULT_STANDBY_IS_HOT_PROPERTY) ?
+ ManagementNodeState.HOT_STANDBY : ManagementNodeState.STANDBY;
+
// n1 comes back and demotes himself
- assertEquals(memento1b.getManagementNodes().get(n1.ownNodeId).getStatus(), ManagementNodeState.HOT_STANDBY);
+ assertEquals(memento1b.getManagementNodes().get(n1.ownNodeId).getStatus(), expectedStateAfterDemotion);
assertEquals(memento1b.getManagementNodes().get(n2.ownNodeId).getStatus(), ManagementNodeState.MASTER);
assertEquals(memento1b.getMasterNodeId(), n2.ownNodeId);
assertEquals(memento1b.getManagementNodes().get(n1.ownNodeId).getRemoteTimestamp(), time2);
@@ -266,7 +270,7 @@ public class HighAvailabilityManagerSplitBrainTest {
// n2 now sees itself as master, with n1 in standby again
ManagementPlaneSyncRecord memento2c = n2.ha.getManagementPlaneSyncState();
log.info(n2+" HA now: "+memento2c);
- assertEquals(memento2c.getManagementNodes().get(n1.ownNodeId).getStatus(), ManagementNodeState.HOT_STANDBY);
+ assertEquals(memento2c.getManagementNodes().get(n1.ownNodeId).getStatus(), expectedStateAfterDemotion);
assertEquals(memento2c.getManagementNodes().get(n2.ownNodeId).getStatus(), ManagementNodeState.MASTER);
assertEquals(memento2c.getMasterNodeId(), n2.ownNodeId);
assertEquals(memento2c.getManagementNodes().get(n1.ownNodeId).getRemoteTimestamp(), time2);
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/32c926b9/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 492f2f1..1a41c19 100644
--- a/core/src/test/java/brooklyn/management/ha/HotStandbyTest.java
+++ b/core/src/test/java/brooklyn/management/ha/HotStandbyTest.java
@@ -114,6 +114,10 @@ public class HotStandbyTest {
public String toString() {
return nodeName+" "+ownNodeId;
}
+
+ public RebindManagerImpl rebinder() {
+ return (RebindManagerImpl)mgmt.getRebindManager();
+ }
}
@BeforeMethod(alwaysRun=true)
@@ -443,6 +447,8 @@ public class HotStandbyTest {
}
@Test(groups="Integration") // because it's slow
+ // Sept 2014 - there is a small leak, of 200 bytes per child created and destroyed;
+ // but this goes away when the app is destroyed; it may be a benign record
public void testHotStandbyDoesNotLeakLotsOfRebindsCreatingAndDestroyingAChildEntity() throws Exception {
log.info("Starting test "+JavaClassNames.niceClassAndMethod());
final int DELTA = 2;
@@ -481,4 +487,76 @@ public class HotStandbyTest {
assertUsedMemoryLessThan("And now all unmanaged", initialUsed + DELTA*1000*1000);
}
+
+ protected void assertHotStandby(HaMgmtNode n1) {
+ assertEquals(n1.ha.getNodeState(), ManagementNodeState.HOT_STANDBY);
+ Assert.assertTrue(n1.rebinder().isReadOnlyRunning());
+ Assert.assertFalse(n1.rebinder().isPersistenceRunning());
+ }
+
+ protected void assertMaster(HaMgmtNode n1) {
+ assertEquals(n1.ha.getNodeState(), ManagementNodeState.MASTER);
+ Assert.assertFalse(n1.rebinder().isReadOnlyRunning());
+ Assert.assertTrue(n1.rebinder().isPersistenceRunning());
+ }
+
+ @Test
+ public void testChangeMode() throws Exception {
+ HaMgmtNode n1 = createMaster(Duration.PRACTICALLY_FOREVER);
+ TestApplication app = createFirstAppAndPersist(n1);
+ HaMgmtNode n2 = createHotStandby(Duration.PRACTICALLY_FOREVER);
+
+ TestEntity child = app.addChild(EntitySpec.create(TestEntity.class).configure(TestEntity.CONF_NAME, "first-child"));
+ Entities.manage(child);
+ TestApplication app2 = TestApplication.Factory.newManagedInstanceForTests(n1.mgmt);
+ app2.setConfig(TestEntity.CONF_NAME, "second-app");
+
+ forcePersistNow(n1);
+ n2.ha.setPriority(1);
+ n1.ha.changeMode(HighAvailabilityMode.HOT_STANDBY);
+
+ // both now hot standby
+ assertHotStandby(n1);
+ assertHotStandby(n2);
+
+ assertEquals(n1.mgmt.getApplications().size(), 2);
+ Application app2RO = n1.mgmt.lookup(app2.getId(), Application.class);
+ Assert.assertNotNull(app2RO);
+ assertEquals(app2RO.getConfig(TestEntity.CONF_NAME), "second-app");
+ try {
+ ((TestApplication)app2RO).setAttribute(TestEntity.SEQUENCE, 4);
+ Assert.fail("Should not have allowed sensor to be set");
+ } catch (Exception e) {
+ Assert.assertTrue(e.toString().toLowerCase().contains("read-only"), "Error message did not contain expected text: "+e);
+ }
+
+ n1.ha.changeMode(HighAvailabilityMode.AUTO);
+ n2.ha.changeMode(HighAvailabilityMode.HOT_STANDBY, true, false);
+ // both still hot standby (n1 will defer to n2 as it has higher priority)
+ assertHotStandby(n1);
+ assertHotStandby(n2);
+
+ // with priority 1, n2 will now be elected
+ n2.ha.changeMode(HighAvailabilityMode.AUTO);
+ assertHotStandby(n1);
+ assertMaster(n2);
+
+ assertEquals(n2.mgmt.getApplications().size(), 2);
+ Application app2B = n2.mgmt.lookup(app2.getId(), Application.class);
+ Assert.assertNotNull(app2B);
+ assertEquals(app2B.getConfig(TestEntity.CONF_NAME), "second-app");
+ ((TestApplication)app2B).setAttribute(TestEntity.SEQUENCE, 4);
+
+ forcePersistNow(n2);
+ forceRebindNow(n1);
+ Application app2BRO = n1.mgmt.lookup(app2.getId(), Application.class);
+ Assert.assertNotNull(app2BRO);
+ EntityTestUtils.assertAttributeEquals(app2BRO, TestEntity.SEQUENCE, 4);
+ }
+
+ @Test(groups="Integration", invocationCount=20)
+ public void testChangeModeManyTimes() throws Exception {
+ testChangeMode();
+ }
+
}
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/32c926b9/core/src/test/java/brooklyn/management/ha/MasterChooserTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/brooklyn/management/ha/MasterChooserTest.java b/core/src/test/java/brooklyn/management/ha/MasterChooserTest.java
index 6f8ba48..4373ea0 100644
--- a/core/src/test/java/brooklyn/management/ha/MasterChooserTest.java
+++ b/core/src/test/java/brooklyn/management/ha/MasterChooserTest.java
@@ -106,11 +106,39 @@ public class MasterChooserTest {
memento.addNode(newManagerMemento("node5", ManagementNodeState.MASTER, now));
if (includeHot)
memento.addNode(newManagerMemento("node6", ManagementNodeState.HOT_STANDBY, now));
- Duration heartbeatTimeout = Duration.THIRTY_SECONDS;
- return getIds(chooser.sort(chooser.filterHealthy(memento, heartbeatTimeout, now)));
+ return getIds(chooser.sort(chooser.filterHealthy(memento, Duration.THIRTY_SECONDS, now)));
}
-
+
+ @Test
+ public void testExplicityPriority() throws Exception {
+ chooser = new AlphabeticMasterChooser();
+ memento.addNode(newManagerMemento("node1", ManagementNodeState.STANDBY, now, BrooklynVersion.get(), 2L));
+ memento.addNode(newManagerMemento("node2", ManagementNodeState.STANDBY, now, BrooklynVersion.get(), -1L));
+ memento.addNode(newManagerMemento("node3", ManagementNodeState.STANDBY, now, BrooklynVersion.get(), null));
+ List<String> order = getIds(chooser.sort(chooser.filterHealthy(memento, Duration.THIRTY_SECONDS, now)));
+ assertEquals(order, ImmutableList.of("node1", "node3", "node2"));
+ }
+
+ @Test
+ public void testVersionsMaybeNull() throws Exception {
+ chooser = new AlphabeticMasterChooser();
+ memento.addNode(newManagerMemento("node1", ManagementNodeState.STANDBY, now, "v10", null));
+ memento.addNode(newManagerMemento("node2", ManagementNodeState.STANDBY, now, "v3-snapshot", null));
+ memento.addNode(newManagerMemento("node3", ManagementNodeState.STANDBY, now, "v3-snapshot", -1L));
+ memento.addNode(newManagerMemento("node4", ManagementNodeState.STANDBY, now, "v3-snapshot", null));
+ memento.addNode(newManagerMemento("node5", ManagementNodeState.STANDBY, now, "v3-stable", null));
+ memento.addNode(newManagerMemento("node6", ManagementNodeState.STANDBY, now, "v1", null));
+ memento.addNode(newManagerMemento("node7", ManagementNodeState.STANDBY, now, null, null));
+ List<String> order = getIds(chooser.sort(chooser.filterHealthy(memento, Duration.THIRTY_SECONDS, now)));
+ assertEquals(order, ImmutableList.of("node1", "node5", "node6", "node2", "node4", "node7", "node3"));
+ }
+
private ManagementNodeSyncRecord newManagerMemento(String nodeId, ManagementNodeState status, long timestamp) {
- return BasicManagementNodeSyncRecord.builder().brooklynVersion(BrooklynVersion.get()).nodeId(nodeId).status(status).localTimestamp(timestamp).remoteTimestamp(timestamp).build();
+ return newManagerMemento(nodeId, status, timestamp, BrooklynVersion.get(), null);
+ }
+ private ManagementNodeSyncRecord newManagerMemento(String nodeId, ManagementNodeState status, long timestamp,
+ String version, Long priority) {
+ return BasicManagementNodeSyncRecord.builder().brooklynVersion(version).nodeId(nodeId).status(status).localTimestamp(timestamp).remoteTimestamp(timestamp).
+ priority(priority).build();
}
}
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/32c926b9/usage/launcher/src/test/java/brooklyn/launcher/BrooklynLauncherRebindToCloudObjectStoreTest.java
----------------------------------------------------------------------
diff --git a/usage/launcher/src/test/java/brooklyn/launcher/BrooklynLauncherRebindToCloudObjectStoreTest.java b/usage/launcher/src/test/java/brooklyn/launcher/BrooklynLauncherRebindToCloudObjectStoreTest.java
index 6d9a71a..e4462e3 100644
--- a/usage/launcher/src/test/java/brooklyn/launcher/BrooklynLauncherRebindToCloudObjectStoreTest.java
+++ b/usage/launcher/src/test/java/brooklyn/launcher/BrooklynLauncherRebindToCloudObjectStoreTest.java
@@ -20,13 +20,9 @@ package brooklyn.launcher;
import static org.testng.Assert.assertEquals;
-import java.io.File;
-
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;
-import com.google.common.io.Files;
-
import brooklyn.config.BrooklynProperties;
import brooklyn.config.BrooklynServerConfig;
import brooklyn.entity.proxying.EntitySpec;
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/32c926b9/utils/common/src/main/java/brooklyn/util/text/NaturalOrderComparator.java
----------------------------------------------------------------------
diff --git a/utils/common/src/main/java/brooklyn/util/text/NaturalOrderComparator.java b/utils/common/src/main/java/brooklyn/util/text/NaturalOrderComparator.java
index 5826a73..34d96e3 100644
--- a/utils/common/src/main/java/brooklyn/util/text/NaturalOrderComparator.java
+++ b/utils/common/src/main/java/brooklyn/util/text/NaturalOrderComparator.java
@@ -54,7 +54,8 @@ import java.util.Comparator;
* e.g. "10">"9", including when those numbers occur in the midst of equal text; e.g. "a10" > "a9";
* but not if the text differs; e.g. "a10" < "b9"
* <p>
- * class is thread-safe
+ * class is thread-safe. nulls not supported. (to support nulls, wrap in guava:
+ * <code>Ordering.from(NaturalOrderComparator.INSTANCE).nullsFirst()</code>)
*/
public class NaturalOrderComparator implements Comparator<String> {