You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sling.apache.org by st...@apache.org on 2015/10/20 16:12:34 UTC
svn commit: r1709601 [5/11] - in /sling/trunk/bundles/extensions/discovery:
base/ base/src/ base/src/main/ base/src/main/java/ base/src/main/java/org/
base/src/main/java/org/apache/ base/src/main/java/org/apache/sling/
base/src/main/java/org/apache/sli...
Added: sling/trunk/bundles/extensions/discovery/base/src/test/java/org/apache/sling/discovery/base/its/AbstractClusterTest.java
URL: http://svn.apache.org/viewvc/sling/trunk/bundles/extensions/discovery/base/src/test/java/org/apache/sling/discovery/base/its/AbstractClusterTest.java?rev=1709601&view=auto
==============================================================================
--- sling/trunk/bundles/extensions/discovery/base/src/test/java/org/apache/sling/discovery/base/its/AbstractClusterTest.java (added)
+++ sling/trunk/bundles/extensions/discovery/base/src/test/java/org/apache/sling/discovery/base/its/AbstractClusterTest.java Tue Oct 20 14:12:31 2015
@@ -0,0 +1,1540 @@
+/*
+ * 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 org.apache.sling.discovery.base.its;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Set;
+import java.util.UUID;
+import java.util.concurrent.Semaphore;
+
+import org.apache.log4j.Level;
+import org.apache.log4j.LogManager;
+import org.apache.sling.discovery.ClusterView;
+import org.apache.sling.discovery.InstanceDescription;
+import org.apache.sling.discovery.TopologyEvent;
+import org.apache.sling.discovery.TopologyEvent.Type;
+import org.apache.sling.discovery.TopologyEventListener;
+import org.apache.sling.discovery.TopologyView;
+import org.apache.sling.discovery.base.commons.ClusterViewHelper;
+import org.apache.sling.discovery.base.commons.ClusterViewService;
+import org.apache.sling.discovery.base.commons.UndefinedClusterViewException;
+import org.apache.sling.discovery.base.connectors.announcement.Announcement;
+import org.apache.sling.discovery.base.connectors.announcement.AnnouncementFilter;
+import org.apache.sling.discovery.base.connectors.announcement.AnnouncementRegistry;
+import org.apache.sling.discovery.base.its.setup.VirtualInstance;
+import org.apache.sling.discovery.base.its.setup.VirtualInstanceBuilder;
+import org.apache.sling.discovery.base.its.setup.mock.AcceptsMultiple;
+import org.apache.sling.discovery.base.its.setup.mock.AssertingTopologyEventListener;
+import org.apache.sling.discovery.base.its.setup.mock.PropertyProviderImpl;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public abstract class AbstractClusterTest {
+
+ private final Logger logger = LoggerFactory.getLogger(this.getClass());
+
+ private class SimpleClusterView {
+
+ private VirtualInstance[] instances;
+
+ SimpleClusterView(VirtualInstance... instances) {
+ this.instances = instances;
+ }
+
+ @Override
+ public String toString() {
+ String instanceSlingIds = "";
+ for(int i=0; i<instances.length; i++) {
+ instanceSlingIds = instanceSlingIds + instances[i].slingId + ",";
+ }
+ return "an expected cluster with "+instances.length+" instances: "+instanceSlingIds;
+ }
+ }
+
+ VirtualInstance instance1;
+ VirtualInstance instance2;
+ VirtualInstance instance3;
+
+ private String property1Value;
+
+ protected String property2Value;
+
+ private String property1Name;
+
+ private String property2Name;
+ VirtualInstance instance4;
+ VirtualInstance instance5;
+ VirtualInstance instance1Restarted;
+ private Level logLevel;
+
+ protected abstract VirtualInstanceBuilder newBuilder();
+
+ @Before
+ public void setup() throws Exception {
+ final org.apache.log4j.Logger discoveryLogger = LogManager.getRootLogger().getLogger("org.apache.sling.discovery");
+ logLevel = discoveryLogger.getLevel();
+ discoveryLogger.setLevel(Level.TRACE);
+ logger.debug("here we are");
+ instance1 = newBuilder().setDebugName("firstInstance").newRepository("/var/discovery/impl/", true).build();
+ instance2 = newBuilder().setDebugName("secondInstance").useRepositoryOf(instance1).build();
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ if (instance5 != null) {
+ instance5.stop();
+ }
+ if (instance4 != null) {
+ instance4.stop();
+ }
+ if (instance3 != null) {
+ instance3.stop();
+ }
+ if (instance3 != null) {
+ instance3.stop();
+ }
+ if (instance2 != null) {
+ instance2.stop();
+ }
+ if (instance1 != null) {
+ instance1.stop();
+ }
+ if (instance1Restarted != null) {
+ instance1Restarted.stop();
+ }
+ instance1Restarted = null;
+ instance1 = null;
+ instance2 = null;
+ instance3 = null;
+ instance4 = null;
+ instance5 = null;
+ final org.apache.log4j.Logger discoveryLogger = LogManager.getRootLogger().getLogger("org.apache.sling.discovery");
+ discoveryLogger.setLevel(logLevel);
+ }
+
+ /** test leader behaviour with ascending slingIds, SLING-3253 **/
+ @Test
+ public void testLeaderAsc() throws Throwable {
+ logger.info("testLeaderAsc: start");
+ doTestLeader("000", "111");
+ logger.info("testLeaderAsc: end");
+ }
+
+ /** test leader behaviour with descending slingIds, SLING-3253 **/
+ @Test
+ public void testLeaderDesc() throws Throwable {
+ logger.info("testLeaderDesc: start");
+ doTestLeader("111", "000");
+ logger.info("testLeaderDesc: end");
+ }
+
+ private void doTestLeader(String slingId1, String slingId2) throws Throwable {
+ logger.info("doTestLeader("+slingId1+","+slingId2+"): start");
+ // stop 1 and 2 and create them with a lower heartbeat timeout
+ instance2.stopViewChecker();
+ instance1.stopViewChecker();
+ instance2.stop();
+ instance1.stop();
+ instance1 = newBuilder().setDebugName("firstInstance")
+ .newRepository("/var/discovery/impl/", true)
+ .setConnectorPingTimeout(30)
+ .setMinEventDelay(1)
+ .setSlingId(slingId1).build();
+ // sleep so that the two dont have the same startup time, and thus leaderElectionId is lower for instance1
+ logger.info("doTestLeader: 1st sleep 200ms");
+ Thread.sleep(200);
+ instance2 = newBuilder().setDebugName("secondInstance")
+ .useRepositoryOf(instance1)
+ .setConnectorPingTimeout(30)
+ .setMinEventDelay(1)
+ .setSlingId(slingId2).build();
+ assertNotNull(instance1);
+ assertNotNull(instance2);
+
+ // the two instances are still isolated - hence they throw an exception
+ try{
+ instance1.getClusterViewService().getLocalClusterView();
+ fail("should complain");
+ } catch(UndefinedClusterViewException e) {
+ // ok
+ }
+ try{
+ instance2.getClusterViewService().getLocalClusterView();
+ fail("should complain");
+ } catch(UndefinedClusterViewException e) {
+ // ok
+ }
+
+ // let the sync/voting happen
+ for(int m=0; m<4; m++) {
+ instance1.heartbeatsAndCheckView();
+ instance2.heartbeatsAndCheckView();
+ logger.info("doTestLeader: sleep 500ms");
+ Thread.sleep(500);
+ }
+ instance1.heartbeatsAndCheckView();
+ instance2.heartbeatsAndCheckView();
+
+ // now they must be in the same cluster, so in a cluster of size 1
+ assertEquals(2, instance1.getClusterViewService().getLocalClusterView().getInstances().size());
+ assertEquals(2, instance2.getClusterViewService().getLocalClusterView().getInstances().size());
+
+ // the first instance should be the leader - since it was started first
+ assertTrue(instance1.getLocalInstanceDescription().isLeader());
+ assertFalse(instance2.getLocalInstanceDescription().isLeader());
+ logger.info("doTestLeader("+slingId1+","+slingId2+"): end");
+ }
+
+ /**
+ * Tests stale announcement reported in SLING-4139:
+ * An instance which crashes but had announcements, never cleans up those announcements.
+ * Thus on a restart, those announcements are still there, even if the connector
+ * would no longer be in use (or point somewhere else etc).
+ * That has various effects, one of them tested in this method: peers in the same cluster,
+ * after the crashed/stopped instance restarts, will assume those stale announcements
+ * as being correct and include them in the topology - hence reporting stale instances
+ * (which can be old instances or even duplicates).
+ */
+ @Test
+ public void testStaleAnnouncementsVisibleToClusterPeers4139() throws Throwable {
+ logger.info("testStaleAnnouncementsVisibleToClusterPeers4139: start");
+ final String instance1SlingId = prepare4139();
+
+ // remove topology connector from instance3 to instance1
+ // -> corresponds to stop pinging
+ // (nothing to assert additionally here)
+
+ // start instance 1
+ instance1Restarted = newBuilder().setDebugName("firstInstance")
+ .useRepositoryOf(instance2)
+ .setConnectorPingTimeout(Integer.MAX_VALUE /* no timeout */)
+ .setMinEventDelay(1)
+ .setSlingId(instance1SlingId).build();
+ runHeartbeatOnceWith(instance1Restarted, instance2, instance3);
+ Thread.sleep(500);
+ runHeartbeatOnceWith(instance1Restarted, instance2, instance3);
+ Thread.sleep(500);
+ runHeartbeatOnceWith(instance1Restarted, instance2, instance3);
+
+ // facts: connector 3->1 does not exist actively anymore,
+ // instance 1+2 should build a cluster,
+ // instance 3 should be isolated
+ logger.info("instance1Restarted.dump: "+instance1Restarted.slingId);
+ instance1Restarted.dumpRepo();
+
+ logger.info("instance2.dump: "+instance2.slingId);
+ instance2.dumpRepo();
+
+ logger.info("instance3.dump: "+instance3.slingId);
+ instance3.dumpRepo();
+
+ assertTopology(instance1Restarted, new SimpleClusterView(instance1Restarted, instance2));
+ assertTopology(instance3, new SimpleClusterView(instance3));
+ assertTopology(instance2, new SimpleClusterView(instance1Restarted, instance2));
+ instance1Restarted.stop();
+ logger.info("testStaleAnnouncementsVisibleToClusterPeers4139: end");
+ }
+
+ /**
+ * Tests a situation where a connector was done to instance1, which eventually
+ * crashed, then the connector is done to instance2. Meanwhile instance1
+ * got restarted and this test assures that the instance3 is not reported
+ * twice in the topology. Did not happen before 4139, but should never afterwards neither
+ */
+ @Test
+ public void testDuplicateInstanceIn2Clusters4139() throws Throwable {
+ logger.info("testDuplicateInstanceIn2Clusters4139: start");
+ final String instance1SlingId = prepare4139();
+
+ // remove topology connector from instance3 to instance1
+ // -> corresponds to stop pinging
+ // (nothing to assert additionally here)
+ // instead, now start a connector from instance3 to instance2
+ pingConnector(instance3, instance2);
+
+ // start instance 1
+ instance1Restarted = newBuilder().setDebugName("firstInstance")
+ .useRepositoryOf(instance2)
+ .setConnectorPingTimeout(Integer.MAX_VALUE /* no timeout */)
+ .setMinEventDelay(1)
+ .setSlingId(instance1SlingId).build();
+ runHeartbeatOnceWith(instance1Restarted, instance2, instance3);
+ pingConnector(instance3, instance2);
+ runHeartbeatOnceWith(instance1Restarted, instance2, instance3);
+ pingConnector(instance3, instance2);
+ logger.info("iteration 0");
+ logger.info("instance1Restarted.slingId: "+instance1Restarted.slingId);
+ logger.info("instance2.slingId: "+instance2.slingId);
+ logger.info("instance3.slingId: "+instance3.slingId);
+ instance1Restarted.dumpRepo();
+ assertSameTopology(new SimpleClusterView(instance1Restarted, instance2), new SimpleClusterView(instance3));
+
+ Thread.sleep(500);
+ runHeartbeatOnceWith(instance1Restarted, instance2, instance3);
+ pingConnector(instance3, instance2);
+ runHeartbeatOnceWith(instance1Restarted, instance2, instance3);
+ pingConnector(instance3, instance2);
+ logger.info("iteration 1");
+ logger.info("instance1Restarted.slingId: "+instance1Restarted.slingId);
+ logger.info("instance2.slingId: "+instance2.slingId);
+ logger.info("instance3.slingId: "+instance3.slingId);
+ instance1Restarted.dumpRepo();
+ assertSameTopology(new SimpleClusterView(instance1Restarted, instance2), new SimpleClusterView(instance3));
+ instance1Restarted.stop();
+
+ logger.info("testDuplicateInstanceIn2Clusters4139: end");
+ }
+
+/* ok, this test should do the following:
+ * cluster A with instance 1 and instance 2
+ * cluster B with instance 3 and instance 4
+ * cluster C with instance 5
+
+ * initially, instance3 is pinging instance1, and instance 5 is pinging instance1 as well (MAC hub)
+ * that should result in instance3 and 5 to inherit the rest from instance1
+ * then simulate load balancer switching from instance1 to instance2 - hence pings go to instance2
+ *
+ */
+ @Test
+ public void testConnectorSwitching4139() throws Throwable {
+ final int MIN_EVENT_DELAY = 1;
+
+ tearDown(); // reset any setup that was done - we start with a different setup than the default one
+ final org.apache.log4j.Logger discoveryLogger = LogManager.getRootLogger().getLogger("org.apache.sling.discovery");
+ logLevel = discoveryLogger.getLevel();
+ discoveryLogger.setLevel(Level.DEBUG);
+
+ instance1 = newBuilder().setDebugName("instance1")
+ .newRepository("/var/discovery/clusterA/", true)
+ .setConnectorPingTimeout(10 /* sec */)
+ .setConnectorPingInterval(999)
+ .setMinEventDelay(MIN_EVENT_DELAY).build();
+ instance2 = newBuilder().setDebugName("instance2")
+ .useRepositoryOf(instance1)
+ .setConnectorPingTimeout(10 /* sec */)
+ .setConnectorPingInterval(999)
+ .setMinEventDelay(MIN_EVENT_DELAY).build();
+ // now launch the remote instance
+ instance3 = newBuilder().setDebugName("instance3")
+ .newRepository("/var/discovery/clusterB/", false)
+ .setConnectorPingTimeout(10 /* sec */)
+ .setConnectorPingInterval(999)
+ .setMinEventDelay(MIN_EVENT_DELAY).build();
+ instance4 = newBuilder().setDebugName("instance4")
+ .useRepositoryOf(instance3)
+ .setConnectorPingTimeout(10 /* sec */)
+ .setConnectorPingInterval(999)
+ .setMinEventDelay(MIN_EVENT_DELAY).build();
+ instance5 = newBuilder().setDebugName("instance5")
+ .newRepository("/var/discovery/clusterC/", false)
+ .setConnectorPingTimeout(10 /* sec */)
+ .setConnectorPingInterval(999)
+ .setMinEventDelay(MIN_EVENT_DELAY).build();
+
+ // join the instances to form a cluster by sending out heartbeats
+ runHeartbeatOnceWith(instance1, instance2, instance3, instance4, instance5);
+ Thread.sleep(500);
+ runHeartbeatOnceWith(instance1, instance2, instance3, instance4, instance5);
+ Thread.sleep(500);
+ runHeartbeatOnceWith(instance1, instance2, instance3, instance4, instance5);
+ Thread.sleep(500);
+
+ assertSameTopology(new SimpleClusterView(instance1, instance2));
+ assertSameTopology(new SimpleClusterView(instance3, instance4));
+ assertSameTopology(new SimpleClusterView(instance5));
+
+ // create a topology connector from instance3 to instance1
+ // -> corresponds to starting to ping
+ runHeartbeatOnceWith(instance1, instance2, instance3, instance4, instance5);
+ pingConnector(instance3, instance1);
+ pingConnector(instance5, instance1);
+ Thread.sleep(500);
+ runHeartbeatOnceWith(instance1, instance2, instance3, instance4, instance5);
+ pingConnector(instance3, instance1);
+ pingConnector(instance5, instance1);
+ Thread.sleep(500);
+
+ // make asserts on the topology
+ logger.info("testConnectorSwitching4139: instance1.slingId="+instance1.slingId);
+ logger.info("testConnectorSwitching4139: instance2.slingId="+instance2.slingId);
+ logger.info("testConnectorSwitching4139: instance3.slingId="+instance3.slingId);
+ logger.info("testConnectorSwitching4139: instance4.slingId="+instance4.slingId);
+ logger.info("testConnectorSwitching4139: instance5.slingId="+instance5.slingId);
+ instance1.dumpRepo();
+
+ assertSameTopology(new SimpleClusterView(instance1, instance2),
+ new SimpleClusterView(instance3, instance4),
+ new SimpleClusterView(instance5));
+
+ // simulate a crash of instance1, resulting in load-balancer to switch the pings
+ boolean success = false;
+ for(int i=0; i<25; i++) {
+ // loop for max 25 times, min 20 times
+ runHeartbeatOnceWith(instance2, instance3, instance4, instance5);
+ final boolean ping1 = pingConnector(instance3, instance2);
+ final boolean ping2 = pingConnector(instance5, instance2);
+ if (ping1 && ping2) {
+ // both pings were fine - hence break
+ success = true;
+ logger.info("testConnectorSwitching4139: successfully switched all pings to instance2 after "+i+" rounds.");
+ if (i<20) {
+ logger.info("testConnectorSwitching4139: min loop cnt not yet reached: i="+i);
+ Thread.sleep(1000); // 20x1000ms = 20sec max - (vs 10sec timeout) - should be enough for timing out
+ continue;
+ }
+ break;
+ }
+ logger.info("testConnectorSwitching4139: looping cos ping1="+ping1+", ping2="+ping2);
+ Thread.sleep(1000); // 25x1000ms = 25sec max - (vs 10sec timeout)
+
+ }
+ assertTrue(success);
+ // one final heartbeat
+ runHeartbeatOnceWith(instance2, instance3, instance4, instance5);
+ assertTrue(pingConnector(instance3, instance2));
+ assertTrue(pingConnector(instance5, instance2));
+
+ instance2.dumpRepo();
+
+ assertSameTopology(new SimpleClusterView(instance2),
+ new SimpleClusterView(instance3, instance4),
+ new SimpleClusterView(instance5));
+
+ // restart instance1, crash instance4
+ instance4.stopViewChecker();
+ instance1Restarted = newBuilder().setDebugName("instance1")
+ .useRepositoryOf(instance2)
+ .setConnectorPingTimeout(Integer.MAX_VALUE /* no timeout */)
+ .setMinEventDelay(1)
+ .setSlingId(instance1.getSlingId()).build();
+ runHeartbeatOnceWith(instance1Restarted, instance2, instance3, instance5);
+ assertTrue(pingConnector(instance3, instance2));
+ assertTrue(pingConnector(instance5, instance2));
+ success = false;
+ for(int i=0; i<25; i++) {
+ runHeartbeatOnceWith(instance1Restarted, instance2, instance3, instance5);
+ instance1.getViewChecker().checkView();
+ assertTrue(pingConnector(instance3, instance2));
+ assertTrue(pingConnector(instance5, instance2));
+ final TopologyView topology = instance3.getDiscoveryService().getTopology();
+ InstanceDescription i3 = null;
+ for (Iterator<InstanceDescription> it = topology.getInstances().iterator(); it.hasNext();) {
+ final InstanceDescription id = it.next();
+ if (id.getSlingId().equals(instance3.slingId)) {
+ i3 = id;
+ break;
+ }
+ }
+ assertNotNull(i3);
+ assertEquals(instance3.slingId, i3.getSlingId());
+ final ClusterView i3Cluster = i3.getClusterView();
+ final int i3ClusterSize = i3Cluster.getInstances().size();
+ if (i3ClusterSize==1) {
+ if (i<20) {
+ logger.info("testConnectorSwitching4139: [2] min loop cnt not yet reached: i="+i);
+ Thread.sleep(500); // 20x500ms = 10sec max - (vs 5sec timeout) - should be enough for timing out
+ continue;
+ }
+ success = true;
+ break;
+ }
+ logger.info("testConnectorSwitching4139: i3ClusterSize: "+i3ClusterSize);
+ Thread.sleep(500);
+ }
+
+ logger.info("testConnectorSwitching4139: instance1Restarted.slingId="+instance1Restarted.slingId);
+ logger.info("testConnectorSwitching4139: instance2.slingId="+instance2.slingId);
+ logger.info("testConnectorSwitching4139: instance3.slingId="+instance3.slingId);
+ logger.info("testConnectorSwitching4139: instance4.slingId="+instance4.slingId);
+ logger.info("testConnectorSwitching4139: instance5.slingId="+instance5.slingId);
+ instance1Restarted.dumpRepo();
+ assertTrue(success);
+
+ assertSameTopology(new SimpleClusterView(instance1Restarted, instance2),
+ new SimpleClusterView(instance3),
+ new SimpleClusterView(instance5));
+ instance1Restarted.stop();
+
+ }
+
+ @Test
+ public void testDuplicateInstance3726() throws Throwable {
+ logger.info("testDuplicateInstance3726: start");
+ final int MIN_EVENT_DELAY = 1;
+
+ tearDown(); // reset any setup that was done - we start with a different setup than the default one
+ final org.apache.log4j.Logger discoveryLogger = LogManager.getRootLogger().getLogger("org.apache.sling.discovery");
+ logLevel = discoveryLogger.getLevel();
+ discoveryLogger.setLevel(Level.DEBUG);
+
+ instance1 = newBuilder().setDebugName("instance1")
+ .newRepository("/var/discovery/clusterA/", true)
+ .setConnectorPingTimeout(15 /* sec */)
+ .setMinEventDelay(MIN_EVENT_DELAY).build();
+ instance2 = newBuilder().setDebugName("instance2")
+ .useRepositoryOf(instance1)
+ .setConnectorPingTimeout(15 /* sec */)
+ .setMinEventDelay(MIN_EVENT_DELAY).build();
+ // now launch the remote instance
+ instance3 = newBuilder().setDebugName("instance3")
+ .newRepository("/var/discovery/clusterB/", false)
+ .setConnectorPingTimeout(15 /* sec */)
+ .setMinEventDelay(MIN_EVENT_DELAY).build();
+ instance5 = newBuilder().setDebugName("instance5")
+ .newRepository("/var/discovery/clusterC/", false)
+ .setConnectorPingTimeout(15 /* sec */)
+ .setMinEventDelay(MIN_EVENT_DELAY).build();
+
+ // join the instances to form a cluster by sending out heartbeats
+ runHeartbeatOnceWith(instance1, instance2, instance3, instance5);
+ Thread.sleep(500);
+ runHeartbeatOnceWith(instance1, instance2, instance3, instance5);
+ Thread.sleep(500);
+ runHeartbeatOnceWith(instance1, instance2, instance3, instance5);
+ Thread.sleep(500);
+
+ assertSameTopology(new SimpleClusterView(instance1, instance2));
+ assertSameTopology(new SimpleClusterView(instance3));
+ assertSameTopology(new SimpleClusterView(instance5));
+
+ // create a topology connector from instance3 to instance1
+ // -> corresponds to starting to ping
+ pingConnector(instance3, instance1);
+ pingConnector(instance5, instance1);
+ pingConnector(instance3, instance1);
+ pingConnector(instance5, instance1);
+
+ // make asserts on the topology
+ logger.info("testDuplicateInstance3726: instance1.slingId="+instance1.slingId);
+ logger.info("testDuplicateInstance3726: instance2.slingId="+instance2.slingId);
+ logger.info("testDuplicateInstance3726: instance3.slingId="+instance3.slingId);
+ logger.info("testDuplicateInstance3726: instance5.slingId="+instance5.slingId);
+ instance1.dumpRepo();
+
+ assertSameTopology(new SimpleClusterView(instance1, instance2),
+ new SimpleClusterView(instance3/*, instance4*/),
+ new SimpleClusterView(instance5));
+
+ // simulate a crash of instance1, resulting in load-balancer to switch the pings
+ instance1.stopViewChecker();
+ boolean success = false;
+ for(int i=0; i<25; i++) {
+ // loop for max 25 times, min 20 times
+ runHeartbeatOnceWith(instance2, instance3, /*instance4, */instance5);
+ final boolean ping1 = pingConnector(instance3, instance2);
+ final boolean ping2 = pingConnector(instance5, instance2);
+ if (ping1 && ping2) {
+ // both pings were fine - hence break
+ success = true;
+ logger.info("testDuplicateInstance3726: successfully switched all pings to instance2 after "+i+" rounds.");
+ if (i<20) {
+ logger.info("testDuplicateInstance3726: min loop cnt not yet reached: i="+i);
+ Thread.sleep(1000); // 20x1000ms = 20sec max - (vs 15sec timeout) - should be enough for timing out
+ continue;
+ }
+ break;
+ }
+ logger.info("testDuplicateInstance3726: looping");
+ Thread.sleep(1000); // 25x1000ms = 25sec max - (vs 15sec timeout)
+
+ }
+ assertTrue(success);
+ // one final heartbeat
+ runHeartbeatOnceWith(instance2, instance3, instance5);
+ assertTrue(pingConnector(instance3, instance2));
+ assertTrue(pingConnector(instance5, instance2));
+
+ instance2.dumpRepo();
+
+ assertSameTopology(new SimpleClusterView(instance2),
+ new SimpleClusterView(instance3),
+ new SimpleClusterView(instance5));
+
+ // restart instance1, start instance4
+ instance1Restarted = newBuilder().setDebugName("instance1")
+ .useRepositoryOf(instance2)
+ .setConnectorPingTimeout(Integer.MAX_VALUE /* no timeout */)
+ .setMinEventDelay(1)
+ .setSlingId(instance1.getSlingId()).build();
+ instance4 = newBuilder().setDebugName("instance4")
+ .useRepositoryOf(instance3)
+ .setConnectorPingTimeout(30 /* sec */)
+ .setMinEventDelay(MIN_EVENT_DELAY).build();
+ for(int i=0; i<3; i++) {
+ runHeartbeatOnceWith(instance1Restarted, instance2, instance3, instance4, instance5);
+ assertTrue(pingConnector(instance3, instance2));
+ assertTrue(pingConnector(instance5, instance2));
+ }
+
+ instance1Restarted.dumpRepo();
+ logger.info("testDuplicateInstance3726: instance1Restarted.slingId="+instance1Restarted.slingId);
+ logger.info("testDuplicateInstance3726: instance2.slingId="+instance2.slingId);
+ logger.info("testDuplicateInstance3726: instance3.slingId="+instance3.slingId);
+ logger.info("testDuplicateInstance3726: instance4.slingId="+instance4.slingId);
+ logger.info("testDuplicateInstance3726: instance5.slingId="+instance5.slingId);
+ assertTrue(success);
+
+ assertSameTopology(new SimpleClusterView(instance1Restarted, instance2),
+ new SimpleClusterView(instance3, instance4),
+ new SimpleClusterView(instance5));
+ instance1Restarted.stop();
+ logger.info("testDuplicateInstance3726: end");
+ }
+
+ private void assertSameTopology(SimpleClusterView... clusters) throws UndefinedClusterViewException {
+ if (clusters==null) {
+ return;
+ }
+ for(int i=0; i<clusters.length; i++) { // go through all clusters
+ final SimpleClusterView aCluster = clusters[i];
+ assertSameClusterIds(aCluster.instances);
+ for(int j=0; j<aCluster.instances.length; j++) { // and all instances therein
+ final VirtualInstance anInstance = aCluster.instances[j];
+ assertTopology(anInstance, clusters); // an verify that they all see the same
+ for(int k=0; k<clusters.length; k++) {
+ final SimpleClusterView otherCluster = clusters[k];
+ if (aCluster==otherCluster) {
+ continue; // then ignore this one
+ }
+ for(int m=0; m<otherCluster.instances.length; m++) {
+ assertNotSameClusterIds(anInstance, otherCluster.instances[m]);
+ }
+ }
+ }
+ }
+ }
+
+ private void runHeartbeatOnceWith(VirtualInstance... instances) {
+ if (instances==null) {
+ return;
+ }
+ for(int i=0; i<instances.length; i++) {
+ instances[i].heartbeatsAndCheckView();
+ }
+ }
+
+ /**
+ * Tests a situation where a connector was done to instance1, which eventually
+ * crashed, then the connector is done to instance4 (which is in a separate, 3rd cluster).
+ * Meanwhile instance1 got restarted and this test assures that the instance3 is not reported
+ * twice in the topology. This used to happen prior to SLING-4139
+ */
+ @Test
+ public void testStaleInstanceIn3Clusters4139() throws Throwable {
+ logger.info("testStaleInstanceIn3Clusters4139: start");
+ final String instance1SlingId = prepare4139();
+
+ // remove topology connector from instance3 to instance1
+ // -> corresponds to stop pinging
+ // (nothing to assert additionally here)
+
+ // start instance4 in a separate cluster
+ instance4 = newBuilder().setDebugName("remoteInstance4")
+ .newRepository("/var/discovery/implremote4/", false)
+ .setConnectorPingTimeout(Integer.MAX_VALUE /* no timeout */)
+ .setMinEventDelay(1).build();
+ try{
+ instance4.getClusterViewService().getLocalClusterView();
+ fail("should complain");
+ } catch(UndefinedClusterViewException e) {
+ // ok
+ }
+
+ // instead, now start a connector from instance3 to instance2
+ instance4.heartbeatsAndCheckView();
+ instance4.heartbeatsAndCheckView();
+ pingConnector(instance3, instance4);
+
+ // start instance 1
+ instance1Restarted = newBuilder().setDebugName("firstInstance")
+ .useRepositoryOf(instance2)
+ .setConnectorPingTimeout(Integer.MAX_VALUE /* no timeout */)
+ .setMinEventDelay(1)
+ .setSlingId(instance1SlingId).build();
+ runHeartbeatOnceWith(instance1Restarted, instance2, instance3, instance4);
+ pingConnector(instance3, instance4);
+ runHeartbeatOnceWith(instance1Restarted, instance2, instance3, instance4);
+ pingConnector(instance3, instance4);
+ logger.info("iteration 0");
+ logger.info("instance1Restarted.slingId: "+instance1Restarted.slingId);
+ logger.info("instance2.slingId: "+instance2.slingId);
+ logger.info("instance3.slingId: "+instance3.slingId);
+ logger.info("instance4.slingId: "+instance4.slingId);
+ instance1Restarted.dumpRepo();
+ assertSameTopology(
+ new SimpleClusterView(instance3),
+ new SimpleClusterView(instance4));
+ assertSameTopology(new SimpleClusterView(instance1Restarted, instance2));
+
+ Thread.sleep(100);
+ runHeartbeatOnceWith(instance1Restarted, instance2, instance3, instance4);
+ pingConnector(instance3, instance4);
+ runHeartbeatOnceWith(instance1Restarted, instance2, instance3, instance4);
+ pingConnector(instance3, instance4);
+ logger.info("iteration 1");
+ logger.info("instance1Restarted.slingId: "+instance1Restarted.slingId);
+ logger.info("instance2.slingId: "+instance2.slingId);
+ logger.info("instance3.slingId: "+instance3.slingId);
+ logger.info("instance4.slingId: "+instance4.slingId);
+ instance1Restarted.dumpRepo();
+ assertSameTopology(new SimpleClusterView(instance1Restarted, instance2));
+ assertSameTopology(
+ new SimpleClusterView(instance3),
+ new SimpleClusterView(instance4));
+
+ Thread.sleep(100);
+ runHeartbeatOnceWith(instance1Restarted, instance2, instance3, instance4);
+ pingConnector(instance3, instance4);
+
+ // now the situation should be as follows:
+ logger.info("iteration 2");
+ logger.info("instance1Restarted.slingId: "+instance1Restarted.slingId);
+ logger.info("instance2.slingId: "+instance2.slingId);
+ logger.info("instance3.slingId: "+instance3.slingId);
+ logger.info("instance4.slingId: "+instance4.slingId);
+ instance1Restarted.dumpRepo();
+ assertSameTopology(new SimpleClusterView(instance1Restarted, instance2));
+ assertSameTopology(
+ new SimpleClusterView(instance3),
+ new SimpleClusterView(instance4));
+ instance1Restarted.stop();
+
+ logger.info("testStaleInstanceIn3Clusters4139: end");
+ }
+
+ /**
+ * Preparation steps for SLING-4139 tests:
+ * Creates two clusters: A: with instance1 and 2, B with instance 3
+ * instance 3 creates a connector to instance 1
+ * then instance 1 is killed (crashes)
+ * @return the slingId of the original (crashed) instance1
+ */
+ private String prepare4139() throws Throwable, Exception,
+ InterruptedException {
+ tearDown(); // stop anything running..
+ instance1 = newBuilder().setDebugName("firstInstance")
+ .newRepository("/var/discovery/impl/", true)
+ .setConnectorPingTimeout(Integer.MAX_VALUE /* no timeout */)
+ .setMinEventDelay(1).build();
+ instance2 = newBuilder().setDebugName("secondInstance")
+ .useRepositoryOf(instance1)
+ .setConnectorPingTimeout(Integer.MAX_VALUE /* no timeout */)
+ .setMinEventDelay(1).build();
+ // join the two instances to form a cluster by sending out heartbeats
+ runHeartbeatOnceWith(instance1, instance2);
+ Thread.sleep(100);
+ runHeartbeatOnceWith(instance1, instance2);
+ Thread.sleep(100);
+ runHeartbeatOnceWith(instance1, instance2);
+ assertSameClusterIds(instance1, instance2);
+
+ // now launch the remote instance
+ instance3 = newBuilder().setDebugName("remoteInstance")
+ .newRepository("/var/discovery/implremote/", false)
+ .setConnectorPingTimeout(Integer.MAX_VALUE /* no timeout */)
+ .setMinEventDelay(1).build();
+ assertSameClusterIds(instance1, instance2);
+ try{
+ instance3.getClusterViewService().getLocalClusterView();
+ fail("should complain");
+ } catch(UndefinedClusterViewException ue) {
+ // ok
+ }
+ assertEquals(0, instance1.getAnnouncementRegistry().listLocalAnnouncements().size());
+ assertEquals(0, instance1.getAnnouncementRegistry().listLocalIncomingAnnouncements().size());
+ assertEquals(0, instance2.getAnnouncementRegistry().listLocalAnnouncements().size());
+ assertEquals(0, instance2.getAnnouncementRegistry().listLocalIncomingAnnouncements().size());
+ assertEquals(0, instance3.getAnnouncementRegistry().listLocalAnnouncements().size());
+ assertEquals(0, instance3.getAnnouncementRegistry().listLocalIncomingAnnouncements().size());
+
+ // create a topology connector from instance3 to instance1
+ // -> corresponds to starting to ping
+ instance3.heartbeatsAndCheckView();
+ instance3.heartbeatsAndCheckView();
+ pingConnector(instance3, instance1);
+ // make asserts on the topology
+ instance1.dumpRepo();
+ assertSameTopology(new SimpleClusterView(instance1, instance2), new SimpleClusterView(instance3));
+
+ // kill instance 1
+ logger.info("instance1.slingId="+instance1.slingId);
+ logger.info("instance2.slingId="+instance2.slingId);
+ logger.info("instance3.slingId="+instance3.slingId);
+ final String instance1SlingId = instance1.slingId;
+ instance1.stopViewChecker(); // and have instance3 no longer pinging instance1
+ instance1.stop(); // otherwise it will have itself still registered with the observation manager and fiddle with future events..
+ instance1 = null; // set to null to early fail if anyone still assumes (original) instance1 is up form now on
+ instance2.getConfig().setViewCheckTimeout(1); // set instance2's heartbeatTimeout to 1 sec to time out instance1 quickly!
+ instance3.getConfig().setViewCheckTimeout(1); // set instance3's heartbeatTimeout to 1 sec to time out instance1 quickly!
+ Thread.sleep(500);
+ runHeartbeatOnceWith(instance2, instance3);
+ Thread.sleep(500);
+ runHeartbeatOnceWith(instance2, instance3);
+ Thread.sleep(500);
+ runHeartbeatOnceWith(instance2, instance3);
+ // instance 2 should now be alone - in fact, 3 should be alone as well
+ instance2.dumpRepo();
+ assertTopology(instance2, new SimpleClusterView(instance2));
+ assertTopology(instance3, new SimpleClusterView(instance3));
+ instance2.getConfig().setViewCheckTimeout(Integer.MAX_VALUE /* no timeout */); // set instance2's heartbeatTimeout back to Integer.MAX_VALUE /* no timeout */
+ instance3.getConfig().setViewCheckTimeout(Integer.MAX_VALUE /* no timeout */); // set instance3's heartbeatTimeout back to Integer.MAX_VALUE /* no timeout */
+ return instance1SlingId;
+ }
+
+ private void assertNotSameClusterIds(VirtualInstance... instances) throws UndefinedClusterViewException {
+ if (instances==null) {
+ fail("must not pass empty set of instances here");
+ }
+ if (instances.length<=1) {
+ fail("must not pass 0 or 1 instance only");
+ }
+ final String clusterId1 = instances[0].getClusterViewService()
+ .getLocalClusterView().getId();
+ for(int i=1; i<instances.length; i++) {
+ final String otherClusterId = instances[i].getClusterViewService()
+ .getLocalClusterView().getId();
+ // cluster ids must NOT be the same
+ assertNotEquals(clusterId1, otherClusterId);
+ }
+ if (instances.length>2) {
+ final VirtualInstance[] subset = new VirtualInstance[instances.length-1];
+ System.arraycopy(instances, 0, subset, 1, instances.length-1);
+ assertNotSameClusterIds(subset);
+ }
+ }
+
+ private void assertSameClusterIds(VirtualInstance... instances) throws UndefinedClusterViewException {
+ if (instances==null) {
+ // then there is nothing to compare
+ return;
+ }
+ if (instances.length==1) {
+ // then there is nothing to compare
+ return;
+ }
+ final String clusterId1 = instances[0].getClusterViewService()
+ .getLocalClusterView().getId();
+ for(int i=1; i<instances.length; i++) {
+ final String otherClusterId = instances[i].getClusterViewService()
+ .getLocalClusterView().getId();
+ // cluster ids must be the same
+ if (!clusterId1.equals(otherClusterId)) {
+ logger.error("assertSameClusterIds: instances[0]: "+instances[0]);
+ logger.error("assertSameClusterIds: instances["+i+"]: "+instances[i]);
+ fail("mismatch in clusterIds: expected to equal: clusterId1="+clusterId1+", otherClusterId="+otherClusterId);
+ }
+ }
+ }
+
+ private void assertTopology(VirtualInstance instance, SimpleClusterView... assertedClusterViews) {
+ final TopologyView topology = instance.getDiscoveryService().getTopology();
+ logger.info("assertTopology: instance "+instance.slingId+" sees topology: "+topology+", expected: "+assertedClusterViews);
+ assertNotNull(topology);
+ if (assertedClusterViews.length!=topology.getClusterViews().size()) {
+ dumpFailureDetails(topology, assertedClusterViews);
+ fail("instance "+instance.slingId+ " expected "+assertedClusterViews.length+", got: "+topology.getClusterViews().size());
+ }
+ final Set<ClusterView> actualClusters = new HashSet<ClusterView>(topology.getClusterViews());
+ for(int i=0; i<assertedClusterViews.length; i++) {
+ final SimpleClusterView assertedClusterView = assertedClusterViews[i];
+ boolean foundMatch = false;
+ for (Iterator<ClusterView> it = actualClusters.iterator(); it
+ .hasNext();) {
+ final ClusterView actualClusterView = it.next();
+ if (matches(assertedClusterView, actualClusterView)) {
+ it.remove();
+ foundMatch = true;
+ break;
+ }
+ }
+ if (!foundMatch) {
+ dumpFailureDetails(topology, assertedClusterViews);
+ fail("instance "+instance.slingId+ " could not find a match in the topology with instance="+instance.slingId+" and clusterViews="+assertedClusterViews.length);
+ }
+ }
+ assertEquals("not all asserted clusterviews are in the actual view with instance="+instance+" and clusterViews="+assertedClusterViews, actualClusters.size(), 0);
+ }
+
+ private void dumpFailureDetails(TopologyView topology, SimpleClusterView... assertedClusterViews) {
+ logger.error("assertTopology: expected: "+assertedClusterViews.length);
+ for(int j=0; j<assertedClusterViews.length; j++) {
+ logger.error("assertTopology: ["+j+"]: "+assertedClusterViews[j].toString());
+ }
+ final Set<ClusterView> clusterViews = topology.getClusterViews();
+ final Set<InstanceDescription> instances = topology.getInstances();
+ logger.error("assertTopology: actual: "+clusterViews.size()+" clusters with a total of "+instances.size()+" instances");
+ for (Iterator<ClusterView> it = clusterViews.iterator(); it.hasNext();) {
+ final ClusterView aCluster = it.next();
+ logger.error("assertTopology: a cluster: "+aCluster.getId());
+ for (Iterator<InstanceDescription> it2 = aCluster.getInstances().iterator(); it2.hasNext();) {
+ final InstanceDescription id = it2.next();
+ logger.error("assertTopology: - an instance "+id.getSlingId());
+ }
+ }
+ logger.error("assertTopology: list of all instances: "+instances.size());
+ for (Iterator<InstanceDescription> it = instances.iterator(); it.hasNext();) {
+ final InstanceDescription id = it.next();
+ logger.error("assertTopology: - an instance: "+id.getSlingId());
+ }
+ }
+
+ private boolean matches(SimpleClusterView assertedClusterView,
+ ClusterView actualClusterView) {
+ assertNotNull(assertedClusterView);
+ assertNotNull(actualClusterView);
+ if (assertedClusterView.instances.length!=actualClusterView.getInstances().size()) {
+ return false;
+ }
+ final Set<InstanceDescription> actualInstances = new HashSet<InstanceDescription>(actualClusterView.getInstances());
+ outerLoop:for(int i=0; i<assertedClusterView.instances.length; i++) {
+ final VirtualInstance assertedInstance = assertedClusterView.instances[i];
+ for (Iterator<InstanceDescription> it = actualInstances.iterator(); it
+ .hasNext();) {
+ final InstanceDescription anActualInstance = it.next();
+ if (assertedInstance.slingId.equals(anActualInstance.getSlingId())) {
+ continue outerLoop;
+ }
+ }
+ return false;
+ }
+ return true;
+ }
+
+ private boolean pingConnector(final VirtualInstance from, final VirtualInstance to) throws UndefinedClusterViewException {
+ final Announcement fromAnnouncement = createFromAnnouncement(from);
+ Announcement replyAnnouncement = null;
+ try{
+ replyAnnouncement = ping(to, fromAnnouncement);
+ } catch(AssertionError e) {
+ logger.warn("pingConnector: ping failed, assertionError: "+e);
+ return false;
+ } catch (UndefinedClusterViewException e) {
+ logger.warn("pingConnector: ping failed, currently the cluster view is undefined: "+e);
+ return false;
+ }
+ registerReplyAnnouncement(from, replyAnnouncement);
+ return true;
+ }
+
+ private void registerReplyAnnouncement(VirtualInstance from,
+ Announcement inheritedAnnouncement) {
+ final AnnouncementRegistry announcementRegistry = from.getAnnouncementRegistry();
+ if (inheritedAnnouncement.isLoop()) {
+ fail("loop detected");
+ // we dont currently support loops here in the junit tests
+ return;
+ } else {
+ inheritedAnnouncement.setInherited(true);
+ if (announcementRegistry
+ .registerAnnouncement(inheritedAnnouncement)==-1) {
+ logger.info("ping: connector response is from an instance which I already see in my topology"
+ + inheritedAnnouncement);
+ return;
+ }
+ }
+// resultingAnnouncement = inheritedAnnouncement;
+// statusDetails = null;
+ }
+
+ private Announcement ping(VirtualInstance to, final Announcement incomingTopologyAnnouncement)
+ throws UndefinedClusterViewException {
+ final String slingId = to.slingId;
+ final ClusterViewService clusterViewService = to.getClusterViewService();
+ final AnnouncementRegistry announcementRegistry = to.getAnnouncementRegistry();
+
+ incomingTopologyAnnouncement.removeInherited(slingId);
+
+ final Announcement replyAnnouncement = new Announcement(
+ slingId);
+
+ long backoffInterval = -1;
+ final ClusterView clusterView = clusterViewService.getLocalClusterView();
+ if (!incomingTopologyAnnouncement.isCorrectVersion()) {
+ fail("incorrect version");
+ return null; // never reached
+ } else if (ClusterViewHelper.contains(clusterView, incomingTopologyAnnouncement
+ .getOwnerId())) {
+ fail("loop=true");
+ return null; // never reached
+ } else if (ClusterViewHelper.containsAny(clusterView, incomingTopologyAnnouncement
+ .listInstances())) {
+ fail("incoming announcement contains instances that are part of my cluster");
+ return null; // never reached
+ } else {
+ backoffInterval = announcementRegistry
+ .registerAnnouncement(incomingTopologyAnnouncement);
+ if (backoffInterval==-1) {
+ fail("rejecting an announcement from an instance that I already see in my topology: ");
+ return null; // never reached
+ } else {
+ // normal, successful case: replying with the part of the topology which this instance sees
+ replyAnnouncement.setLocalCluster(clusterView);
+ announcementRegistry.addAllExcept(replyAnnouncement, clusterView,
+ new AnnouncementFilter() {
+
+ public boolean accept(final String receivingSlingId, Announcement announcement) {
+ if (announcement.getPrimaryKey().equals(
+ incomingTopologyAnnouncement
+ .getPrimaryKey())) {
+ return false;
+ }
+ return true;
+ }
+ });
+ return replyAnnouncement;
+ }
+ }
+ }
+
+ private Announcement createFromAnnouncement(final VirtualInstance from) throws UndefinedClusterViewException {
+ // TODO: refactor TopologyConnectorClient to avoid duplicating code from there (ping())
+ Announcement topologyAnnouncement = new Announcement(from.slingId);
+ topologyAnnouncement.setServerInfo(from.slingId);
+ final ClusterView clusterView = from.getClusterViewService().getLocalClusterView();
+ topologyAnnouncement.setLocalCluster(clusterView);
+ from.getAnnouncementRegistry().addAllExcept(topologyAnnouncement, clusterView, new AnnouncementFilter() {
+
+ public boolean accept(final String receivingSlingId, final Announcement announcement) {
+ // filter out announcements that are of old cluster instances
+ // which I dont really have in my cluster view at the moment
+ final Iterator<InstanceDescription> it =
+ clusterView.getInstances().iterator();
+ while(it.hasNext()) {
+ final InstanceDescription instance = it.next();
+ if (instance.getSlingId().equals(receivingSlingId)) {
+ // then I have the receiving instance in my cluster view
+ // all fine then
+ return true;
+ }
+ }
+ // looks like I dont have the receiving instance in my cluster view
+ // then I should also not propagate that announcement anywhere
+ return false;
+ }
+ });
+ return topologyAnnouncement;
+ }
+
+ @Test
+ public void testStableClusterId() throws Throwable {
+ logger.info("testStableClusterId: start");
+ // stop 1 and 2 and create them with a lower heartbeat timeout
+ instance2.stopViewChecker();
+ instance1.stopViewChecker();
+ instance2.stop();
+ instance1.stop();
+ // SLING-4302 : first set the heartbeatTimeout to 100 sec - large enough to work on all CI instances
+ instance1 = newBuilder().setDebugName("firstInstance")
+ .newRepository("/var/discovery/impl/", true)
+ .setConnectorPingTimeout(100)
+ .setMinEventDelay(1).build();
+ instance2 = newBuilder().setDebugName("secondInstance")
+ .useRepositoryOf(instance1)
+ .setConnectorPingTimeout(100)
+ .setMinEventDelay(1).build();
+ assertNotNull(instance1);
+ assertNotNull(instance2);
+
+ try{
+ instance1.getClusterViewService().getLocalClusterView();
+ fail("should complain");
+ } catch(UndefinedClusterViewException e) {
+ // ok
+ }
+ try{
+ instance2.getClusterViewService().getLocalClusterView();
+ fail("should complain");
+ } catch(UndefinedClusterViewException e) {
+ // ok
+ }
+
+ // let the sync/voting happen
+ instance1.heartbeatsAndCheckView();
+ instance2.heartbeatsAndCheckView();
+ Thread.sleep(500);
+ instance1.heartbeatsAndCheckView();
+ instance2.heartbeatsAndCheckView();
+ Thread.sleep(500);
+ instance1.heartbeatsAndCheckView();
+ instance2.heartbeatsAndCheckView();
+
+ String newClusterId1 = instance1.getClusterViewService()
+ .getLocalClusterView().getId();
+ String newClusterId2 = instance2.getClusterViewService()
+ .getLocalClusterView().getId();
+ // both cluster ids must be the same
+ assertEquals(newClusterId1, newClusterId1);
+
+ instance1.dumpRepo();
+ assertEquals(2, instance1.getClusterViewService().getLocalClusterView().getInstances().size());
+ assertEquals(2, instance2.getClusterViewService().getLocalClusterView().getInstances().size());
+
+ // let instance2 'die' by now longer doing heartbeats
+ // SLING-4302 : then set the heartbeatTimeouts back to 1 sec to have them properly time out with the sleeps applied below
+ instance2.getConfig().setViewCheckTimeout(1);
+ instance1.getConfig().setViewCheckTimeout(1);
+ instance2.stopViewChecker(); // would actually not be necessary as it was never started.. this test only runs heartbeats manually
+ instance1.heartbeatsAndCheckView();
+ Thread.sleep(500);
+ instance1.heartbeatsAndCheckView();
+ Thread.sleep(500);
+ instance1.heartbeatsAndCheckView();
+ Thread.sleep(500);
+ instance1.heartbeatsAndCheckView();
+ Thread.sleep(500);
+ instance1.heartbeatsAndCheckView();
+ Thread.sleep(500);
+ instance1.heartbeatsAndCheckView();
+ // the cluster should now have size 1
+ assertEquals(1, instance1.getClusterViewService().getLocalClusterView().getInstances().size());
+ // the instance 2 should be in isolated mode as it is no longer in the established view
+ // hence null
+ try{
+ instance2.getViewChecker().checkView();
+ instance2.getClusterViewService().getLocalClusterView();
+ fail("should complain");
+ } catch(UndefinedClusterViewException e) {
+ // ok
+ }
+
+ // but the cluster id must have remained stable
+ instance1.dumpRepo();
+ String actualClusterId = instance1.getClusterViewService()
+ .getLocalClusterView().getId();
+ logger.info("expected cluster id: "+newClusterId1);
+ logger.info("actual cluster id: "+actualClusterId);
+ assertEquals(newClusterId1, actualClusterId);
+ logger.info("testStableClusterId: end");
+ }
+
+ @Test
+ public void testClusterView() throws Exception {
+ logger.info("testClusterView: start");
+ assertNotNull(instance1);
+ assertNotNull(instance2);
+ assertNull(instance3);
+ instance3 = newBuilder().setDebugName("thirdInstance")
+ .useRepositoryOf(instance1)
+ .build();
+ assertNotNull(instance3);
+
+ assertEquals(instance1.getSlingId(), instance1.getClusterViewService()
+ .getSlingId());
+ assertEquals(instance2.getSlingId(), instance2.getClusterViewService()
+ .getSlingId());
+ assertEquals(instance3.getSlingId(), instance3.getClusterViewService()
+ .getSlingId());
+
+ try{
+ instance1.getClusterViewService().getLocalClusterView();
+ fail("should complain");
+ } catch(UndefinedClusterViewException e) {
+ // ok
+ }
+ try{
+ instance2.getClusterViewService().getLocalClusterView();
+ fail("should complain");
+ } catch(UndefinedClusterViewException e) {
+ // ok
+ }
+ try{
+ instance3.getClusterViewService().getLocalClusterView();
+ fail("should complain");
+ } catch(UndefinedClusterViewException e) {
+ // ok
+ }
+
+ instance1.dumpRepo();
+
+ instance1.heartbeatsAndCheckView();
+ instance2.heartbeatsAndCheckView();
+ instance3.heartbeatsAndCheckView();
+
+ instance1.dumpRepo();
+ logger.info("testClusterView: 1st 2s sleep");
+ Thread.sleep(2000);
+
+ instance1.heartbeatsAndCheckView();
+ instance2.heartbeatsAndCheckView();
+ instance3.heartbeatsAndCheckView();
+ logger.info("testClusterView: 2nd 2s sleep");
+ Thread.sleep(2000);
+
+ instance1.dumpRepo();
+ String clusterId1 = instance1.getClusterViewService().getLocalClusterView()
+ .getId();
+ logger.info("clusterId1=" + clusterId1);
+ String clusterId2 = instance2.getClusterViewService().getLocalClusterView()
+ .getId();
+ logger.info("clusterId2=" + clusterId2);
+ String clusterId3 = instance3.getClusterViewService().getLocalClusterView()
+ .getId();
+ logger.info("clusterId3=" + clusterId3);
+ assertEquals(clusterId1, clusterId2);
+ assertEquals(clusterId1, clusterId3);
+
+ assertEquals(3, instance1.getClusterViewService().getLocalClusterView()
+ .getInstances().size());
+ assertEquals(3, instance2.getClusterViewService().getLocalClusterView()
+ .getInstances().size());
+ assertEquals(3, instance3.getClusterViewService().getLocalClusterView()
+ .getInstances().size());
+ logger.info("testClusterView: end");
+ }
+
+ @Test
+ public void testAdditionalInstance() throws Throwable {
+ logger.info("testAdditionalInstance: start");
+ assertNotNull(instance1);
+ assertNotNull(instance2);
+
+ assertEquals(instance1.getSlingId(), instance1.getClusterViewService()
+ .getSlingId());
+ assertEquals(instance2.getSlingId(), instance2.getClusterViewService()
+ .getSlingId());
+
+ try{
+ instance1.getClusterViewService().getLocalClusterView();
+ fail("should complain");
+ } catch(UndefinedClusterViewException e) {
+ // ok
+ }
+ try{
+ instance2.getClusterViewService().getLocalClusterView();
+ fail("should complain");
+ } catch(UndefinedClusterViewException e) {
+ // ok
+ }
+
+ instance1.heartbeatsAndCheckView();
+ instance2.heartbeatsAndCheckView();
+
+ instance1.dumpRepo();
+ logger.info("testAdditionalInstance: 1st 2s sleep");
+ Thread.sleep(2000);
+
+ instance1.heartbeatsAndCheckView();
+ instance2.heartbeatsAndCheckView();
+ logger.info("testAdditionalInstance: 2nd 2s sleep");
+ Thread.sleep(2000);
+
+ instance1.dumpRepo();
+ String clusterId1 = instance1.getClusterViewService().getLocalClusterView()
+ .getId();
+ logger.info("clusterId1=" + clusterId1);
+ String clusterId2 = instance2.getClusterViewService().getLocalClusterView()
+ .getId();
+ logger.info("clusterId2=" + clusterId2);
+ assertEquals(clusterId1, clusterId2);
+
+ assertEquals(2, instance1.getClusterViewService().getLocalClusterView()
+ .getInstances().size());
+ assertEquals(2, instance2.getClusterViewService().getLocalClusterView()
+ .getInstances().size());
+
+ AssertingTopologyEventListener assertingTopologyEventListener = new AssertingTopologyEventListener();
+ assertingTopologyEventListener.addExpected(Type.TOPOLOGY_INIT);
+ assertEquals(1, assertingTopologyEventListener.getRemainingExpectedCount());
+ instance1.bindTopologyEventListener(assertingTopologyEventListener);
+ Thread.sleep(500); // SLING-4755: async event sending requires some minimal wait time nowadays
+ assertEquals(0, assertingTopologyEventListener.getRemainingExpectedCount());
+
+ // startup instance 3
+ AcceptsMultiple acceptsMultiple = new AcceptsMultiple(
+ Type.TOPOLOGY_CHANGING, Type.TOPOLOGY_CHANGED);
+ assertingTopologyEventListener.addExpected(acceptsMultiple);
+ assertingTopologyEventListener.addExpected(acceptsMultiple);
+ instance3 = newBuilder().setDebugName("thirdInstance")
+ .useRepositoryOf(instance1)
+ .build();
+ instance1.heartbeatsAndCheckView();
+ instance2.heartbeatsAndCheckView();
+ instance3.heartbeatsAndCheckView();
+ logger.info("testAdditionalInstance: 3rd 2s sleep");
+ Thread.sleep(2000);
+ instance1.heartbeatsAndCheckView();
+ instance2.heartbeatsAndCheckView();
+ instance3.heartbeatsAndCheckView();
+ logger.info("testAdditionalInstance: 4th 2s sleep");
+ Thread.sleep(2000);
+ assertEquals(1, acceptsMultiple.getEventCnt(Type.TOPOLOGY_CHANGING));
+ assertEquals(1, acceptsMultiple.getEventCnt(Type.TOPOLOGY_CHANGED));
+ logger.info("testAdditionalInstance: end");
+ }
+
+ @Test
+ public void testPropertyProviders() throws Throwable {
+ logger.info("testPropertyProviders: start");
+ instance1.heartbeatsAndCheckView();
+ instance2.heartbeatsAndCheckView();
+ assertNull(instance3);
+ instance3 = newBuilder().setDebugName("thirdInstance")
+ .useRepositoryOf(instance1)
+ .build();
+ instance3.heartbeatsAndCheckView();
+ logger.info("testPropertyProviders: 1st 2s sleep");
+ Thread.sleep(2000);
+ instance1.heartbeatsAndCheckView();
+ instance2.heartbeatsAndCheckView();
+ instance3.heartbeatsAndCheckView();
+ logger.info("testPropertyProviders: 2nd 2s sleep");
+ Thread.sleep(2000);
+ instance1.heartbeatsAndCheckView();
+ instance2.heartbeatsAndCheckView();
+ instance3.heartbeatsAndCheckView();
+ logger.info("testPropertyProviders: 3rd 2s sleep");
+ Thread.sleep(2000);
+
+ property1Value = UUID.randomUUID().toString();
+ property1Name = UUID.randomUUID().toString();
+ PropertyProviderImpl pp1 = new PropertyProviderImpl();
+ pp1.setProperty(property1Name, property1Value);
+ instance1.bindPropertyProvider(pp1, property1Name);
+
+ property2Value = UUID.randomUUID().toString();
+ property2Name = UUID.randomUUID().toString();
+ PropertyProviderImpl pp2 = new PropertyProviderImpl();
+ pp2.setProperty(property2Name, property2Value);
+ instance2.bindPropertyProvider(pp2, property2Name);
+
+ assertPropertyValues();
+
+ property1Value = UUID.randomUUID().toString();
+ pp1.setProperty(property1Name, property1Value);
+ instance1.heartbeatsAndCheckView();
+ instance2.heartbeatsAndCheckView();
+
+ assertPropertyValues();
+ assertNull(instance1.getClusterViewService().getLocalClusterView()
+ .getInstances().get(0)
+ .getProperty(UUID.randomUUID().toString()));
+ assertNull(instance2.getClusterViewService().getLocalClusterView()
+ .getInstances().get(0)
+ .getProperty(UUID.randomUUID().toString()));
+ logger.info("testPropertyProviders: end");
+ }
+
+ private void assertPropertyValues() throws UndefinedClusterViewException {
+ assertPropertyValues(instance1.getSlingId(), property1Name,
+ property1Value);
+ assertPropertyValues(instance2.getSlingId(), property2Name,
+ property2Value);
+ }
+
+ private void assertPropertyValues(String slingId, String name, String value) throws UndefinedClusterViewException {
+ assertEquals(value, getInstance(instance1, slingId).getProperty(name));
+ assertEquals(value, getInstance(instance2, slingId).getProperty(name));
+ }
+
+ private InstanceDescription getInstance(VirtualInstance instance, String slingId) throws UndefinedClusterViewException {
+ Iterator<InstanceDescription> it = instance.getClusterViewService()
+ .getLocalClusterView().getInstances().iterator();
+ while (it.hasNext()) {
+ InstanceDescription id = it.next();
+ if (id.getSlingId().equals(slingId)) {
+ return id;
+ }
+ }
+ throw new IllegalStateException("instance not found: instance="
+ + instance + ", slingId=" + slingId);
+ }
+
+ class LongRunningListener implements TopologyEventListener {
+
+ String failMsg = null;
+
+ boolean initReceived = false;
+ int noninitReceived;
+
+ private Semaphore changedSemaphore = new Semaphore(0);
+
+ public void assertNoFail() {
+ if (failMsg!=null) {
+ fail(failMsg);
+ }
+ }
+
+ public Semaphore getChangedSemaphore() {
+ return changedSemaphore;
+ }
+
+ public void handleTopologyEvent(TopologyEvent event) {
+ if (failMsg!=null) {
+ failMsg += "/ Already failed, got another event; "+event;
+ return;
+ }
+ if (!initReceived) {
+ if (event.getType()!=Type.TOPOLOGY_INIT) {
+ failMsg = "Expected TOPOLOGY_INIT first, got: "+event.getType();
+ return;
+ }
+ initReceived = true;
+ return;
+ }
+ if (event.getType()==Type.TOPOLOGY_CHANGED) {
+ try {
+ changedSemaphore.acquire();
+ } catch (InterruptedException e) {
+ throw new Error("don't interrupt me pls: "+e);
+ }
+ }
+ noninitReceived++;
+ }
+ }
+
+ /**
+ * Test plan:
+ * * have a discoveryservice with two listeners registered
+ * * one of them (the 'first' one) is long running
+ * * during one of the topology changes, when the first
+ * one is hit, deactivate the discovery service
+ * * that deactivation used to block (SLING-4755) due
+ * to synchronized(lock) which was blocked by the
+ * long running listener. With having asynchronous
+ * event sending this should no longer be the case
+ * * also, once asserted that deactivation finished,
+ * and that the first listener is still busy, make
+ * sure that once the first listener finishes, that
+ * the second listener still gets the event
+ * @throws Throwable
+ */
+ @Test
+ public void testLongRunningListener() throws Throwable {
+ // let the instance1 become alone, instance2 is idle
+ instance1.getConfig().setViewCheckTimeout(2);
+ instance2.getConfig().setViewCheckTimeout(2);
+ logger.info("testLongRunningListener : letting instance2 remain silent from now on");
+ instance1.heartbeatsAndCheckView();
+ Thread.sleep(1500);
+ instance1.heartbeatsAndCheckView();
+ Thread.sleep(1500);
+ instance1.heartbeatsAndCheckView();
+ Thread.sleep(1500);
+ instance1.heartbeatsAndCheckView();
+ logger.info("testLongRunningListener : instance 2 should now be considered dead");
+// instance1.dumpRepo();
+
+ LongRunningListener longRunningListener1 = new LongRunningListener();
+ AssertingTopologyEventListener fastListener2 = new AssertingTopologyEventListener();
+ fastListener2.addExpected(Type.TOPOLOGY_INIT);
+ longRunningListener1.assertNoFail();
+ assertEquals(1, fastListener2.getRemainingExpectedCount());
+ logger.info("testLongRunningListener : binding longRunningListener1 ...");
+ instance1.bindTopologyEventListener(longRunningListener1);
+ logger.info("testLongRunningListener : binding fastListener2 ...");
+ instance1.bindTopologyEventListener(fastListener2);
+ logger.info("testLongRunningListener : waiting a bit for longRunningListener1 to receive the TOPOLOGY_INIT event");
+ Thread.sleep(2500); // SLING-4755: async event sending requires some minimal wait time nowadays
+ assertEquals(0, fastListener2.getRemainingExpectedCount());
+ assertTrue(longRunningListener1.initReceived);
+
+ // after INIT, now do an actual change where listener1 will do a long-running handling
+ fastListener2.addExpected(Type.TOPOLOGY_CHANGING);
+ fastListener2.addExpected(Type.TOPOLOGY_CHANGED);
+ instance1.getConfig().setViewCheckTimeout(10);
+ instance2.getConfig().setViewCheckTimeout(10);
+ instance1.heartbeatsAndCheckView();
+ instance2.heartbeatsAndCheckView();
+ Thread.sleep(500);
+ instance1.heartbeatsAndCheckView();
+ instance2.heartbeatsAndCheckView();
+ Thread.sleep(500);
+ instance1.heartbeatsAndCheckView();
+ instance2.heartbeatsAndCheckView();
+ Thread.sleep(500);
+
+ instance1.dumpRepo();
+ longRunningListener1.assertNoFail();
+ // nothing unexpected should arrive at listener2:
+ assertEquals(0, fastListener2.getUnexpectedCount());
+ // however, listener2 should only get one (CHANGING) event, cos the CHANGED event is still blocked
+ assertEquals(1, fastListener2.getRemainingExpectedCount());
+ // and also listener2 should only get CHANGING, the CHANGED is blocked via changedSemaphore
+ assertEquals(1, longRunningListener1.noninitReceived);
+ assertTrue(longRunningListener1.getChangedSemaphore().hasQueuedThreads());
+ Thread.sleep(2000);
+ // even after a 2sec sleep things should be unchanged:
+ assertEquals(0, fastListener2.getUnexpectedCount());
+ assertEquals(1, fastListener2.getRemainingExpectedCount());
+ assertEquals(1, longRunningListener1.noninitReceived);
+ assertTrue(longRunningListener1.getChangedSemaphore().hasQueuedThreads());
+
+ // now let's simulate SLING-4755: deactivation while longRunningListener1 does long processing
+ // - which is simulated by waiting on changedSemaphore.
+ final List<Exception> asyncException = new LinkedList<Exception>();
+ Thread th = new Thread(new Runnable() {
+
+ public void run() {
+ try {
+ instance1.stop();
+ } catch (Exception e) {
+ synchronized(asyncException) {
+ asyncException.add(e);
+ }
+ }
+ }
+
+ });
+ th.start();
+ logger.info("Waiting max 4 sec...");
+ th.join(4000);
+ logger.info("Done waiting max 4 sec...");
+ if (th.isAlive()) {
+ logger.warn("Thread still alive: "+th.isAlive());
+ // release before issuing fail as otherwise test will block forever
+ longRunningListener1.getChangedSemaphore().release();
+ fail("Thread was still alive");
+ }
+ logger.info("Thread was no longer alive: "+th.isAlive());
+ synchronized(asyncException) {
+ logger.info("Async exceptions: "+asyncException.size());
+ if (asyncException.size()!=0) {
+ // release before issuing fail as otherwise test will block forever
+ longRunningListener1.getChangedSemaphore().release();
+ fail("async exceptions: "+asyncException.size()+", first: "+asyncException.get(0));
+ }
+ }
+
+ // now the test consists of
+ // a) the fact that we reached this place without unlocking the changedSemaphore
+ // b) when we now unlock the changedSemaphore the remaining events should flush through
+ longRunningListener1.getChangedSemaphore().release();
+ Thread.sleep(500);// shouldn't take long and then things should have flushed:
+ assertEquals(0, fastListener2.getUnexpectedCount());
+ assertEquals(0, fastListener2.getRemainingExpectedCount());
+ assertEquals(2, longRunningListener1.noninitReceived);
+ assertFalse(longRunningListener1.getChangedSemaphore().hasQueuedThreads());
+ }
+
+
+}
Propchange: sling/trunk/bundles/extensions/discovery/base/src/test/java/org/apache/sling/discovery/base/its/AbstractClusterTest.java
------------------------------------------------------------------------------
svn:eol-style = native
Added: sling/trunk/bundles/extensions/discovery/base/src/test/java/org/apache/sling/discovery/base/its/AbstractSingleInstanceTest.java
URL: http://svn.apache.org/viewvc/sling/trunk/bundles/extensions/discovery/base/src/test/java/org/apache/sling/discovery/base/its/AbstractSingleInstanceTest.java?rev=1709601&view=auto
==============================================================================
--- sling/trunk/bundles/extensions/discovery/base/src/test/java/org/apache/sling/discovery/base/its/AbstractSingleInstanceTest.java (added)
+++ sling/trunk/bundles/extensions/discovery/base/src/test/java/org/apache/sling/discovery/base/its/AbstractSingleInstanceTest.java Tue Oct 20 14:12:31 2015
@@ -0,0 +1,293 @@
+/*
+ * 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 org.apache.sling.discovery.base.its;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNotSame;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import java.util.List;
+import java.util.Map;
+import java.util.UUID;
+
+import org.apache.log4j.Level;
+import org.apache.log4j.LogManager;
+import org.apache.sling.discovery.ClusterView;
+import org.apache.sling.discovery.InstanceDescription;
+import org.apache.sling.discovery.TopologyEvent;
+import org.apache.sling.discovery.TopologyEvent.Type;
+import org.apache.sling.discovery.base.commons.UndefinedClusterViewException;
+import org.apache.sling.discovery.base.its.setup.VirtualInstance;
+import org.apache.sling.discovery.base.its.setup.VirtualInstanceBuilder;
+import org.apache.sling.discovery.base.its.setup.mock.AssertingTopologyEventListener;
+import org.apache.sling.discovery.base.its.setup.mock.PropertyProviderImpl;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public abstract class AbstractSingleInstanceTest {
+
+ private final Logger logger = LoggerFactory.getLogger(this.getClass());
+
+ VirtualInstance instance;
+
+ String propertyValue;
+
+ private Level logLevel;
+
+ protected abstract VirtualInstanceBuilder newBuilder();
+
+ @Before
+ public void setup() throws Exception {
+ final org.apache.log4j.Logger discoveryLogger = LogManager.getRootLogger().getLogger("org.apache.sling.discovery");
+ logLevel = discoveryLogger.getLevel();
+ discoveryLogger.setLevel(Level.DEBUG);
+ logger.info("setup: creating new standalone instance");
+ instance = newBuilder().setDebugName("standaloneInstance")
+ .newRepository("/var/discovery/impl/", true)
+ .setConnectorPingTimeout(20)
+ .setConnectorPingInterval(999)/*long enough heartbeat interval to prevent them to disturb the explicit heartbeats during the test*/
+ .setMinEventDelay(3).build();
+ logger.info("setup: creating new standalone instance done.");
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ final org.apache.log4j.Logger discoveryLogger = LogManager.getRootLogger().getLogger("org.apache.sling.discovery");
+ discoveryLogger.setLevel(logLevel);
+ logger.info("tearDown: stopping standalone instance");
+ if (instance!=null) {
+ instance.stop();
+ instance = null;
+ }
+ logger.info("tearDown: stopping standalone instance done");
+ }
+
+ @Test
+ public void testGetters() throws UndefinedClusterViewException, InterruptedException {
+ logger.info("testGetters: start");
+ assertNotNull(instance);
+ logger.info("sling id=" + instance.getSlingId());
+ try{
+ instance.getClusterViewService().getLocalClusterView();
+ fail("should complain"); // SLING-5030
+ } catch(UndefinedClusterViewException e) {
+ // ok
+ }
+
+ instance.heartbeatsAndCheckView();
+ // wait 100ms for the vote to happen
+ Thread.sleep(100);
+
+ assertNotNull(instance.getClusterViewService().getLocalClusterView());
+ ClusterView cv = instance.getClusterViewService().getLocalClusterView();
+ logger.info("cluster view: id=" + cv.getId());
+ assertNotNull(cv.getId());
+ assertNotSame(cv.getId(), "");
+
+ List<InstanceDescription> instances = cv.getInstances();
+ assertNotNull(instances);
+ assertTrue(instances.size() == 1);
+
+ InstanceDescription myInstance = instances.get(0);
+ assertNotNull(myInstance);
+ assertTrue(myInstance.getClusterView() == cv);
+ logger.info("instance id: " + myInstance.getSlingId());
+ assertEquals(instance.getSlingId(), myInstance.getSlingId());
+
+ Map<String, String> properties = myInstance.getProperties();
+ assertNotNull(properties);
+
+ assertNull(myInstance.getProperty("foo"));
+
+ assertTrue(myInstance.isLeader());
+
+ assertTrue(myInstance.isLocal());
+ logger.info("testGetters: end");
+ }
+
+ @Test
+ public void testPropertyProviders() throws Throwable {
+ logger.info("testPropertyProviders: start");
+ final String propertyName = UUID.randomUUID().toString();
+ propertyValue = UUID.randomUUID().toString();
+ PropertyProviderImpl pp = new PropertyProviderImpl();
+ pp.setProperty(propertyName, propertyValue);
+ instance.bindPropertyProvider(pp, propertyName);
+
+ instance.heartbeatsAndCheckView();
+ // wait 100ms for the vote to happen
+ Thread.sleep(100);
+ assertEquals(propertyValue,
+ instance.getClusterViewService().getLocalClusterView()
+ .getInstances().get(0).getProperty(propertyName));
+
+ propertyValue = UUID.randomUUID().toString();
+ pp.setProperty(propertyName, propertyValue);
+ instance.heartbeatsAndCheckView();
+
+ assertEquals(propertyValue,
+ instance.getClusterViewService().getLocalClusterView()
+ .getInstances().get(0).getProperty(propertyName));
+ assertNull(instance.getClusterViewService().getLocalClusterView()
+ .getInstances().get(0)
+ .getProperty(UUID.randomUUID().toString()));
+ logger.info("testPropertyProviders: end");
+ }
+
+ @Test
+ public void testInvalidProperties() throws Throwable {
+ logger.info("testInvalidProperties: start");
+
+ instance.heartbeatsAndCheckView();
+ instance.heartbeatsAndCheckView();
+
+ final String propertyValue = UUID.randomUUID().toString();
+ doTestProperty(UUID.randomUUID().toString(), propertyValue, propertyValue);
+
+ doTestProperty("", propertyValue, null);
+ doTestProperty("-", propertyValue, propertyValue);
+ doTestProperty("_", propertyValue, propertyValue);
+ doTestProperty("jcr:" + UUID.randomUUID().toString(), propertyValue, null);
+ doTestProperty("var/" + UUID.randomUUID().toString(), propertyValue, null);
+ doTestProperty(UUID.randomUUID().toString() + "@test", propertyValue, null);
+ doTestProperty(UUID.randomUUID().toString() + "!test", propertyValue, null);
+ logger.info("testInvalidProperties: end");
+ }
+
+ private void doTestProperty(final String propertyName,
+ final String propertyValue,
+ final String expectedPropertyValue) throws Throwable {
+ PropertyProviderImpl pp = new PropertyProviderImpl();
+ pp.setProperty(propertyName, propertyValue);
+ instance.bindPropertyProvider(pp, propertyName);
+ assertEquals(expectedPropertyValue,
+ instance.getClusterViewService().getLocalClusterView()
+ .getInstances().get(0).getProperty(propertyName));
+ }
+
+ @Test
+ public void testTopologyEventListeners() throws Throwable {
+ logger.info("testTopologyEventListeners: start");
+ instance.heartbeatsAndCheckView();
+ logger.info("testTopologyEventListeners: 1st sleep 2s");
+ Thread.sleep(2000);
+ instance.heartbeatsAndCheckView();
+ logger.info("testTopologyEventListeners: 2nd sleep 2s");
+ Thread.sleep(2000);
+
+ AssertingTopologyEventListener assertingTopologyEventListener = new AssertingTopologyEventListener();
+ assertingTopologyEventListener.addExpected(Type.TOPOLOGY_INIT);
+ logger.info("testTopologyEventListeners: binding the event listener");
+ instance.bindTopologyEventListener(assertingTopologyEventListener);
+ Thread.sleep(500); // SLING-4755: async event sending requires some minimal wait time nowadays
+ assertEquals(0, assertingTopologyEventListener.getRemainingExpectedCount());
+
+ final String propertyName = UUID.randomUUID().toString();
+ propertyValue = UUID.randomUUID().toString();
+ PropertyProviderImpl pp = new PropertyProviderImpl();
+ pp.setProperty(propertyName, propertyValue);
+
+ assertingTopologyEventListener.addExpected(Type.PROPERTIES_CHANGED);
+
+ assertEquals(1, assertingTopologyEventListener.getRemainingExpectedCount());
+ assertEquals(0, pp.getGetCnt());
+ instance.bindPropertyProvider(pp, propertyName);
+ logger.info("testTopologyEventListeners: 3rd sleep 1.5s");
+ Thread.sleep(1500);
+ logger.info("testTopologyEventListeners: dumping due to failure: ");
+ assertingTopologyEventListener.dump();
+ assertEquals(0, assertingTopologyEventListener.getRemainingExpectedCount());
+ // we can only assume that the getProperty was called at least once - it
+ // could be called multiple times though..
+ assertTrue(pp.getGetCnt() > 0);
+
+ assertingTopologyEventListener.addExpected(Type.PROPERTIES_CHANGED);
+
+ assertEquals(1, assertingTopologyEventListener.getRemainingExpectedCount());
+ pp.setGetCnt(0);
+ propertyValue = UUID.randomUUID().toString();
+ pp.setProperty(propertyName, propertyValue);
+ assertEquals(0, pp.getGetCnt());
+ instance.heartbeatsAndCheckView();
+ logger.info("testTopologyEventListeners: 4th sleep 2s");
+ Thread.sleep(2000);
+ assertEquals(0, assertingTopologyEventListener.getRemainingExpectedCount());
+ assertEquals(1, pp.getGetCnt());
+
+ // a heartbeat repeat should not result in another call though
+ instance.heartbeatsAndCheckView();
+ logger.info("testTopologyEventListeners: 5th sleep 2s");
+ Thread.sleep(2000);
+ assertEquals(0, assertingTopologyEventListener.getRemainingExpectedCount());
+ assertEquals(2, pp.getGetCnt());
+ logger.info("testTopologyEventListeners: done");
+ }
+
+ @Test
+ public void testBootstrap() throws Throwable {
+ logger.info("testBootstrap: start");
+ try{
+ instance.getClusterViewService().getLocalClusterView();
+ fail("should complain");
+ } catch(UndefinedClusterViewException e) {
+ // SLING-5030 : isolated mode is gone, replaced with exception
+ // ok
+ }
+
+ // SLING-3750 : with delaying the init event, we now should NOT get any events
+ // before we let the view establish (which happens via heartbeats below)
+ AssertingTopologyEventListener ada = new AssertingTopologyEventListener();
+ instance.bindTopologyEventListener(ada);
+ assertEquals(0, ada.getEvents().size());
+ assertEquals(0, ada.getUnexpectedCount());
+
+ try{
+ instance.getClusterViewService().getLocalClusterView();
+ fail("should complain");
+ } catch(UndefinedClusterViewException e) {
+ // ok
+ }
+
+ ada.addExpected(Type.TOPOLOGY_INIT);
+ instance.heartbeatsAndCheckView();
+ Thread.sleep(1000);
+ instance.heartbeatsAndCheckView();
+ Thread.sleep(1000);
+ instance.dumpRepo();
+ ada.dump();
+ assertEquals(0, ada.getUnexpectedCount());
+ assertEquals(1, ada.getEvents().size());
+ TopologyEvent initEvent = ada.getEvents().remove(0);
+ assertNotNull(initEvent);
+ assertNotNull(initEvent.getNewView());
+ assertNotNull(initEvent.getNewView().getClusterViews());
+
+ // after the view was established though, we expect it to be a normal
+ // EstablishedInstanceDescription
+ instance.assertEstablishedView();
+ logger.info("testBootstrap: end");
+ }
+
+}
Propchange: sling/trunk/bundles/extensions/discovery/base/src/test/java/org/apache/sling/discovery/base/its/AbstractSingleInstanceTest.java
------------------------------------------------------------------------------
svn:eol-style = native