Repository: brooklyn-server Updated Branches: refs/heads/master 6dafb3fae -> e601350d7
BROOKLYN-600: cleanup entities on deploy-failure Project: http://git-wip-us.apache.org/repos/asf/brooklyn-server/repo Commit: http://git-wip-us.apache.org/repos/asf/brooklyn-server/commit/a87e6692 Tree: http://git-wip-us.apache.org/repos/asf/brooklyn-server/tree/a87e6692 Diff: http://git-wip-us.apache.org/repos/asf/brooklyn-server/diff/a87e6692 Branch: refs/heads/master Commit: a87e6692a54a10d389165fd82c93b3fd2f4ccc03 Parents: 6dafb3f Author: Aled Sage <aled.s...@gmail.com> Authored: Thu Sep 6 12:38:05 2018 +0100 Committer: Aled Sage <aled.s...@gmail.com> Committed: Thu Sep 6 12:38:05 2018 +0100 ---------------------------------------------------------------------- .../mgmt/internal/EntityManagerInternal.java | 8 ++ .../core/mgmt/internal/LocalEntityManager.java | 43 +++++++++ .../internal/NonDeploymentEntityManager.java | 6 +- .../core/objs/proxy/InternalEntityFactory.java | 15 +++- .../core/entity/proxying/EntityManagerTest.java | 27 ++++++ .../brooklyn/core/mgmt/DeployFailureTest.java | 95 +++++++++++++++++++- 6 files changed, 191 insertions(+), 3 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/a87e6692/core/src/main/java/org/apache/brooklyn/core/mgmt/internal/EntityManagerInternal.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/org/apache/brooklyn/core/mgmt/internal/EntityManagerInternal.java b/core/src/main/java/org/apache/brooklyn/core/mgmt/internal/EntityManagerInternal.java index 9d0ab49..4e6adb9 100644 --- a/core/src/main/java/org/apache/brooklyn/core/mgmt/internal/EntityManagerInternal.java +++ b/core/src/main/java/org/apache/brooklyn/core/mgmt/internal/EntityManagerInternal.java @@ -39,4 +39,12 @@ public interface EntityManagerInternal extends EntityManager, BrooklynObjectMana */ @Beta <T extends Entity> T createEntity(EntitySpec<T> spec, Optional<String> entityId); + + /** + * Similar to {@link #unmanage(Entity)}, but used to discard partially constructed entities. + * + * @throws IllegalStateException if the entity, or any of its descendents are already managed. + */ + @Beta + void discardPremanaged(Entity e); } http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/a87e6692/core/src/main/java/org/apache/brooklyn/core/mgmt/internal/LocalEntityManager.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/org/apache/brooklyn/core/mgmt/internal/LocalEntityManager.java b/core/src/main/java/org/apache/brooklyn/core/mgmt/internal/LocalEntityManager.java index f09ddab..35ab40b 100644 --- a/core/src/main/java/org/apache/brooklyn/core/mgmt/internal/LocalEntityManager.java +++ b/core/src/main/java/org/apache/brooklyn/core/mgmt/internal/LocalEntityManager.java @@ -23,9 +23,11 @@ import static com.google.common.base.Preconditions.checkNotNull; import java.lang.reflect.Proxy; import java.util.Collection; import java.util.Collections; +import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Set; +import java.util.Stack; import java.util.WeakHashMap; import java.util.concurrent.ConcurrentMap; import java.util.regex.Pattern; @@ -460,7 +462,48 @@ public class LocalEntityManager implements EntityManagerInternal { public void unmanage(final Entity e, final ManagementTransitionMode mode) { unmanage(e, mode, false); } + + @Override + public void discardPremanaged(final Entity e) { + if (e == null) return; + if (!isRunning()) return; + + Set<String> todiscard = new LinkedHashSet<>(); + Stack<Entity> tovisit = new Stack<>(); + Set<Entity> visited = new LinkedHashSet<>(); + + tovisit.push(e); + + while (!tovisit.isEmpty()) { + Entity next = tovisit.pop(); + visited.add(next); + for (Entity child : next.getChildren()) { + if (!visited.contains(child)) { + tovisit.push(child); + } + } + + if (isManaged(next)) { + throw new IllegalStateException("Cannot discard entity "+e+" because it or a descendent is already managed ("+next+")"); + } + Entity realNext = deproxyIfNecessary(next); + String id = next.getId(); + Entity realFound = preRegisteredEntitiesById.get(id); + if (realFound == null) preManagedEntitiesById.get(id); + + if (realFound != null && realFound != realNext) { + throw new IllegalStateException("Cannot discard pre-managed entity "+e+" because it or a descendent's id ("+id+") clashes with a different entity (given "+next+" but found "+realFound+")"); + } + todiscard.add(id); + } + + for (String id : todiscard) { + preRegisteredEntitiesById.remove(id); + preManagedEntitiesById.remove(id); + } + } + private void unmanage(final Entity e, ManagementTransitionMode mode, boolean hasBeenReplaced) { if (shouldSkipUnmanagement(e)) return; final ManagementTransitionInfo info = new ManagementTransitionInfo(managementContext, mode); http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/a87e6692/core/src/main/java/org/apache/brooklyn/core/mgmt/internal/NonDeploymentEntityManager.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/org/apache/brooklyn/core/mgmt/internal/NonDeploymentEntityManager.java b/core/src/main/java/org/apache/brooklyn/core/mgmt/internal/NonDeploymentEntityManager.java index 8289ab7..9a739ea 100644 --- a/core/src/main/java/org/apache/brooklyn/core/mgmt/internal/NonDeploymentEntityManager.java +++ b/core/src/main/java/org/apache/brooklyn/core/mgmt/internal/NonDeploymentEntityManager.java @@ -190,6 +190,11 @@ public class NonDeploymentEntityManager implements EntityManagerInternal { throw new IllegalStateException("Non-deployment context "+this+" is not valid for this operation: cannot unmanage "+e); } + @Override + public void discardPremanaged(Entity e) { + throw new IllegalStateException("Non-deployment context "+this+" is not valid for this operation: cannot discardPremanaged "+e); + } + private boolean isInitialManagementContextReal() { return (initialManagementContext != null && !(initialManagementContext instanceof NonDeploymentManagementContext)); } @@ -202,5 +207,4 @@ public class NonDeploymentEntityManager implements EntityManagerInternal { throw new IllegalStateException("Non-deployment context "+this+" (with no initial management context supplied) is not valid for this operation."); } } - } http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/a87e6692/core/src/main/java/org/apache/brooklyn/core/objs/proxy/InternalEntityFactory.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/org/apache/brooklyn/core/objs/proxy/InternalEntityFactory.java b/core/src/main/java/org/apache/brooklyn/core/objs/proxy/InternalEntityFactory.java index 9953e8c..28208cc 100644 --- a/core/src/main/java/org/apache/brooklyn/core/objs/proxy/InternalEntityFactory.java +++ b/core/src/main/java/org/apache/brooklyn/core/objs/proxy/InternalEntityFactory.java @@ -48,6 +48,7 @@ import org.apache.brooklyn.core.entity.EntityInternal; import org.apache.brooklyn.core.mgmt.BrooklynTags; import org.apache.brooklyn.core.mgmt.BrooklynTags.NamedStringTag; import org.apache.brooklyn.core.mgmt.BrooklynTaskTags; +import org.apache.brooklyn.core.mgmt.internal.EntityManagerInternal; import org.apache.brooklyn.core.mgmt.internal.ManagementContextInternal; import org.apache.brooklyn.util.collections.MutableList; import org.apache.brooklyn.util.collections.MutableMap; @@ -163,7 +164,19 @@ public class InternalEntityFactory extends InternalFactory { Map<String,EntitySpec<?>> specsByEntityId = MutableMap.of(); T entity = createEntityAndDescendantsUninitialized(spec, entityId, entitiesByEntityId, specsByEntityId); - initEntityAndDescendants(entity.getId(), entitiesByEntityId, specsByEntityId); + try { + initEntityAndDescendants(entity.getId(), entitiesByEntityId, specsByEntityId); + } catch (RuntimeException e) { + Exceptions.propagateIfFatal(e); + log.info("Failed to initialise entity "+entity+" and its descendants - unmanaging and propagating original exception: "+Exceptions.collapseText(e)); + try { + ((EntityManagerInternal)managementContext.getEntityManager()).discardPremanaged(entity); + } catch (Exception e2) { + Exceptions.propagateIfFatal(e2); + log.info("Failed to unmanage entity "+entity+" and its descendants, after failure to initialise (rethrowing original exception)", e2); + } + throw e; + } return entity; } http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/a87e6692/core/src/test/java/org/apache/brooklyn/core/entity/proxying/EntityManagerTest.java ---------------------------------------------------------------------- diff --git a/core/src/test/java/org/apache/brooklyn/core/entity/proxying/EntityManagerTest.java b/core/src/test/java/org/apache/brooklyn/core/entity/proxying/EntityManagerTest.java index 7d21071..125bb29 100644 --- a/core/src/test/java/org/apache/brooklyn/core/entity/proxying/EntityManagerTest.java +++ b/core/src/test/java/org/apache/brooklyn/core/entity/proxying/EntityManagerTest.java @@ -45,6 +45,7 @@ import org.apache.brooklyn.core.test.entity.TestEntity; import org.apache.brooklyn.core.test.entity.TestEntityImpl; import org.apache.brooklyn.test.Asserts; import org.apache.brooklyn.util.collections.MutableMap; +import org.apache.brooklyn.util.text.Identifiers; import org.apache.brooklyn.util.time.Duration; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -137,6 +138,32 @@ public class EntityManagerTest extends BrooklynAppUnitTestSupport { assertSame(Entities.deproxy(postApp), origDeproxiedApp); } + @Test + public void testDiscardPremanaged() { + String id = Identifiers.makeRandomId(12); + TestEntityImpl entity = mgmt.getEntityFactory().constructEntity(TestEntityImpl.class, ImmutableList.of(TestEntity.class), id); + assertTrue(((LocalEntityManager)entityManager).isKnownEntityId(id)); + assertFalse(Entities.isManaged(entity)); + + ((EntityManagerInternal)entityManager).discardPremanaged(entity); + assertFalse(((LocalEntityManager)entityManager).isKnownEntityId(id)); + assertFalse(Entities.isManaged(entity)); + } + + + @Test + public void testDiscardPremanagedFailsIfManaged() { + try { + ((EntityManagerInternal)entityManager).discardPremanaged(app); + Asserts.shouldHaveFailedPreviously(); + } catch (IllegalStateException e) { + Asserts.expectedFailureContains(e, "Cannot discard", "it or a descendent is already managed"); + } + + // Should have had no effect + Entities.isManaged(app); + } + // See https://issues.apache.org/jira/browse/BROOKLYN-352 // Before the fix, 250ms was sufficient to cause the ConcurrentModificationException @Test http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/a87e6692/core/src/test/java/org/apache/brooklyn/core/mgmt/DeployFailureTest.java ---------------------------------------------------------------------- diff --git a/core/src/test/java/org/apache/brooklyn/core/mgmt/DeployFailureTest.java b/core/src/test/java/org/apache/brooklyn/core/mgmt/DeployFailureTest.java index f5a1de5..b9b5bb0 100644 --- a/core/src/test/java/org/apache/brooklyn/core/mgmt/DeployFailureTest.java +++ b/core/src/test/java/org/apache/brooklyn/core/mgmt/DeployFailureTest.java @@ -18,28 +18,85 @@ */ package org.apache.brooklyn.core.mgmt; +import static org.testng.Assert.assertFalse; + +import java.util.Collections; +import java.util.LinkedHashSet; +import java.util.Set; + import org.apache.brooklyn.api.entity.Entity; import org.apache.brooklyn.api.entity.EntitySpec; +import org.apache.brooklyn.api.entity.ImplementedBy; +import org.apache.brooklyn.config.ConfigKey; +import org.apache.brooklyn.core.config.ConfigKeys; +import org.apache.brooklyn.core.config.ConstraintViolationException; import org.apache.brooklyn.core.mgmt.internal.LocalEntityManager; import org.apache.brooklyn.core.test.BrooklynAppUnitTestSupport; import org.apache.brooklyn.core.test.entity.TestEntity; import org.apache.brooklyn.core.test.entity.TestEntityImpl; import org.apache.brooklyn.entity.stock.BasicApplication; import org.apache.brooklyn.test.Asserts; +import org.testng.annotations.AfterMethod; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; +import com.google.common.base.Predicates; + public class DeployFailureTest extends BrooklynAppUnitTestSupport { + private static final Set<Entity> constructedEntities = Collections.synchronizedSet(new LinkedHashSet<>()); + private LocalEntityManager entityManager; @BeforeMethod(alwaysRun=true) @Override public void setUp() throws Exception { + constructedEntities.clear(); super.setUp(); entityManager = (LocalEntityManager) mgmt.getEntityManager(); } + @AfterMethod(alwaysRun=true) + @Override + public void tearDown() throws Exception { + try { + super.tearDown(); + } finally { + constructedEntities.clear(); + } + } + + @Test + public void testConfigConstraintViolation() throws Exception { + try { + mgmt.getEntityManager().createEntity(EntitySpec.create(BasicApplication.class) + .child(EntitySpec.create(TestEntityWithConfigConstraint.class))); + Asserts.shouldHaveFailedPreviously(); + } catch (ConstraintViolationException e) { + // Check gives nice exception + Asserts.expectedFailureContains(e, "myNonNullKey", "Predicates.notNull()"); + } + + // Should have disposed of entities that failed to be created + assertEntitiesNotKnown(constructedEntities); + } + + @Test + public void testFailedInit() throws Exception { + try { + mgmt.getEntityManager().createEntity(EntitySpec.create(BasicApplication.class) + .child(EntitySpec.create(TestEntity.class) + .impl(TestEntityFailingInitImpl.class))); + Asserts.shouldHaveFailedPreviously(); + } catch (Exception e) { + // Check gives nice exception + Asserts.expectedFailureContains(e, "Simulating failure in entity init"); + } + + // Should have disposed of entities that failed to be created + assertEntitiesNotKnown(constructedEntities); + } + @Test public void testFailedGetParent() throws Exception { entityManager.getAllEntitiesInApplication(app); @@ -50,15 +107,51 @@ public class DeployFailureTest extends BrooklynAppUnitTestSupport { .impl(TestEntityFailingGetParentImpl.class))); Asserts.shouldHaveFailedPreviously(); } catch (ClassCastException e) { + // TODO Should give nicer exception Asserts.expectedFailureContains(e, "cannot be cast", "WrongParentEntity"); } // This should continue to work, after the failed entity-deploy above // See https://issues.apache.org/jira/browse/BROOKLYN-599 entityManager.getAllEntitiesInApplication(app); + + // Should have disposed of entities that failed to be created + assertEntitiesNotKnown(constructedEntities); } - public static class TestEntityFailingGetParentImpl extends TestEntityImpl { + private void assertEntitiesNotKnown(Iterable<Entity> entities) { + for (Entity entity : constructedEntities) { + if (entity.getId() != null) { + assertFalse(entityManager.isKnownEntityId(entity.getId()), "entity="+entity); + } + } + } + + public static class TestEntityRecordingConstructionImpl extends TestEntityImpl { + public TestEntityRecordingConstructionImpl() { + constructedEntities.add(this); + } + } + + @ImplementedBy(TestEntityWithConfigConstraintImpl.class) + public static interface TestEntityWithConfigConstraint extends TestEntity { + ConfigKey<String> NON_NULL_KEY = ConfigKeys.builder(String.class) + .name("myNonNullKey") + .constraint(Predicates.notNull()) + .build(); + } + + public static class TestEntityWithConfigConstraintImpl extends TestEntityRecordingConstructionImpl implements TestEntityWithConfigConstraint { + } + + public static class TestEntityFailingInitImpl extends TestEntityRecordingConstructionImpl { + @Override + public void init() { + throw new RuntimeException("Simulating failure in entity init()"); + } + } + + public static class TestEntityFailingGetParentImpl extends TestEntityRecordingConstructionImpl { private static interface WrongParentEntity extends Entity {} @Override