| /* |
| * 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 SF 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.fail; |
| |
| import java.util.Iterator; |
| import java.util.LinkedList; |
| import java.util.List; |
| import java.util.Random; |
| |
| import org.apache.sling.commons.testing.junit.categories.Slow; |
| 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.WithholdingAppender; |
| import org.apache.sling.testing.tools.retry.RetryLoop; |
| import org.junit.After; |
| import org.junit.Test; |
| import org.junit.experimental.categories.Category; |
| import org.slf4j.Logger; |
| import org.slf4j.LoggerFactory; |
| |
| /** |
| * Migrated from org.apache.sling.discovery.impl.cluster.ClusterLoadTest |
| */ |
| public abstract class AbstractClusterLoadTest { |
| |
| // wait up to 120 sec - in 1sec wait-intervals |
| private static final int INSTANCE_VIEW_TIMEOUT_SECONDS = 120; |
| private static final int INSTANCE_VIEW_POLL_INTERVAL_MILLIS = 500; |
| |
| private final Random random = new Random(); |
| |
| private final Logger logger = LoggerFactory.getLogger(this.getClass()); |
| |
| List<VirtualInstance> instances = new LinkedList<VirtualInstance>(); |
| |
| @After |
| public void tearDown() throws Exception { |
| if (instances==null || instances.size()==0) { |
| return; |
| } |
| for (Iterator<VirtualInstance> it = instances.iterator(); it.hasNext();) { |
| VirtualInstance i = it.next(); |
| i.stop(); |
| it.remove(); |
| } |
| } |
| |
| public abstract VirtualInstanceBuilder newBuilder(); |
| |
| |
| @Test |
| public void testFramework() throws Exception { |
| logger.info("testFramework: building 1st instance.."); |
| VirtualInstanceBuilder builder = newBuilder() |
| .newRepository("/var/discovery/impl/ClusterLoadTest/testFramework/", true) |
| .setDebugName("firstInstance") |
| .setConnectorPingTimeout(3) |
| .setConnectorPingInterval(20) |
| .setMinEventDelay(0); |
| VirtualInstance firstInstance = builder.build(); |
| instances.add(firstInstance); |
| Thread.sleep(2000); |
| // without any heartbeat action, the discovery service reports its local instance |
| // in so called 'isolated' mode - lets test for that |
| try{ |
| firstInstance.getClusterViewService().getLocalClusterView(); |
| fail("should complain"); |
| } catch(UndefinedClusterViewException e) { |
| // SLING-5030: |
| } |
| firstInstance.startViewChecker(1); |
| Thread.sleep(4000); |
| // after a heartbeat and letting it settle, the discovery service must have |
| // established a view - test for that |
| firstInstance.dumpRepo(); |
| firstInstance.assertEstablishedView(); |
| |
| VirtualInstanceBuilder builder2 = newBuilder() |
| .useRepositoryOf(builder) |
| .setDebugName("secondInstance") |
| .setConnectorPingTimeout(3) |
| .setConnectorPingInterval(20) |
| .setMinEventDelay(0); |
| firstInstance.dumpRepo(); |
| logger.info("testFramework: building 2nd instance.."); |
| VirtualInstance secondInstance = builder2.build(); |
| instances.add(secondInstance); |
| secondInstance.startViewChecker(1); |
| Thread.sleep(4000); |
| firstInstance.dumpRepo(); |
| assertEquals(firstInstance.getClusterViewService().getLocalClusterView().getInstances().size(), 2); |
| assertEquals(secondInstance.getClusterViewService().getLocalClusterView().getInstances().size(), 2); |
| } |
| |
| @Test |
| public void testTwoInstancesFast() throws Throwable { |
| doTest(2, 3); |
| } |
| |
| @Test |
| public void testThreeInstancesFast() throws Throwable { |
| doTest(3, 3); |
| } |
| |
| @Category(Slow.class) |
| @Test |
| public void testTwoInstances() throws Throwable { |
| doTest(2, 5); |
| } |
| |
| @Category(Slow.class) |
| @Test |
| public void testThreeInstances() throws Throwable { |
| doTest(3, 6); |
| } |
| |
| @Category(Slow.class) |
| @Test |
| public void testFourInstances() throws Throwable { |
| doTest(4, 7); |
| } |
| |
| @Category(Slow.class) |
| @Test |
| public void testFiveInstances() throws Throwable { |
| doTest(5, 8); |
| } |
| |
| @Category(Slow.class) |
| @Test |
| public void testSixInstances() throws Throwable { |
| doTest(6, 9); |
| } |
| |
| @Category(Slow.class) |
| @Test |
| public void testSevenInstances() throws Throwable { |
| doTest(7, 10); |
| } |
| |
| @Category(Slow.class) |
| @Test |
| public void testEightInstances() throws Throwable { |
| doTest(8, 50); |
| } |
| |
| private void doTest(final int size, final int loopCnt) throws Throwable { |
| WithholdingAppender withholdingAppender = null; |
| boolean failure = true; |
| try{ |
| logger.info("doTest("+size+","+loopCnt+"): muting log output..."); |
| withholdingAppender = WithholdingAppender.install(); |
| doDoTest(size, loopCnt); |
| failure = false; |
| } finally { |
| if (withholdingAppender!=null) { |
| if (failure) { |
| logger.info("doTest("+size+","+loopCnt+"): writing muted log output due to failure..."); |
| } |
| withholdingAppender.release(failure); |
| if (!failure) { |
| logger.info("doTest("+size+","+loopCnt+"): not writing muted log output due to success..."); |
| } |
| } |
| logger.info("doTest("+size+","+loopCnt+"): unmuted log output."); |
| } |
| } |
| |
| private void doDoTest(final int size, final int loopCnt) throws Throwable { |
| if (size<2) { |
| fail("can only test 2 or more instances"); |
| } |
| VirtualInstanceBuilder builder = newBuilder() |
| .newRepository("/var/discovery/impl/ClusterLoadTest/doTest-"+size+"-"+loopCnt+"/", true) |
| .setDebugName("firstInstance-"+size+"_"+loopCnt) |
| .setConnectorPingTimeout(3) |
| .setConnectorPingInterval(20) |
| .setMinEventDelay(0); |
| VirtualInstance firstInstance = builder.build(); |
| firstInstance.startViewChecker(1); |
| instances.add(firstInstance); |
| for(int i=1; i<size; i++) { |
| VirtualInstanceBuilder builder2 = newBuilder() |
| .useRepositoryOf(builder) |
| .setDebugName("subsequentInstance-"+i+"-"+size+"_"+loopCnt) |
| .setConnectorPingTimeout(3) |
| .setMinEventDelay(0) |
| .setConnectorPingInterval(20); |
| VirtualInstance subsequentInstance = builder2.build(); |
| instances.add(subsequentInstance); |
| subsequentInstance.startViewChecker(1); |
| } |
| |
| for(int i=0; i<loopCnt; i++) { |
| logger.info("====================="); |
| logger.info(" START of LOOP "+i); |
| logger.info("====================="); |
| |
| // count how many instances had heartbeats running in the first place |
| int aliveCnt = 0; |
| for (Iterator<VirtualInstance> it = instances.iterator(); it.hasNext();) { |
| VirtualInstance instance = it.next(); |
| if (instance.isViewCheckerRunning()) { |
| aliveCnt++; |
| } |
| } |
| logger.info("====================="); |
| logger.info(" original aliveCnt "+aliveCnt); |
| logger.info("====================="); |
| if (aliveCnt==0) { |
| // if no one is sending heartbeats, all instances go back to isolated mode |
| aliveCnt=1; |
| } |
| |
| final int aliveCntFinal = aliveCnt; |
| |
| for (Iterator<VirtualInstance> it = instances.iterator(); it.hasNext();) { |
| VirtualInstance instance = it.next(); |
| try { |
| instance.dumpRepo(); |
| } catch (Exception e) { |
| logger.error("Failed dumping repo for instance " + instance.getSlingId(), e); |
| } |
| } |
| |
| // then verify that each instance sees that many instances |
| for (Iterator<VirtualInstance> it = instances.iterator(); it.hasNext();) { |
| final VirtualInstance instance = it.next(); |
| if (!instance.isViewCheckerRunning()) { |
| // if the heartbeat is not running, this instance is considered dead |
| // hence we're not doing any assert here (as the count is only |
| // valid if heartbeat/checkView is running and that would void the test) |
| } else { |
| new RetryLoop(new ConditionImplementation(instance, aliveCntFinal), INSTANCE_VIEW_TIMEOUT_SECONDS, |
| INSTANCE_VIEW_POLL_INTERVAL_MILLIS); |
| } |
| } |
| |
| // start/stop heartbeats accordingly |
| logger.info("Starting/Stopping heartbeats with count="+instances.size()); |
| for (Iterator<VirtualInstance> it = instances.iterator(); it.hasNext();) { |
| VirtualInstance instance = it.next(); |
| if (random.nextBoolean()) { |
| logger.info("Starting heartbeats with "+instance.slingId); |
| instance.startViewChecker(1); |
| logger.info("Started heartbeats with "+instance.slingId); |
| } else { |
| logger.info("Stopping heartbeats with "+instance.slingId); |
| instance.stopViewChecker(); |
| logger.info("Stopped heartbeats with "+instance.slingId); |
| } |
| } |
| |
| } |
| } |
| |
| class ConditionImplementation implements RetryLoop.Condition { |
| |
| private final int expectedAliveCount; |
| private final VirtualInstance instance; |
| |
| private ConditionImplementation(VirtualInstance instance, int expectedAliveCount) { |
| this.expectedAliveCount = expectedAliveCount; |
| this.instance = instance; |
| } |
| |
| public boolean isTrue() throws Exception { |
| boolean result = false; |
| int actualAliveCount = -1; |
| try{ |
| actualAliveCount = instance.getClusterViewService().getLocalClusterView().getInstances().size(); |
| result = expectedAliveCount == actualAliveCount; |
| } catch(UndefinedClusterViewException e) { |
| logger.info("no view at the moment: "+e); |
| return false; |
| } catch(Exception e) { |
| logger.error("isTrue: got exception: "+e, e); |
| throw e; |
| } |
| if (!result) { |
| logger.info("isTrue: expected="+expectedAliveCount+", actual="+actualAliveCount+", result="+result); |
| } |
| return result; |
| } |
| |
| public String getDescription() { |
| return "Waiting for instance with " + instance.getSlingId() + " to see " + expectedAliveCount |
| + " instances"; |
| } |
| } |
| |
| } |