Merge branch 'master' into object-store-persistence Conflicts: core/src/test/java/brooklyn/management/ha/HighAvailabilityManagerTestFixture.java -- new test added to master, integrated with method rename here and tested with refactoring
Project: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/repo Commit: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/commit/e6e59f7e Tree: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/tree/e6e59f7e Diff: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/diff/e6e59f7e Branch: refs/heads/master Commit: e6e59f7e911ef6f65062677b48e9a259f1deefb3 Parents: de3fb4b ab3e297 Author: Alex Heneveld <alex.henev...@cloudsoftcorp.com> Authored: Mon Jun 9 03:05:51 2014 +0100 Committer: Alex Heneveld <alex.henev...@cloudsoftcorp.com> Committed: Mon Jun 9 03:34:25 2014 +0100 ---------------------------------------------------------------------- .../catalog/internal/BasicBrooklynCatalog.java | 22 +- .../catalog/internal/CatalogClasspathDo.java | 2 +- .../brooklyn/catalog/internal/CatalogDo.java | 18 ++ .../brooklyn/config/render/RendererHints.java | 1 - .../brooklyn/entity/basic/AbstractEntity.java | 5 + .../brooklyn/entity/basic/EntityInternal.java | 6 + .../brooklyn/entity/group/DynamicCluster.java | 2 +- .../entity/group/DynamicClusterImpl.java | 46 +++- .../brooklyn/entity/group/QuarantineGroup.java | 14 + .../entity/group/QuarantineGroupImpl.java | 70 +++++ .../dto/BasicManagementNodeSyncRecord.java | 2 +- .../location/access/BrooklynAccessUtils.java | 4 +- .../location/access/PortForwardManager.java | 266 +++++++------------ .../access/PortForwardManagerAuthority.java | 233 ++++++++++++++++ .../access/PortForwardManagerClient.java | 139 ++++++++++ .../brooklyn/location/access/PortMapping.java | 16 ++ .../ha/HighAvailabilityManagerImpl.java | 32 ++- .../util/xstream/StringKeyMapConverter.java | 6 +- .../entity/group/DynamicClusterTest.java | 13 + .../entity/group/QuarantineGroupTest.java | 74 ++++++ .../entity/rebind/RebindCatalogEntityTest.java | 6 + .../access/PortForwardManagerRebindTest.java | 95 +++++++ .../ha/HighAvailabilityManagerTestFixture.java | 26 +- .../util/xstream/StringKeyMapConverterTest.java | 7 + docs/start/walkthrough/index.md | 4 +- docs/use/guide/quickstart/brooklyn.properties | 34 +++ ...ailabilityManagerJcloudsObjectStoreTest.java | 7 +- .../java/JavaSoftwareProcessSshDriver.java | 3 +- .../java/brooklyn/entity/java/JmxSupport.java | 7 +- .../entity/proxy/nginx/NginxSshDriver.java | 3 + .../ControlledDynamicWebAppClusterImpl.java | 75 ++++-- .../entity/webapp/tomcat/Tomcat7SshDriver.java | 12 +- .../java/brooklyn/util/text/StringEscapes.java | 4 - .../brooklyn/util/text/StringEscapesTest.java | 7 +- 34 files changed, 1030 insertions(+), 231 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/e6e59f7e/core/src/main/java/brooklyn/management/ha/HighAvailabilityManagerImpl.java ---------------------------------------------------------------------- diff --cc core/src/main/java/brooklyn/management/ha/HighAvailabilityManagerImpl.java index 5762268,891cb1a..ee6427d --- a/core/src/main/java/brooklyn/management/ha/HighAvailabilityManagerImpl.java +++ b/core/src/main/java/brooklyn/management/ha/HighAvailabilityManagerImpl.java @@@ -491,6 -485,24 +497,24 @@@ public class HighAvailabilityManagerImp * specific timing scenarios. */ protected long currentTimeMillis() { - return ticker.read(); + return tickerUtc.read(); } + + /** + * Infers the health of a node - if it last reported itself as healthy (standby or master), but we haven't heard + * from it in a long time then report that node as failed; otherwise report its health as-is. + */ + private class DetectNodesGoneAwol implements Function<ManagementNodeSyncRecord, ManagementNodeSyncRecord> { + @Nullable + @Override + public ManagementNodeSyncRecord apply(@Nullable ManagementNodeSyncRecord input) { + if (input == null) return null; + if (!(input.getStatus() == ManagementNodeState.STANDBY || input.getStatus() == ManagementNodeState.MASTER)) return input; + if (isHeartbeatOk(input, currentTimeMillis())) return input; + return BasicManagementNodeSyncRecord.builder() + .from(input) + .status(ManagementNodeState.FAILED) + .build(); + } + } } http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/e6e59f7e/core/src/test/java/brooklyn/management/ha/HighAvailabilityManagerTestFixture.java ---------------------------------------------------------------------- diff --cc core/src/test/java/brooklyn/management/ha/HighAvailabilityManagerTestFixture.java index 6b0450d,0000000..9f7601a mode 100644,000000..100644 --- a/core/src/test/java/brooklyn/management/ha/HighAvailabilityManagerTestFixture.java +++ b/core/src/test/java/brooklyn/management/ha/HighAvailabilityManagerTestFixture.java @@@ -1,190 -1,0 +1,214 @@@ +package brooklyn.management.ha; + +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertFalse; ++import static org.testng.Assert.assertNotEquals; +import static org.testng.Assert.assertTrue; + +import java.util.List; +import java.util.concurrent.atomic.AtomicLong; + +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.BrooklynVersion; +import brooklyn.entity.basic.Entities; +import brooklyn.entity.rebind.persister.BrooklynMementoPersisterToObjectStore; +import brooklyn.entity.rebind.persister.PersistMode; +import brooklyn.entity.rebind.persister.PersistenceObjectStore; +import brooklyn.entity.rebind.plane.dto.BasicManagementNodeSyncRecord; +import brooklyn.management.ha.HighAvailabilityManagerImpl.PromotionListener; +import brooklyn.management.internal.ManagementContextInternal; +import brooklyn.test.Asserts; +import brooklyn.test.entity.LocalManagementContextForTests; +import brooklyn.util.time.Duration; + +import com.google.common.base.Ticker; +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Lists; + +@Test +public abstract class HighAvailabilityManagerTestFixture { + + @SuppressWarnings("unused") + private static final Logger log = LoggerFactory.getLogger(HighAvailabilityManagerTestFixture.class); + + private ManagementPlaneSyncRecordPersister persister; + private ManagementContextInternal managementContext; + private String ownNodeId; + private HighAvailabilityManagerImpl manager; + private Ticker ticker; + private AtomicLong currentTime; // used to set the ticker's return value + private RecordingPromotionListener promotionListener; + private ClassLoader classLoader = getClass().getClassLoader(); + private PersistenceObjectStore objectStore; + + @BeforeMethod(alwaysRun=true) + public void setUp() throws Exception { + currentTime = new AtomicLong(System.currentTimeMillis()); + ticker = new Ticker() { + // strictly not a ticker because returns millis UTC, but it works fine even so + @Override public long read() { + return currentTime.get(); + } + }; + promotionListener = new RecordingPromotionListener(); + managementContext = newLocalManagementContext(); + ownNodeId = managementContext.getManagementNodeId(); + objectStore = newPersistenceObjectStore(); + objectStore.prepareForUse(managementContext, PersistMode.CLEAN); + persister = new ManagementPlaneSyncRecordPersisterToObjectStore(managementContext, objectStore, classLoader); + BrooklynMementoPersisterToObjectStore persisterObj = new BrooklynMementoPersisterToObjectStore(objectStore, classLoader); + managementContext.getRebindManager().setPersister(persisterObj); + manager = new HighAvailabilityManagerImpl(managementContext) + .setPollPeriod(Duration.millis(10)) + .setHeartbeatTimeout(Duration.THIRTY_SECONDS) + .setPromotionListener(promotionListener) + .setTicker(ticker) + .setPersister(persister); + } + + protected ManagementContextInternal newLocalManagementContext() { + return new LocalManagementContextForTests(); + } + + protected abstract PersistenceObjectStore newPersistenceObjectStore(); + + @AfterMethod(alwaysRun=true) + public void tearDown() throws Exception { + if (manager != null) manager.stop(); + if (managementContext != null) Entities.destroyAll(managementContext); + if (objectStore != null) objectStore.deleteCompletely(); + } + + // Can get a log.error about our management node's heartbeat being out of date. Caused by + // poller first writing a heartbeat record, and then the clock being incremented. But the + // next poll fixes it. + public void testPromotes() throws Exception { + persister.delta(ManagementPlaneSyncRecordDeltaImpl.builder() + .node(newManagerMemento(ownNodeId, ManagementNodeState.STANDBY, tickerCurrentMillis())) + .node(newManagerMemento("node1", ManagementNodeState.MASTER, tickerCurrentMillis())) + .setMaster("node1") + .build()); + + manager.start(HighAvailabilityMode.AUTO); + + // 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. + tickerAdvance(Duration.seconds(31)); + + // Expect to be notified of our promotion, as the only other node + promotionListener.assertCalledEventually(); + } + + @Test(groups="Integration") // because one second wait in succeedsContinually + public void testDoesNotPromoteIfMasterTimeoutNotExpired() throws Exception { + persister.delta(ManagementPlaneSyncRecordDeltaImpl.builder() + .node(newManagerMemento(ownNodeId, ManagementNodeState.STANDBY, tickerCurrentMillis())) + .node(newManagerMemento("node1", ManagementNodeState.MASTER, tickerCurrentMillis())) + .setMaster("node1") + .build()); + + manager.start(HighAvailabilityMode.AUTO); + + tickerAdvance(Duration.seconds(29)); + + // Expect not to be notified, as 29s < 30s timeout (it's a fake clock so won't hit 30, even waiting 1s below) + Asserts.succeedsContinually(new Runnable() { + @Override public void run() { + assertTrue(promotionListener.callTimestamps.isEmpty(), "calls="+promotionListener.callTimestamps); + }}); + } + + public void testGetManagementPlaneStatus() throws Exception { + // with the name zzzzz the mgr created here should never be promoted by the alphabetical strategy! + + tickerAdvance(Duration.FIVE_SECONDS); + persister.delta(ManagementPlaneSyncRecordDeltaImpl.builder() + .node(newManagerMemento("zzzzzzz_node1", ManagementNodeState.STANDBY, tickerCurrentMillis())) + .build()); + long zzzTime = tickerCurrentMillis(); + tickerAdvance(Duration.FIVE_SECONDS); + + manager.start(HighAvailabilityMode.AUTO); + ManagementPlaneSyncRecord memento = manager.getManagementPlaneSyncState(); + + // Note can assert timestamp because not "real" time; it's using our own Ticker + assertEquals(memento.getMasterNodeId(), ownNodeId); + assertEquals(memento.getManagementNodes().keySet(), ImmutableSet.of(ownNodeId, "zzzzzzz_node1")); + assertEquals(memento.getManagementNodes().get(ownNodeId).getNodeId(), ownNodeId); + assertEquals(memento.getManagementNodes().get(ownNodeId).getStatus(), ManagementNodeState.MASTER); + assertEquals(memento.getManagementNodes().get(ownNodeId).getTimestampUtc(), tickerCurrentMillis()); + assertEquals(memento.getManagementNodes().get("zzzzzzz_node1").getNodeId(), "zzzzzzz_node1"); + assertEquals(memento.getManagementNodes().get("zzzzzzz_node1").getStatus(), ManagementNodeState.STANDBY); + assertEquals(memento.getManagementNodes().get("zzzzzzz_node1").getTimestampUtc(), zzzTime); + } + + @Test(groups="Integration", invocationCount=50) //because we have had non-deterministic failures + public void testGetManagementPlaneStatusManyTimes() throws Exception { + testGetManagementPlaneStatus(); + } + ++ @Test ++ public void testGetManagementPlaneSyncStateInfersTimedOutNodeAsFailed() throws Exception { ++ persister.delta(ManagementPlaneSyncRecordDeltaImpl.builder() ++ .node(newManagerMemento(ownNodeId, ManagementNodeState.STANDBY, tickerCurrentMillis())) ++ .node(newManagerMemento("node1", ManagementNodeState.MASTER, tickerCurrentMillis())) ++ .setMaster("node1") ++ .build()); ++ ++ manager.start(HighAvailabilityMode.AUTO); ++ ++ ManagementPlaneSyncRecord state = manager.getManagementPlaneSyncState(); ++ assertEquals(state.getManagementNodes().get("node1").getStatus(), ManagementNodeState.MASTER); ++ assertEquals(state.getManagementNodes().get(ownNodeId).getStatus(), ManagementNodeState.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. ++ tickerAdvance(Duration.seconds(31)); ++ ++ ManagementPlaneSyncRecord state2 = manager.getManagementPlaneSyncState(); ++ assertEquals(state2.getManagementNodes().get("node1").getStatus(), ManagementNodeState.FAILED); ++ assertNotEquals(state.getManagementNodes().get(ownNodeId).getStatus(), ManagementNodeState.FAILED); ++ } ++ + private long tickerCurrentMillis() { + return ticker.read(); + } + + private long tickerAdvance(Duration duration) { + currentTime.addAndGet(duration.toMilliseconds()); + return tickerCurrentMillis(); + } - ++ + private ManagementNodeSyncRecord newManagerMemento(String nodeId, ManagementNodeState status, long timestamp) { + return BasicManagementNodeSyncRecord.builder().brooklynVersion(BrooklynVersion.get()).nodeId(nodeId).status(status).timestampUtc(timestamp).build(); + } + + public static class RecordingPromotionListener implements PromotionListener { + public final List<Long> callTimestamps = Lists.newCopyOnWriteArrayList(); + + @Override + public void promotingToMaster() { + callTimestamps.add(System.currentTimeMillis()); + } + + public void assertNotCalled() { + assertTrue(callTimestamps.isEmpty(), "calls="+callTimestamps); + } + + public void assertCalled() { + assertFalse(callTimestamps.isEmpty(), "calls="+callTimestamps); + } + + public void assertCalledEventually() { + Asserts.succeedsEventually(new Runnable() { + @Override public void run() { + assertCalled(); + }}); + } + }; +} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/e6e59f7e/locations/jclouds/src/test/java/brooklyn/entity/rebind/persister/jclouds/HighAvailabilityManagerJcloudsObjectStoreTest.java ---------------------------------------------------------------------- diff --cc locations/jclouds/src/test/java/brooklyn/entity/rebind/persister/jclouds/HighAvailabilityManagerJcloudsObjectStoreTest.java index 17c5adc,0000000..b87b78d mode 100644,000000..100644 --- a/locations/jclouds/src/test/java/brooklyn/entity/rebind/persister/jclouds/HighAvailabilityManagerJcloudsObjectStoreTest.java +++ b/locations/jclouds/src/test/java/brooklyn/entity/rebind/persister/jclouds/HighAvailabilityManagerJcloudsObjectStoreTest.java @@@ -1,51 -1,0 +1,56 @@@ +package brooklyn.entity.rebind.persister.jclouds; + +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +import brooklyn.config.BrooklynProperties; +import brooklyn.entity.rebind.persister.PersistenceObjectStore; +import brooklyn.management.ha.HighAvailabilityManagerTestFixture; +import brooklyn.management.internal.ManagementContextInternal; +import brooklyn.test.entity.LocalManagementContextForTests; +import brooklyn.util.text.Identifiers; + +@Test(groups="Integration") +public class HighAvailabilityManagerJcloudsObjectStoreTest extends HighAvailabilityManagerTestFixture { + + protected ManagementContextInternal newLocalManagementContext() { + return new LocalManagementContextForTests(BrooklynProperties.Factory.newDefault()); + } + + @Override @BeforeMethod + public void setUp() throws Exception { super.setUp(); } + + protected PersistenceObjectStore newPersistenceObjectStore() { + return new JcloudsBlobStoreBasedObjectStore( + BlobStoreTest.PERSIST_TO_OBJECT_STORE_FOR_TEST_SPEC, BlobStoreTest.CONTAINER_PREFIX+"-"+Identifiers.makeRandomId(4)); + } + + @Test(groups="Integration", invocationCount=5) //run fewer times w softlayer... + public void testGetManagementPlaneStatusManyTimes() throws Exception { + testGetManagementPlaneStatus(); + } + + @Test(groups="Integration") + @Override + public void testDoesNotPromoteIfMasterTimeoutNotExpired() throws Exception { + super.testDoesNotPromoteIfMasterTimeoutNotExpired(); + } + + @Test(groups="Integration") + @Override + public void testGetManagementPlaneStatus() throws Exception { + super.testGetManagementPlaneStatus(); + } + + @Test(groups="Integration") + @Override + public void testPromotes() throws Exception { + super.testPromotes(); + } - ++ ++ @Test(groups="Integration") ++ @Override ++ public void testGetManagementPlaneSyncStateInfersTimedOutNodeAsFailed() throws Exception { ++ super.testGetManagementPlaneSyncStateInfersTimedOutNodeAsFailed(); ++ } +}