Return-Path: X-Original-To: apmail-brooklyn-commits-archive@minotaur.apache.org Delivered-To: apmail-brooklyn-commits-archive@minotaur.apache.org Received: from mail.apache.org (hermes.apache.org [140.211.11.3]) by minotaur.apache.org (Postfix) with SMTP id 62D3F17844 for ; Wed, 1 Oct 2014 18:03:27 +0000 (UTC) Received: (qmail 95209 invoked by uid 500); 1 Oct 2014 18:03:27 -0000 Delivered-To: apmail-brooklyn-commits-archive@brooklyn.apache.org Received: (qmail 95183 invoked by uid 500); 1 Oct 2014 18:03:27 -0000 Mailing-List: contact commits-help@brooklyn.incubator.apache.org; run by ezmlm Precedence: bulk List-Help: List-Unsubscribe: List-Post: List-Id: Reply-To: dev@brooklyn.incubator.apache.org Delivered-To: mailing list commits@brooklyn.incubator.apache.org Received: (qmail 95174 invoked by uid 99); 1 Oct 2014 18:03:27 -0000 Received: from athena.apache.org (HELO athena.apache.org) (140.211.11.136) by apache.org (qpsmtpd/0.29) with ESMTP; Wed, 01 Oct 2014 18:03:27 +0000 X-ASF-Spam-Status: No, hits=-2000.6 required=5.0 tests=ALL_TRUSTED,RP_MATCHES_RCVD X-Spam-Check-By: apache.org Received: from [140.211.11.3] (HELO mail.apache.org) (140.211.11.3) by apache.org (qpsmtpd/0.29) with SMTP; Wed, 01 Oct 2014 18:03:22 +0000 Received: (qmail 89754 invoked by uid 99); 1 Oct 2014 18:03:02 -0000 Received: from tyr.zones.apache.org (HELO tyr.zones.apache.org) (140.211.11.114) by apache.org (qpsmtpd/0.29) with ESMTP; Wed, 01 Oct 2014 18:03:02 +0000 Received: by tyr.zones.apache.org (Postfix, from userid 65534) id CC8CE9257E4; Wed, 1 Oct 2014 18:03:01 +0000 (UTC) Content-Type: text/plain; charset="us-ascii" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit From: heneveld@apache.org To: commits@brooklyn.incubator.apache.org Date: Wed, 01 Oct 2014 18:03:05 -0000 Message-Id: In-Reply-To: References: X-Mailer: ASF-Git Admin Mailer Subject: [05/15] git commit: hot standby / read-only rebind is working. more to do, but this is the guts of it. X-Virus-Checked: Checked by ClamAV on 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 Authored: Mon Sep 29 16:07:08 2014 +0100 Committer: Alex Heneveld 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 { * 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 extends AbstractEnricher implements SensorEventLis if (transformationFromEvent != null) { transformation = transformationFromEvent; } else { + // TODO new named class transformation = new Function, U>() { @Override public U apply(SensorEvent 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. *

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 getAllAttributes(); + Map 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 getChildren(); + Collection getPolicies(); + Collection getEnrichers(); + Collection getGroups(); + Collection getLocations(); + T getAttribute(AttributeSensor sensor); + T getConfig(ConfigKey key); + T getConfig(HasConfigKey key); + Maybe getConfigRaw(ConfigKey key, boolean includeInherited); + Maybe getConfigRaw(HasConfigKey key, boolean includeInherited); + @Deprecated Set getTags(); + @Deprecated boolean containsTag(@Nonnull Object tag); + + + // from entity local + + void setDisplayName(String displayName); + @Deprecated T getConfig(ConfigKey key, T defaultValue); + @Deprecated T getConfig(HasConfigKey key, T defaultValue); + + // from EntityInternal: + + EntityConfigMap getConfigMap(); + Map,Object> getAllConfig(); + // for rebind mainly + ConfigBag getAllConfigBag(); + ConfigBag getLocalConfigBag(); + Map getAllAttributes(); + EntityManagementSupport getManagementSupport(); + ManagementContext getManagementContext(); + Effector getEffector(String effectorName); + FeedSupport getFeedSupport(); + RebindSupport 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 void updateMapSensorEntry(EntityLocal entity, AttributeSensor> sensor, TKey key, TVal v) { Map 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 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 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 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 extras = new LinkedHashSet(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 createEntityProxy(EntitySpec spec, T entity) { Set> interfaces = Sets.newLinkedHashSet(); if (spec.getType().isInterface()) { @@ -96,9 +99,10 @@ public class InternalEntityFactory extends InternalFactory { } @SuppressWarnings("unchecked") - public T createEntityProxy(Iterable> 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 createEntityProxy(Iterable> 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> allInterfaces = MutableSet.>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 createEntity(EntitySpec 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. + *

+ * If {@link EntitySpec#id(String)} was set then uses that to override the entity's id, * but that behaviour is deprecated. + *

+ * The new-style no-arg constructor is preferred, and + * configuration from the {@link EntitySpec} is not normally applied, + * although for old-style entities flags from the spec are passed to the constructor. */ public T constructEntity(Class clazz, EntitySpec 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. + *

+ * 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 constructEntity(Class clazz, Iterable> 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 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> 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 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 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 implements SensorEvent { + + private static final Logger log = LoggerFactory.getLogger(BasicSensorEvent.class); + private final Sensor sensor; private final Entity source; private final T value; @@ -77,6 +85,16 @@ public class BasicSensorEvent implements SensorEvent { @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 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 entitiesById = Maps.newLinkedHashMap(); - + + protected final Set 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 allEntities = Lists.newArrayList(); - final ManagementTransitionInfo info = new ManagementTransitionInfo(managementContext, ManagementTransitionMode.NORMAL); - recursively(e, new Predicate() { 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 allEntities = Lists.newArrayList(); + final ManagementTransitionInfo info = new ManagementTransitionInfo(managementContext, ManagementTransitionMode.NORMAL); + recursively(e, new Predicate() { 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 groups = e.getGroups(); - e.clearParent(); - for (Group group : groups) { - group.removeMember(e); - } - if (e instanceof Group) { - Collection members = ((Group)e).getMembers(); - for (Entity member : members) { - member.removeGroup((Group)e); + if (!isReadOnly(e)) { + Collection groups = e.getGroups(); + e.clearParent(); + for (Group group : groups) { + group.removeMember(e); } + if (e instanceof Group) { + Collection 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 nodes = new MutableList(); + Map sharedBackingStore = MutableMap.of(); + Map 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.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() { @Override public void onEvent(SensorEvent 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 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 members = Iterables.filter(cluster.getChildren(), StubAppServer.class);