/*
* 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.impl.cluster;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
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 java.util.Iterator;
import java.util.UUID;
import org.apache.sling.discovery.InstanceDescription;
import org.apache.sling.discovery.TopologyEvent.Type;
import org.apache.sling.discovery.impl.cluster.helpers.AcceptsMultiple;
import org.apache.sling.discovery.impl.cluster.helpers.AssertingTopologyEventListener;
import org.apache.sling.discovery.impl.setup.Instance;
import org.apache.sling.discovery.impl.setup.PropertyProviderImpl;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class ClusterTest {
private final Logger logger = LoggerFactory.getLogger(this.getClass());
Instance instance1;
Instance instance2;
Instance instance3;
private String property1Value;
protected String property2Value;
private String property1Name;
private String property2Name;
@Before
public void setup() throws Exception {
logger.debug("here we are");
instance1 = Instance.newStandaloneInstance("firstInstance", true);
instance2 = Instance.newClusterInstance("secondInstance", instance1,
false);
}
@After
public void tearDown() throws Exception {
if (instance3 != null) {
instance3.stop();
}
instance2.stop();
instance1.stop();
instance1 = null;
instance2 = null;
instance3 = null;
}
/** 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.stopHeartbeats();
instance1.stopHeartbeats();
instance2.stop();
instance1.stop();
instance1 = Instance.newStandaloneInstance("/var/discovery/impl/", "firstInstance", true, 1, 1, slingId1);
// 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 = Instance.newClusterInstance("/var/discovery/impl/", "secondInstance", instance1,
false, 1, 1, slingId2);
assertNotNull(instance1);
assertNotNull(instance2);
// the two instances are still isolated - so in a cluster of size 1
assertEquals(1, instance1.getClusterViewService().getClusterView().getInstances().size());
assertEquals(1, instance2.getClusterViewService().getClusterView().getInstances().size());
assertTrue(instance1.getLocalInstanceDescription().isLeader());
assertTrue(instance2.getLocalInstanceDescription().isLeader());
// let the sync/voting happen
instance1.runHeartbeatOnce();
instance2.runHeartbeatOnce();
logger.info("doTestLeader: 2nd sleep 500ms");
Thread.sleep(500);
instance1.runHeartbeatOnce();
instance2.runHeartbeatOnce();
logger.info("doTestLeader: 3rd sleep 500ms");
Thread.sleep(500);
instance1.runHeartbeatOnce();
instance2.runHeartbeatOnce();
// now they must be in the same cluster, so in a cluster of size 1
assertEquals(2, instance1.getClusterViewService().getClusterView().getInstances().size());
assertEquals(2, instance2.getClusterViewService().getClusterView().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");
}
@Test
public void testStableClusterId() throws Throwable {
logger.info("testStableClusterId: start");
// stop 1 and 2 and create them with a lower heartbeat timeout
instance2.stopHeartbeats();
instance1.stopHeartbeats();
instance2.stop();
instance1.stop();
instance1 = Instance.newStandaloneInstance("/var/discovery/impl/", "firstInstance", true, 1, 1);
instance2 = Instance.newClusterInstance("/var/discovery/impl/", "secondInstance", instance1,
false, 1, 1);
assertNotNull(instance1);
assertNotNull(instance2);
String clusterId1 = instance1.getClusterViewService()
.getClusterView().getId();
String clusterId2 = instance2.getClusterViewService()
.getClusterView().getId();
// the cluster ids must differ
assertNotEquals(clusterId1, clusterId2);
assertEquals(1, instance1.getClusterViewService().getClusterView().getInstances().size());
assertEquals(1, instance2.getClusterViewService().getClusterView().getInstances().size());
// let the sync/voting happen
instance1.runHeartbeatOnce();
instance2.runHeartbeatOnce();
Thread.sleep(500);
instance1.runHeartbeatOnce();
instance2.runHeartbeatOnce();
Thread.sleep(500);
instance1.runHeartbeatOnce();
instance2.runHeartbeatOnce();
String newClusterId1 = instance1.getClusterViewService()
.getClusterView().getId();
String newClusterId2 = instance2.getClusterViewService()
.getClusterView().getId();
// both cluster ids must be the same
assertEquals(newClusterId1, newClusterId1);
// either instance1 or instance2 must have kept the cluster id
if (!newClusterId1.equals(clusterId1)) {
assertEquals(newClusterId2, clusterId2);
}
instance1.dumpRepo();
assertEquals(2, instance1.getClusterViewService().getClusterView().getInstances().size());
assertEquals(2, instance2.getClusterViewService().getClusterView().getInstances().size());
// let instance2 'die' by now longer doing heartbeats
instance2.stopHeartbeats(); // would actually not be necessary as it was never started.. this test only runs heartbeats manually
instance1.runHeartbeatOnce();
Thread.sleep(500);
instance1.runHeartbeatOnce();
Thread.sleep(500);
instance1.runHeartbeatOnce();
Thread.sleep(500);
instance1.runHeartbeatOnce();
Thread.sleep(500);
instance1.runHeartbeatOnce();
Thread.sleep(500);
instance1.runHeartbeatOnce();
// the cluster should now have size 1
assertEquals(1, instance1.getClusterViewService().getClusterView().getInstances().size());
// the instance 2 should be in isolated mode as it is no longer in the established view
// hence also size 1
assertEquals(1, instance2.getClusterViewService().getClusterView().getInstances().size());
// but the cluster id must have remained stable
instance1.dumpRepo();
String actualClusterId = instance1.getClusterViewService()
.getClusterView().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 = Instance.newClusterInstance("thirdInstance", instance1,
false);
assertNotNull(instance3);
assertEquals(instance1.getSlingId(), instance1.getClusterViewService()
.getSlingId());
assertEquals(instance2.getSlingId(), instance2.getClusterViewService()
.getSlingId());
assertEquals(instance3.getSlingId(), instance3.getClusterViewService()
.getSlingId());
int numC1 = instance1.getClusterViewService().getClusterView()
.getInstances().size();
assertEquals(1, numC1);
int numC2 = instance2.getClusterViewService().getClusterView()
.getInstances().size();
assertEquals(1, numC2);
int numC3 = instance3.getClusterViewService().getClusterView()
.getInstances().size();
assertEquals(1, numC3);
instance1.dumpRepo();
instance1.runHeartbeatOnce();
instance2.runHeartbeatOnce();
instance3.runHeartbeatOnce();
instance1.dumpRepo();
logger.info("testClusterView: 1st 2s sleep");
Thread.sleep(2000);
instance1.runHeartbeatOnce();
instance2.runHeartbeatOnce();
instance3.runHeartbeatOnce();
logger.info("testClusterView: 2nd 2s sleep");
Thread.sleep(2000);
instance1.dumpRepo();
String clusterId1 = instance1.getClusterViewService().getClusterView()
.getId();
logger.info("clusterId1=" + clusterId1);
String clusterId2 = instance2.getClusterViewService().getClusterView()
.getId();
logger.info("clusterId2=" + clusterId2);
String clusterId3 = instance3.getClusterViewService().getClusterView()
.getId();
logger.info("clusterId3=" + clusterId3);
assertEquals(clusterId1, clusterId2);
assertEquals(clusterId1, clusterId3);
assertEquals(3, instance1.getClusterViewService().getClusterView()
.getInstances().size());
assertEquals(3, instance2.getClusterViewService().getClusterView()
.getInstances().size());
assertEquals(3, instance3.getClusterViewService().getClusterView()
.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());
int numC1 = instance1.getClusterViewService().getClusterView()
.getInstances().size();
assertEquals(1, numC1);
int numC2 = instance2.getClusterViewService().getClusterView()
.getInstances().size();
assertEquals(1, numC2);
instance1.runHeartbeatOnce();
instance2.runHeartbeatOnce();
instance1.dumpRepo();
logger.info("testAdditionalInstance: 1st 2s sleep");
Thread.sleep(2000);
instance1.runHeartbeatOnce();
instance2.runHeartbeatOnce();
logger.info("testAdditionalInstance: 2nd 2s sleep");
Thread.sleep(2000);
instance1.dumpRepo();
String clusterId1 = instance1.getClusterViewService().getClusterView()
.getId();
logger.info("clusterId1=" + clusterId1);
String clusterId2 = instance2.getClusterViewService().getClusterView()
.getId();
logger.info("clusterId2=" + clusterId2);
assertEquals(clusterId1, clusterId2);
assertEquals(2, instance1.getClusterViewService().getClusterView()
.getInstances().size());
assertEquals(2, instance2.getClusterViewService().getClusterView()
.getInstances().size());
AssertingTopologyEventListener assertingTopologyEventListener = new AssertingTopologyEventListener();
assertingTopologyEventListener.addExpected(Type.TOPOLOGY_INIT);
assertEquals(1, assertingTopologyEventListener.getRemainingExpectedCount());
instance1.bindTopologyEventListener(assertingTopologyEventListener);
assertEquals(0, assertingTopologyEventListener.getRemainingExpectedCount());
// startup instance 3
AcceptsMultiple acceptsMultiple = new AcceptsMultiple(
Type.TOPOLOGY_CHANGING, Type.TOPOLOGY_CHANGED);
assertingTopologyEventListener.addExpected(acceptsMultiple);
assertingTopologyEventListener.addExpected(acceptsMultiple);
instance3 = Instance.newClusterInstance("thirdInstance", instance1,
false);
instance1.runHeartbeatOnce();
instance2.runHeartbeatOnce();
instance3.runHeartbeatOnce();
logger.info("testAdditionalInstance: 3rd 2s sleep");
Thread.sleep(2000);
instance1.runHeartbeatOnce();
instance2.runHeartbeatOnce();
instance3.runHeartbeatOnce();
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.runHeartbeatOnce();
instance2.runHeartbeatOnce();
assertNull(instance3);
instance3 = Instance.newClusterInstance("thirdInstance", instance1,
false);
instance3.runHeartbeatOnce();
logger.info("testPropertyProviders: 1st 2s sleep");
Thread.sleep(2000);
instance1.runHeartbeatOnce();
instance2.runHeartbeatOnce();
instance3.runHeartbeatOnce();
logger.info("testPropertyProviders: 2nd 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.runHeartbeatOnce();
instance2.runHeartbeatOnce();
assertPropertyValues();
assertNull(instance1.getClusterViewService().getClusterView()
.getInstances().get(0)
.getProperty(UUID.randomUUID().toString()));
assertNull(instance2.getClusterViewService().getClusterView()
.getInstances().get(0)
.getProperty(UUID.randomUUID().toString()));
logger.info("testPropertyProviders: end");
}
private void assertPropertyValues() {
assertPropertyValues(instance1.getSlingId(), property1Name,
property1Value);
assertPropertyValues(instance2.getSlingId(), property2Name,
property2Value);
}
private void assertPropertyValues(String slingId, String name, String value) {
assertEquals(value, getInstance(instance1, slingId).getProperty(name));
assertEquals(value, getInstance(instance2, slingId).getProperty(name));
}
private InstanceDescription getInstance(Instance instance, String slingId) {
Iterator<InstanceDescription> it = instance.getClusterViewService()
.getClusterView().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);
}
}