You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@lucene.apache.org by ab...@apache.org on 2018/08/27 13:15:23 UTC
[1/6] lucene-solr:branch_7x: SOLR-12669: Rename tests that use the
autoscaling simulation framework.
Repository: lucene-solr
Updated Branches:
refs/heads/branch_7x 34a8c023b -> b949f57fc
http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/b949f57f/solr/core/src/test/org/apache/solr/cloud/autoscaling/sim/TestTriggerIntegration.java
----------------------------------------------------------------------
diff --git a/solr/core/src/test/org/apache/solr/cloud/autoscaling/sim/TestTriggerIntegration.java b/solr/core/src/test/org/apache/solr/cloud/autoscaling/sim/TestTriggerIntegration.java
deleted file mode 100644
index 9f58364..0000000
--- a/solr/core/src/test/org/apache/solr/cloud/autoscaling/sim/TestTriggerIntegration.java
+++ /dev/null
@@ -1,1322 +0,0 @@
-/*
- * 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.solr.cloud.autoscaling.sim;
-
-import java.lang.invoke.MethodHandles;
-import java.util.ArrayList;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-import java.util.SortedSet;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.atomic.AtomicBoolean;
-import java.util.concurrent.atomic.AtomicInteger;
-import java.util.concurrent.atomic.AtomicLong;
-import java.util.concurrent.locks.ReentrantLock;
-
-import com.google.common.util.concurrent.AtomicDouble;
-import org.apache.lucene.util.LuceneTestCase;
-import org.apache.solr.client.solrj.SolrClient;
-import org.apache.solr.client.solrj.SolrRequest;
-import org.apache.solr.client.solrj.cloud.autoscaling.AutoScalingConfig;
-import org.apache.solr.client.solrj.cloud.autoscaling.ReplicaInfo;
-import org.apache.solr.client.solrj.cloud.SolrCloudManager;
-import org.apache.solr.client.solrj.cloud.autoscaling.TriggerEventProcessorStage;
-import org.apache.solr.client.solrj.cloud.autoscaling.TriggerEventType;
-import org.apache.solr.client.solrj.request.CollectionAdminRequest;
-import org.apache.solr.cloud.CloudTestUtils;
-import org.apache.solr.cloud.autoscaling.ActionContext;
-import org.apache.solr.cloud.autoscaling.ComputePlanAction;
-import org.apache.solr.cloud.autoscaling.ExecutePlanAction;
-import org.apache.solr.cloud.autoscaling.NodeLostTrigger;
-import org.apache.solr.cloud.autoscaling.ScheduledTriggers;
-import org.apache.solr.cloud.autoscaling.SearchRateTrigger;
-import org.apache.solr.cloud.autoscaling.TriggerActionBase;
-import org.apache.solr.cloud.autoscaling.TriggerEvent;
-import org.apache.solr.cloud.autoscaling.TriggerEventQueue;
-import org.apache.solr.cloud.autoscaling.TriggerListenerBase;
-import org.apache.solr.cloud.autoscaling.CapturedEvent;
-import org.apache.solr.cloud.autoscaling.TriggerValidationException;
-import org.apache.solr.common.cloud.LiveNodesListener;
-import org.apache.solr.common.cloud.ZkStateReader;
-import org.apache.solr.common.params.CollectionAdminParams;
-import org.apache.solr.common.util.NamedList;
-import org.apache.solr.common.util.TimeSource;
-import org.apache.solr.core.SolrResourceLoader;
-import org.apache.solr.util.LogLevel;
-import org.apache.solr.util.TimeOut;
-import org.junit.Before;
-import org.junit.BeforeClass;
-import org.junit.Test;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import static org.apache.solr.cloud.autoscaling.AutoScalingHandlerTest.createAutoScalingRequest;
-import static org.apache.solr.cloud.autoscaling.ScheduledTriggers.DEFAULT_SCHEDULED_TRIGGER_DELAY_SECONDS;
-
-/**
- * An end-to-end integration test for triggers
- */
-@LogLevel("org.apache.solr.cloud.autoscaling=DEBUG")
-public class TestTriggerIntegration extends SimSolrCloudTestCase {
- private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
-
- public static final int SPEED = 50;
-
- private static CountDownLatch actionConstructorCalled;
- private static CountDownLatch actionInitCalled;
- private static CountDownLatch triggerFiredLatch;
- private static int waitForSeconds = 1;
- private static CountDownLatch actionStarted;
- private static CountDownLatch actionInterrupted;
- private static CountDownLatch actionCompleted;
- private static CountDownLatch triggerStartedLatch;
- private static CountDownLatch triggerFinishedLatch;
- private static AtomicInteger triggerStartedCount;
- private static AtomicInteger triggerFinishedCount;
- private static AtomicBoolean triggerFired;
- private static Set<TriggerEvent> events = ConcurrentHashMap.newKeySet();
-
- private static final long WAIT_FOR_DELTA_NANOS = TimeUnit.MILLISECONDS.toNanos(5);
-
- @BeforeClass
- public static void setupCluster() throws Exception {
- configureCluster(2, TimeSource.get("simTime:" + SPEED));
- }
-
- private static CountDownLatch getTriggerFiredLatch() {
- return triggerFiredLatch;
- }
-
- private static CountDownLatch getActionStarted() {
- return actionStarted;
- }
-
- private static CountDownLatch getActionInterrupted() {
- return actionInterrupted;
- }
-
- private static CountDownLatch getActionCompleted() {
- return actionCompleted;
- }
-
- @Before
- public void setupTest() throws Exception {
- // disable .scheduled_maintenance
- String suspendTriggerCommand = "{" +
- "'suspend-trigger' : {'name' : '.scheduled_maintenance'}" +
- "}";
- SolrRequest req = createAutoScalingRequest(SolrRequest.METHOD.POST, suspendTriggerCommand);
- SolrClient solrClient = cluster.simGetSolrClient();
- NamedList<Object> response = solrClient.request(req);
- assertEquals(response.get("result").toString(), "success");
-
- waitForSeconds = 1 + random().nextInt(3);
- actionConstructorCalled = new CountDownLatch(1);
- actionInitCalled = new CountDownLatch(1);
- triggerFiredLatch = new CountDownLatch(1);
- triggerFired = new AtomicBoolean(false);
- actionStarted = new CountDownLatch(1);
- actionInterrupted = new CountDownLatch(1);
- actionCompleted = new CountDownLatch(1);
- triggerStartedLatch = new CountDownLatch(1);
- triggerFinishedLatch = new CountDownLatch(1);
- triggerStartedCount = new AtomicInteger();
- triggerFinishedCount = new AtomicInteger();
- events.clear();
- listenerEvents.clear();
- while (cluster.getClusterStateProvider().getLiveNodes().size() < 2) {
- // perhaps a test stopped a node but didn't start it back
- // lets start a node
- cluster.simAddNode();
- }
- // do this in advance if missing
- if (!cluster.getSimClusterStateProvider().simListCollections().contains(CollectionAdminParams.SYSTEM_COLL)) {
- cluster.getSimClusterStateProvider().createSystemCollection();
- CloudTestUtils.waitForState(cluster, CollectionAdminParams.SYSTEM_COLL, 120, TimeUnit.SECONDS,
- CloudTestUtils.clusterShape(1, 1, false, true));
- }
- }
-
- @Test
- @LuceneTestCase.BadApple(bugUrl="https://issues.apache.org/jira/browse/SOLR-12028") // 2018-06-18
- public void testTriggerThrottling() throws Exception {
- // for this test we want to create two triggers so we must assert that the actions were created twice
- actionInitCalled = new CountDownLatch(2);
- // similarly we want both triggers to fire
- triggerFiredLatch = new CountDownLatch(2);
-
- SolrClient solrClient = cluster.simGetSolrClient();
-
- // first trigger
- String setTriggerCommand = "{" +
- "'set-trigger' : {" +
- "'name' : 'node_added_trigger1'," +
- "'event' : 'nodeAdded'," +
- "'waitFor' : '0s'," +
- "'enabled' : true," +
- "'actions' : [{'name':'test','class':'" + ThrottlingTesterAction.class.getName() + "'}]" +
- "}}";
- SolrRequest req = createAutoScalingRequest(SolrRequest.METHOD.POST, setTriggerCommand);
- NamedList<Object> response = solrClient.request(req);
- assertEquals(response.get("result").toString(), "success");
-
- // second trigger
- setTriggerCommand = "{" +
- "'set-trigger' : {" +
- "'name' : 'node_added_trigger2'," +
- "'event' : 'nodeAdded'," +
- "'waitFor' : '0s'," +
- "'enabled' : true," +
- "'actions' : [{'name':'test','class':'" + ThrottlingTesterAction.class.getName() + "'}]" +
- "}}";
- req = createAutoScalingRequest(SolrRequest.METHOD.POST, setTriggerCommand);
- response = solrClient.request(req);
- assertEquals(response.get("result").toString(), "success");
-
- // wait until the two instances of action are created
- if (!actionInitCalled.await(3000 / SPEED, TimeUnit.MILLISECONDS)) {
- fail("Two TriggerAction instances should have been created by now");
- }
-
- String newNode = cluster.simAddNode();
-
- if (!triggerFiredLatch.await(20000 / SPEED, TimeUnit.MILLISECONDS)) {
- fail("Both triggers should have fired by now");
- }
-
- // reset shared state
- lastActionExecutedAt.set(0);
- TestTriggerIntegration.actionInitCalled = new CountDownLatch(2);
- triggerFiredLatch = new CountDownLatch(2);
-
- setTriggerCommand = "{" +
- "'set-trigger' : {" +
- "'name' : 'node_lost_trigger1'," +
- "'event' : 'nodeLost'," +
- "'waitFor' : '0s'," +
- "'enabled' : true," +
- "'actions' : [{'name':'test','class':'" + ThrottlingTesterAction.class.getName() + "'}]" +
- "}}";
- req = createAutoScalingRequest(SolrRequest.METHOD.POST, setTriggerCommand);
- response = solrClient.request(req);
- assertEquals(response.get("result").toString(), "success");
-
- setTriggerCommand = "{" +
- "'set-trigger' : {" +
- "'name' : 'node_lost_trigger2'," +
- "'event' : 'nodeLost'," +
- "'waitFor' : '0s'," +
- "'enabled' : true," +
- "'actions' : [{'name':'test','class':'" + ThrottlingTesterAction.class.getName() + "'}]" +
- "}}";
- req = createAutoScalingRequest(SolrRequest.METHOD.POST, setTriggerCommand);
- response = solrClient.request(req);
- assertEquals(response.get("result").toString(), "success");
-
- // wait until the two instances of action are created
- if (!actionInitCalled.await(3000 / SPEED, TimeUnit.MILLISECONDS)) {
- fail("Two TriggerAction instances should have been created by now");
- }
-
- // stop the node we had started earlier
- cluster.simRemoveNode(newNode, false);
-
- if (!triggerFiredLatch.await(20000 / SPEED, TimeUnit.MILLISECONDS)) {
- fail("Both triggers should have fired by now");
- }
- }
-
- static AtomicLong lastActionExecutedAt = new AtomicLong(0);
- static ReentrantLock lock = new ReentrantLock();
- public static class ThrottlingTesterAction extends TestTriggerAction {
- // nanos are very precise so we need a delta for comparison with ms
- private static final long DELTA_MS = 2;
-
- // sanity check that an action instance is only invoked once
- private final AtomicBoolean onlyOnce = new AtomicBoolean(false);
-
- @Override
- public void process(TriggerEvent event, ActionContext actionContext) {
- boolean locked = lock.tryLock();
- if (!locked) {
- log.info("We should never have a tryLock fail because actions are never supposed to be executed concurrently");
- return;
- }
- try {
- if (lastActionExecutedAt.get() != 0) {
- log.info("last action at " + lastActionExecutedAt.get() + " time = " + cluster.getTimeSource().getTimeNs());
- if (TimeUnit.NANOSECONDS.toMillis(cluster.getTimeSource().getTimeNs() - lastActionExecutedAt.get()) <
- TimeUnit.SECONDS.toMillis(ScheduledTriggers.DEFAULT_ACTION_THROTTLE_PERIOD_SECONDS) - DELTA_MS) {
- log.info("action executed again before minimum wait time from {}", event.getSource());
- fail("TriggerListener was fired before the throttling period");
- }
- }
- if (onlyOnce.compareAndSet(false, true)) {
- log.info("action executed from {}", event.getSource());
- lastActionExecutedAt.set(cluster.getTimeSource().getTimeNs());
- getTriggerFiredLatch().countDown();
- } else {
- log.info("action executed more than once from {}", event.getSource());
- fail("Trigger should not have fired more than once!");
- }
- } finally {
- lock.unlock();
- }
- }
- }
-
- @Test
- // commented 20-July-2018 @BadApple(bugUrl="https://issues.apache.org/jira/browse/SOLR-12028")
- @BadApple(bugUrl="https://issues.apache.org/jira/browse/SOLR-12028") // added 09-Aug-2018
- public void testNodeLostTriggerRestoreState() throws Exception {
- // for this test we want to update the trigger so we must assert that the actions were created twice
- TestTriggerIntegration.actionInitCalled = new CountDownLatch(2);
-
- // start a new node
- String nodeName = cluster.simAddNode();
-
- SolrClient solrClient = cluster.simGetSolrClient();
- waitForSeconds = 5;
- String setTriggerCommand = "{" +
- "'set-trigger' : {" +
- "'name' : 'node_lost_restore_trigger'," +
- "'event' : 'nodeLost'," +
- "'waitFor' : '5s'," + // should be enough for us to update the trigger
- "'enabled' : true," +
- "'actions' : [{'name':'test','class':'" + TestTriggerAction.class.getName() + "'}]" +
- "}}";
- SolrRequest req = createAutoScalingRequest(SolrRequest.METHOD.POST, setTriggerCommand);
- NamedList<Object> response = solrClient.request(req);
- assertEquals(response.get("result").toString(), "success");
-
- TimeOut timeOut = new TimeOut(2, TimeUnit.SECONDS, cluster.getTimeSource());
- while (actionInitCalled.getCount() == 0 && !timeOut.hasTimedOut()) {
- timeOut.sleep(200);
- }
- assertTrue("The action specified in node_lost_restore_trigger was not instantiated even after 2 seconds", actionInitCalled.getCount() > 0);
-
- cluster.simRemoveNode(nodeName, false);
-
- // ensure that the old trigger sees the stopped node, todo find a better way to do this
- timeOut.sleep(500 + TimeUnit.SECONDS.toMillis(DEFAULT_SCHEDULED_TRIGGER_DELAY_SECONDS));
-
- waitForSeconds = 0;
- setTriggerCommand = "{" +
- "'set-trigger' : {" +
- "'name' : 'node_lost_restore_trigger'," +
- "'event' : 'nodeLost'," +
- "'waitFor' : '0s'," + // update a property so that it replaces the old trigger, also we want it to fire immediately
- "'enabled' : true," +
- "'actions' : [{'name':'test','class':'" + TestTriggerAction.class.getName() + "'}]" +
- "}}";
- req = createAutoScalingRequest(SolrRequest.METHOD.POST, setTriggerCommand);
- response = solrClient.request(req);
- assertEquals(response.get("result").toString(), "success");
-
- // wait until the second instance of action is created
- if (!actionInitCalled.await(3000 / SPEED, TimeUnit.MILLISECONDS)) {
- fail("Two TriggerAction instances should have been created by now");
- }
-
- boolean await = triggerFiredLatch.await(5000 / SPEED, TimeUnit.MILLISECONDS);
- assertTrue("The trigger did not fire at all", await);
- assertTrue(triggerFired.get());
- NodeLostTrigger.NodeLostEvent nodeLostEvent = (NodeLostTrigger.NodeLostEvent) events.iterator().next();
- assertNotNull(nodeLostEvent);
- List<String> nodeNames = (List<String>)nodeLostEvent.getProperty(TriggerEvent.NODE_NAMES);
- assertTrue(nodeNames.contains(nodeName));
- }
-
- @Test
- @BadApple(bugUrl="https://issues.apache.org/jira/browse/SOLR-12028") // 09-Apr-2018
- public void testNodeAddedTriggerRestoreState() throws Exception {
- // for this test we want to update the trigger so we must assert that the actions were created twice
- TestTriggerIntegration.actionInitCalled = new CountDownLatch(2);
-
- SolrClient solrClient = cluster.simGetSolrClient();
- waitForSeconds = 5;
- String setTriggerCommand = "{" +
- "'set-trigger' : {" +
- "'name' : 'node_added_restore_trigger'," +
- "'event' : 'nodeAdded'," +
- "'waitFor' : '5s'," + // should be enough for us to update the trigger
- "'enabled' : true," +
- "'actions' : [{'name':'test','class':'" + TestTriggerAction.class.getName() + "'}]" +
- "}}";
- SolrRequest req = createAutoScalingRequest(SolrRequest.METHOD.POST, setTriggerCommand);
- NamedList<Object> response = solrClient.request(req);
- assertEquals(response.get("result").toString(), "success");
-
- TimeOut timeOut = new TimeOut(2, TimeUnit.SECONDS, cluster.getTimeSource());
- while (actionInitCalled.getCount() == 0 && !timeOut.hasTimedOut()) {
- timeOut.sleep(200);
- }
- assertTrue("The action specified in node_added_restore_trigger was not instantiated even after 2 seconds", actionInitCalled.getCount() > 0);
-
- // start a new node
- String newNode = cluster.simAddNode();
-
- // ensure that the old trigger sees the new node, todo find a better way to do this
- cluster.getTimeSource().sleep(500 + TimeUnit.SECONDS.toMillis(DEFAULT_SCHEDULED_TRIGGER_DELAY_SECONDS));
-
- waitForSeconds = 0;
- setTriggerCommand = "{" +
- "'set-trigger' : {" +
- "'name' : 'node_added_restore_trigger'," +
- "'event' : 'nodeAdded'," +
- "'waitFor' : '0s'," + // update a property so that it replaces the old trigger, also we want it to fire immediately
- "'enabled' : true," +
- "'actions' : [{'name':'test','class':'" + TestTriggerAction.class.getName() + "'}]" +
- "}}";
- req = createAutoScalingRequest(SolrRequest.METHOD.POST, setTriggerCommand);
- response = solrClient.request(req);
- assertEquals(response.get("result").toString(), "success");
-
- // wait until the second instance of action is created
- if (!actionInitCalled.await(3000 / SPEED, TimeUnit.MILLISECONDS)) {
- fail("Two TriggerAction instances should have been created by now");
- }
-
- boolean await = triggerFiredLatch.await(5000 / SPEED, TimeUnit.MILLISECONDS);
- assertTrue("The trigger did not fire at all", await);
- assertTrue(triggerFired.get());
- TriggerEvent nodeAddedEvent = events.iterator().next();
- assertNotNull(nodeAddedEvent);
- List<String> nodeNames = (List<String>)nodeAddedEvent.getProperty(TriggerEvent.NODE_NAMES);
- assertTrue(nodeNames.toString(), nodeNames.contains(newNode));
- }
-
- @Test
- @LuceneTestCase.BadApple(bugUrl="https://issues.apache.org/jira/browse/SOLR-12028") // 2018-06-18
- public void testNodeAddedTrigger() throws Exception {
- SolrClient solrClient = cluster.simGetSolrClient();
- String setTriggerCommand = "{" +
- "'set-trigger' : {" +
- "'name' : 'node_added_trigger'," +
- "'event' : 'nodeAdded'," +
- "'waitFor' : '" + waitForSeconds + "s'," +
- "'enabled' : true," +
- "'actions' : [{'name':'test','class':'" + TestTriggerAction.class.getName() + "'}]" +
- "}}";
- SolrRequest req = createAutoScalingRequest(SolrRequest.METHOD.POST, setTriggerCommand);
- NamedList<Object> response = solrClient.request(req);
- assertEquals(response.get("result").toString(), "success");
-
- if (!actionInitCalled.await(5000 / SPEED, TimeUnit.MILLISECONDS)) {
- fail("The TriggerAction should have been created by now");
- }
-
- String newNode = cluster.simAddNode();
- boolean await = triggerFiredLatch.await(20000 / SPEED, TimeUnit.MILLISECONDS);
- assertTrue("The trigger did not fire at all", await);
- assertTrue(triggerFired.get());
- TriggerEvent nodeAddedEvent = events.iterator().next();
- assertNotNull(nodeAddedEvent);
- List<String> nodeNames = (List<String>)nodeAddedEvent.getProperty(TriggerEvent.NODE_NAMES);
- assertTrue(nodeAddedEvent.toString(), nodeNames.contains(newNode));
-
- // reset
- actionConstructorCalled = new CountDownLatch(1);
- actionInitCalled = new CountDownLatch(1);
-
- // update the trigger with exactly the same data
- setTriggerCommand = "{" +
- "'set-trigger' : {" +
- "'name' : 'node_added_trigger'," +
- "'event' : 'nodeAdded'," +
- "'waitFor' : '" + waitForSeconds + "s'," +
- "'enabled' : true," +
- "'actions' : [{'name':'test','class':'" + TestTriggerAction.class.getName() + "'}]" +
- "}}";
- req = createAutoScalingRequest(SolrRequest.METHOD.POST, setTriggerCommand);
- response = solrClient.request(req);
- assertEquals(response.get("result").toString(), "success");
-
- // this should be a no-op so the action should have been created but init should not be called
- if (!actionConstructorCalled.await(3000 / SPEED, TimeUnit.MILLISECONDS)) {
- fail("The TriggerAction should have been created by now");
- }
-
- assertFalse(actionInitCalled.await(2000 / SPEED, TimeUnit.MILLISECONDS));
- }
-
- @Test
- @BadApple(bugUrl="https://issues.apache.org/jira/browse/SOLR-12028") // 26-Mar-2018
- public void testNodeLostTrigger() throws Exception {
- SolrClient solrClient = cluster.simGetSolrClient();
- String setTriggerCommand = "{" +
- "'set-trigger' : {" +
- "'name' : 'node_lost_trigger'," +
- "'event' : 'nodeLost'," +
- "'waitFor' : '" + waitForSeconds + "s'," +
- "'enabled' : true," +
- "'actions' : [{'name':'test','class':'" + TestTriggerAction.class.getName() + "'}]" +
- "}}";
- SolrRequest req = createAutoScalingRequest(SolrRequest.METHOD.POST, setTriggerCommand);
- NamedList<Object> response = solrClient.request(req);
- assertEquals(response.get("result").toString(), "success");
-
- if (!actionInitCalled.await(5000 / SPEED, TimeUnit.MILLISECONDS)) {
- fail("The TriggerAction should have been created by now");
- }
-
- String lostNodeName = cluster.getSimClusterStateProvider().simGetRandomNode(random());
- cluster.simRemoveNode(lostNodeName, false);
- boolean await = triggerFiredLatch.await(20000 / SPEED, TimeUnit.MILLISECONDS);
- assertTrue("The trigger did not fire at all", await);
- assertTrue(triggerFired.get());
- TriggerEvent nodeLostEvent = events.iterator().next();
- assertNotNull(nodeLostEvent);
- List<String> nodeNames = (List<String>)nodeLostEvent.getProperty(TriggerEvent.NODE_NAMES);
- assertTrue(nodeNames.contains(lostNodeName));
-
- // reset
- actionConstructorCalled = new CountDownLatch(1);
- actionInitCalled = new CountDownLatch(1);
-
- // update the trigger with exactly the same data
- setTriggerCommand = "{" +
- "'set-trigger' : {" +
- "'name' : 'node_lost_trigger'," +
- "'event' : 'nodeLost'," +
- "'waitFor' : '" + waitForSeconds + "s'," +
- "'enabled' : true," +
- "'actions' : [{'name':'test','class':'" + TestTriggerAction.class.getName() + "'}]" +
- "}}";
- req = createAutoScalingRequest(SolrRequest.METHOD.POST, setTriggerCommand);
- response = solrClient.request(req);
- assertEquals(response.get("result").toString(), "success");
-
- // this should be a no-op so the action should have been created but init should not be called
- if (!actionConstructorCalled.await(3000 / SPEED, TimeUnit.MILLISECONDS)) {
- fail("The TriggerAction should have been created by now");
- }
-
- assertFalse(actionInitCalled.await(2000 / SPEED, TimeUnit.MILLISECONDS));
- }
-
- // simulator doesn't support overseer functionality yet
- /*
- @Test
- public void testContinueTriggersOnOverseerRestart() throws Exception {
- CollectionAdminRequest.OverseerStatus status = new CollectionAdminRequest.OverseerStatus();
- CloudSolrClient solrClient = cluster.getSolrClient();
- CollectionAdminResponse adminResponse = status.process(solrClient);
- NamedList<Object> response = adminResponse.getResponse();
- String leader = (String) response.get("leader");
- JettySolrRunner overseerNode = null;
- int index = -1;
- List<JettySolrRunner> jettySolrRunners = cluster.getJettySolrRunners();
- for (int i = 0; i < jettySolrRunners.size(); i++) {
- JettySolrRunner runner = jettySolrRunners.get(i);
- if (runner.getNodeName().equals(leader)) {
- overseerNode = runner;
- index = i;
- break;
- }
- }
- assertNotNull(overseerNode);
-
- String setTriggerCommand = "{" +
- "'set-trigger' : {" +
- "'name' : 'node_added_trigger'," +
- "'event' : 'nodeAdded'," +
- "'waitFor' : '" + waitForSeconds + "s'," +
- "'enabled' : true," +
- "'actions' : [{'name':'test','class':'" + TestTriggerAction.class.getName() + "'}]" +
- "}}";
- SolrRequest req = createAutoScalingRequest(SolrRequest.METHOD.POST, setTriggerCommand);
- response = solrClient.request(req);
- assertEquals(response.get("result").toString(), "success");
- if (!actionInitCalled.await(3, TimeUnit.SECONDS)) {
- fail("The TriggerAction should have been created by now");
- }
-
- // stop the overseer, somebody else will take over as the overseer
- cluster.stopJettySolrRunner(index);
- Thread.sleep(10000);
- JettySolrRunner newNode = cluster.startJettySolrRunner();
- boolean await = triggerFiredLatch.await(20, TimeUnit.SECONDS);
- assertTrue("The trigger did not fire at all", await);
- assertTrue(triggerFired.get());
- NodeAddedTrigger.NodeAddedEvent nodeAddedEvent = (NodeAddedTrigger.NodeAddedEvent) events.iterator().next();
- assertNotNull(nodeAddedEvent);
- List<String> nodeNames = (List<String>)nodeAddedEvent.getProperty(TriggerEvent.NODE_NAMES);
- assertTrue(nodeNames.contains(newNode.getNodeName()));
- }
-
-*/
-
- public static class TestTriggerAction extends TriggerActionBase {
-
- public TestTriggerAction() {
- actionConstructorCalled.countDown();
- }
-
- @Override
- public void process(TriggerEvent event, ActionContext actionContext) {
- try {
- if (triggerFired.compareAndSet(false, true)) {
- events.add(event);
- long currentTimeNanos = cluster.getTimeSource().getTimeNs();
- long eventTimeNanos = event.getEventTime();
- long waitForNanos = TimeUnit.NANOSECONDS.convert(waitForSeconds, TimeUnit.SECONDS) - WAIT_FOR_DELTA_NANOS;
- if (currentTimeNanos - eventTimeNanos <= waitForNanos) {
- fail(event.getSource() + " was fired before the configured waitFor period");
- }
- getTriggerFiredLatch().countDown();
- } else {
- fail(event.getSource() + " was fired more than once!");
- }
- } catch (Throwable t) {
- log.debug("--throwable", t);
- throw t;
- }
- }
-
- @Override
- public void init() throws Exception {
- log.info("TestTriggerAction init");
- super.init();
- actionInitCalled.countDown();
- }
-
- }
-
- public static class TestEventQueueAction extends TriggerActionBase {
-
- public TestEventQueueAction() {
- log.info("TestEventQueueAction instantiated");
- }
-
- @Override
- public void process(TriggerEvent event, ActionContext actionContext) {
- log.info("-- event: " + event);
- events.add(event);
- getActionStarted().countDown();
- try {
- Thread.sleep(eventQueueActionWait);
- triggerFired.compareAndSet(false, true);
- getActionCompleted().countDown();
- } catch (InterruptedException e) {
- getActionInterrupted().countDown();
- return;
- }
- }
-
- @Override
- public void configure(SolrResourceLoader loader, SolrCloudManager cloudManager, Map<String, Object> args) throws TriggerValidationException {
- log.debug("TestTriggerAction init");
- actionInitCalled.countDown();
- super.configure(loader, cloudManager, args);
- }
- }
-
- public static long eventQueueActionWait = 5000;
-
- @Test
- @BadApple(bugUrl="https://issues.apache.org/jira/browse/SOLR-12028") // 16-Apr-2018
- public void testEventQueue() throws Exception {
- waitForSeconds = 1;
- SolrClient solrClient = cluster.simGetSolrClient();
- String setTriggerCommand = "{" +
- "'set-trigger' : {" +
- "'name' : 'node_added_trigger1'," +
- "'event' : 'nodeAdded'," +
- "'waitFor' : '" + waitForSeconds + "s'," +
- "'enabled' : true," +
- "'actions' : [{'name':'test','class':'" + TestEventQueueAction.class.getName() + "'}]" +
- "}}";
-
- String overseerLeader = cluster.getSimClusterStateProvider().simGetRandomNode(random());
-
- SolrRequest req = createAutoScalingRequest(SolrRequest.METHOD.POST, setTriggerCommand);
- NamedList<Object> response = solrClient.request(req);
- assertEquals(response.get("result").toString(), "success");
-
- if (!actionInitCalled.await(3000 / SPEED, TimeUnit.MILLISECONDS)) {
- fail("The TriggerAction should have been created by now");
- }
-
- // add node to generate the event
- String newNode = cluster.simAddNode();
- boolean await = actionStarted.await(60000 / SPEED, TimeUnit.MILLISECONDS);
- assertTrue("action did not start", await);
- // event should be there
- TriggerEvent nodeAddedEvent = events.iterator().next();
- assertNotNull(nodeAddedEvent);
- // but action did not complete yet so the event is still enqueued
- assertFalse(triggerFired.get());
- events.clear();
- actionStarted = new CountDownLatch(1);
- eventQueueActionWait = 1;
- // kill overseer
- cluster.simRestartOverseer(overseerLeader);
- cluster.getTimeSource().sleep(5000);
- // new overseer leader should be elected and run triggers
- await = actionInterrupted.await(3000 / SPEED, TimeUnit.MILLISECONDS);
- assertTrue("action wasn't interrupted", await);
- // it should fire again from enqueued event
- await = actionStarted.await(60000 / SPEED, TimeUnit.MILLISECONDS);
- assertTrue("action wasn't started", await);
- TriggerEvent replayedEvent = events.iterator().next();
- assertTrue(replayedEvent.getProperty(TriggerEventQueue.ENQUEUE_TIME) != null);
- assertTrue(events + "\n" + replayedEvent.toString(), replayedEvent.getProperty(TriggerEventQueue.DEQUEUE_TIME) != null);
- await = actionCompleted.await(10000 / SPEED, TimeUnit.MILLISECONDS);
- assertTrue("action wasn't completed", await);
- assertTrue(triggerFired.get());
- }
-
- @Test
- @BadApple(bugUrl="https://issues.apache.org/jira/browse/SOLR-12028") //2018-03-10
- public void testEventFromRestoredState() throws Exception {
- SolrClient solrClient = cluster.simGetSolrClient();
- String setTriggerCommand = "{" +
- "'set-trigger' : {" +
- "'name' : 'node_added_trigger'," +
- "'event' : 'nodeAdded'," +
- "'waitFor' : '10s'," +
- "'enabled' : true," +
- "'actions' : [{'name':'test','class':'" + TestTriggerAction.class.getName() + "'}]" +
- "}}";
- SolrRequest req = createAutoScalingRequest(SolrRequest.METHOD.POST, setTriggerCommand);
- NamedList<Object> response = solrClient.request(req);
- assertEquals(response.get("result").toString(), "success");
-
- if (!actionInitCalled.await(10000 / SPEED, TimeUnit.MILLISECONDS)) {
- fail("The TriggerAction should have been created by now");
- }
-
- events.clear();
-
- String newNode = cluster.simAddNode();
- boolean await = triggerFiredLatch.await(20000 / SPEED, TimeUnit.MILLISECONDS);
- assertTrue("The trigger did not fire at all", await);
- assertTrue(triggerFired.get());
- // reset
- triggerFired.set(false);
- triggerFiredLatch = new CountDownLatch(1);
- TriggerEvent nodeAddedEvent = events.iterator().next();
- assertNotNull(nodeAddedEvent);
- List<String> nodeNames = (List<String>)nodeAddedEvent.getProperty(TriggerEvent.NODE_NAMES);
- assertTrue(nodeNames.contains(newNode));
- // add a second node - state of the trigger will change but it won't fire for waitFor sec.
- String newNode2 = cluster.simAddNode();
- cluster.getTimeSource().sleep(10000);
- // kill overseer
- cluster.simRestartOverseer(null);
- await = triggerFiredLatch.await(20000 / SPEED, TimeUnit.MILLISECONDS);
- assertTrue("The trigger did not fire at all", await);
- assertTrue(triggerFired.get());
- }
-
- private static class TestLiveNodesListener implements LiveNodesListener {
- Set<String> lostNodes = new HashSet<>();
- Set<String> addedNodes = new HashSet<>();
- CountDownLatch onChangeLatch = new CountDownLatch(1);
-
- public void reset() {
- lostNodes.clear();
- addedNodes.clear();
- onChangeLatch = new CountDownLatch(1);
- }
-
- @Override
- public void onChange(SortedSet<String> oldLiveNodes, SortedSet<String> newLiveNodes) {
- onChangeLatch.countDown();
- Set<String> old = new HashSet<>(oldLiveNodes);
- old.removeAll(newLiveNodes);
- if (!old.isEmpty()) {
- lostNodes.addAll(old);
- }
- newLiveNodes.removeAll(oldLiveNodes);
- if (!newLiveNodes.isEmpty()) {
- addedNodes.addAll(newLiveNodes);
- }
- }
- }
-
- private TestLiveNodesListener registerLiveNodesListener() {
- TestLiveNodesListener listener = new TestLiveNodesListener();
- cluster.getLiveNodesSet().registerLiveNodesListener(listener);
- return listener;
- }
-
- public static class TestEventMarkerAction extends TriggerActionBase {
-
- public TestEventMarkerAction() {
- actionConstructorCalled.countDown();
- }
-
- @Override
- public void process(TriggerEvent event, ActionContext actionContext) {
- boolean locked = lock.tryLock();
- if (!locked) {
- log.info("We should never have a tryLock fail because actions are never supposed to be executed concurrently");
- return;
- }
- try {
- events.add(event);
- getTriggerFiredLatch().countDown();
- } catch (Throwable t) {
- log.debug("--throwable", t);
- throw t;
- } finally {
- lock.unlock();
- }
- }
-
- @Override
- public void configure(SolrResourceLoader loader, SolrCloudManager cloudManager, Map<String, Object> args) throws TriggerValidationException {
- log.info("TestEventMarkerAction init");
- actionInitCalled.countDown();
- super.configure(loader, cloudManager, args);
- }
- }
-
- @Test
- @BadApple(bugUrl="https://issues.apache.org/jira/browse/SOLR-12028")
- public void testNodeMarkersRegistration() throws Exception {
- // for this test we want to create two triggers so we must assert that the actions were created twice
- actionInitCalled = new CountDownLatch(2);
- // similarly we want both triggers to fire
- triggerFiredLatch = new CountDownLatch(2);
- TestLiveNodesListener listener = registerLiveNodesListener();
-
- SolrClient solrClient = cluster.simGetSolrClient();
-
- // pick overseer node
- String overseerLeader = cluster.getSimClusterStateProvider().simGetRandomNode(random());
-
- // add a node
- String node = cluster.simAddNode();
- if (!listener.onChangeLatch.await(10000 / SPEED, TimeUnit.MILLISECONDS)) {
- fail("onChange listener didn't execute on cluster change");
- }
- assertEquals(1, listener.addedNodes.size());
- assertEquals(node, listener.addedNodes.iterator().next());
- // verify that a znode doesn't exist (no trigger)
- String pathAdded = ZkStateReader.SOLR_AUTOSCALING_NODE_ADDED_PATH + "/" + node;
- assertFalse("Path " + pathAdded + " was created but there are no nodeAdded triggers",
- cluster.getDistribStateManager().hasData(pathAdded));
- listener.reset();
- // stop overseer
- log.info("====== KILL OVERSEER 1");
- cluster.simRestartOverseer(overseerLeader);
- if (!listener.onChangeLatch.await(10000 / SPEED, TimeUnit.MILLISECONDS)) {
- fail("onChange listener didn't execute on cluster change");
- }
- assertEquals(1, listener.lostNodes.size());
- assertEquals(overseerLeader, listener.lostNodes.iterator().next());
- assertEquals(0, listener.addedNodes.size());
- // wait until the new overseer is up
- cluster.getTimeSource().sleep(5000);
- // verify that a znode does NOT exist - there's no nodeLost trigger,
- // so the new overseer cleaned up existing nodeLost markers
- String pathLost = ZkStateReader.SOLR_AUTOSCALING_NODE_LOST_PATH + "/" + overseerLeader;
- assertFalse("Path " + pathLost + " exists", cluster.getDistribStateManager().hasData(pathLost));
-
- listener.reset();
-
- // set up triggers
-
- log.info("====== ADD TRIGGERS");
- String setTriggerCommand = "{" +
- "'set-trigger' : {" +
- "'name' : 'node_added_trigger'," +
- "'event' : 'nodeAdded'," +
- "'waitFor' : '1s'," +
- "'enabled' : true," +
- "'actions' : [{'name':'test','class':'" + TestEventMarkerAction.class.getName() + "'}]" +
- "}}";
- SolrRequest req = createAutoScalingRequest(SolrRequest.METHOD.POST, setTriggerCommand);
- NamedList<Object> response = solrClient.request(req);
- assertEquals(response.get("result").toString(), "success");
-
- setTriggerCommand = "{" +
- "'set-trigger' : {" +
- "'name' : 'node_lost_trigger'," +
- "'event' : 'nodeLost'," +
- "'waitFor' : '1s'," +
- "'enabled' : true," +
- "'actions' : [{'name':'test','class':'" + TestEventMarkerAction.class.getName() + "'}]" +
- "}}";
- req = createAutoScalingRequest(SolrRequest.METHOD.POST, setTriggerCommand);
- response = solrClient.request(req);
- assertEquals(response.get("result").toString(), "success");
-
- overseerLeader = cluster.getSimClusterStateProvider().simGetRandomNode(random());
-
- // create another node
- log.info("====== ADD NODE 1");
- String node1 = cluster.simAddNode();
- if (!listener.onChangeLatch.await(10000 / SPEED, TimeUnit.MILLISECONDS)) {
- fail("onChange listener didn't execute on cluster change");
- }
- assertEquals(1, listener.addedNodes.size());
- assertEquals(node1, listener.addedNodes.iterator().next());
- // verify that a znode exists
- pathAdded = ZkStateReader.SOLR_AUTOSCALING_NODE_ADDED_PATH + "/" + node1;
- assertTrue("Path " + pathAdded + " wasn't created", cluster.getDistribStateManager().hasData(pathAdded));
-
- cluster.getTimeSource().sleep(5000);
- // nodeAdded marker should be consumed now by nodeAdded trigger
- assertFalse("Path " + pathAdded + " should have been deleted",
- cluster.getDistribStateManager().hasData(pathAdded));
-
- listener.reset();
- events.clear();
- triggerFiredLatch = new CountDownLatch(1);
- // kill overseer again
- log.info("====== KILL OVERSEER 2");
- cluster.simRestartOverseer(overseerLeader);
- if (!listener.onChangeLatch.await(10000 / SPEED, TimeUnit.MILLISECONDS)) {
- fail("onChange listener didn't execute on cluster change");
- }
-
-
- if (!triggerFiredLatch.await(20000 / SPEED, TimeUnit.MILLISECONDS)) {
- fail("Trigger should have fired by now");
- }
- assertEquals(1, events.size());
- TriggerEvent ev = events.iterator().next();
- List<String> nodeNames = (List<String>)ev.getProperty(TriggerEvent.NODE_NAMES);
- assertTrue(nodeNames.contains(overseerLeader));
- assertEquals(TriggerEventType.NODELOST, ev.getEventType());
- }
-
- static Map<String, List<CapturedEvent>> listenerEvents = new ConcurrentHashMap<>();
- static List<CapturedEvent> allListenerEvents = new ArrayList<>();
- static CountDownLatch listenerCreated = new CountDownLatch(1);
- static boolean failDummyAction = false;
-
- public static class TestTriggerListener extends TriggerListenerBase {
- @Override
- public void configure(SolrResourceLoader loader, SolrCloudManager cloudManager, AutoScalingConfig.TriggerListenerConfig config) throws TriggerValidationException {
- super.configure(loader, cloudManager, config);
- listenerCreated.countDown();
- }
-
- @Override
- public synchronized void onEvent(TriggerEvent event, TriggerEventProcessorStage stage, String actionName,
- ActionContext context, Throwable error, String message) {
- List<CapturedEvent> lst = listenerEvents.computeIfAbsent(config.name, s -> new ArrayList<>());
- CapturedEvent ev = new CapturedEvent(cluster.getTimeSource().getTimeNs(), context, config, stage, actionName, event, message);
- lst.add(ev);
- allListenerEvents.add(ev);
- }
- }
-
- public static class TestDummyAction extends TriggerActionBase {
-
- @Override
- public void process(TriggerEvent event, ActionContext context) {
- if (failDummyAction) {
- throw new RuntimeException("failure");
- }
-
- }
- }
-
- @Test
- @BadApple(bugUrl="https://issues.apache.org/jira/browse/SOLR-12028")
- public void testListeners() throws Exception {
- SolrClient solrClient = cluster.simGetSolrClient();
- String setTriggerCommand = "{" +
- "'set-trigger' : {" +
- "'name' : 'node_added_trigger'," +
- "'event' : 'nodeAdded'," +
- "'waitFor' : '" + waitForSeconds + "s'," +
- "'enabled' : true," +
- "'actions' : [" +
- "{'name':'test','class':'" + TestTriggerAction.class.getName() + "'}," +
- "{'name':'test1','class':'" + TestDummyAction.class.getName() + "'}," +
- "]" +
- "}}";
- SolrRequest req = createAutoScalingRequest(SolrRequest.METHOD.POST, setTriggerCommand);
- NamedList<Object> response = solrClient.request(req);
- assertEquals(response.get("result").toString(), "success");
-
- if (!actionInitCalled.await(3000 / SPEED, TimeUnit.MILLISECONDS)) {
- fail("The TriggerAction should have been created by now");
- }
-
- String setListenerCommand = "{" +
- "'set-listener' : " +
- "{" +
- "'name' : 'foo'," +
- "'trigger' : 'node_added_trigger'," +
- "'stage' : ['STARTED','ABORTED','SUCCEEDED', 'FAILED']," +
- "'beforeAction' : 'test'," +
- "'afterAction' : ['test', 'test1']," +
- "'class' : '" + TestTriggerListener.class.getName() + "'" +
- "}" +
- "}";
- req = createAutoScalingRequest(SolrRequest.METHOD.POST, setListenerCommand);
- response = solrClient.request(req);
- assertEquals(response.get("result").toString(), "success");
-
- String setListenerCommand1 = "{" +
- "'set-listener' : " +
- "{" +
- "'name' : 'bar'," +
- "'trigger' : 'node_added_trigger'," +
- "'stage' : ['FAILED','SUCCEEDED']," +
- "'beforeAction' : ['test', 'test1']," +
- "'afterAction' : 'test'," +
- "'class' : '" + TestTriggerListener.class.getName() + "'" +
- "}" +
- "}";
- req = createAutoScalingRequest(SolrRequest.METHOD.POST, setListenerCommand1);
- response = solrClient.request(req);
- assertEquals(response.get("result").toString(), "success");
-
- listenerEvents.clear();
- failDummyAction = false;
-
- String newNode = cluster.simAddNode();
- boolean await = triggerFiredLatch.await(20000 / SPEED, TimeUnit.MILLISECONDS);
- assertTrue("The trigger did not fire at all", await);
- assertTrue(triggerFired.get());
-
- assertEquals("both listeners should have fired", 2, listenerEvents.size());
-
- cluster.getTimeSource().sleep(2000);
-
- // check foo events
- List<CapturedEvent> testEvents = listenerEvents.get("foo");
- assertNotNull("foo events: " + testEvents, testEvents);
- assertEquals("foo events: " + testEvents, 5, testEvents.size());
-
- assertEquals(TriggerEventProcessorStage.STARTED, testEvents.get(0).stage);
-
- assertEquals(TriggerEventProcessorStage.BEFORE_ACTION, testEvents.get(1).stage);
- assertEquals("test", testEvents.get(1).actionName);
-
- assertEquals(TriggerEventProcessorStage.AFTER_ACTION, testEvents.get(2).stage);
- assertEquals("test", testEvents.get(2).actionName);
-
- assertEquals(TriggerEventProcessorStage.AFTER_ACTION, testEvents.get(3).stage);
- assertEquals("test1", testEvents.get(3).actionName);
-
- assertEquals(TriggerEventProcessorStage.SUCCEEDED, testEvents.get(4).stage);
-
- // check bar events
- testEvents = listenerEvents.get("bar");
- assertNotNull("bar events", testEvents);
- assertEquals("bar events", 4, testEvents.size());
-
- assertEquals(TriggerEventProcessorStage.BEFORE_ACTION, testEvents.get(0).stage);
- assertEquals("test", testEvents.get(0).actionName);
-
- assertEquals(TriggerEventProcessorStage.AFTER_ACTION, testEvents.get(1).stage);
- assertEquals("test", testEvents.get(1).actionName);
-
- assertEquals(TriggerEventProcessorStage.BEFORE_ACTION, testEvents.get(2).stage);
- assertEquals("test1", testEvents.get(2).actionName);
-
- assertEquals(TriggerEventProcessorStage.SUCCEEDED, testEvents.get(3).stage);
-
- // check global ordering of events (SOLR-12668)
- int fooIdx = -1;
- int barIdx = -1;
- for (int i = 0; i < allListenerEvents.size(); i++) {
- CapturedEvent ev = allListenerEvents.get(i);
- if (ev.stage == TriggerEventProcessorStage.BEFORE_ACTION && ev.actionName.equals("test")) {
- if (ev.config.name.equals("foo")) {
- fooIdx = i;
- } else if (ev.config.name.equals("bar")) {
- barIdx = i;
- }
- }
- }
- assertTrue("fooIdx not found", fooIdx != -1);
- assertTrue("barIdx not found", barIdx != -1);
- assertTrue("foo fired later than bar: fooIdx=" + fooIdx + ", barIdx=" + barIdx, fooIdx < barIdx);
-
- // reset
- triggerFired.set(false);
- triggerFiredLatch = new CountDownLatch(1);
- listenerEvents.clear();
- failDummyAction = true;
-
- newNode = cluster.simAddNode();
- await = triggerFiredLatch.await(20000 / SPEED, TimeUnit.MILLISECONDS);
- assertTrue("The trigger did not fire at all", await);
-
- cluster.getTimeSource().sleep(2000);
-
- // check foo events
- testEvents = listenerEvents.get("foo");
- assertNotNull("foo events: " + testEvents, testEvents);
- assertEquals("foo events: " + testEvents, 4, testEvents.size());
-
- assertEquals(TriggerEventProcessorStage.STARTED, testEvents.get(0).stage);
-
- assertEquals(TriggerEventProcessorStage.BEFORE_ACTION, testEvents.get(1).stage);
- assertEquals("test", testEvents.get(1).actionName);
-
- assertEquals(TriggerEventProcessorStage.AFTER_ACTION, testEvents.get(2).stage);
- assertEquals("test", testEvents.get(2).actionName);
-
- assertEquals(TriggerEventProcessorStage.FAILED, testEvents.get(3).stage);
- assertEquals("test1", testEvents.get(3).actionName);
-
- // check bar events
- testEvents = listenerEvents.get("bar");
- assertNotNull("bar events", testEvents);
- assertEquals("bar events", 4, testEvents.size());
-
- assertEquals(TriggerEventProcessorStage.BEFORE_ACTION, testEvents.get(0).stage);
- assertEquals("test", testEvents.get(0).actionName);
-
- assertEquals(TriggerEventProcessorStage.AFTER_ACTION, testEvents.get(1).stage);
- assertEquals("test", testEvents.get(1).actionName);
-
- assertEquals(TriggerEventProcessorStage.BEFORE_ACTION, testEvents.get(2).stage);
- assertEquals("test1", testEvents.get(2).actionName);
-
- assertEquals(TriggerEventProcessorStage.FAILED, testEvents.get(3).stage);
- assertEquals("test1", testEvents.get(3).actionName);
- }
-
- @Test
- @BadApple(bugUrl="https://issues.apache.org/jira/browse/SOLR-12028")
- public void testCooldown() throws Exception {
- SolrClient solrClient = cluster.simGetSolrClient();
- failDummyAction = false;
- waitForSeconds = 1;
- String setTriggerCommand = "{" +
- "'set-trigger' : {" +
- "'name' : 'node_added_cooldown_trigger'," +
- "'event' : 'nodeAdded'," +
- "'waitFor' : '" + waitForSeconds + "s'," +
- "'enabled' : true," +
- "'actions' : [" +
- "{'name':'test','class':'" + TestTriggerAction.class.getName() + "'}" +
- "]" +
- "}}";
- SolrRequest req = createAutoScalingRequest(SolrRequest.METHOD.POST, setTriggerCommand);
- NamedList<Object> response = solrClient.request(req);
- assertEquals(response.get("result").toString(), "success");
-
- String setListenerCommand1 = "{" +
- "'set-listener' : " +
- "{" +
- "'name' : 'bar'," +
- "'trigger' : 'node_added_cooldown_trigger'," +
- "'stage' : ['FAILED','SUCCEEDED', 'IGNORED']," +
- "'class' : '" + TestTriggerListener.class.getName() + "'" +
- "}" +
- "}";
- req = createAutoScalingRequest(SolrRequest.METHOD.POST, setListenerCommand1);
- response = solrClient.request(req);
- assertEquals(response.get("result").toString(), "success");
-
- listenerCreated = new CountDownLatch(1);
- listenerEvents.clear();
-
- String newNode = cluster.simAddNode();
- boolean await = triggerFiredLatch.await(20000 / SPEED, TimeUnit.MILLISECONDS);
- assertTrue("The trigger did not fire at all", await);
- assertTrue(triggerFired.get());
- // wait for listener to capture the SUCCEEDED stage
- cluster.getTimeSource().sleep(5000);
-
- List<CapturedEvent> capturedEvents = listenerEvents.get("bar");
- assertNotNull("no events for 'bar'!", capturedEvents);
- // we may get a few IGNORED events if other tests caused events within cooldown period
- assertTrue(capturedEvents.toString(), capturedEvents.size() > 0);
- long prevTimestamp = capturedEvents.get(capturedEvents.size() - 1).timestamp;
-
- // reset the trigger and captured events
- listenerEvents.clear();
- triggerFiredLatch = new CountDownLatch(1);
- triggerFired.compareAndSet(true, false);
-
- String newNode2 = cluster.simAddNode();
- await = triggerFiredLatch.await(20000 / SPEED, TimeUnit.MILLISECONDS);
- assertTrue("The trigger did not fire at all", await);
- // wait for listener to capture the SUCCEEDED stage
- cluster.getTimeSource().sleep(2000);
-
- // there must be exactly one SUCCEEDED event
- capturedEvents = listenerEvents.get("bar");
- assertTrue(capturedEvents.toString(), capturedEvents.size() >= 1);
- CapturedEvent ev = capturedEvents.get(capturedEvents.size() - 1);
- assertEquals(ev.toString(), TriggerEventProcessorStage.SUCCEEDED, ev.stage);
- // the difference between timestamps of the first SUCCEEDED and the last SUCCEEDED
- // must be larger than cooldown period
- assertTrue("timestamp delta is less than default cooldown period", ev.timestamp - prevTimestamp > TimeUnit.SECONDS.toNanos(ScheduledTriggers.DEFAULT_COOLDOWN_PERIOD_SECONDS));
- }
-
- public static class TestSearchRateAction extends TriggerActionBase {
-
- @Override
- public void process(TriggerEvent event, ActionContext context) throws Exception {
- try {
- events.add(event);
- long currentTimeNanos = cluster.getTimeSource().getTimeNs();
- long eventTimeNanos = event.getEventTime();
- long waitForNanos = TimeUnit.NANOSECONDS.convert(waitForSeconds, TimeUnit.SECONDS) - WAIT_FOR_DELTA_NANOS;
- if (currentTimeNanos - eventTimeNanos <= waitForNanos) {
- fail(event.getSource() + " was fired before the configured waitFor period");
- }
- getTriggerFiredLatch().countDown();
- } catch (Throwable t) {
- log.debug("--throwable", t);
- throw t;
- }
- }
- }
-
- public static class FinishTriggerAction extends TriggerActionBase {
- @Override
- public void process(TriggerEvent event, ActionContext context) throws Exception {
- triggerFinishedCount.incrementAndGet();
- triggerFinishedLatch.countDown();
- }
- }
-
- public static class StartTriggerAction extends TriggerActionBase {
- @Override
- public void process(TriggerEvent event, ActionContext context) throws Exception {
- triggerStartedLatch.countDown();
- triggerStartedCount.incrementAndGet();
- }
- }
-
-
-
- @Test
- //@BadApple(bugUrl="https://issues.apache.org/jira/browse/SOLR-12028")
- public void testSearchRate() throws Exception {
- SolrClient solrClient = cluster.simGetSolrClient();
- String COLL1 = "collection1";
- CollectionAdminRequest.Create create = CollectionAdminRequest.createCollection(COLL1,
- "conf", 1, 2);
- create.process(solrClient);
- CloudTestUtils.waitForState(cluster, COLL1, 10, TimeUnit.SECONDS, CloudTestUtils.clusterShape(1, 2, false, true));
-
- String setTriggerCommand = "{" +
- "'set-trigger' : {" +
- "'name' : 'search_rate_trigger'," +
- "'event' : 'searchRate'," +
- "'waitFor' : '" + waitForSeconds + "s'," +
- "'enabled' : true," +
- "'aboveRate' : 1.0," +
- "'aboveNodeRate' : 1.0," +
- "'actions' : [" +
- "{'name':'start','class':'" + StartTriggerAction.class.getName() + "'}," +
- "{'name':'compute','class':'" + ComputePlanAction.class.getName() + "'}," +
- "{'name':'execute','class':'" + ExecutePlanAction.class.getName() + "'}," +
- "{'name':'test','class':'" + TestSearchRateAction.class.getName() + "'}" +
- "{'name':'finish','class':'" + FinishTriggerAction.class.getName() + "'}," +
- "]" +
- "}}";
- SolrRequest req = createAutoScalingRequest(SolrRequest.METHOD.POST, setTriggerCommand);
- NamedList<Object> response = solrClient.request(req);
- assertEquals(response.get("result").toString(), "success");
-
- String setListenerCommand1 = "{" +
- "'set-listener' : " +
- "{" +
- "'name' : 'srt'," +
- "'trigger' : 'search_rate_trigger'," +
- "'stage' : ['FAILED','SUCCEEDED']," +
- "'afterAction': ['compute', 'execute', 'test']," +
- "'class' : '" + TestTriggerListener.class.getName() + "'" +
- "}" +
- "}";
- req = createAutoScalingRequest(SolrRequest.METHOD.POST, setListenerCommand1);
- response = solrClient.request(req);
- assertEquals(response.get("result").toString(), "success");
-
-// SolrParams query = params(CommonParams.Q, "*:*");
-// for (int i = 0; i < 500; i++) {
-// solrClient.query(COLL1, query);
-// }
-
- cluster.getSimClusterStateProvider().simSetCollectionValue(COLL1, "QUERY./select.requestTimes:1minRate", 500, false, true);
-
- boolean await = triggerStartedLatch.await(20000 / SPEED, TimeUnit.MILLISECONDS);
- assertTrue("The trigger did not start in time", await);
- await = triggerFinishedLatch.await(60000 / SPEED, TimeUnit.MILLISECONDS);
- assertTrue("The trigger did not finish in time", await);
- // wait for listener to capture the SUCCEEDED stage
- cluster.getTimeSource().sleep(5000);
- List<CapturedEvent> events = listenerEvents.get("srt");
-
- assertEquals(listenerEvents.toString(), 4, events.size());
- assertEquals("AFTER_ACTION", events.get(0).stage.toString());
- assertEquals("compute", events.get(0).actionName);
- assertEquals("AFTER_ACTION", events.get(1).stage.toString());
- assertEquals("execute", events.get(1).actionName);
- assertEquals("AFTER_ACTION", events.get(2).stage.toString());
- assertEquals("test", events.get(2).actionName);
- assertEquals("SUCCEEDED", events.get(3).stage.toString());
- assertNull(events.get(3).actionName);
-
- CapturedEvent ev = events.get(0);
- long now = cluster.getTimeSource().getTimeNs();
- // verify waitFor
- assertTrue(TimeUnit.SECONDS.convert(waitForSeconds, TimeUnit.NANOSECONDS) - WAIT_FOR_DELTA_NANOS <= now - ev.event.getEventTime());
- Map<String, Double> nodeRates = (Map<String, Double>)ev.event.getProperties().get(SearchRateTrigger.HOT_NODES);
- assertNotNull("nodeRates", nodeRates);
- assertTrue(nodeRates.toString(), nodeRates.size() > 0);
- AtomicDouble totalNodeRate = new AtomicDouble();
- nodeRates.forEach((n, r) -> totalNodeRate.addAndGet(r));
- List<ReplicaInfo> replicaRates = (List<ReplicaInfo>)ev.event.getProperties().get(SearchRateTrigger.HOT_REPLICAS);
- assertNotNull("replicaRates", replicaRates);
- assertTrue(replicaRates.toString(), replicaRates.size() > 0);
- AtomicDouble totalReplicaRate = new AtomicDouble();
- replicaRates.forEach(r -> {
- assertTrue(r.toString(), r.getVariable("rate") != null);
- totalReplicaRate.addAndGet((Double)r.getVariable("rate"));
- });
- Map<String, Object> shardRates = (Map<String, Object>)ev.event.getProperties().get(SearchRateTrigger.HOT_SHARDS);
- assertNotNull("shardRates", shardRates);
- assertEquals(shardRates.toString(), 1, shardRates.size());
- shardRates = (Map<String, Object>)shardRates.get(COLL1);
- assertNotNull("shardRates", shardRates);
- assertEquals(shardRates.toString(), 1, shardRates.size());
- AtomicDouble totalShardRate = new AtomicDouble();
- shardRates.forEach((s, r) -> totalShardRate.addAndGet((Double)r));
- Map<String, Double> collectionRates = (Map<String, Double>)ev.event.getProperties().get(SearchRateTrigger.HOT_COLLECTIONS);
- assertNotNull("collectionRates", collectionRates);
- assertEquals(collectionRates.toString(), 1, collectionRates.size());
- Double collectionRate = collectionRates.get(COLL1);
- assertNotNull(collectionRate);
- assertTrue(collectionRate > 100.0);
- assertTrue(totalNodeRate.get() > 100.0);
- assertTrue(totalShardRate.get() > 100.0);
- assertTrue(totalReplicaRate.get() > 100.0);
-
- // check operations
- List<Map<String, Object>> ops = (List<Map<String, Object>>)ev.context.get("properties.operations");
- assertNotNull(ops);
- assertTrue(ops.size() > 1);
- for (Map<String, Object> m : ops) {
- assertEquals("ADDREPLICA", m.get("params.action"));
- }
- }
-}
[6/6] lucene-solr:branch_7x: SOLR-12669: Rename tests that use the
autoscaling simulation framework.
Posted by ab...@apache.org.
SOLR-12669: Rename tests that use the autoscaling simulation framework.
Project: http://git-wip-us.apache.org/repos/asf/lucene-solr/repo
Commit: http://git-wip-us.apache.org/repos/asf/lucene-solr/commit/b949f57f
Tree: http://git-wip-us.apache.org/repos/asf/lucene-solr/tree/b949f57f
Diff: http://git-wip-us.apache.org/repos/asf/lucene-solr/diff/b949f57f
Branch: refs/heads/branch_7x
Commit: b949f57fcbcd67b7d953136d358353af60db731e
Parents: 34a8c02
Author: Andrzej Bialecki <ab...@apache.org>
Authored: Mon Aug 27 15:14:03 2018 +0200
Committer: Andrzej Bialecki <ab...@apache.org>
Committed: Mon Aug 27 15:15:10 2018 +0200
----------------------------------------------------------------------
.../sim/TestClusterStateProvider.java | 225 ---
.../autoscaling/sim/TestComputePlanAction.java | 358 -----
.../sim/TestDistribStateManager.java | 342 -----
.../autoscaling/sim/TestExecutePlanAction.java | 209 ---
.../sim/TestGenericDistributedQueue.java | 40 -
.../cloud/autoscaling/sim/TestLargeCluster.java | 727 ----------
.../autoscaling/sim/TestNodeAddedTrigger.java | 327 -----
.../autoscaling/sim/TestNodeLostTrigger.java | 346 -----
.../cloud/autoscaling/sim/TestPolicyCloud.java | 366 -----
.../sim/TestSimClusterStateProvider.java | 225 +++
.../sim/TestSimComputePlanAction.java | 358 +++++
.../sim/TestSimDistribStateManager.java | 342 +++++
.../sim/TestSimExecutePlanAction.java | 209 +++
.../sim/TestSimGenericDistributedQueue.java | 40 +
.../autoscaling/sim/TestSimLargeCluster.java | 727 ++++++++++
.../sim/TestSimNodeAddedTrigger.java | 327 +++++
.../autoscaling/sim/TestSimNodeLostTrigger.java | 346 +++++
.../autoscaling/sim/TestSimPolicyCloud.java | 368 +++++
.../sim/TestSimTriggerIntegration.java | 1322 ++++++++++++++++++
.../autoscaling/sim/TestTriggerIntegration.java | 1322 ------------------
20 files changed, 4264 insertions(+), 4262 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/b949f57f/solr/core/src/test/org/apache/solr/cloud/autoscaling/sim/TestClusterStateProvider.java
----------------------------------------------------------------------
diff --git a/solr/core/src/test/org/apache/solr/cloud/autoscaling/sim/TestClusterStateProvider.java b/solr/core/src/test/org/apache/solr/cloud/autoscaling/sim/TestClusterStateProvider.java
deleted file mode 100644
index 2cdc456..0000000
--- a/solr/core/src/test/org/apache/solr/cloud/autoscaling/sim/TestClusterStateProvider.java
+++ /dev/null
@@ -1,225 +0,0 @@
-/*
- * 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.solr.cloud.autoscaling.sim;
-
-import java.lang.invoke.MethodHandles;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-
-import org.apache.solr.client.solrj.cloud.autoscaling.AutoScalingConfig;
-import org.apache.solr.client.solrj.cloud.autoscaling.Preference;
-import org.apache.solr.client.solrj.cloud.autoscaling.ReplicaInfo;
-import org.apache.solr.client.solrj.cloud.SolrCloudManager;
-import org.apache.solr.client.solrj.embedded.JettySolrRunner;
-import org.apache.solr.client.solrj.request.CollectionAdminRequest;
-import org.apache.solr.cloud.SolrCloudTestCase;
-import org.apache.solr.common.cloud.ClusterState;
-import org.apache.solr.common.cloud.DocCollection;
-import org.apache.solr.common.cloud.Replica;
-import org.apache.solr.common.cloud.Slice;
-import org.apache.solr.common.cloud.ZkStateReader;
-import org.apache.solr.common.cloud.rule.ImplicitSnitch;
-import org.apache.solr.common.params.CollectionAdminParams;
-import org.apache.solr.common.util.TimeSource;
-import org.apache.solr.common.util.Utils;
-import org.apache.zookeeper.Watcher;
-import org.junit.AfterClass;
-import org.junit.BeforeClass;
-import org.junit.Test;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-/**
- * This test compares the cluster state of a real cluster and a simulated one.
- */
-public class TestClusterStateProvider extends SolrCloudTestCase {
- private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
-
- private static int NODE_COUNT = 3;
- private static boolean simulated;
-
- private static SolrCloudManager cloudManager;
-
- private static Collection<String> liveNodes;
- private static Map<String, Object> clusterProperties;
- private static AutoScalingConfig autoScalingConfig;
- private static Map<String, Map<String, Map<String, List<ReplicaInfo>>>> replicas;
- private static Map<String, Map<String, Object>> nodeValues;
- private static ClusterState realState;
-
- // set up a real cluster as the source of test data
- @BeforeClass
- public static void setupCluster() throws Exception {
- simulated = random().nextBoolean();
- log.info("####### Using simulated components? " + simulated);
-
- configureCluster(NODE_COUNT)
- .addConfig("conf", configset("cloud-minimal"))
- .configure();
- CollectionAdminRequest.createCollection(CollectionAdminParams.SYSTEM_COLL, null, 1, 2, 0, 1)
- .process(cluster.getSolrClient());
- init();
- }
-
- @AfterClass
- public static void closeCloudManager() throws Exception {
- if (simulated && cloudManager != null) {
- cloudManager.close();
- }
- }
-
- private static void init() throws Exception {
- SolrCloudManager realManager = cluster.getJettySolrRunner(cluster.getJettySolrRunners().size() - 1).getCoreContainer()
- .getZkController().getSolrCloudManager();
- liveNodes = realManager.getClusterStateProvider().getLiveNodes();
- clusterProperties = realManager.getClusterStateProvider().getClusterProperties();
- autoScalingConfig = realManager.getDistribStateManager().getAutoScalingConfig();
- replicas = new HashMap<>();
- nodeValues = new HashMap<>();
- liveNodes.forEach(n -> {
- replicas.put(n, realManager.getNodeStateProvider().getReplicaInfo(n, Collections.emptySet()));
- nodeValues.put(n, realManager.getNodeStateProvider().getNodeValues(n, ImplicitSnitch.tags));
- });
- realState = realManager.getClusterStateProvider().getClusterState();
-
- if (simulated) {
- // initialize simulated provider
- SimCloudManager simCloudManager = new SimCloudManager(TimeSource.get("simTime:10"));
- simCloudManager.getSimClusterStateProvider().simSetClusterProperties(clusterProperties);
- simCloudManager.getSimDistribStateManager().simSetAutoScalingConfig(autoScalingConfig);
- nodeValues.forEach((n, values) -> {
- try {
- simCloudManager.getSimNodeStateProvider().simSetNodeValues(n, values);
- } catch (InterruptedException e) {
- fail("Interrupted:" + e);
- }
- });
- simCloudManager.getSimClusterStateProvider().simSetClusterState(realState);
- ClusterState simState = simCloudManager.getClusterStateProvider().getClusterState();
- assertClusterStateEquals(realState, simState);
- cloudManager = simCloudManager;
- } else {
- cloudManager = realManager;
- }
- }
-
- private static void assertClusterStateEquals(ClusterState one, ClusterState two) {
- assertEquals(one.getLiveNodes(), two.getLiveNodes());
- assertEquals(one.getCollectionsMap().keySet(), two.getCollectionsMap().keySet());
- one.forEachCollection(oneColl -> {
- DocCollection twoColl = two.getCollection(oneColl.getName());
- Map<String, Slice> oneSlices = oneColl.getSlicesMap();
- Map<String, Slice> twoSlices = twoColl.getSlicesMap();
- assertEquals(oneSlices.keySet(), twoSlices.keySet());
- oneSlices.forEach((s, slice) -> {
- Slice sTwo = twoSlices.get(s);
- for (Replica oneReplica : slice.getReplicas()) {
- Replica twoReplica = sTwo.getReplica(oneReplica.getName());
- assertNotNull(twoReplica);
- assertEquals(oneReplica, twoReplica);
- }
- });
- });
- }
-
- private String addNode() throws Exception {
- JettySolrRunner solr = cluster.startJettySolrRunner();
- String nodeId = solr.getNodeName();
- if (simulated) {
- ((SimCloudManager) cloudManager).getSimClusterStateProvider().simAddNode(nodeId);
- }
- return nodeId;
- }
-
- private String deleteNode() throws Exception {
- String nodeId = cluster.getJettySolrRunner(0).getNodeName();
- cluster.stopJettySolrRunner(0);
- if (simulated) {
- ((SimCloudManager) cloudManager).getSimClusterStateProvider().simRemoveNode(nodeId);
- }
- return nodeId;
- }
-
- private void setAutoScalingConfig(AutoScalingConfig cfg) throws Exception {
- cluster.getJettySolrRunner(0).getCoreContainer().getZkController().getZkClient().setData(ZkStateReader.SOLR_AUTOSCALING_CONF_PATH,
- Utils.toJSON(cfg), -1, true);
- if (simulated) {
- ((SimCloudManager) cloudManager).getSimDistribStateManager().simSetAutoScalingConfig(cfg);
- }
- }
-
- @Test
- public void testAddRemoveNode() throws Exception {
- Set<String> lastNodes = new HashSet<>(cloudManager.getClusterStateProvider().getLiveNodes());
- List<String> liveNodes = cloudManager.getDistribStateManager().listData(ZkStateReader.LIVE_NODES_ZKNODE);
- assertEquals(lastNodes.size(), liveNodes.size());
- liveNodes.removeAll(lastNodes);
- assertTrue(liveNodes.isEmpty());
-
- String node = addNode();
- cloudManager.getTimeSource().sleep(2000);
- assertFalse(lastNodes.contains(node));
- lastNodes = new HashSet<>(cloudManager.getClusterStateProvider().getLiveNodes());
- assertTrue(lastNodes.contains(node));
- liveNodes = cloudManager.getDistribStateManager().listData(ZkStateReader.LIVE_NODES_ZKNODE);
- assertEquals(lastNodes.size(), liveNodes.size());
- liveNodes.removeAll(lastNodes);
- assertTrue(liveNodes.isEmpty());
-
- node = deleteNode();
- cloudManager.getTimeSource().sleep(2000);
- assertTrue(lastNodes.contains(node));
- lastNodes = new HashSet<>(cloudManager.getClusterStateProvider().getLiveNodes());
- assertFalse(lastNodes.contains(node));
- liveNodes = cloudManager.getDistribStateManager().listData(ZkStateReader.LIVE_NODES_ZKNODE);
- assertEquals(lastNodes.size(), liveNodes.size());
- liveNodes.removeAll(lastNodes);
- assertTrue(liveNodes.isEmpty()); }
-
- @Test
- public void testAutoScalingConfig() throws Exception {
- final CountDownLatch triggered = new CountDownLatch(1);
- Watcher w = ev -> {
- if (triggered.getCount() == 0) {
- fail("already triggered once!");
- }
- triggered.countDown();
- };
- AutoScalingConfig cfg = cloudManager.getDistribStateManager().getAutoScalingConfig(w);
- assertEquals(autoScalingConfig, cfg);
- Preference p = new Preference(Collections.singletonMap("maximize", "freedisk"));
- cfg = cfg.withPolicy(cfg.getPolicy().withClusterPreferences(Collections.singletonList(p)));
- setAutoScalingConfig(cfg);
- if (!triggered.await(10, TimeUnit.SECONDS)) {
- fail("Watch should be triggered on update!");
- }
- AutoScalingConfig cfg1 = cloudManager.getDistribStateManager().getAutoScalingConfig(null);
- assertEquals(cfg, cfg1);
-
- // restore
- setAutoScalingConfig(autoScalingConfig);
- cfg1 = cloudManager.getDistribStateManager().getAutoScalingConfig(null);
- assertEquals(autoScalingConfig, cfg1);
- }
-}
http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/b949f57f/solr/core/src/test/org/apache/solr/cloud/autoscaling/sim/TestComputePlanAction.java
----------------------------------------------------------------------
diff --git a/solr/core/src/test/org/apache/solr/cloud/autoscaling/sim/TestComputePlanAction.java b/solr/core/src/test/org/apache/solr/cloud/autoscaling/sim/TestComputePlanAction.java
deleted file mode 100644
index 3faa2d7..0000000
--- a/solr/core/src/test/org/apache/solr/cloud/autoscaling/sim/TestComputePlanAction.java
+++ /dev/null
@@ -1,358 +0,0 @@
-/*
- * 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.solr.cloud.autoscaling.sim;
-
-import java.io.IOException;
-import java.lang.invoke.MethodHandles;
-import java.util.Collection;
-import java.util.List;
-import java.util.Map;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.atomic.AtomicBoolean;
-import java.util.concurrent.atomic.AtomicReference;
-
-import org.apache.solr.client.solrj.SolrClient;
-import org.apache.solr.client.solrj.SolrRequest;
-import org.apache.solr.client.solrj.SolrResponse;
-import org.apache.solr.client.solrj.cloud.SolrCloudManager;
-import org.apache.solr.client.solrj.cloud.autoscaling.TriggerEventType;
-import org.apache.solr.client.solrj.request.CollectionAdminRequest;
-import org.apache.solr.cloud.CloudTestUtils;
-import org.apache.solr.cloud.autoscaling.ActionContext;
-import org.apache.solr.cloud.autoscaling.ComputePlanAction;
-import org.apache.solr.cloud.autoscaling.ScheduledTriggers;
-import org.apache.solr.cloud.autoscaling.TriggerAction;
-import org.apache.solr.cloud.autoscaling.TriggerEvent;
-import org.apache.solr.cloud.autoscaling.TriggerValidationException;
-import org.apache.solr.common.cloud.ClusterState;
-import org.apache.solr.common.cloud.DocCollection;
-import org.apache.solr.common.cloud.Replica;
-import org.apache.solr.common.params.CollectionParams;
-import org.apache.solr.common.params.SolrParams;
-import org.apache.solr.common.util.NamedList;
-import org.apache.solr.common.util.TimeSource;
-import org.apache.solr.common.util.Utils;
-import org.apache.solr.core.SolrResourceLoader;
-import org.apache.solr.util.LogLevel;
-import org.junit.After;
-import org.junit.Before;
-import org.junit.BeforeClass;
-import org.junit.Test;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import static org.apache.solr.cloud.autoscaling.AutoScalingHandlerTest.createAutoScalingRequest;
-import static org.apache.solr.common.params.CollectionParams.CollectionAction.MOVEREPLICA;
-
-/**
- * Test for {@link ComputePlanAction}
- */
-@LogLevel("org.apache.solr.cloud.autoscaling=DEBUG;org.apache.solr.cloud.Overseer=DEBUG;org.apache.solr.cloud.overseer=DEBUG;org.apache.solr.client.solrj.cloud.autoscaling=DEBUG;")
-public class TestComputePlanAction extends SimSolrCloudTestCase {
- private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
-
- private static final AtomicBoolean fired = new AtomicBoolean(false);
- private static final int NODE_COUNT = 1;
- private static CountDownLatch triggerFiredLatch = new CountDownLatch(1);
- private static final AtomicReference<Map> actionContextPropsRef = new AtomicReference<>();
- private static final AtomicReference<TriggerEvent> eventRef = new AtomicReference<>();
-
- @BeforeClass
- public static void setupCluster() throws Exception {
- configureCluster(1, TimeSource.get("simTime:50"));
- }
-
- @Before
- public void init() throws Exception {
-
- fired.set(false);
- triggerFiredLatch = new CountDownLatch(1);
- actionContextPropsRef.set(null);
-
- String setClusterPolicyCommand = "{" +
- " 'set-cluster-policy': [" +
- " {'cores':'<10', 'node':'#ANY'}," +
- " {'replica':'<2', 'shard': '#EACH', 'node': '#ANY'}," +
- " {'nodeRole':'overseer', 'replica':0}" +
- " ]" +
- "}";
- SolrRequest req = createAutoScalingRequest(SolrRequest.METHOD.POST, setClusterPolicyCommand);
- SolrResponse rsp = cluster.request(req);
- NamedList<Object> response = rsp.getResponse();
- assertEquals(response.get("result").toString(), "success");
-
- String setClusterPreferencesCommand = "{" +
- "'set-cluster-preferences': [" +
- "{'minimize': 'cores'}," +
- "{'maximize': 'freedisk','precision': 100}]" +
- "}";
- req = createAutoScalingRequest(SolrRequest.METHOD.POST, setClusterPreferencesCommand);
- rsp = cluster.request(req);
- response = rsp.getResponse();
- assertEquals(response.get("result").toString(), "success");
- cluster.getTimeSource().sleep(TimeUnit.SECONDS.toMillis(ScheduledTriggers.DEFAULT_COOLDOWN_PERIOD_SECONDS));
- }
-
- @After
- public void printState() throws Exception {
- log.info("-------------_ FINAL STATE --------------");
- log.info("* Node values: " + Utils.toJSONString(cluster.getSimNodeStateProvider().simGetAllNodeValues()));
- log.info("* Live nodes: " + cluster.getClusterStateProvider().getLiveNodes());
- ClusterState state = cluster.getClusterStateProvider().getClusterState();
- for (String coll: cluster.getSimClusterStateProvider().simListCollections()) {
- log.info("* Collection " + coll + " state: " + state.getCollection(coll));
- }
-
- }
-
- @Test
- public void testNodeLost() throws Exception {
- // let's start a node so that we have at least two
- String node = cluster.simAddNode();
- AssertingTriggerAction.expectedNode = node;
-
- SolrClient solrClient = cluster.simGetSolrClient();
- String setTriggerCommand = "{" +
- "'set-trigger' : {" +
- "'name' : 'node_lost_trigger'," +
- "'event' : 'nodeLost'," +
- "'waitFor' : '7s'," +
- "'enabled' : true," +
- "'actions' : [{'name':'compute_plan', 'class' : 'solr.ComputePlanAction'}," +
- "{'name':'test','class':'" + TestComputePlanAction.AssertingTriggerAction.class.getName() + "'}]" +
- "}}";
- SolrRequest req = createAutoScalingRequest(SolrRequest.METHOD.POST, setTriggerCommand);
- NamedList<Object> response = solrClient.request(req);
- assertEquals(response.get("result").toString(), "success");
-
- CollectionAdminRequest.Create create = CollectionAdminRequest.createCollection("testNodeLost",
- "conf",1, 2);
- create.process(solrClient);
-
- CloudTestUtils.waitForState(cluster, "Timed out waiting for replicas of new collection to be active",
- "testNodeLost", CloudTestUtils.clusterShape(1, 2, false, true));
-
- ClusterState clusterState = cluster.getClusterStateProvider().getClusterState();
- log.debug("-- cluster state: {}", clusterState);
- DocCollection collection = clusterState.getCollection("testNodeLost");
- List<Replica> replicas = collection.getReplicas(node);
- assertNotNull(replicas);
- assertFalse(replicas.isEmpty());
-
- // start another node because because when the other node goes away, the cluster policy requires only
- // 1 replica per node and none on the overseer
- String node2 = cluster.simAddNode();
- assertTrue(node2 + "is not live yet", cluster.getClusterStateProvider().getClusterState().liveNodesContain(node2) );
-
- // stop the original node
- cluster.simRemoveNode(node, false);
- log.info("Stopped_node : {}", node);
-
- assertTrue("Trigger was not fired even after 10 seconds", triggerFiredLatch.await(10, TimeUnit.SECONDS));
- assertTrue(fired.get());
- Map context = actionContextPropsRef.get();
- assertNotNull(context);
- List<SolrRequest> operations = (List<SolrRequest>) context.get("operations");
- assertNotNull("The operations computed by ComputePlanAction should not be null , " + eventRef.get(), operations);
- assertEquals("ComputePlanAction should have computed exactly 1 operation", 1, operations.size());
- SolrRequest solrRequest = operations.get(0);
- SolrParams params = solrRequest.getParams();
- assertEquals("Expected MOVEREPLICA action after adding node", MOVEREPLICA, CollectionParams.CollectionAction.get(params.get("action")));
- String replicaToBeMoved = params.get("replica");
- assertEquals("Unexpected node in computed operation", replicas.get(0).getName(), replicaToBeMoved);
-
- // shutdown the extra node that we had started
- cluster.simRemoveNode(node2, false);
- }
-
- public void testNodeWithMultipleReplicasLost() throws Exception {
- AssertingTriggerAction.expectedNode = null;
-
- // start 3 more nodes
- cluster.simAddNode();
- cluster.simAddNode();
- cluster.simAddNode();
-
- SolrClient solrClient = cluster.simGetSolrClient();
- String setTriggerCommand = "{" +
- "'set-trigger' : {" +
- "'name' : 'node_lost_trigger'," +
- "'event' : 'nodeLost'," +
- "'waitFor' : '1s'," +
- "'enabled' : true," +
- "'actions' : [{'name':'compute_plan', 'class' : 'solr.ComputePlanAction'}," +
- "{'name':'test','class':'" + AssertingTriggerAction.class.getName() + "'}]" +
- "}}";
- SolrRequest req = createAutoScalingRequest(SolrRequest.METHOD.POST, setTriggerCommand);
- NamedList<Object> response = solrClient.request(req);
- assertEquals(response.get("result").toString(), "success");
-
- CollectionAdminRequest.Create create = CollectionAdminRequest.createCollection("testNodeWithMultipleReplicasLost",
- "conf",2, 3);
-// create.setMaxShardsPerNode(2);
- create.process(solrClient);
-
- CloudTestUtils.waitForState(cluster, "Timed out waiting for replicas of new collection to be active",
- "testNodeWithMultipleReplicasLost", CloudTestUtils.clusterShape(2, 3, false, true));
-
- ClusterState clusterState = cluster.getClusterStateProvider().getClusterState();
- log.debug("-- cluster state: {}", clusterState);
- DocCollection docCollection = clusterState.getCollection("testNodeWithMultipleReplicasLost");
-
- // lets find a node with at least 2 replicas
- String stoppedNodeName = null;
- List<Replica> replicasToBeMoved = null;
- for (String node : cluster.getClusterStateProvider().getLiveNodes()) {
- List<Replica> replicas = docCollection.getReplicas(node);
- if (replicas != null && replicas.size() == 2) {
- stoppedNodeName = node;
- replicasToBeMoved = replicas;
- cluster.simRemoveNode(node, false);
- break;
- }
- }
- assertNotNull(stoppedNodeName);
-
- assertTrue("Trigger was not fired even after 5 seconds", triggerFiredLatch.await(5, TimeUnit.SECONDS));
- assertTrue(fired.get());
-
- TriggerEvent triggerEvent = eventRef.get();
- assertNotNull(triggerEvent);
- assertEquals(TriggerEventType.NODELOST, triggerEvent.getEventType());
- // TODO assertEquals(stoppedNodeName, triggerEvent.getProperty(TriggerEvent.NODE_NAME));
-
- Map context = actionContextPropsRef.get();
- assertNotNull(context);
- List<SolrRequest> operations = (List<SolrRequest>) context.get("operations");
- assertNotNull("The operations computed by ComputePlanAction should not be null " + actionContextPropsRef.get() + "\nevent: " + eventRef.get(), operations);
- operations.forEach(solrRequest -> log.info(solrRequest.getParams().toString()));
- assertEquals("ComputePlanAction should have computed exactly 2 operation", 2, operations.size());
-
- for (SolrRequest solrRequest : operations) {
- SolrParams params = solrRequest.getParams();
- assertEquals("Expected MOVEREPLICA action after adding node", MOVEREPLICA, CollectionParams.CollectionAction.get(params.get("action")));
- String moved = params.get("replica");
- assertTrue(replicasToBeMoved.stream().anyMatch(replica -> replica.getName().equals(moved)));
- }
- }
-
- @Test
- //17-Aug-2018 commented @LuceneTestCase.BadApple(bugUrl="https://issues.apache.org/jira/browse/SOLR-12028") // 28-June-2018
- public void testNodeAdded() throws Exception {
- AssertingTriggerAction.expectedNode = null;
- SolrClient solrClient = cluster.simGetSolrClient();
- String setTriggerCommand = "{" +
- "'set-trigger' : {" +
- "'name' : 'node_added_trigger'," +
- "'event' : 'nodeAdded'," +
- "'waitFor' : '1s'," +
- "'enabled' : true," +
- "'actions' : [{'name':'compute_plan', 'class' : 'solr.ComputePlanAction'}," +
- "{'name':'test','class':'" + TestComputePlanAction.AssertingTriggerAction.class.getName() + "'}]" +
- "}}";
- SolrRequest req = createAutoScalingRequest(SolrRequest.METHOD.POST, setTriggerCommand);
- NamedList<Object> response = solrClient.request(req);
- assertEquals(response.get("result").toString(), "success");
-
- // the default policy limits 1 replica per node, we need more right now
- String setClusterPolicyCommand = "{" +
- " 'set-cluster-policy': [" +
- " {'cores':'<10', 'node':'#ANY'}," +
- " {'replica':'<5', 'shard': '#EACH', 'node': '#ANY'}," +
- " {'nodeRole':'overseer', 'replica':0}" +
- " ]" +
- "}";
- req = createAutoScalingRequest(SolrRequest.METHOD.POST, setClusterPolicyCommand);
- response = solrClient.request(req);
- assertEquals(response.get("result").toString(), "success");
-
- CollectionAdminRequest.Create create = CollectionAdminRequest.createCollection("testNodeAdded",
- "conf",1, 4);
- create.process(solrClient);
-
- CloudTestUtils.waitForState(cluster, "Timed out waiting for replicas of new collection to be active",
- "testNodeAdded", (liveNodes, collectionState) -> collectionState.getReplicas().stream().allMatch(replica -> replica.isActive(liveNodes)));
-
- // reset to the original policy which has only 1 replica per shard per node
- setClusterPolicyCommand = "{" +
- " 'set-cluster-policy': [" +
- " {'cores':'<10', 'node':'#ANY'}," +
- " {'replica':'<3', 'shard': '#EACH', 'node': '#ANY'}," +
- " {'nodeRole':'overseer', 'replica':0}" +
- " ]" +
- "}";
- req = createAutoScalingRequest(SolrRequest.METHOD.POST, setClusterPolicyCommand);
- response = solrClient.request(req);
- assertEquals(response.get("result").toString(), "success");
-
- // start a node so that the 'violation' created by the previous policy update is fixed
- String newNode = cluster.simAddNode();
- assertTrue("Trigger was not fired even after 5 seconds", triggerFiredLatch.await(5, TimeUnit.SECONDS));
- assertTrue(fired.get());
- Map context = actionContextPropsRef.get();
- assertNotNull(context);
- log.info("Node values: " + Utils.toJSONString(cluster.getSimNodeStateProvider().simGetAllNodeValues()));
- log.info("Live nodes: " + cluster.getClusterStateProvider().getLiveNodes() + ", collection state: " + cluster.getClusterStateProvider().getClusterState().getCollection("testNodeAdded"));
- List<SolrRequest> operations = (List<SolrRequest>) context.get("operations");
- assertNotNull("The operations computed by ComputePlanAction should not be null" + context, operations);
- assertEquals("ComputePlanAction should have computed exactly 1 operation, but was: " + operations, 1, operations.size());
- SolrRequest request = operations.get(0);
- SolrParams params = request.getParams();
- assertEquals("Expected MOVEREPLICA action after adding node", MOVEREPLICA, CollectionParams.CollectionAction.get(params.get("action")));
- String nodeAdded = params.get("targetNode");
- assertEquals("Unexpected node in computed operation", newNode, nodeAdded);
- }
-
- public static class AssertingTriggerAction implements TriggerAction {
- static String expectedNode;
-
- @Override
- public void configure(SolrResourceLoader loader, SolrCloudManager cloudManager, Map<String, Object> properties) throws TriggerValidationException {
-
- }
-
- @Override
- public void init() {
-
- }
-
- @Override
- public String getName() {
- return null;
- }
-
- @Override
- public void process(TriggerEvent event, ActionContext context) {
- if (expectedNode != null) {
- Collection nodes = (Collection) event.getProperty(TriggerEvent.NODE_NAMES);
- if (nodes == null || !nodes.contains(expectedNode)) return;//this is not the event we are looking for
- }
- if (fired.compareAndSet(false, true)) {
- eventRef.set(event);
- actionContextPropsRef.set(context.getProperties());
- triggerFiredLatch.countDown();
- }
- }
-
- @Override
- public void close() throws IOException {
-
- }
- }
-}
http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/b949f57f/solr/core/src/test/org/apache/solr/cloud/autoscaling/sim/TestDistribStateManager.java
----------------------------------------------------------------------
diff --git a/solr/core/src/test/org/apache/solr/cloud/autoscaling/sim/TestDistribStateManager.java b/solr/core/src/test/org/apache/solr/cloud/autoscaling/sim/TestDistribStateManager.java
deleted file mode 100644
index 74d9bb1..0000000
--- a/solr/core/src/test/org/apache/solr/cloud/autoscaling/sim/TestDistribStateManager.java
+++ /dev/null
@@ -1,342 +0,0 @@
-/*
- * 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.solr.cloud.autoscaling.sim;
-
-import java.lang.invoke.MethodHandles;
-import java.util.Arrays;
-import java.util.List;
-import java.util.Locale;
-import java.util.NoSuchElementException;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-
-import org.apache.solr.SolrTestCaseJ4;
-import org.apache.solr.client.solrj.cloud.autoscaling.AlreadyExistsException;
-import org.apache.solr.client.solrj.cloud.autoscaling.BadVersionException;
-import org.apache.solr.client.solrj.cloud.DistribStateManager;
-import org.apache.solr.client.solrj.cloud.autoscaling.NotEmptyException;
-import org.apache.solr.client.solrj.cloud.autoscaling.VersionedData;
-import org.apache.solr.client.solrj.impl.ZkDistribStateManager;
-import org.apache.solr.cloud.ZkTestServer;
-import org.apache.solr.common.cloud.SolrZkClient;
-import org.apache.zookeeper.CreateMode;
-import org.apache.zookeeper.WatchedEvent;
-import org.apache.zookeeper.Watcher;
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-/**
- * This test compares a ZK-based {@link DistribStateManager} to the simulated one.
- */
-public class TestDistribStateManager extends SolrTestCaseJ4 {
- private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
-
- private DistribStateManager stateManager;
- private ZkTestServer zkTestServer;
- private SolrZkClient solrZkClient;
- private boolean simulated;
- private SimDistribStateManager.Node root;
-
- @Before
- public void setup() throws Exception {
- simulated = random().nextBoolean();
- if (simulated) {
- root = SimDistribStateManager.createNewRootNode();
- } else {
- zkTestServer = new ZkTestServer(createTempDir("zkDir").toString());
- zkTestServer.run();
- }
- reInit();
- }
-
- private void reInit() throws Exception {
- if (stateManager != null) {
- stateManager.close();
- }
- if (simulated) {
- stateManager = new SimDistribStateManager(root);
- } else {
- if (solrZkClient != null) {
- solrZkClient.close();
- }
- solrZkClient = new SolrZkClient(zkTestServer.getZkHost(), 30000);
- stateManager = new ZkDistribStateManager(solrZkClient);
- }
- log.info("Using " + stateManager.getClass().getName());
- }
-
- private DistribStateManager createDistribStateManager() {
- if (simulated) {
- return new SimDistribStateManager(root);
- } else {
- SolrZkClient cli = new SolrZkClient(zkTestServer.getZkHost(), 30000);
- return new ZkDistribStateManager(cli);
- }
- }
-
- private void destroyDistribStateManager(DistribStateManager mgr) throws Exception {
- mgr.close();
- if (mgr instanceof ZkDistribStateManager) {
- ((ZkDistribStateManager)mgr).getZkClient().close();
- }
- }
-
- @After
- public void teardown() throws Exception {
- if (solrZkClient != null) {
- solrZkClient.close();
- solrZkClient = null;
- }
- if (zkTestServer != null) {
- zkTestServer.shutdown();
- zkTestServer = null;
- }
- if (stateManager != null) {
- stateManager.close();
- }
- stateManager = null;
- }
-
- @Test
- public void testHasData() throws Exception {
- assertFalse(stateManager.hasData("/hasData/foo"));
- assertFalse(stateManager.hasData("/hasData/bar"));
- try {
- stateManager.createData("/hasData/foo", new byte[0], CreateMode.PERSISTENT);
- fail("should have failed (parent /hasData doesn't exist)");
- } catch (NoSuchElementException e) {
- // expected
- }
- stateManager.makePath("/hasData");
- stateManager.createData("/hasData/foo", new byte[0], CreateMode.PERSISTENT);
- stateManager.createData("/hasData/bar", new byte[0], CreateMode.PERSISTENT);
- assertTrue(stateManager.hasData("/hasData/foo"));
- assertTrue(stateManager.hasData("/hasData/bar"));
- }
-
- @Test
- public void testRemoveData() throws Exception {
- assertFalse(stateManager.hasData("/removeData/foo"));
- assertFalse(stateManager.hasData("/removeData/foo/bar"));
- assertFalse(stateManager.hasData("/removeData/baz"));
- assertFalse(stateManager.hasData("/removeData/baz/1/2/3"));
- stateManager.makePath("/removeData/foo/bar");
- stateManager.makePath("/removeData/baz/1/2/3");
- assertTrue(stateManager.hasData("/removeData/foo"));
- assertTrue(stateManager.hasData("/removeData/foo/bar"));
- assertTrue(stateManager.hasData("/removeData/baz/1/2/3"));
- try {
- stateManager.removeData("/removeData/foo", -1);
- fail("should have failed (node has children)");
- } catch (NotEmptyException e) {
- // expected
- }
- stateManager.removeData("/removeData/foo/bar", -1);
- stateManager.removeData("/removeData/foo", -1);
- // test recursive listing and removal
- stateManager.removeRecursively("/removeData/baz/1", false, false);
- assertFalse(stateManager.hasData("/removeData/baz/1/2"));
- assertTrue(stateManager.hasData("/removeData/baz/1"));
- // should silently ignore
- stateManager.removeRecursively("/removeData/baz/1/2", true, true);
- stateManager.removeRecursively("/removeData/baz/1", false, true);
- assertFalse(stateManager.hasData("/removeData/baz/1"));
- try {
- stateManager.removeRecursively("/removeData/baz/1", false, true);
- fail("should throw exception - missing path");
- } catch (NoSuchElementException e) {
- // expected
- }
- stateManager.removeRecursively("/removeData", true, true);
- assertFalse(stateManager.hasData("/removeData"));
- }
-
- @Test
- public void testListData() throws Exception {
- assertFalse(stateManager.hasData("/listData/foo"));
- assertFalse(stateManager.hasData("/listData/foo/bar"));
- try {
- stateManager.createData("/listData/foo/bar", new byte[0], CreateMode.PERSISTENT);
- fail("should not succeed");
- } catch (NoSuchElementException e) {
- // expected
- }
- try {
- stateManager.listData("/listData/foo");
- fail("should not succeed");
- } catch (NoSuchElementException e) {
- // expected
- }
- stateManager.makePath("/listData");
- List<String> kids = stateManager.listData("/listData");
- assertEquals(0, kids.size());
- stateManager.makePath("/listData/foo");
- kids = stateManager.listData("/listData");
- assertEquals(1, kids.size());
- assertEquals("foo", kids.get(0));
- stateManager.createData("/listData/foo/bar", new byte[0], CreateMode.PERSISTENT);
- stateManager.createData("/listData/foo/baz", new byte[0], CreateMode.PERSISTENT);
- kids = stateManager.listData("/listData/foo");
- assertEquals(2, kids.size());
- assertTrue(kids.contains("bar"));
- assertTrue(kids.contains("baz"));
- try {
- stateManager.createData("/listData/foo/bar", new byte[0], CreateMode.PERSISTENT);
- fail("should not succeed");
- } catch (AlreadyExistsException e) {
- // expected
- }
- }
-
- static final byte[] firstData = new byte[] {
- (byte)0xca, (byte)0xfe, (byte)0xba, (byte)0xbe
- };
-
- static final byte[] secondData = new byte[] {
- (byte)0xbe, (byte)0xba, (byte)0xfe, (byte)0xca
- };
-
- @Test
- public void testCreateMode() throws Exception {
- stateManager.makePath("/createMode");
- stateManager.createData("/createMode/persistent", firstData, CreateMode.PERSISTENT);
- stateManager.createData("/createMode/persistent_seq", firstData, CreateMode.PERSISTENT);
- for (int i = 0; i < 10; i++) {
- stateManager.createData("/createMode/persistent_seq/data", firstData, CreateMode.PERSISTENT_SEQUENTIAL);
- }
- // check what happens with gaps
- stateManager.createData("/createMode/persistent_seq/data", firstData, CreateMode.PERSISTENT_SEQUENTIAL);
- stateManager.removeData("/createMode/persistent_seq/data" + String.format(Locale.ROOT, "%010d", 10), -1);
- stateManager.createData("/createMode/persistent_seq/data", firstData, CreateMode.PERSISTENT_SEQUENTIAL);
-
- stateManager.createData("/createMode/ephemeral", firstData, CreateMode.EPHEMERAL);
- stateManager.createData("/createMode/ephemeral_seq", firstData, CreateMode.PERSISTENT);
- for (int i = 0; i < 10; i++) {
- stateManager.createData("/createMode/ephemeral_seq/data", firstData, CreateMode.EPHEMERAL_SEQUENTIAL);
- }
- assertTrue(stateManager.hasData("/createMode"));
- assertTrue(stateManager.hasData("/createMode/persistent"));
- assertTrue(stateManager.hasData("/createMode/ephemeral"));
- List<String> kids = stateManager.listData("/createMode/persistent_seq");
- assertEquals(11, kids.size());
- kids = stateManager.listData("/createMode/ephemeral_seq");
- assertEquals(10, kids.size());
- for (int i = 0; i < 10; i++) {
- assertTrue(stateManager.hasData("/createMode/persistent_seq/data" + String.format(Locale.ROOT, "%010d", i)));
- }
- assertFalse(stateManager.hasData("/createMode/persistent_seq/data" + String.format(Locale.ROOT, "%010d", 10)));
- assertTrue(stateManager.hasData("/createMode/persistent_seq/data" + String.format(Locale.ROOT, "%010d", 11)));
-
- for (int i = 0; i < 10; i++) {
- assertTrue(stateManager.hasData("/createMode/ephemeral_seq/data" + String.format(Locale.ROOT, "%010d", i)));
- }
- // check that ephemeral nodes disappear on disconnect
- reInit();
- assertTrue(stateManager.hasData("/createMode/persistent"));
- for (int i = 0; i < 10; i++) {
- assertTrue(stateManager.hasData("/createMode/persistent_seq/data" + String.format(Locale.ROOT, "%010d", i)));
- }
- assertTrue(stateManager.hasData("/createMode/persistent_seq/data" + String.format(Locale.ROOT, "%010d", 11)));
-
- assertFalse(stateManager.hasData("/createMode/ephemeral"));
- assertTrue(stateManager.hasData("/createMode/ephemeral_seq"));
- kids = stateManager.listData("/createMode/ephemeral_seq");
- assertEquals(0, kids.size());
- }
-
- static class OnceWatcher implements Watcher {
- CountDownLatch triggered = new CountDownLatch(1);
- WatchedEvent event;
-
- @Override
- public void process(WatchedEvent event) {
- if (triggered.getCount() == 0) {
- fail("Watch was already triggered once!");
- }
- triggered.countDown();
- this.event = event;
- }
- }
-
- @Test
- public void testGetSetRemoveData() throws Exception {
- stateManager.makePath("/getData");
- stateManager.createData("/getData/persistentData", firstData, CreateMode.PERSISTENT);
- OnceWatcher nodeWatcher = new OnceWatcher();
- VersionedData vd = stateManager.getData("/getData/persistentData", nodeWatcher);
- assertNotNull(vd);
- assertEquals(0, vd.getVersion());
- assertTrue(Arrays.equals(firstData, vd.getData()));
-
- // update data, test versioning
- try {
- stateManager.setData("/getData/persistentData", secondData, 1);
- fail("should have failed");
- } catch (BadVersionException e) {
- // expected
- }
- // watch should not have fired
- assertEquals(1, nodeWatcher.triggered.getCount());
-
- stateManager.setData("/getData/persistentData", secondData, 0);
- if (!nodeWatcher.triggered.await(5, TimeUnit.SECONDS)) {
- fail("Node watch should have fired!");
- }
- // watch should not fire now because it needs to be reset
- stateManager.setData("/getData/persistentData", secondData, -1);
-
- // create ephemeral node using another ZK connection
- DistribStateManager ephemeralMgr = createDistribStateManager();
- ephemeralMgr.createData("/getData/ephemeralData", firstData, CreateMode.EPHEMERAL);
-
- nodeWatcher = new OnceWatcher();
- vd = stateManager.getData("/getData/ephemeralData", nodeWatcher);
- destroyDistribStateManager(ephemeralMgr);
- if (!nodeWatcher.triggered.await(5, TimeUnit.SECONDS)) {
- fail("Node watch should have fired!");
- }
- assertTrue(stateManager.hasData("/getData/persistentData"));
- assertFalse(stateManager.hasData("/getData/ephemeralData"));
-
- nodeWatcher = new OnceWatcher();
- vd = stateManager.getData("/getData/persistentData", nodeWatcher);
- // try wrong version
- try {
- stateManager.removeData("/getData/persistentData", vd.getVersion() - 1);
- fail("should have failed");
- } catch (BadVersionException e) {
- // expected
- }
- // watch should not have fired
- assertEquals(1, nodeWatcher.triggered.getCount());
-
- stateManager.removeData("/getData/persistentData", vd.getVersion());
- if (!nodeWatcher.triggered.await(5, TimeUnit.SECONDS)) {
- fail("Node watch should have fired!");
- }
- }
-
- @Test
- public void testMulti() throws Exception {
-
- }
-
-}
http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/b949f57f/solr/core/src/test/org/apache/solr/cloud/autoscaling/sim/TestExecutePlanAction.java
----------------------------------------------------------------------
diff --git a/solr/core/src/test/org/apache/solr/cloud/autoscaling/sim/TestExecutePlanAction.java b/solr/core/src/test/org/apache/solr/cloud/autoscaling/sim/TestExecutePlanAction.java
deleted file mode 100644
index 796dc3b..0000000
--- a/solr/core/src/test/org/apache/solr/cloud/autoscaling/sim/TestExecutePlanAction.java
+++ /dev/null
@@ -1,209 +0,0 @@
-/*
- * 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.solr.cloud.autoscaling.sim;
-
-import java.lang.invoke.MethodHandles;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.atomic.AtomicBoolean;
-import java.util.stream.Collectors;
-
-import com.google.common.collect.Lists;
-import org.apache.lucene.util.LuceneTestCase;
-import org.apache.solr.client.solrj.SolrClient;
-import org.apache.solr.client.solrj.SolrRequest;
-import org.apache.solr.client.solrj.cloud.autoscaling.TriggerEventType;
-import org.apache.solr.client.solrj.cloud.autoscaling.VersionedData;
-import org.apache.solr.client.solrj.request.CollectionAdminRequest;
-import org.apache.solr.cloud.CloudTestUtils;
-import org.apache.solr.cloud.autoscaling.ActionContext;
-import org.apache.solr.cloud.autoscaling.AutoScalingHandlerTest;
-import org.apache.solr.cloud.autoscaling.ExecutePlanAction;
-import org.apache.solr.cloud.autoscaling.NodeLostTrigger;
-import org.apache.solr.common.cloud.ClusterState;
-import org.apache.solr.common.cloud.DocCollection;
-import org.apache.solr.common.cloud.Replica;
-import org.apache.solr.common.cloud.ZkStateReader;
-import org.apache.solr.common.params.CollectionParams;
-import org.apache.solr.common.util.NamedList;
-import org.apache.solr.common.util.Utils;
-import org.apache.solr.util.LogLevel;
-import org.apache.solr.common.util.TimeSource;
-import org.junit.After;
-import org.junit.BeforeClass;
-import org.junit.Test;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-/**
- * Test for {@link ExecutePlanAction}
- */
-@LogLevel("org.apache.solr.cloud=DEBUG")
-public class TestExecutePlanAction extends SimSolrCloudTestCase {
- private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
-
- private static final int NODE_COUNT = 2;
-
- @BeforeClass
- public static void setupCluster() throws Exception {
- configureCluster(NODE_COUNT, TimeSource.get("simTime:50"));
- }
-
- @After
- public void printState() throws Exception {
- log.info("-------------_ FINAL STATE --------------");
- log.info("* Node values: " + Utils.toJSONString(cluster.getSimNodeStateProvider().simGetAllNodeValues()));
- log.info("* Live nodes: " + cluster.getClusterStateProvider().getLiveNodes());
- ClusterState state = cluster.getClusterStateProvider().getClusterState();
- for (String coll: cluster.getSimClusterStateProvider().simListCollections()) {
- log.info("* Collection " + coll + " state: " + state.getCollection(coll));
- }
-
- }
-
- @Test
- @LuceneTestCase.BadApple(bugUrl="https://issues.apache.org/jira/browse/SOLR-12028") // 28-June-2018
- public void testExecute() throws Exception {
- SolrClient solrClient = cluster.simGetSolrClient();
- String collectionName = "testExecute";
- CollectionAdminRequest.Create create = CollectionAdminRequest.createCollection(collectionName,
- "conf", 1, 2);
- create.setMaxShardsPerNode(1);
- create.process(solrClient);
-
- log.info("Collection ready after " + CloudTestUtils.waitForState(cluster, collectionName, 120, TimeUnit.SECONDS,
- CloudTestUtils.clusterShape(1, 2, false, true)) + "ms");
-
- String sourceNodeName = cluster.getSimClusterStateProvider().simGetRandomNode(random());
- ClusterState clusterState = cluster.getClusterStateProvider().getClusterState();
- DocCollection docCollection = clusterState.getCollection(collectionName);
- List<Replica> replicas = docCollection.getReplicas(sourceNodeName);
- assertNotNull(replicas);
- assertFalse(replicas.isEmpty());
-
- List<String> otherNodes = cluster.getClusterStateProvider().getLiveNodes().stream()
- .filter(node -> !node.equals(sourceNodeName)).collect(Collectors.toList());
- assertFalse(otherNodes.isEmpty());
- String survivor = otherNodes.get(0);
-
- try (ExecutePlanAction action = new ExecutePlanAction()) {
- action.configure(cluster.getLoader(), cluster, Collections.singletonMap("name", "execute_plan"));
-
- // used to signal if we found that ExecutePlanAction did in fact create the right znode before executing the operation
- AtomicBoolean znodeCreated = new AtomicBoolean(false);
-
- CollectionAdminRequest.AsyncCollectionAdminRequest moveReplica = new CollectionAdminRequest.MoveReplica(collectionName, replicas.get(0).getName(), survivor);
- CollectionAdminRequest.AsyncCollectionAdminRequest mockRequest = new CollectionAdminRequest.AsyncCollectionAdminRequest(CollectionParams.CollectionAction.OVERSEERSTATUS) {
- @Override
- public void setAsyncId(String asyncId) {
- super.setAsyncId(asyncId);
- String parentPath = ZkStateReader.SOLR_AUTOSCALING_TRIGGER_STATE_PATH + "/xyz/execute_plan";
- try {
- if (cluster.getDistribStateManager().hasData(parentPath)) {
- java.util.List<String> children = cluster.getDistribStateManager().listData(parentPath);
- if (!children.isEmpty()) {
- String child = children.get(0);
- VersionedData data = cluster.getDistribStateManager().getData(parentPath + "/" + child);
- Map m = (Map) Utils.fromJSON(data.getData());
- if (m.containsKey("requestid")) {
- znodeCreated.set(m.get("requestid").equals(asyncId));
- }
- }
- }
- } catch (Exception e) {
- throw new RuntimeException(e);
- }
-
- }
- };
- List<CollectionAdminRequest.AsyncCollectionAdminRequest> operations = Lists.asList(moveReplica, new CollectionAdminRequest.AsyncCollectionAdminRequest[]{mockRequest});
- NodeLostTrigger.NodeLostEvent nodeLostEvent = new NodeLostTrigger.NodeLostEvent(TriggerEventType.NODELOST,
- "mock_trigger_name", Collections.singletonList(TimeSource.CURRENT_TIME.getTimeNs()),
- Collections.singletonList(sourceNodeName));
- ActionContext actionContext = new ActionContext(cluster, null,
- new HashMap<>(Collections.singletonMap("operations", operations)));
- action.process(nodeLostEvent, actionContext);
-
-// assertTrue("ExecutePlanAction should have stored the requestid in ZK before executing the request", znodeCreated.get());
- List<NamedList<Object>> responses = (List<NamedList<Object>>) actionContext.getProperty("responses");
- assertNotNull(responses);
- assertEquals(2, responses.size());
- NamedList<Object> response = responses.get(0);
- assertNull(response.get("failure"));
- assertNotNull(response.get("success"));
- }
-
- log.info("Collection ready after " + CloudTestUtils.waitForState(cluster, collectionName, 300, TimeUnit.SECONDS,
- CloudTestUtils.clusterShape(1, 2, false, true)) + "ms");
- }
-
- @Test
- public void testIntegration() throws Exception {
- SolrClient solrClient = cluster.simGetSolrClient();
-
- String setTriggerCommand = "{" +
- "'set-trigger' : {" +
- "'name' : 'node_lost_trigger'," +
- "'event' : 'nodeLost'," +
- "'waitFor' : '1s'," +
- "'enabled' : true," +
- "'actions' : [{'name':'compute_plan', 'class' : 'solr.ComputePlanAction'}," +
- "{'name':'execute_plan','class':'solr.ExecutePlanAction'}]" +
- "}}";
- SolrRequest req = AutoScalingHandlerTest.createAutoScalingRequest(SolrRequest.METHOD.POST, setTriggerCommand);
- NamedList<Object> response = solrClient.request(req);
- assertEquals(response.get("result").toString(), "success");
-
- String collectionName = "testIntegration";
- CollectionAdminRequest.Create create = CollectionAdminRequest.createCollection(collectionName,
- "conf", 1, 2);
- create.setMaxShardsPerNode(1);
- create.process(solrClient);
-
- CloudTestUtils.waitForState(cluster, "Timed out waiting for replicas of new collection to be active",
- collectionName, CloudTestUtils.clusterShape(1, 2, false, true));
-
- String sourceNodeName = cluster.getSimClusterStateProvider().simGetRandomNode(random());
- ClusterState clusterState = cluster.getClusterStateProvider().getClusterState();
- DocCollection docCollection = clusterState.getCollection(collectionName);
- List<Replica> replicas = docCollection.getReplicas(sourceNodeName);
- assertNotNull(replicas);
- assertFalse(replicas.isEmpty());
-
- List<String> otherNodes = cluster.getClusterStateProvider().getLiveNodes().stream()
- .filter(node -> !node.equals(sourceNodeName)).collect(Collectors.toList());
- assertFalse(otherNodes.isEmpty());
- String survivor = otherNodes.get(0);
-
- cluster.simRemoveNode(sourceNodeName, false);
-
- cluster.getTimeSource().sleep(3000);
-
- CloudTestUtils.waitForState(cluster, "Timed out waiting for replicas of collection to be 2 again",
- collectionName, CloudTestUtils.clusterShape(1, 2, false, true));
-
- clusterState = cluster.getClusterStateProvider().getClusterState();
- docCollection = clusterState.getCollection(collectionName);
- List<Replica> replicasOnSurvivor = docCollection.getReplicas(survivor);
- assertNotNull(replicasOnSurvivor);
- assertEquals(2, replicasOnSurvivor.size());
- }
-}
http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/b949f57f/solr/core/src/test/org/apache/solr/cloud/autoscaling/sim/TestGenericDistributedQueue.java
----------------------------------------------------------------------
diff --git a/solr/core/src/test/org/apache/solr/cloud/autoscaling/sim/TestGenericDistributedQueue.java b/solr/core/src/test/org/apache/solr/cloud/autoscaling/sim/TestGenericDistributedQueue.java
deleted file mode 100644
index 7e19f00..0000000
--- a/solr/core/src/test/org/apache/solr/cloud/autoscaling/sim/TestGenericDistributedQueue.java
+++ /dev/null
@@ -1,40 +0,0 @@
-/*
- * 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.solr.cloud.autoscaling.sim;
-
-import org.apache.lucene.util.LuceneTestCase;
-import org.apache.solr.client.solrj.cloud.DistributedQueue;
-import org.apache.solr.client.solrj.cloud.DistribStateManager;
-
-/**
- *
- */
-@LuceneTestCase.BadApple(bugUrl="https://issues.apache.org/jira/browse/SOLR-12028") // 2018-02-26
-public class TestGenericDistributedQueue extends TestSimDistributedQueue {
- DistribStateManager stateManager = new SimDistribStateManager();
-
- @Override
- @LuceneTestCase.BadApple(bugUrl="https://issues.apache.org/jira/browse/SOLR-12028") // 2-Aug-2018
- protected DistributedQueue makeDistributedQueue(String dqZNode) throws Exception {
- return new GenericDistributedQueue(stateManager, dqZNode);
- }
-
- @BadApple(bugUrl="https://issues.apache.org/jira/browse/SOLR-12028") // added 09-Aug-2018
- public void testDistributedQueue() throws Exception {
- super.testDistributedQueue();
- }
-}
[4/6] lucene-solr:branch_7x: SOLR-12669: Rename tests that use the
autoscaling simulation framework.
Posted by ab...@apache.org.
http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/b949f57f/solr/core/src/test/org/apache/solr/cloud/autoscaling/sim/TestSimClusterStateProvider.java
----------------------------------------------------------------------
diff --git a/solr/core/src/test/org/apache/solr/cloud/autoscaling/sim/TestSimClusterStateProvider.java b/solr/core/src/test/org/apache/solr/cloud/autoscaling/sim/TestSimClusterStateProvider.java
new file mode 100644
index 0000000..40ca91b
--- /dev/null
+++ b/solr/core/src/test/org/apache/solr/cloud/autoscaling/sim/TestSimClusterStateProvider.java
@@ -0,0 +1,225 @@
+/*
+ * 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.solr.cloud.autoscaling.sim;
+
+import java.lang.invoke.MethodHandles;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+import org.apache.solr.client.solrj.cloud.autoscaling.AutoScalingConfig;
+import org.apache.solr.client.solrj.cloud.autoscaling.Preference;
+import org.apache.solr.client.solrj.cloud.autoscaling.ReplicaInfo;
+import org.apache.solr.client.solrj.cloud.SolrCloudManager;
+import org.apache.solr.client.solrj.embedded.JettySolrRunner;
+import org.apache.solr.client.solrj.request.CollectionAdminRequest;
+import org.apache.solr.cloud.SolrCloudTestCase;
+import org.apache.solr.common.cloud.ClusterState;
+import org.apache.solr.common.cloud.DocCollection;
+import org.apache.solr.common.cloud.Replica;
+import org.apache.solr.common.cloud.Slice;
+import org.apache.solr.common.cloud.ZkStateReader;
+import org.apache.solr.common.cloud.rule.ImplicitSnitch;
+import org.apache.solr.common.params.CollectionAdminParams;
+import org.apache.solr.common.util.TimeSource;
+import org.apache.solr.common.util.Utils;
+import org.apache.zookeeper.Watcher;
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * This test compares the cluster state of a real cluster and a simulated one.
+ */
+public class TestSimClusterStateProvider extends SolrCloudTestCase {
+ private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
+
+ private static int NODE_COUNT = 3;
+ private static boolean simulated;
+
+ private static SolrCloudManager cloudManager;
+
+ private static Collection<String> liveNodes;
+ private static Map<String, Object> clusterProperties;
+ private static AutoScalingConfig autoScalingConfig;
+ private static Map<String, Map<String, Map<String, List<ReplicaInfo>>>> replicas;
+ private static Map<String, Map<String, Object>> nodeValues;
+ private static ClusterState realState;
+
+ // set up a real cluster as the source of test data
+ @BeforeClass
+ public static void setupCluster() throws Exception {
+ simulated = random().nextBoolean();
+ log.info("####### Using simulated components? " + simulated);
+
+ configureCluster(NODE_COUNT)
+ .addConfig("conf", configset("cloud-minimal"))
+ .configure();
+ CollectionAdminRequest.createCollection(CollectionAdminParams.SYSTEM_COLL, null, 1, 2, 0, 1)
+ .process(cluster.getSolrClient());
+ init();
+ }
+
+ @AfterClass
+ public static void closeCloudManager() throws Exception {
+ if (simulated && cloudManager != null) {
+ cloudManager.close();
+ }
+ }
+
+ private static void init() throws Exception {
+ SolrCloudManager realManager = cluster.getJettySolrRunner(cluster.getJettySolrRunners().size() - 1).getCoreContainer()
+ .getZkController().getSolrCloudManager();
+ liveNodes = realManager.getClusterStateProvider().getLiveNodes();
+ clusterProperties = realManager.getClusterStateProvider().getClusterProperties();
+ autoScalingConfig = realManager.getDistribStateManager().getAutoScalingConfig();
+ replicas = new HashMap<>();
+ nodeValues = new HashMap<>();
+ liveNodes.forEach(n -> {
+ replicas.put(n, realManager.getNodeStateProvider().getReplicaInfo(n, Collections.emptySet()));
+ nodeValues.put(n, realManager.getNodeStateProvider().getNodeValues(n, ImplicitSnitch.tags));
+ });
+ realState = realManager.getClusterStateProvider().getClusterState();
+
+ if (simulated) {
+ // initialize simulated provider
+ SimCloudManager simCloudManager = new SimCloudManager(TimeSource.get("simTime:10"));
+ simCloudManager.getSimClusterStateProvider().simSetClusterProperties(clusterProperties);
+ simCloudManager.getSimDistribStateManager().simSetAutoScalingConfig(autoScalingConfig);
+ nodeValues.forEach((n, values) -> {
+ try {
+ simCloudManager.getSimNodeStateProvider().simSetNodeValues(n, values);
+ } catch (InterruptedException e) {
+ fail("Interrupted:" + e);
+ }
+ });
+ simCloudManager.getSimClusterStateProvider().simSetClusterState(realState);
+ ClusterState simState = simCloudManager.getClusterStateProvider().getClusterState();
+ assertClusterStateEquals(realState, simState);
+ cloudManager = simCloudManager;
+ } else {
+ cloudManager = realManager;
+ }
+ }
+
+ private static void assertClusterStateEquals(ClusterState one, ClusterState two) {
+ assertEquals(one.getLiveNodes(), two.getLiveNodes());
+ assertEquals(one.getCollectionsMap().keySet(), two.getCollectionsMap().keySet());
+ one.forEachCollection(oneColl -> {
+ DocCollection twoColl = two.getCollection(oneColl.getName());
+ Map<String, Slice> oneSlices = oneColl.getSlicesMap();
+ Map<String, Slice> twoSlices = twoColl.getSlicesMap();
+ assertEquals(oneSlices.keySet(), twoSlices.keySet());
+ oneSlices.forEach((s, slice) -> {
+ Slice sTwo = twoSlices.get(s);
+ for (Replica oneReplica : slice.getReplicas()) {
+ Replica twoReplica = sTwo.getReplica(oneReplica.getName());
+ assertNotNull(twoReplica);
+ assertEquals(oneReplica, twoReplica);
+ }
+ });
+ });
+ }
+
+ private String addNode() throws Exception {
+ JettySolrRunner solr = cluster.startJettySolrRunner();
+ String nodeId = solr.getNodeName();
+ if (simulated) {
+ ((SimCloudManager) cloudManager).getSimClusterStateProvider().simAddNode(nodeId);
+ }
+ return nodeId;
+ }
+
+ private String deleteNode() throws Exception {
+ String nodeId = cluster.getJettySolrRunner(0).getNodeName();
+ cluster.stopJettySolrRunner(0);
+ if (simulated) {
+ ((SimCloudManager) cloudManager).getSimClusterStateProvider().simRemoveNode(nodeId);
+ }
+ return nodeId;
+ }
+
+ private void setAutoScalingConfig(AutoScalingConfig cfg) throws Exception {
+ cluster.getJettySolrRunner(0).getCoreContainer().getZkController().getZkClient().setData(ZkStateReader.SOLR_AUTOSCALING_CONF_PATH,
+ Utils.toJSON(cfg), -1, true);
+ if (simulated) {
+ ((SimCloudManager) cloudManager).getSimDistribStateManager().simSetAutoScalingConfig(cfg);
+ }
+ }
+
+ @Test
+ public void testAddRemoveNode() throws Exception {
+ Set<String> lastNodes = new HashSet<>(cloudManager.getClusterStateProvider().getLiveNodes());
+ List<String> liveNodes = cloudManager.getDistribStateManager().listData(ZkStateReader.LIVE_NODES_ZKNODE);
+ assertEquals(lastNodes.size(), liveNodes.size());
+ liveNodes.removeAll(lastNodes);
+ assertTrue(liveNodes.isEmpty());
+
+ String node = addNode();
+ cloudManager.getTimeSource().sleep(2000);
+ assertFalse(lastNodes.contains(node));
+ lastNodes = new HashSet<>(cloudManager.getClusterStateProvider().getLiveNodes());
+ assertTrue(lastNodes.contains(node));
+ liveNodes = cloudManager.getDistribStateManager().listData(ZkStateReader.LIVE_NODES_ZKNODE);
+ assertEquals(lastNodes.size(), liveNodes.size());
+ liveNodes.removeAll(lastNodes);
+ assertTrue(liveNodes.isEmpty());
+
+ node = deleteNode();
+ cloudManager.getTimeSource().sleep(2000);
+ assertTrue(lastNodes.contains(node));
+ lastNodes = new HashSet<>(cloudManager.getClusterStateProvider().getLiveNodes());
+ assertFalse(lastNodes.contains(node));
+ liveNodes = cloudManager.getDistribStateManager().listData(ZkStateReader.LIVE_NODES_ZKNODE);
+ assertEquals(lastNodes.size(), liveNodes.size());
+ liveNodes.removeAll(lastNodes);
+ assertTrue(liveNodes.isEmpty()); }
+
+ @Test
+ public void testAutoScalingConfig() throws Exception {
+ final CountDownLatch triggered = new CountDownLatch(1);
+ Watcher w = ev -> {
+ if (triggered.getCount() == 0) {
+ fail("already triggered once!");
+ }
+ triggered.countDown();
+ };
+ AutoScalingConfig cfg = cloudManager.getDistribStateManager().getAutoScalingConfig(w);
+ assertEquals(autoScalingConfig, cfg);
+ Preference p = new Preference(Collections.singletonMap("maximize", "freedisk"));
+ cfg = cfg.withPolicy(cfg.getPolicy().withClusterPreferences(Collections.singletonList(p)));
+ setAutoScalingConfig(cfg);
+ if (!triggered.await(10, TimeUnit.SECONDS)) {
+ fail("Watch should be triggered on update!");
+ }
+ AutoScalingConfig cfg1 = cloudManager.getDistribStateManager().getAutoScalingConfig(null);
+ assertEquals(cfg, cfg1);
+
+ // restore
+ setAutoScalingConfig(autoScalingConfig);
+ cfg1 = cloudManager.getDistribStateManager().getAutoScalingConfig(null);
+ assertEquals(autoScalingConfig, cfg1);
+ }
+}
http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/b949f57f/solr/core/src/test/org/apache/solr/cloud/autoscaling/sim/TestSimComputePlanAction.java
----------------------------------------------------------------------
diff --git a/solr/core/src/test/org/apache/solr/cloud/autoscaling/sim/TestSimComputePlanAction.java b/solr/core/src/test/org/apache/solr/cloud/autoscaling/sim/TestSimComputePlanAction.java
new file mode 100644
index 0000000..719bb7b
--- /dev/null
+++ b/solr/core/src/test/org/apache/solr/cloud/autoscaling/sim/TestSimComputePlanAction.java
@@ -0,0 +1,358 @@
+/*
+ * 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.solr.cloud.autoscaling.sim;
+
+import java.io.IOException;
+import java.lang.invoke.MethodHandles;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicReference;
+
+import org.apache.solr.client.solrj.SolrClient;
+import org.apache.solr.client.solrj.SolrRequest;
+import org.apache.solr.client.solrj.SolrResponse;
+import org.apache.solr.client.solrj.cloud.SolrCloudManager;
+import org.apache.solr.client.solrj.cloud.autoscaling.TriggerEventType;
+import org.apache.solr.client.solrj.request.CollectionAdminRequest;
+import org.apache.solr.cloud.CloudTestUtils;
+import org.apache.solr.cloud.autoscaling.ActionContext;
+import org.apache.solr.cloud.autoscaling.ComputePlanAction;
+import org.apache.solr.cloud.autoscaling.ScheduledTriggers;
+import org.apache.solr.cloud.autoscaling.TriggerAction;
+import org.apache.solr.cloud.autoscaling.TriggerEvent;
+import org.apache.solr.cloud.autoscaling.TriggerValidationException;
+import org.apache.solr.common.cloud.ClusterState;
+import org.apache.solr.common.cloud.DocCollection;
+import org.apache.solr.common.cloud.Replica;
+import org.apache.solr.common.params.CollectionParams;
+import org.apache.solr.common.params.SolrParams;
+import org.apache.solr.common.util.NamedList;
+import org.apache.solr.common.util.TimeSource;
+import org.apache.solr.common.util.Utils;
+import org.apache.solr.core.SolrResourceLoader;
+import org.apache.solr.util.LogLevel;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import static org.apache.solr.cloud.autoscaling.AutoScalingHandlerTest.createAutoScalingRequest;
+import static org.apache.solr.common.params.CollectionParams.CollectionAction.MOVEREPLICA;
+
+/**
+ * Test for {@link ComputePlanAction}
+ */
+@LogLevel("org.apache.solr.cloud.autoscaling=DEBUG;org.apache.solr.cloud.Overseer=DEBUG;org.apache.solr.cloud.overseer=DEBUG;org.apache.solr.client.solrj.cloud.autoscaling=DEBUG;")
+public class TestSimComputePlanAction extends SimSolrCloudTestCase {
+ private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
+
+ private static final AtomicBoolean fired = new AtomicBoolean(false);
+ private static final int NODE_COUNT = 1;
+ private static CountDownLatch triggerFiredLatch = new CountDownLatch(1);
+ private static final AtomicReference<Map> actionContextPropsRef = new AtomicReference<>();
+ private static final AtomicReference<TriggerEvent> eventRef = new AtomicReference<>();
+
+ @BeforeClass
+ public static void setupCluster() throws Exception {
+ configureCluster(1, TimeSource.get("simTime:50"));
+ }
+
+ @Before
+ public void init() throws Exception {
+
+ fired.set(false);
+ triggerFiredLatch = new CountDownLatch(1);
+ actionContextPropsRef.set(null);
+
+ String setClusterPolicyCommand = "{" +
+ " 'set-cluster-policy': [" +
+ " {'cores':'<10', 'node':'#ANY'}," +
+ " {'replica':'<2', 'shard': '#EACH', 'node': '#ANY'}," +
+ " {'nodeRole':'overseer', 'replica':0}" +
+ " ]" +
+ "}";
+ SolrRequest req = createAutoScalingRequest(SolrRequest.METHOD.POST, setClusterPolicyCommand);
+ SolrResponse rsp = cluster.request(req);
+ NamedList<Object> response = rsp.getResponse();
+ assertEquals(response.get("result").toString(), "success");
+
+ String setClusterPreferencesCommand = "{" +
+ "'set-cluster-preferences': [" +
+ "{'minimize': 'cores'}," +
+ "{'maximize': 'freedisk','precision': 100}]" +
+ "}";
+ req = createAutoScalingRequest(SolrRequest.METHOD.POST, setClusterPreferencesCommand);
+ rsp = cluster.request(req);
+ response = rsp.getResponse();
+ assertEquals(response.get("result").toString(), "success");
+ cluster.getTimeSource().sleep(TimeUnit.SECONDS.toMillis(ScheduledTriggers.DEFAULT_COOLDOWN_PERIOD_SECONDS));
+ }
+
+ @After
+ public void printState() throws Exception {
+ log.info("-------------_ FINAL STATE --------------");
+ log.info("* Node values: " + Utils.toJSONString(cluster.getSimNodeStateProvider().simGetAllNodeValues()));
+ log.info("* Live nodes: " + cluster.getClusterStateProvider().getLiveNodes());
+ ClusterState state = cluster.getClusterStateProvider().getClusterState();
+ for (String coll: cluster.getSimClusterStateProvider().simListCollections()) {
+ log.info("* Collection " + coll + " state: " + state.getCollection(coll));
+ }
+
+ }
+
+ @Test
+ public void testNodeLost() throws Exception {
+ // let's start a node so that we have at least two
+ String node = cluster.simAddNode();
+ AssertingTriggerAction.expectedNode = node;
+
+ SolrClient solrClient = cluster.simGetSolrClient();
+ String setTriggerCommand = "{" +
+ "'set-trigger' : {" +
+ "'name' : 'node_lost_trigger'," +
+ "'event' : 'nodeLost'," +
+ "'waitFor' : '7s'," +
+ "'enabled' : true," +
+ "'actions' : [{'name':'compute_plan', 'class' : 'solr.ComputePlanAction'}," +
+ "{'name':'test','class':'" + TestSimComputePlanAction.AssertingTriggerAction.class.getName() + "'}]" +
+ "}}";
+ SolrRequest req = createAutoScalingRequest(SolrRequest.METHOD.POST, setTriggerCommand);
+ NamedList<Object> response = solrClient.request(req);
+ assertEquals(response.get("result").toString(), "success");
+
+ CollectionAdminRequest.Create create = CollectionAdminRequest.createCollection("testNodeLost",
+ "conf",1, 2);
+ create.process(solrClient);
+
+ CloudTestUtils.waitForState(cluster, "Timed out waiting for replicas of new collection to be active",
+ "testNodeLost", CloudTestUtils.clusterShape(1, 2, false, true));
+
+ ClusterState clusterState = cluster.getClusterStateProvider().getClusterState();
+ log.debug("-- cluster state: {}", clusterState);
+ DocCollection collection = clusterState.getCollection("testNodeLost");
+ List<Replica> replicas = collection.getReplicas(node);
+ assertNotNull(replicas);
+ assertFalse(replicas.isEmpty());
+
+ // start another node because because when the other node goes away, the cluster policy requires only
+ // 1 replica per node and none on the overseer
+ String node2 = cluster.simAddNode();
+ assertTrue(node2 + "is not live yet", cluster.getClusterStateProvider().getClusterState().liveNodesContain(node2) );
+
+ // stop the original node
+ cluster.simRemoveNode(node, false);
+ log.info("Stopped_node : {}", node);
+
+ assertTrue("Trigger was not fired even after 10 seconds", triggerFiredLatch.await(10, TimeUnit.SECONDS));
+ assertTrue(fired.get());
+ Map context = actionContextPropsRef.get();
+ assertNotNull(context);
+ List<SolrRequest> operations = (List<SolrRequest>) context.get("operations");
+ assertNotNull("The operations computed by ComputePlanAction should not be null , " + eventRef.get(), operations);
+ assertEquals("ComputePlanAction should have computed exactly 1 operation", 1, operations.size());
+ SolrRequest solrRequest = operations.get(0);
+ SolrParams params = solrRequest.getParams();
+ assertEquals("Expected MOVEREPLICA action after adding node", MOVEREPLICA, CollectionParams.CollectionAction.get(params.get("action")));
+ String replicaToBeMoved = params.get("replica");
+ assertEquals("Unexpected node in computed operation", replicas.get(0).getName(), replicaToBeMoved);
+
+ // shutdown the extra node that we had started
+ cluster.simRemoveNode(node2, false);
+ }
+
+ public void testNodeWithMultipleReplicasLost() throws Exception {
+ AssertingTriggerAction.expectedNode = null;
+
+ // start 3 more nodes
+ cluster.simAddNode();
+ cluster.simAddNode();
+ cluster.simAddNode();
+
+ SolrClient solrClient = cluster.simGetSolrClient();
+ String setTriggerCommand = "{" +
+ "'set-trigger' : {" +
+ "'name' : 'node_lost_trigger'," +
+ "'event' : 'nodeLost'," +
+ "'waitFor' : '1s'," +
+ "'enabled' : true," +
+ "'actions' : [{'name':'compute_plan', 'class' : 'solr.ComputePlanAction'}," +
+ "{'name':'test','class':'" + AssertingTriggerAction.class.getName() + "'}]" +
+ "}}";
+ SolrRequest req = createAutoScalingRequest(SolrRequest.METHOD.POST, setTriggerCommand);
+ NamedList<Object> response = solrClient.request(req);
+ assertEquals(response.get("result").toString(), "success");
+
+ CollectionAdminRequest.Create create = CollectionAdminRequest.createCollection("testNodeWithMultipleReplicasLost",
+ "conf",2, 3);
+// create.setMaxShardsPerNode(2);
+ create.process(solrClient);
+
+ CloudTestUtils.waitForState(cluster, "Timed out waiting for replicas of new collection to be active",
+ "testNodeWithMultipleReplicasLost", CloudTestUtils.clusterShape(2, 3, false, true));
+
+ ClusterState clusterState = cluster.getClusterStateProvider().getClusterState();
+ log.debug("-- cluster state: {}", clusterState);
+ DocCollection docCollection = clusterState.getCollection("testNodeWithMultipleReplicasLost");
+
+ // lets find a node with at least 2 replicas
+ String stoppedNodeName = null;
+ List<Replica> replicasToBeMoved = null;
+ for (String node : cluster.getClusterStateProvider().getLiveNodes()) {
+ List<Replica> replicas = docCollection.getReplicas(node);
+ if (replicas != null && replicas.size() == 2) {
+ stoppedNodeName = node;
+ replicasToBeMoved = replicas;
+ cluster.simRemoveNode(node, false);
+ break;
+ }
+ }
+ assertNotNull(stoppedNodeName);
+
+ assertTrue("Trigger was not fired even after 5 seconds", triggerFiredLatch.await(5, TimeUnit.SECONDS));
+ assertTrue(fired.get());
+
+ TriggerEvent triggerEvent = eventRef.get();
+ assertNotNull(triggerEvent);
+ assertEquals(TriggerEventType.NODELOST, triggerEvent.getEventType());
+ // TODO assertEquals(stoppedNodeName, triggerEvent.getProperty(TriggerEvent.NODE_NAME));
+
+ Map context = actionContextPropsRef.get();
+ assertNotNull(context);
+ List<SolrRequest> operations = (List<SolrRequest>) context.get("operations");
+ assertNotNull("The operations computed by ComputePlanAction should not be null " + actionContextPropsRef.get() + "\nevent: " + eventRef.get(), operations);
+ operations.forEach(solrRequest -> log.info(solrRequest.getParams().toString()));
+ assertEquals("ComputePlanAction should have computed exactly 2 operation", 2, operations.size());
+
+ for (SolrRequest solrRequest : operations) {
+ SolrParams params = solrRequest.getParams();
+ assertEquals("Expected MOVEREPLICA action after adding node", MOVEREPLICA, CollectionParams.CollectionAction.get(params.get("action")));
+ String moved = params.get("replica");
+ assertTrue(replicasToBeMoved.stream().anyMatch(replica -> replica.getName().equals(moved)));
+ }
+ }
+
+ @Test
+ //17-Aug-2018 commented @LuceneTestCase.BadApple(bugUrl="https://issues.apache.org/jira/browse/SOLR-12028") // 28-June-2018
+ public void testNodeAdded() throws Exception {
+ AssertingTriggerAction.expectedNode = null;
+ SolrClient solrClient = cluster.simGetSolrClient();
+ String setTriggerCommand = "{" +
+ "'set-trigger' : {" +
+ "'name' : 'node_added_trigger'," +
+ "'event' : 'nodeAdded'," +
+ "'waitFor' : '1s'," +
+ "'enabled' : true," +
+ "'actions' : [{'name':'compute_plan', 'class' : 'solr.ComputePlanAction'}," +
+ "{'name':'test','class':'" + TestSimComputePlanAction.AssertingTriggerAction.class.getName() + "'}]" +
+ "}}";
+ SolrRequest req = createAutoScalingRequest(SolrRequest.METHOD.POST, setTriggerCommand);
+ NamedList<Object> response = solrClient.request(req);
+ assertEquals(response.get("result").toString(), "success");
+
+ // the default policy limits 1 replica per node, we need more right now
+ String setClusterPolicyCommand = "{" +
+ " 'set-cluster-policy': [" +
+ " {'cores':'<10', 'node':'#ANY'}," +
+ " {'replica':'<5', 'shard': '#EACH', 'node': '#ANY'}," +
+ " {'nodeRole':'overseer', 'replica':0}" +
+ " ]" +
+ "}";
+ req = createAutoScalingRequest(SolrRequest.METHOD.POST, setClusterPolicyCommand);
+ response = solrClient.request(req);
+ assertEquals(response.get("result").toString(), "success");
+
+ CollectionAdminRequest.Create create = CollectionAdminRequest.createCollection("testNodeAdded",
+ "conf",1, 4);
+ create.process(solrClient);
+
+ CloudTestUtils.waitForState(cluster, "Timed out waiting for replicas of new collection to be active",
+ "testNodeAdded", (liveNodes, collectionState) -> collectionState.getReplicas().stream().allMatch(replica -> replica.isActive(liveNodes)));
+
+ // reset to the original policy which has only 1 replica per shard per node
+ setClusterPolicyCommand = "{" +
+ " 'set-cluster-policy': [" +
+ " {'cores':'<10', 'node':'#ANY'}," +
+ " {'replica':'<3', 'shard': '#EACH', 'node': '#ANY'}," +
+ " {'nodeRole':'overseer', 'replica':0}" +
+ " ]" +
+ "}";
+ req = createAutoScalingRequest(SolrRequest.METHOD.POST, setClusterPolicyCommand);
+ response = solrClient.request(req);
+ assertEquals(response.get("result").toString(), "success");
+
+ // start a node so that the 'violation' created by the previous policy update is fixed
+ String newNode = cluster.simAddNode();
+ assertTrue("Trigger was not fired even after 5 seconds", triggerFiredLatch.await(5, TimeUnit.SECONDS));
+ assertTrue(fired.get());
+ Map context = actionContextPropsRef.get();
+ assertNotNull(context);
+ log.info("Node values: " + Utils.toJSONString(cluster.getSimNodeStateProvider().simGetAllNodeValues()));
+ log.info("Live nodes: " + cluster.getClusterStateProvider().getLiveNodes() + ", collection state: " + cluster.getClusterStateProvider().getClusterState().getCollection("testNodeAdded"));
+ List<SolrRequest> operations = (List<SolrRequest>) context.get("operations");
+ assertNotNull("The operations computed by ComputePlanAction should not be null" + context, operations);
+ assertEquals("ComputePlanAction should have computed exactly 1 operation, but was: " + operations, 1, operations.size());
+ SolrRequest request = operations.get(0);
+ SolrParams params = request.getParams();
+ assertEquals("Expected MOVEREPLICA action after adding node", MOVEREPLICA, CollectionParams.CollectionAction.get(params.get("action")));
+ String nodeAdded = params.get("targetNode");
+ assertEquals("Unexpected node in computed operation", newNode, nodeAdded);
+ }
+
+ public static class AssertingTriggerAction implements TriggerAction {
+ static String expectedNode;
+
+ @Override
+ public void configure(SolrResourceLoader loader, SolrCloudManager cloudManager, Map<String, Object> properties) throws TriggerValidationException {
+
+ }
+
+ @Override
+ public void init() {
+
+ }
+
+ @Override
+ public String getName() {
+ return null;
+ }
+
+ @Override
+ public void process(TriggerEvent event, ActionContext context) {
+ if (expectedNode != null) {
+ Collection nodes = (Collection) event.getProperty(TriggerEvent.NODE_NAMES);
+ if (nodes == null || !nodes.contains(expectedNode)) return;//this is not the event we are looking for
+ }
+ if (fired.compareAndSet(false, true)) {
+ eventRef.set(event);
+ actionContextPropsRef.set(context.getProperties());
+ triggerFiredLatch.countDown();
+ }
+ }
+
+ @Override
+ public void close() throws IOException {
+
+ }
+ }
+}
http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/b949f57f/solr/core/src/test/org/apache/solr/cloud/autoscaling/sim/TestSimDistribStateManager.java
----------------------------------------------------------------------
diff --git a/solr/core/src/test/org/apache/solr/cloud/autoscaling/sim/TestSimDistribStateManager.java b/solr/core/src/test/org/apache/solr/cloud/autoscaling/sim/TestSimDistribStateManager.java
new file mode 100644
index 0000000..b161a15
--- /dev/null
+++ b/solr/core/src/test/org/apache/solr/cloud/autoscaling/sim/TestSimDistribStateManager.java
@@ -0,0 +1,342 @@
+/*
+ * 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.solr.cloud.autoscaling.sim;
+
+import java.lang.invoke.MethodHandles;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Locale;
+import java.util.NoSuchElementException;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+import org.apache.solr.SolrTestCaseJ4;
+import org.apache.solr.client.solrj.cloud.autoscaling.AlreadyExistsException;
+import org.apache.solr.client.solrj.cloud.autoscaling.BadVersionException;
+import org.apache.solr.client.solrj.cloud.DistribStateManager;
+import org.apache.solr.client.solrj.cloud.autoscaling.NotEmptyException;
+import org.apache.solr.client.solrj.cloud.autoscaling.VersionedData;
+import org.apache.solr.client.solrj.impl.ZkDistribStateManager;
+import org.apache.solr.cloud.ZkTestServer;
+import org.apache.solr.common.cloud.SolrZkClient;
+import org.apache.zookeeper.CreateMode;
+import org.apache.zookeeper.WatchedEvent;
+import org.apache.zookeeper.Watcher;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * This test compares a ZK-based {@link DistribStateManager} to the simulated one.
+ */
+public class TestSimDistribStateManager extends SolrTestCaseJ4 {
+ private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
+
+ private DistribStateManager stateManager;
+ private ZkTestServer zkTestServer;
+ private SolrZkClient solrZkClient;
+ private boolean simulated;
+ private SimDistribStateManager.Node root;
+
+ @Before
+ public void setup() throws Exception {
+ simulated = random().nextBoolean();
+ if (simulated) {
+ root = SimDistribStateManager.createNewRootNode();
+ } else {
+ zkTestServer = new ZkTestServer(createTempDir("zkDir").toString());
+ zkTestServer.run();
+ }
+ reInit();
+ }
+
+ private void reInit() throws Exception {
+ if (stateManager != null) {
+ stateManager.close();
+ }
+ if (simulated) {
+ stateManager = new SimDistribStateManager(root);
+ } else {
+ if (solrZkClient != null) {
+ solrZkClient.close();
+ }
+ solrZkClient = new SolrZkClient(zkTestServer.getZkHost(), 30000);
+ stateManager = new ZkDistribStateManager(solrZkClient);
+ }
+ log.info("Using " + stateManager.getClass().getName());
+ }
+
+ private DistribStateManager createDistribStateManager() {
+ if (simulated) {
+ return new SimDistribStateManager(root);
+ } else {
+ SolrZkClient cli = new SolrZkClient(zkTestServer.getZkHost(), 30000);
+ return new ZkDistribStateManager(cli);
+ }
+ }
+
+ private void destroyDistribStateManager(DistribStateManager mgr) throws Exception {
+ mgr.close();
+ if (mgr instanceof ZkDistribStateManager) {
+ ((ZkDistribStateManager)mgr).getZkClient().close();
+ }
+ }
+
+ @After
+ public void teardown() throws Exception {
+ if (solrZkClient != null) {
+ solrZkClient.close();
+ solrZkClient = null;
+ }
+ if (zkTestServer != null) {
+ zkTestServer.shutdown();
+ zkTestServer = null;
+ }
+ if (stateManager != null) {
+ stateManager.close();
+ }
+ stateManager = null;
+ }
+
+ @Test
+ public void testHasData() throws Exception {
+ assertFalse(stateManager.hasData("/hasData/foo"));
+ assertFalse(stateManager.hasData("/hasData/bar"));
+ try {
+ stateManager.createData("/hasData/foo", new byte[0], CreateMode.PERSISTENT);
+ fail("should have failed (parent /hasData doesn't exist)");
+ } catch (NoSuchElementException e) {
+ // expected
+ }
+ stateManager.makePath("/hasData");
+ stateManager.createData("/hasData/foo", new byte[0], CreateMode.PERSISTENT);
+ stateManager.createData("/hasData/bar", new byte[0], CreateMode.PERSISTENT);
+ assertTrue(stateManager.hasData("/hasData/foo"));
+ assertTrue(stateManager.hasData("/hasData/bar"));
+ }
+
+ @Test
+ public void testRemoveData() throws Exception {
+ assertFalse(stateManager.hasData("/removeData/foo"));
+ assertFalse(stateManager.hasData("/removeData/foo/bar"));
+ assertFalse(stateManager.hasData("/removeData/baz"));
+ assertFalse(stateManager.hasData("/removeData/baz/1/2/3"));
+ stateManager.makePath("/removeData/foo/bar");
+ stateManager.makePath("/removeData/baz/1/2/3");
+ assertTrue(stateManager.hasData("/removeData/foo"));
+ assertTrue(stateManager.hasData("/removeData/foo/bar"));
+ assertTrue(stateManager.hasData("/removeData/baz/1/2/3"));
+ try {
+ stateManager.removeData("/removeData/foo", -1);
+ fail("should have failed (node has children)");
+ } catch (NotEmptyException e) {
+ // expected
+ }
+ stateManager.removeData("/removeData/foo/bar", -1);
+ stateManager.removeData("/removeData/foo", -1);
+ // test recursive listing and removal
+ stateManager.removeRecursively("/removeData/baz/1", false, false);
+ assertFalse(stateManager.hasData("/removeData/baz/1/2"));
+ assertTrue(stateManager.hasData("/removeData/baz/1"));
+ // should silently ignore
+ stateManager.removeRecursively("/removeData/baz/1/2", true, true);
+ stateManager.removeRecursively("/removeData/baz/1", false, true);
+ assertFalse(stateManager.hasData("/removeData/baz/1"));
+ try {
+ stateManager.removeRecursively("/removeData/baz/1", false, true);
+ fail("should throw exception - missing path");
+ } catch (NoSuchElementException e) {
+ // expected
+ }
+ stateManager.removeRecursively("/removeData", true, true);
+ assertFalse(stateManager.hasData("/removeData"));
+ }
+
+ @Test
+ public void testListData() throws Exception {
+ assertFalse(stateManager.hasData("/listData/foo"));
+ assertFalse(stateManager.hasData("/listData/foo/bar"));
+ try {
+ stateManager.createData("/listData/foo/bar", new byte[0], CreateMode.PERSISTENT);
+ fail("should not succeed");
+ } catch (NoSuchElementException e) {
+ // expected
+ }
+ try {
+ stateManager.listData("/listData/foo");
+ fail("should not succeed");
+ } catch (NoSuchElementException e) {
+ // expected
+ }
+ stateManager.makePath("/listData");
+ List<String> kids = stateManager.listData("/listData");
+ assertEquals(0, kids.size());
+ stateManager.makePath("/listData/foo");
+ kids = stateManager.listData("/listData");
+ assertEquals(1, kids.size());
+ assertEquals("foo", kids.get(0));
+ stateManager.createData("/listData/foo/bar", new byte[0], CreateMode.PERSISTENT);
+ stateManager.createData("/listData/foo/baz", new byte[0], CreateMode.PERSISTENT);
+ kids = stateManager.listData("/listData/foo");
+ assertEquals(2, kids.size());
+ assertTrue(kids.contains("bar"));
+ assertTrue(kids.contains("baz"));
+ try {
+ stateManager.createData("/listData/foo/bar", new byte[0], CreateMode.PERSISTENT);
+ fail("should not succeed");
+ } catch (AlreadyExistsException e) {
+ // expected
+ }
+ }
+
+ static final byte[] firstData = new byte[] {
+ (byte)0xca, (byte)0xfe, (byte)0xba, (byte)0xbe
+ };
+
+ static final byte[] secondData = new byte[] {
+ (byte)0xbe, (byte)0xba, (byte)0xfe, (byte)0xca
+ };
+
+ @Test
+ public void testCreateMode() throws Exception {
+ stateManager.makePath("/createMode");
+ stateManager.createData("/createMode/persistent", firstData, CreateMode.PERSISTENT);
+ stateManager.createData("/createMode/persistent_seq", firstData, CreateMode.PERSISTENT);
+ for (int i = 0; i < 10; i++) {
+ stateManager.createData("/createMode/persistent_seq/data", firstData, CreateMode.PERSISTENT_SEQUENTIAL);
+ }
+ // check what happens with gaps
+ stateManager.createData("/createMode/persistent_seq/data", firstData, CreateMode.PERSISTENT_SEQUENTIAL);
+ stateManager.removeData("/createMode/persistent_seq/data" + String.format(Locale.ROOT, "%010d", 10), -1);
+ stateManager.createData("/createMode/persistent_seq/data", firstData, CreateMode.PERSISTENT_SEQUENTIAL);
+
+ stateManager.createData("/createMode/ephemeral", firstData, CreateMode.EPHEMERAL);
+ stateManager.createData("/createMode/ephemeral_seq", firstData, CreateMode.PERSISTENT);
+ for (int i = 0; i < 10; i++) {
+ stateManager.createData("/createMode/ephemeral_seq/data", firstData, CreateMode.EPHEMERAL_SEQUENTIAL);
+ }
+ assertTrue(stateManager.hasData("/createMode"));
+ assertTrue(stateManager.hasData("/createMode/persistent"));
+ assertTrue(stateManager.hasData("/createMode/ephemeral"));
+ List<String> kids = stateManager.listData("/createMode/persistent_seq");
+ assertEquals(11, kids.size());
+ kids = stateManager.listData("/createMode/ephemeral_seq");
+ assertEquals(10, kids.size());
+ for (int i = 0; i < 10; i++) {
+ assertTrue(stateManager.hasData("/createMode/persistent_seq/data" + String.format(Locale.ROOT, "%010d", i)));
+ }
+ assertFalse(stateManager.hasData("/createMode/persistent_seq/data" + String.format(Locale.ROOT, "%010d", 10)));
+ assertTrue(stateManager.hasData("/createMode/persistent_seq/data" + String.format(Locale.ROOT, "%010d", 11)));
+
+ for (int i = 0; i < 10; i++) {
+ assertTrue(stateManager.hasData("/createMode/ephemeral_seq/data" + String.format(Locale.ROOT, "%010d", i)));
+ }
+ // check that ephemeral nodes disappear on disconnect
+ reInit();
+ assertTrue(stateManager.hasData("/createMode/persistent"));
+ for (int i = 0; i < 10; i++) {
+ assertTrue(stateManager.hasData("/createMode/persistent_seq/data" + String.format(Locale.ROOT, "%010d", i)));
+ }
+ assertTrue(stateManager.hasData("/createMode/persistent_seq/data" + String.format(Locale.ROOT, "%010d", 11)));
+
+ assertFalse(stateManager.hasData("/createMode/ephemeral"));
+ assertTrue(stateManager.hasData("/createMode/ephemeral_seq"));
+ kids = stateManager.listData("/createMode/ephemeral_seq");
+ assertEquals(0, kids.size());
+ }
+
+ static class OnceWatcher implements Watcher {
+ CountDownLatch triggered = new CountDownLatch(1);
+ WatchedEvent event;
+
+ @Override
+ public void process(WatchedEvent event) {
+ if (triggered.getCount() == 0) {
+ fail("Watch was already triggered once!");
+ }
+ triggered.countDown();
+ this.event = event;
+ }
+ }
+
+ @Test
+ public void testGetSetRemoveData() throws Exception {
+ stateManager.makePath("/getData");
+ stateManager.createData("/getData/persistentData", firstData, CreateMode.PERSISTENT);
+ OnceWatcher nodeWatcher = new OnceWatcher();
+ VersionedData vd = stateManager.getData("/getData/persistentData", nodeWatcher);
+ assertNotNull(vd);
+ assertEquals(0, vd.getVersion());
+ assertTrue(Arrays.equals(firstData, vd.getData()));
+
+ // update data, test versioning
+ try {
+ stateManager.setData("/getData/persistentData", secondData, 1);
+ fail("should have failed");
+ } catch (BadVersionException e) {
+ // expected
+ }
+ // watch should not have fired
+ assertEquals(1, nodeWatcher.triggered.getCount());
+
+ stateManager.setData("/getData/persistentData", secondData, 0);
+ if (!nodeWatcher.triggered.await(5, TimeUnit.SECONDS)) {
+ fail("Node watch should have fired!");
+ }
+ // watch should not fire now because it needs to be reset
+ stateManager.setData("/getData/persistentData", secondData, -1);
+
+ // create ephemeral node using another ZK connection
+ DistribStateManager ephemeralMgr = createDistribStateManager();
+ ephemeralMgr.createData("/getData/ephemeralData", firstData, CreateMode.EPHEMERAL);
+
+ nodeWatcher = new OnceWatcher();
+ vd = stateManager.getData("/getData/ephemeralData", nodeWatcher);
+ destroyDistribStateManager(ephemeralMgr);
+ if (!nodeWatcher.triggered.await(5, TimeUnit.SECONDS)) {
+ fail("Node watch should have fired!");
+ }
+ assertTrue(stateManager.hasData("/getData/persistentData"));
+ assertFalse(stateManager.hasData("/getData/ephemeralData"));
+
+ nodeWatcher = new OnceWatcher();
+ vd = stateManager.getData("/getData/persistentData", nodeWatcher);
+ // try wrong version
+ try {
+ stateManager.removeData("/getData/persistentData", vd.getVersion() - 1);
+ fail("should have failed");
+ } catch (BadVersionException e) {
+ // expected
+ }
+ // watch should not have fired
+ assertEquals(1, nodeWatcher.triggered.getCount());
+
+ stateManager.removeData("/getData/persistentData", vd.getVersion());
+ if (!nodeWatcher.triggered.await(5, TimeUnit.SECONDS)) {
+ fail("Node watch should have fired!");
+ }
+ }
+
+ @Test
+ public void testMulti() throws Exception {
+
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/b949f57f/solr/core/src/test/org/apache/solr/cloud/autoscaling/sim/TestSimExecutePlanAction.java
----------------------------------------------------------------------
diff --git a/solr/core/src/test/org/apache/solr/cloud/autoscaling/sim/TestSimExecutePlanAction.java b/solr/core/src/test/org/apache/solr/cloud/autoscaling/sim/TestSimExecutePlanAction.java
new file mode 100644
index 0000000..d0d08fd
--- /dev/null
+++ b/solr/core/src/test/org/apache/solr/cloud/autoscaling/sim/TestSimExecutePlanAction.java
@@ -0,0 +1,209 @@
+/*
+ * 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.solr.cloud.autoscaling.sim;
+
+import java.lang.invoke.MethodHandles;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.stream.Collectors;
+
+import com.google.common.collect.Lists;
+import org.apache.lucene.util.LuceneTestCase;
+import org.apache.solr.client.solrj.SolrClient;
+import org.apache.solr.client.solrj.SolrRequest;
+import org.apache.solr.client.solrj.cloud.autoscaling.TriggerEventType;
+import org.apache.solr.client.solrj.cloud.autoscaling.VersionedData;
+import org.apache.solr.client.solrj.request.CollectionAdminRequest;
+import org.apache.solr.cloud.CloudTestUtils;
+import org.apache.solr.cloud.autoscaling.ActionContext;
+import org.apache.solr.cloud.autoscaling.AutoScalingHandlerTest;
+import org.apache.solr.cloud.autoscaling.ExecutePlanAction;
+import org.apache.solr.cloud.autoscaling.NodeLostTrigger;
+import org.apache.solr.common.cloud.ClusterState;
+import org.apache.solr.common.cloud.DocCollection;
+import org.apache.solr.common.cloud.Replica;
+import org.apache.solr.common.cloud.ZkStateReader;
+import org.apache.solr.common.params.CollectionParams;
+import org.apache.solr.common.util.NamedList;
+import org.apache.solr.common.util.Utils;
+import org.apache.solr.util.LogLevel;
+import org.apache.solr.common.util.TimeSource;
+import org.junit.After;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Test for {@link ExecutePlanAction}
+ */
+@LogLevel("org.apache.solr.cloud=DEBUG")
+public class TestSimExecutePlanAction extends SimSolrCloudTestCase {
+ private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
+
+ private static final int NODE_COUNT = 2;
+
+ @BeforeClass
+ public static void setupCluster() throws Exception {
+ configureCluster(NODE_COUNT, TimeSource.get("simTime:50"));
+ }
+
+ @After
+ public void printState() throws Exception {
+ log.info("-------------_ FINAL STATE --------------");
+ log.info("* Node values: " + Utils.toJSONString(cluster.getSimNodeStateProvider().simGetAllNodeValues()));
+ log.info("* Live nodes: " + cluster.getClusterStateProvider().getLiveNodes());
+ ClusterState state = cluster.getClusterStateProvider().getClusterState();
+ for (String coll: cluster.getSimClusterStateProvider().simListCollections()) {
+ log.info("* Collection " + coll + " state: " + state.getCollection(coll));
+ }
+
+ }
+
+ @Test
+ @LuceneTestCase.BadApple(bugUrl="https://issues.apache.org/jira/browse/SOLR-12028") // 28-June-2018
+ public void testExecute() throws Exception {
+ SolrClient solrClient = cluster.simGetSolrClient();
+ String collectionName = "testExecute";
+ CollectionAdminRequest.Create create = CollectionAdminRequest.createCollection(collectionName,
+ "conf", 1, 2);
+ create.setMaxShardsPerNode(1);
+ create.process(solrClient);
+
+ log.info("Collection ready after " + CloudTestUtils.waitForState(cluster, collectionName, 120, TimeUnit.SECONDS,
+ CloudTestUtils.clusterShape(1, 2, false, true)) + "ms");
+
+ String sourceNodeName = cluster.getSimClusterStateProvider().simGetRandomNode(random());
+ ClusterState clusterState = cluster.getClusterStateProvider().getClusterState();
+ DocCollection docCollection = clusterState.getCollection(collectionName);
+ List<Replica> replicas = docCollection.getReplicas(sourceNodeName);
+ assertNotNull(replicas);
+ assertFalse(replicas.isEmpty());
+
+ List<String> otherNodes = cluster.getClusterStateProvider().getLiveNodes().stream()
+ .filter(node -> !node.equals(sourceNodeName)).collect(Collectors.toList());
+ assertFalse(otherNodes.isEmpty());
+ String survivor = otherNodes.get(0);
+
+ try (ExecutePlanAction action = new ExecutePlanAction()) {
+ action.configure(cluster.getLoader(), cluster, Collections.singletonMap("name", "execute_plan"));
+
+ // used to signal if we found that ExecutePlanAction did in fact create the right znode before executing the operation
+ AtomicBoolean znodeCreated = new AtomicBoolean(false);
+
+ CollectionAdminRequest.AsyncCollectionAdminRequest moveReplica = new CollectionAdminRequest.MoveReplica(collectionName, replicas.get(0).getName(), survivor);
+ CollectionAdminRequest.AsyncCollectionAdminRequest mockRequest = new CollectionAdminRequest.AsyncCollectionAdminRequest(CollectionParams.CollectionAction.OVERSEERSTATUS) {
+ @Override
+ public void setAsyncId(String asyncId) {
+ super.setAsyncId(asyncId);
+ String parentPath = ZkStateReader.SOLR_AUTOSCALING_TRIGGER_STATE_PATH + "/xyz/execute_plan";
+ try {
+ if (cluster.getDistribStateManager().hasData(parentPath)) {
+ java.util.List<String> children = cluster.getDistribStateManager().listData(parentPath);
+ if (!children.isEmpty()) {
+ String child = children.get(0);
+ VersionedData data = cluster.getDistribStateManager().getData(parentPath + "/" + child);
+ Map m = (Map) Utils.fromJSON(data.getData());
+ if (m.containsKey("requestid")) {
+ znodeCreated.set(m.get("requestid").equals(asyncId));
+ }
+ }
+ }
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+
+ }
+ };
+ List<CollectionAdminRequest.AsyncCollectionAdminRequest> operations = Lists.asList(moveReplica, new CollectionAdminRequest.AsyncCollectionAdminRequest[]{mockRequest});
+ NodeLostTrigger.NodeLostEvent nodeLostEvent = new NodeLostTrigger.NodeLostEvent(TriggerEventType.NODELOST,
+ "mock_trigger_name", Collections.singletonList(TimeSource.CURRENT_TIME.getTimeNs()),
+ Collections.singletonList(sourceNodeName));
+ ActionContext actionContext = new ActionContext(cluster, null,
+ new HashMap<>(Collections.singletonMap("operations", operations)));
+ action.process(nodeLostEvent, actionContext);
+
+// assertTrue("ExecutePlanAction should have stored the requestid in ZK before executing the request", znodeCreated.get());
+ List<NamedList<Object>> responses = (List<NamedList<Object>>) actionContext.getProperty("responses");
+ assertNotNull(responses);
+ assertEquals(2, responses.size());
+ NamedList<Object> response = responses.get(0);
+ assertNull(response.get("failure"));
+ assertNotNull(response.get("success"));
+ }
+
+ log.info("Collection ready after " + CloudTestUtils.waitForState(cluster, collectionName, 300, TimeUnit.SECONDS,
+ CloudTestUtils.clusterShape(1, 2, false, true)) + "ms");
+ }
+
+ @Test
+ public void testIntegration() throws Exception {
+ SolrClient solrClient = cluster.simGetSolrClient();
+
+ String setTriggerCommand = "{" +
+ "'set-trigger' : {" +
+ "'name' : 'node_lost_trigger'," +
+ "'event' : 'nodeLost'," +
+ "'waitFor' : '1s'," +
+ "'enabled' : true," +
+ "'actions' : [{'name':'compute_plan', 'class' : 'solr.ComputePlanAction'}," +
+ "{'name':'execute_plan','class':'solr.ExecutePlanAction'}]" +
+ "}}";
+ SolrRequest req = AutoScalingHandlerTest.createAutoScalingRequest(SolrRequest.METHOD.POST, setTriggerCommand);
+ NamedList<Object> response = solrClient.request(req);
+ assertEquals(response.get("result").toString(), "success");
+
+ String collectionName = "testIntegration";
+ CollectionAdminRequest.Create create = CollectionAdminRequest.createCollection(collectionName,
+ "conf", 1, 2);
+ create.setMaxShardsPerNode(1);
+ create.process(solrClient);
+
+ CloudTestUtils.waitForState(cluster, "Timed out waiting for replicas of new collection to be active",
+ collectionName, CloudTestUtils.clusterShape(1, 2, false, true));
+
+ String sourceNodeName = cluster.getSimClusterStateProvider().simGetRandomNode(random());
+ ClusterState clusterState = cluster.getClusterStateProvider().getClusterState();
+ DocCollection docCollection = clusterState.getCollection(collectionName);
+ List<Replica> replicas = docCollection.getReplicas(sourceNodeName);
+ assertNotNull(replicas);
+ assertFalse(replicas.isEmpty());
+
+ List<String> otherNodes = cluster.getClusterStateProvider().getLiveNodes().stream()
+ .filter(node -> !node.equals(sourceNodeName)).collect(Collectors.toList());
+ assertFalse(otherNodes.isEmpty());
+ String survivor = otherNodes.get(0);
+
+ cluster.simRemoveNode(sourceNodeName, false);
+
+ cluster.getTimeSource().sleep(3000);
+
+ CloudTestUtils.waitForState(cluster, "Timed out waiting for replicas of collection to be 2 again",
+ collectionName, CloudTestUtils.clusterShape(1, 2, false, true));
+
+ clusterState = cluster.getClusterStateProvider().getClusterState();
+ docCollection = clusterState.getCollection(collectionName);
+ List<Replica> replicasOnSurvivor = docCollection.getReplicas(survivor);
+ assertNotNull(replicasOnSurvivor);
+ assertEquals(2, replicasOnSurvivor.size());
+ }
+}
http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/b949f57f/solr/core/src/test/org/apache/solr/cloud/autoscaling/sim/TestSimGenericDistributedQueue.java
----------------------------------------------------------------------
diff --git a/solr/core/src/test/org/apache/solr/cloud/autoscaling/sim/TestSimGenericDistributedQueue.java b/solr/core/src/test/org/apache/solr/cloud/autoscaling/sim/TestSimGenericDistributedQueue.java
new file mode 100644
index 0000000..25e36f3
--- /dev/null
+++ b/solr/core/src/test/org/apache/solr/cloud/autoscaling/sim/TestSimGenericDistributedQueue.java
@@ -0,0 +1,40 @@
+/*
+ * 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.solr.cloud.autoscaling.sim;
+
+import org.apache.lucene.util.LuceneTestCase;
+import org.apache.solr.client.solrj.cloud.DistributedQueue;
+import org.apache.solr.client.solrj.cloud.DistribStateManager;
+
+/**
+ *
+ */
+@LuceneTestCase.BadApple(bugUrl="https://issues.apache.org/jira/browse/SOLR-12028") // 2018-02-26
+public class TestSimGenericDistributedQueue extends TestSimDistributedQueue {
+ DistribStateManager stateManager = new SimDistribStateManager();
+
+ @Override
+ @LuceneTestCase.BadApple(bugUrl="https://issues.apache.org/jira/browse/SOLR-12028") // 2-Aug-2018
+ protected DistributedQueue makeDistributedQueue(String dqZNode) throws Exception {
+ return new GenericDistributedQueue(stateManager, dqZNode);
+ }
+
+ @BadApple(bugUrl="https://issues.apache.org/jira/browse/SOLR-12028") // added 09-Aug-2018
+ public void testDistributedQueue() throws Exception {
+ super.testDistributedQueue();
+ }
+}
http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/b949f57f/solr/core/src/test/org/apache/solr/cloud/autoscaling/sim/TestSimLargeCluster.java
----------------------------------------------------------------------
diff --git a/solr/core/src/test/org/apache/solr/cloud/autoscaling/sim/TestSimLargeCluster.java b/solr/core/src/test/org/apache/solr/cloud/autoscaling/sim/TestSimLargeCluster.java
new file mode 100644
index 0000000..decb585
--- /dev/null
+++ b/solr/core/src/test/org/apache/solr/cloud/autoscaling/sim/TestSimLargeCluster.java
@@ -0,0 +1,727 @@
+/*
+ * 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.solr.cloud.autoscaling.sim;
+
+import java.lang.invoke.MethodHandles;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import com.carrotsearch.randomizedtesting.annotations.ThreadLeakLingering;
+import com.carrotsearch.randomizedtesting.annotations.TimeoutSuite;
+import org.apache.commons.math3.stat.descriptive.SummaryStatistics;
+import org.apache.lucene.util.LuceneTestCase;
+import org.apache.solr.client.solrj.SolrClient;
+import org.apache.solr.client.solrj.SolrRequest;
+import org.apache.solr.client.solrj.cloud.autoscaling.AutoScalingConfig;
+import org.apache.solr.client.solrj.cloud.SolrCloudManager;
+import org.apache.solr.client.solrj.cloud.autoscaling.Suggester;
+import org.apache.solr.client.solrj.cloud.autoscaling.TriggerEventProcessorStage;
+import org.apache.solr.client.solrj.cloud.autoscaling.TriggerEventType;
+import org.apache.solr.client.solrj.request.CollectionAdminRequest;
+import org.apache.solr.cloud.CloudTestUtils;
+import org.apache.solr.cloud.autoscaling.ActionContext;
+import org.apache.solr.cloud.autoscaling.ComputePlanAction;
+import org.apache.solr.cloud.autoscaling.ExecutePlanAction;
+import org.apache.solr.cloud.autoscaling.SearchRateTrigger;
+import org.apache.solr.cloud.autoscaling.TriggerActionBase;
+import org.apache.solr.cloud.autoscaling.TriggerEvent;
+import org.apache.solr.cloud.autoscaling.TriggerListenerBase;
+import org.apache.solr.cloud.autoscaling.CapturedEvent;
+import org.apache.solr.cloud.autoscaling.TriggerValidationException;
+import org.apache.solr.common.SolrInputDocument;
+import org.apache.solr.common.cloud.Replica;
+import org.apache.solr.common.params.CollectionAdminParams;
+import org.apache.solr.common.params.CollectionParams;
+import org.apache.solr.common.util.NamedList;
+import org.apache.solr.common.util.Pair;
+import org.apache.solr.common.util.TimeSource;
+import org.apache.solr.core.SolrResourceLoader;
+import org.apache.solr.util.LogLevel;
+import org.apache.solr.util.TimeOut;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import static org.apache.solr.cloud.autoscaling.AutoScalingHandlerTest.createAutoScalingRequest;
+
+/**
+ *
+ */
+@TimeoutSuite(millis = 4 * 3600 * 1000)
+@LogLevel("org.apache.solr.cloud.autoscaling=DEBUG")
+@ThreadLeakLingering(linger = 20000) // ComputePlanAction may take significant time to complete
+//05-Jul-2018 @LuceneTestCase.BadApple(bugUrl = "https://issues.apache.org/jira/browse/SOLR-12075")
+public class TestSimLargeCluster extends SimSolrCloudTestCase {
+ private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
+
+ public static final int SPEED = 50;
+
+ public static final int NUM_NODES = 100;
+
+ static Map<String, List<CapturedEvent>> listenerEvents = new ConcurrentHashMap<>();
+ static AtomicInteger triggerFinishedCount = new AtomicInteger();
+ static AtomicInteger triggerStartedCount = new AtomicInteger();
+ static CountDownLatch triggerStartedLatch;
+ static CountDownLatch triggerFinishedLatch;
+ static int waitForSeconds;
+
+ @BeforeClass
+ public static void setupCluster() throws Exception {
+ configureCluster(NUM_NODES, TimeSource.get("simTime:" + SPEED));
+ }
+
+ @Before
+ public void setupTest() throws Exception {
+ waitForSeconds = 5;
+ triggerStartedCount.set(0);
+ triggerFinishedCount.set(0);
+ triggerStartedLatch = new CountDownLatch(1);
+ triggerFinishedLatch = new CountDownLatch(1);
+ listenerEvents.clear();
+ // disable .scheduled_maintenance and .auto_add_replicas
+ String suspendTriggerCommand = "{" +
+ "'suspend-trigger' : {'name' : '.scheduled_maintenance'}" +
+ "}";
+ SolrRequest req = createAutoScalingRequest(SolrRequest.METHOD.POST, suspendTriggerCommand);
+ SolrClient solrClient = cluster.simGetSolrClient();
+ NamedList<Object> response;
+ try {
+ response = solrClient.request(req);
+ assertEquals(response.get("result").toString(), "success");
+ } catch (Exception e) {
+ if (!e.toString().contains("No trigger exists")) {
+ throw e;
+ }
+ }
+ suspendTriggerCommand = "{" +
+ "'suspend-trigger' : {'name' : '.auto_add_replicas'}" +
+ "}";
+ req = createAutoScalingRequest(SolrRequest.METHOD.POST, suspendTriggerCommand);
+ try {
+ response = solrClient.request(req);
+ assertEquals(response.get("result").toString(), "success");
+ } catch (Exception e) {
+ if (!e.toString().contains("No trigger exists")) {
+ throw e;
+ }
+ }
+
+ // do this in advance if missing
+ if (!cluster.getSimClusterStateProvider().simListCollections().contains(CollectionAdminParams.SYSTEM_COLL)) {
+ cluster.getSimClusterStateProvider().createSystemCollection();
+ CloudTestUtils.waitForState(cluster, CollectionAdminParams.SYSTEM_COLL, 120, TimeUnit.SECONDS,
+ CloudTestUtils.clusterShape(1, 1, false, true));
+ }
+ }
+
+ public static class TestTriggerListener extends TriggerListenerBase {
+ @Override
+ public void configure(SolrResourceLoader loader, SolrCloudManager cloudManager, AutoScalingConfig.TriggerListenerConfig config) throws TriggerValidationException {
+ super.configure(loader, cloudManager, config);
+ }
+
+ @Override
+ public synchronized void onEvent(TriggerEvent event, TriggerEventProcessorStage stage, String actionName,
+ ActionContext context, Throwable error, String message) {
+ List<CapturedEvent> lst = listenerEvents.computeIfAbsent(config.name, s -> new ArrayList<>());
+ lst.add(new CapturedEvent(cluster.getTimeSource().getTimeNs(), context, config, stage, actionName, event, message));
+ }
+ }
+
+ public static class FinishTriggerAction extends TriggerActionBase {
+ @Override
+ public void process(TriggerEvent event, ActionContext context) throws Exception {
+ triggerFinishedCount.incrementAndGet();
+ triggerFinishedLatch.countDown();
+ }
+ }
+
+ public static class StartTriggerAction extends TriggerActionBase {
+ @Override
+ public void process(TriggerEvent event, ActionContext context) throws Exception {
+ triggerStartedLatch.countDown();
+ triggerStartedCount.incrementAndGet();
+ }
+ }
+
+ @Test
+ @LuceneTestCase.BadApple(bugUrl="https://issues.apache.org/jira/browse/SOLR-12028") // 2018-06-18
+ public void testBasic() throws Exception {
+ SolrClient solrClient = cluster.simGetSolrClient();
+ String setTriggerCommand = "{" +
+ "'set-trigger' : {" +
+ "'name' : 'node_lost_trigger1'," +
+ "'event' : 'nodeLost'," +
+ "'waitFor' : '" + waitForSeconds + "s'," +
+ "'enabled' : true," +
+ "'actions' : [" +
+ "{'name':'start','class':'" + StartTriggerAction.class.getName() + "'}," +
+ "{'name':'compute','class':'" + ComputePlanAction.class.getName() + "'}," +
+ "{'name':'execute','class':'" + ExecutePlanAction.class.getName() + "'}," +
+ "{'name':'test','class':'" + FinishTriggerAction.class.getName() + "'}" +
+ "]" +
+ "}}";
+ SolrRequest req = createAutoScalingRequest(SolrRequest.METHOD.POST, setTriggerCommand);
+ NamedList<Object> response = solrClient.request(req);
+ assertEquals(response.get("result").toString(), "success");
+
+ String setListenerCommand = "{" +
+ "'set-listener' : " +
+ "{" +
+ "'name' : 'foo'," +
+ "'trigger' : 'node_lost_trigger1'," +
+ "'stage' : ['STARTED','ABORTED','SUCCEEDED', 'FAILED']," +
+ "'beforeAction' : ['compute', 'execute']," +
+ "'afterAction' : ['compute', 'execute']," +
+ "'class' : '" + TestTriggerListener.class.getName() + "'" +
+ "}" +
+ "}";
+ req = createAutoScalingRequest(SolrRequest.METHOD.POST, setListenerCommand);
+ response = solrClient.request(req);
+ assertEquals(response.get("result").toString(), "success");
+
+ cluster.getTimeSource().sleep(5000);
+
+ // pick a few random nodes
+ List<String> nodes = new ArrayList<>();
+ int limit = 75;
+ for (String node : cluster.getClusterStateProvider().getLiveNodes()) {
+ nodes.add(node);
+ if (nodes.size() > limit) {
+ break;
+ }
+ }
+ Collections.shuffle(nodes, random());
+ // create collection on these nodes
+ String collectionName = "testBasic";
+ CollectionAdminRequest.Create create = CollectionAdminRequest.createCollection(collectionName,
+ "conf", 5, 5, 5, 5);
+ create.setMaxShardsPerNode(1);
+ create.setAutoAddReplicas(false);
+ create.setCreateNodeSet(String.join(",", nodes));
+ create.process(solrClient);
+
+ log.info("Ready after " + CloudTestUtils.waitForState(cluster, collectionName, 30 * nodes.size(), TimeUnit.SECONDS,
+ CloudTestUtils.clusterShape(5, 15, false, true)) + "ms");
+
+ int KILL_NODES = 8;
+ // kill off a number of nodes
+ for (int i = 0; i < KILL_NODES; i++) {
+ cluster.simRemoveNode(nodes.get(i), false);
+ }
+ // should fully recover
+ log.info("Ready after " + CloudTestUtils.waitForState(cluster, collectionName, 90 * KILL_NODES, TimeUnit.SECONDS,
+ CloudTestUtils.clusterShape(5, 15, false, true)) + "ms");
+
+ log.info("OP COUNTS: " + cluster.simGetOpCounts());
+ long moveReplicaOps = cluster.simGetOpCount(CollectionParams.CollectionAction.MOVEREPLICA.name());
+
+ // simulate a number of flaky nodes
+ int FLAKY_NODES = 10;
+ int flakyReplicas = 0;
+ for (int cnt = 0; cnt < 10; cnt++) {
+ for (int i = KILL_NODES; i < KILL_NODES + FLAKY_NODES; i++) {
+ flakyReplicas += cluster.getSimClusterStateProvider().simGetReplicaInfos(nodes.get(i))
+ .stream().filter(r -> r.getState().equals(Replica.State.ACTIVE)).count();
+ cluster.simRemoveNode(nodes.get(i), false);
+ }
+ cluster.getTimeSource().sleep(TimeUnit.SECONDS.toMillis(waitForSeconds) * 2);
+ for (int i = KILL_NODES; i < KILL_NODES + FLAKY_NODES; i++) {
+ final String nodeId = nodes.get(i);
+ cluster.submit(() -> cluster.getSimClusterStateProvider().simRestoreNode(nodeId));
+ }
+ }
+
+ // wait until started == finished
+ TimeOut timeOut = new TimeOut(20 * waitForSeconds * NUM_NODES, TimeUnit.SECONDS, cluster.getTimeSource());
+ while (!timeOut.hasTimedOut()) {
+ if (triggerStartedCount.get() == triggerFinishedCount.get()) {
+ break;
+ }
+ timeOut.sleep(1000);
+ }
+ if (timeOut.hasTimedOut()) {
+ fail("did not finish processing all events in time: started=" + triggerStartedCount.get() + ", finished=" + triggerFinishedCount.get());
+ }
+
+
+ log.info("Ready after " + CloudTestUtils.waitForState(cluster, collectionName, 30 * nodes.size(), TimeUnit.SECONDS,
+ CloudTestUtils.clusterShape(5, 15, false, true)) + "ms");
+ long newMoveReplicaOps = cluster.simGetOpCount(CollectionParams.CollectionAction.MOVEREPLICA.name());
+ log.info("==== Flaky replicas: {}. Additional MOVEREPLICA count: {}", flakyReplicas, (newMoveReplicaOps - moveReplicaOps));
+ // flaky nodes lead to a number of MOVEREPLICA that is non-zero but lower than the number of flaky replicas
+ assertTrue("there should be new MOVERPLICA ops", newMoveReplicaOps - moveReplicaOps > 0);
+ assertTrue("there should be less than flakyReplicas=" + flakyReplicas + " MOVEREPLICA ops",
+ newMoveReplicaOps - moveReplicaOps < flakyReplicas);
+ }
+
+ @Test
+ @LuceneTestCase.BadApple(bugUrl="https://issues.apache.org/jira/browse/SOLR-12028") // 28-June-2018
+ public void testAddNode() throws Exception {
+ SolrClient solrClient = cluster.simGetSolrClient();
+ String setTriggerCommand = "{" +
+ "'set-trigger' : {" +
+ "'name' : 'node_added_trigger2'," +
+ "'event' : 'nodeAdded'," +
+ "'waitFor' : '" + waitForSeconds + "s'," +
+ "'enabled' : true," +
+ "'actions' : [" +
+ "{'name':'start','class':'" + StartTriggerAction.class.getName() + "'}," +
+ "{'name':'compute','class':'" + ComputePlanAction.class.getName() + "'}," +
+ "{'name':'execute','class':'" + ExecutePlanAction.class.getName() + "'}," +
+ "{'name':'test','class':'" + FinishTriggerAction.class.getName() + "'}" +
+ "]" +
+ "}}";
+ SolrRequest req = createAutoScalingRequest(SolrRequest.METHOD.POST, setTriggerCommand);
+ NamedList<Object> response = solrClient.request(req);
+ assertEquals(response.get("result").toString(), "success");
+
+ // create a collection with more than 1 replica per node
+ String collectionName = "testNodeAdded";
+ CollectionAdminRequest.Create create = CollectionAdminRequest.createCollection(collectionName,
+ "conf", NUM_NODES / 10, NUM_NODES / 8, NUM_NODES / 8, NUM_NODES / 8);
+ create.setMaxShardsPerNode(5);
+ create.setAutoAddReplicas(false);
+ create.process(solrClient);
+
+ log.info("Ready after " + CloudTestUtils.waitForState(cluster, collectionName, 20 * NUM_NODES, TimeUnit.SECONDS,
+ CloudTestUtils.clusterShape(NUM_NODES / 10, NUM_NODES / 8 * 3, false, true)) + " ms");
+
+ // start adding nodes
+ int numAddNode = NUM_NODES / 5;
+ List<String> addNodesList = new ArrayList<>(numAddNode);
+ for (int i = 0; i < numAddNode; i++) {
+ addNodesList.add(cluster.simAddNode());
+ cluster.getTimeSource().sleep(5000);
+ }
+ // wait until at least one event is generated
+ boolean await = triggerStartedLatch.await(20000 / SPEED, TimeUnit.MILLISECONDS);
+ assertTrue("trigger did not fire", await);
+
+ // wait until started == finished
+ TimeOut timeOut = new TimeOut(20 * waitForSeconds * NUM_NODES, TimeUnit.SECONDS, cluster.getTimeSource());
+ while (!timeOut.hasTimedOut()) {
+ if (triggerStartedCount.get() == triggerFinishedCount.get()) {
+ break;
+ }
+ timeOut.sleep(1000);
+ }
+ if (timeOut.hasTimedOut()) {
+ fail("did not finish processing all events in time: started=" + triggerStartedCount.get() + ", finished=" + triggerFinishedCount.get());
+ }
+
+ List<SolrInputDocument> systemColl = cluster.simGetSystemCollection();
+ int startedEventPos = -1;
+ for (int i = 0; i < systemColl.size(); i++) {
+ SolrInputDocument d = systemColl.get(i);
+ if (!"node_added_trigger2".equals(d.getFieldValue("event.source_s"))) {
+ continue;
+ }
+ if ("NODEADDED".equals(d.getFieldValue("event.type_s")) &&
+ "STARTED".equals(d.getFieldValue("stage_s"))) {
+ startedEventPos = i;
+ break;
+ }
+ }
+ assertTrue("no STARTED event", startedEventPos > -1);
+ SolrInputDocument startedEvent = systemColl.get(startedEventPos);
+ int lastIgnoredPos = startedEventPos;
+ // make sure some replicas have been moved
+ assertTrue("no MOVEREPLICA ops?", cluster.simGetOpCount("MOVEREPLICA") > 0);
+
+ log.info("Ready after " + CloudTestUtils.waitForState(cluster, collectionName, 20 * NUM_NODES, TimeUnit.SECONDS,
+ CloudTestUtils.clusterShape(NUM_NODES / 10, NUM_NODES / 8 * 3, false, true)) + " ms");
+
+ int count = 50;
+ SolrInputDocument finishedEvent = null;
+ long lastNumOps = cluster.simGetOpCount("MOVEREPLICA");
+ while (count-- > 0) {
+ cluster.getTimeSource().sleep(10000);
+ long currentNumOps = cluster.simGetOpCount("MOVEREPLICA");
+ if (currentNumOps == lastNumOps) {
+ int size = systemColl.size() - 1;
+ for (int i = size; i > lastIgnoredPos; i--) {
+ SolrInputDocument d = systemColl.get(i);
+ if (!"node_added_trigger2".equals(d.getFieldValue("event.source_s"))) {
+ continue;
+ }
+ if ("SUCCEEDED".equals(d.getFieldValue("stage_s"))) {
+ finishedEvent = d;
+ break;
+ }
+ }
+ break;
+ } else {
+ lastNumOps = currentNumOps;
+ }
+ }
+
+ assertTrue("did not finish processing changes", finishedEvent != null);
+ long delta = (Long)finishedEvent.getFieldValue("event.time_l") - (Long)startedEvent.getFieldValue("event.time_l");
+ log.info("#### System stabilized after " + TimeUnit.NANOSECONDS.toMillis(delta) + " ms");
+ assertTrue("unexpected number of MOVEREPLICA ops", cluster.simGetOpCount("MOVEREPLICA") > 1);
+ }
+
+ @Test
+ @LuceneTestCase.BadApple(bugUrl="https://issues.apache.org/jira/browse/SOLR-12028") // 2018-06-18
+ public void testNodeLost() throws Exception {
+ doTestNodeLost(waitForSeconds, 5000, 0);
+ }
+
+ // Renard R5 series - evenly covers a log10 range
+ private static final int[] renard5 = new int[] {
+ 1, 2, 3, 4, 6,
+ 10
+ };
+ private static final int[] renard5x = new int[] {
+ 1, 2, 3, 4, 6,
+ 10, 16, 25, 40, 63,
+ 100
+ };
+ private static final int[] renard5xx = new int[] {
+ 1, 2, 3, 4, 6,
+ 10, 16, 25, 40, 63,
+ 100, 158, 251, 398, 631,
+ 1000, 1585, 2512, 3981, 6310,
+ 10000
+ };
+ // Renard R10 series
+ private static final double[] renard10 = new double[] {
+ 1, 1.3, 1.6, 2, 2.5, 3.2, 4, 5, 6.3, 7.9,
+ 10
+ };
+ private static final double[] renard10x = new double[] {
+ 1, 1.3, 1.6, 2, 2.5, 3.2, 4, 5, 6.3, 7.9,
+ 10, 12.6, 15.8, 20, 25.1, 31.6, 39.8, 50.1, 63.1, 79.4,
+ 100
+ };
+
+ private static final AtomicInteger ZERO = new AtomicInteger(0);
+
+ //@Test
+ public void benchmarkNodeLost() throws Exception {
+ List<String> results = new ArrayList<>();
+ for (int wait : renard5x) {
+ for (int delay : renard5x) {
+ SummaryStatistics totalTime = new SummaryStatistics();
+ SummaryStatistics ignoredOurEvents = new SummaryStatistics();
+ SummaryStatistics ignoredOtherEvents = new SummaryStatistics();
+ SummaryStatistics startedOurEvents = new SummaryStatistics();
+ SummaryStatistics startedOtherEvents = new SummaryStatistics();
+ for (int i = 0; i < 5; i++) {
+ if (cluster != null) {
+ cluster.close();
+ }
+ setupCluster();
+ setUp();
+ setupTest();
+ long total = doTestNodeLost(wait, delay * 1000, 0);
+ totalTime.addValue(total);
+ // get event counts
+ Map<String, Map<String, AtomicInteger>> counts = cluster.simGetEventCounts();
+ Map<String, AtomicInteger> map = counts.remove("node_lost_trigger");
+ startedOurEvents.addValue(map.getOrDefault("STARTED", ZERO).get());
+ ignoredOurEvents.addValue(map.getOrDefault("IGNORED", ZERO).get());
+ int otherStarted = 0;
+ int otherIgnored = 0;
+ for (Map<String, AtomicInteger> m : counts.values()) {
+ otherStarted += m.getOrDefault("STARTED", ZERO).get();
+ otherIgnored += m.getOrDefault("IGNORED", ZERO).get();
+ }
+ startedOtherEvents.addValue(otherStarted);
+ ignoredOtherEvents.addValue(otherIgnored);
+ }
+ results.add(String.format(Locale.ROOT, "%d\t%d\t%4.0f\t%4.0f\t%4.0f\t%4.0f\t%6.0f\t%6.0f\t%6.0f\t%6.0f\t%6.0f",
+ wait, delay, startedOurEvents.getMean(), ignoredOurEvents.getMean(),
+ startedOtherEvents.getMean(), ignoredOtherEvents.getMean(),
+ totalTime.getMin(), totalTime.getMax(), totalTime.getMean(), totalTime.getStandardDeviation(), totalTime.getVariance()));
+ }
+ }
+ log.info("===== RESULTS ======");
+ log.info("waitFor\tdelay\tSTRT\tIGN\toSTRT\toIGN\tmin\tmax\tmean\tstdev\tvar");
+ results.forEach(s -> log.info(s));
+ }
+
+ private long doTestNodeLost(int waitFor, long killDelay, int minIgnored) throws Exception {
+ SolrClient solrClient = cluster.simGetSolrClient();
+ String setTriggerCommand = "{" +
+ "'set-trigger' : {" +
+ "'name' : 'node_lost_trigger3'," +
+ "'event' : 'nodeLost'," +
+ "'waitFor' : '" + waitFor + "s'," +
+ "'enabled' : true," +
+ "'actions' : [" +
+ "{'name':'start','class':'" + StartTriggerAction.class.getName() + "'}," +
+ "{'name':'compute','class':'" + ComputePlanAction.class.getName() + "'}," +
+ "{'name':'execute','class':'" + ExecutePlanAction.class.getName() + "'}," +
+ "{'name':'test','class':'" + FinishTriggerAction.class.getName() + "'}" +
+ "]" +
+ "}}";
+ SolrRequest req = createAutoScalingRequest(SolrRequest.METHOD.POST, setTriggerCommand);
+ NamedList<Object> response = solrClient.request(req);
+ assertEquals(response.get("result").toString(), "success");
+
+ String setListenerCommand = "{" +
+ "'set-listener' : " +
+ "{" +
+ "'name' : 'failures'," +
+ "'trigger' : 'node_lost_trigger3'," +
+ "'stage' : ['FAILED']," +
+ "'class' : '" + TestTriggerListener.class.getName() + "'" +
+ "}" +
+ "}";
+ req = createAutoScalingRequest(SolrRequest.METHOD.POST, setListenerCommand);
+ response = solrClient.request(req);
+ assertEquals(response.get("result").toString(), "success");
+
+
+ // create a collection with 1 replica per node
+ String collectionName = "testNodeLost";
+ CollectionAdminRequest.Create create = CollectionAdminRequest.createCollection(collectionName,
+ "conf", NUM_NODES / 5, NUM_NODES / 10);
+ create.setMaxShardsPerNode(5);
+ create.setAutoAddReplicas(false);
+ create.process(solrClient);
+
+ log.info("Ready after " + CloudTestUtils.waitForState(cluster, collectionName, 20 * NUM_NODES, TimeUnit.SECONDS,
+ CloudTestUtils.clusterShape(NUM_NODES / 5, NUM_NODES / 10, false, true)) + " ms");
+
+ // start killing nodes
+ int numNodes = NUM_NODES / 5;
+ List<String> nodes = new ArrayList<>(cluster.getLiveNodesSet().get());
+ for (int i = 0; i < numNodes; i++) {
+ // this may also select a node where a replica is moved to, so the total number of
+ // MOVEREPLICA may vary
+ cluster.simRemoveNode(nodes.get(i), false);
+ cluster.getTimeSource().sleep(killDelay);
+ }
+ // wait for the trigger to fire and complete at least once
+ boolean await = triggerFinishedLatch.await(20 * waitFor * 1000 / SPEED, TimeUnit.MILLISECONDS);
+ assertTrue("trigger did not fire within timeout, " +
+ "waitFor=" + waitFor + ", killDelay=" + killDelay + ", minIgnored=" + minIgnored,
+ await);
+ List<SolrInputDocument> systemColl = cluster.simGetSystemCollection();
+ int startedEventPos = -1;
+ for (int i = 0; i < systemColl.size(); i++) {
+ SolrInputDocument d = systemColl.get(i);
+ if (!"node_lost_trigger3".equals(d.getFieldValue("event.source_s"))) {
+ continue;
+ }
+ if ("NODELOST".equals(d.getFieldValue("event.type_s")) &&
+ "STARTED".equals(d.getFieldValue("stage_s"))) {
+ startedEventPos = i;
+ break;
+ }
+ }
+ assertTrue("no STARTED event: " + systemColl + ", " +
+ "waitFor=" + waitFor + ", killDelay=" + killDelay + ", minIgnored=" + minIgnored,
+ startedEventPos > -1);
+ SolrInputDocument startedEvent = systemColl.get(startedEventPos);
+ // we can expect some failures when target node in MOVEREPLICA has been killed
+ // between when the event processing started and the actual moment of MOVEREPLICA execution
+ // wait until started == (finished + failed)
+ TimeOut timeOut = new TimeOut(20 * waitFor * NUM_NODES, TimeUnit.SECONDS, cluster.getTimeSource());
+ while (!timeOut.hasTimedOut()) {
+ if (triggerStartedCount.get() == triggerFinishedCount.get()) {
+ break;
+ }
+ log.debug("started=" + triggerStartedCount.get() + ", finished=" + triggerFinishedCount.get() +
+ ", failed=" + listenerEvents.size());
+ timeOut.sleep(1000);
+ }
+ if (timeOut.hasTimedOut()) {
+ if (triggerStartedCount.get() > triggerFinishedCount.get() + listenerEvents.size()) {
+ fail("did not finish processing all events in time: started=" + triggerStartedCount.get() + ", finished=" + triggerFinishedCount.get() +
+ ", failed=" + listenerEvents.size());
+ }
+ }
+ int ignored = 0;
+ int lastIgnoredPos = startedEventPos;
+ for (int i = startedEventPos + 1; i < systemColl.size(); i++) {
+ SolrInputDocument d = systemColl.get(i);
+ if (!"node_lost_trigger3".equals(d.getFieldValue("event.source_s"))) {
+ continue;
+ }
+ if ("NODELOST".equals(d.getFieldValue("event.type_s"))) {
+ if ("IGNORED".equals(d.getFieldValue("stage_s"))) {
+ ignored++;
+ lastIgnoredPos = i;
+ }
+ }
+ }
+ assertTrue("should be at least " + minIgnored + " IGNORED events, " +
+ "waitFor=" + waitFor + ", killDelay=" + killDelay + ", minIgnored=" + minIgnored,
+ ignored >= minIgnored);
+ // make sure some replicas have been moved
+ assertTrue("no MOVEREPLICA ops? " +
+ "waitFor=" + waitFor + ", killDelay=" + killDelay + ", minIgnored=" + minIgnored,
+ cluster.simGetOpCount("MOVEREPLICA") > 0);
+
+ if (listenerEvents.isEmpty()) {
+ // no failed movements - verify collection shape
+ log.info("Ready after " + CloudTestUtils.waitForState(cluster, collectionName, 20 * NUM_NODES, TimeUnit.SECONDS,
+ CloudTestUtils.clusterShape(NUM_NODES / 5, NUM_NODES / 10, false, true)) + " ms");
+ } else {
+ cluster.getTimeSource().sleep(NUM_NODES * 100);
+ }
+
+ int count = 50;
+ SolrInputDocument finishedEvent = null;
+ long lastNumOps = cluster.simGetOpCount("MOVEREPLICA");
+ while (count-- > 0) {
+ cluster.getTimeSource().sleep(waitFor * 10000);
+ long currentNumOps = cluster.simGetOpCount("MOVEREPLICA");
+ if (currentNumOps == lastNumOps) {
+ int size = systemColl.size() - 1;
+ for (int i = size; i > lastIgnoredPos; i--) {
+ SolrInputDocument d = systemColl.get(i);
+ if (!"node_lost_trigger3".equals(d.getFieldValue("event.source_s"))) {
+ continue;
+ }
+ if ("SUCCEEDED".equals(d.getFieldValue("stage_s"))) {
+ finishedEvent = d;
+ break;
+ }
+ }
+ break;
+ } else {
+ lastNumOps = currentNumOps;
+ }
+ }
+
+ assertTrue("did not finish processing changes, " +
+ "waitFor=" + waitFor + ", killDelay=" + killDelay + ", minIgnored=" + minIgnored,
+ finishedEvent != null);
+ long delta = (Long)finishedEvent.getFieldValue("event.time_l") - (Long)startedEvent.getFieldValue("event.time_l");
+ delta = TimeUnit.NANOSECONDS.toMillis(delta);
+ log.info("#### System stabilized after " + delta + " ms");
+ long ops = cluster.simGetOpCount("MOVEREPLICA");
+ long expectedMinOps = 40;
+ if (!listenerEvents.isEmpty()) {
+ expectedMinOps = 20;
+ }
+ assertTrue("unexpected number (" + expectedMinOps + ") of MOVEREPLICA ops: " + ops + ", " +
+ "waitFor=" + waitFor + ", killDelay=" + killDelay + ", minIgnored=" + minIgnored,
+ ops >= expectedMinOps);
+ return delta;
+ }
+
+ @Test
+ //commented 2-Aug-2018 @LuceneTestCase.BadApple(bugUrl="https://issues.apache.org/jira/browse/SOLR-12028") // 2018-06-18
+ public void testSearchRate() throws Exception {
+ SolrClient solrClient = cluster.simGetSolrClient();
+ String collectionName = "testSearchRate";
+ CollectionAdminRequest.Create create = CollectionAdminRequest.createCollection(collectionName,
+ "conf", 2, 10);
+ create.process(solrClient);
+
+ log.info("Ready after " + CloudTestUtils.waitForState(cluster, collectionName, 300, TimeUnit.SECONDS,
+ CloudTestUtils.clusterShape(2, 10, false, true)) + " ms");
+
+ // collect the node names for shard1
+ Set<String> nodes = new HashSet<>();
+ cluster.getSimClusterStateProvider().getClusterState().getCollection(collectionName)
+ .getSlice("shard1")
+ .getReplicas()
+ .forEach(r -> nodes.add(r.getNodeName()));
+
+ String metricName = "QUERY./select.requestTimes:1minRate";
+ // simulate search traffic
+ cluster.getSimClusterStateProvider().simSetShardValue(collectionName, "shard1", metricName, 40, false, true);
+
+ // now define the trigger. doing it earlier may cause partial events to be generated (where only some
+ // nodes / replicas exceeded the threshold).
+ String setTriggerCommand = "{" +
+ "'set-trigger' : {" +
+ "'name' : 'search_rate_trigger'," +
+ "'event' : 'searchRate'," +
+ "'waitFor' : '" + waitForSeconds + "s'," +
+ "'aboveRate' : 1.0," +
+ "'aboveNodeRate' : 1.0," +
+ "'enabled' : true," +
+ "'actions' : [" +
+ "{'name':'compute','class':'" + ComputePlanAction.class.getName() + "'}," +
+ "{'name':'execute','class':'" + ExecutePlanAction.class.getName() + "'}," +
+ "{'name':'test','class':'" + FinishTriggerAction.class.getName() + "'}" +
+ "]" +
+ "}}";
+ SolrRequest req = createAutoScalingRequest(SolrRequest.METHOD.POST, setTriggerCommand);
+ NamedList<Object> response = solrClient.request(req);
+ assertEquals(response.get("result").toString(), "success");
+ String setListenerCommand1 = "{" +
+ "'set-listener' : " +
+ "{" +
+ "'name' : 'srt'," +
+ "'trigger' : 'search_rate_trigger'," +
+ "'stage' : ['FAILED','SUCCEEDED']," +
+ "'class' : '" + TestTriggerListener.class.getName() + "'" +
+ "}" +
+ "}";
+ req = createAutoScalingRequest(SolrRequest.METHOD.POST, setListenerCommand1);
+ response = solrClient.request(req);
+ assertEquals(response.get("result").toString(), "success");
+
+
+ boolean await = triggerFinishedLatch.await(waitForSeconds * 20000 / SPEED, TimeUnit.MILLISECONDS);
+ assertTrue("The trigger did not fire at all", await);
+ // wait for listener to capture the SUCCEEDED stage
+ cluster.getTimeSource().sleep(2000);
+ assertEquals(listenerEvents.toString(), 1, listenerEvents.get("srt").size());
+ CapturedEvent ev = listenerEvents.get("srt").get(0);
+ assertEquals(TriggerEventType.SEARCHRATE, ev.event.getEventType());
+ Map<String, Number> m = (Map<String, Number>)ev.event.getProperty(SearchRateTrigger.HOT_NODES);
+ assertNotNull(m);
+ assertEquals(nodes.size(), m.size());
+ assertEquals(nodes, m.keySet());
+ m.forEach((k, v) -> assertEquals(4.0, v.doubleValue(), 0.01));
+ List<TriggerEvent.Op> ops = (List<TriggerEvent.Op>)ev.event.getProperty(TriggerEvent.REQUESTED_OPS);
+ assertNotNull(ops);
+ assertEquals(ops.toString(), 1, ops.size());
+ ops.forEach(op -> {
+ assertEquals(CollectionParams.CollectionAction.ADDREPLICA, op.getAction());
+ assertEquals(1, op.getHints().size());
+ Object o = op.getHints().get(Suggester.Hint.COLL_SHARD);
+ // this may be a pair or a HashSet of pairs with size 1
+ Pair<String, String> hint = null;
+ if (o instanceof Pair) {
+ hint = (Pair<String, String>)o;
+ } else if (o instanceof Set) {
+ assertEquals("unexpected number of hints: " + o, 1, ((Set)o).size());
+ o = ((Set)o).iterator().next();
+ assertTrue("unexpected hint: " + o, o instanceof Pair);
+ hint = (Pair<String, String>)o;
+ } else {
+ fail("unexpected hints: " + o);
+ }
+ assertNotNull(hint);
+ assertEquals(collectionName, hint.first());
+ assertEquals("shard1", hint.second());
+ });
+ }
+}
[2/6] lucene-solr:branch_7x: SOLR-12669: Rename tests that use the
autoscaling simulation framework.
Posted by ab...@apache.org.
http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/b949f57f/solr/core/src/test/org/apache/solr/cloud/autoscaling/sim/TestSimTriggerIntegration.java
----------------------------------------------------------------------
diff --git a/solr/core/src/test/org/apache/solr/cloud/autoscaling/sim/TestSimTriggerIntegration.java b/solr/core/src/test/org/apache/solr/cloud/autoscaling/sim/TestSimTriggerIntegration.java
new file mode 100644
index 0000000..81952af
--- /dev/null
+++ b/solr/core/src/test/org/apache/solr/cloud/autoscaling/sim/TestSimTriggerIntegration.java
@@ -0,0 +1,1322 @@
+/*
+ * 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.solr.cloud.autoscaling.sim;
+
+import java.lang.invoke.MethodHandles;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.SortedSet;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicLong;
+import java.util.concurrent.locks.ReentrantLock;
+
+import com.google.common.util.concurrent.AtomicDouble;
+import org.apache.lucene.util.LuceneTestCase;
+import org.apache.solr.client.solrj.SolrClient;
+import org.apache.solr.client.solrj.SolrRequest;
+import org.apache.solr.client.solrj.cloud.autoscaling.AutoScalingConfig;
+import org.apache.solr.client.solrj.cloud.autoscaling.ReplicaInfo;
+import org.apache.solr.client.solrj.cloud.SolrCloudManager;
+import org.apache.solr.client.solrj.cloud.autoscaling.TriggerEventProcessorStage;
+import org.apache.solr.client.solrj.cloud.autoscaling.TriggerEventType;
+import org.apache.solr.client.solrj.request.CollectionAdminRequest;
+import org.apache.solr.cloud.CloudTestUtils;
+import org.apache.solr.cloud.autoscaling.ActionContext;
+import org.apache.solr.cloud.autoscaling.ComputePlanAction;
+import org.apache.solr.cloud.autoscaling.ExecutePlanAction;
+import org.apache.solr.cloud.autoscaling.NodeLostTrigger;
+import org.apache.solr.cloud.autoscaling.ScheduledTriggers;
+import org.apache.solr.cloud.autoscaling.SearchRateTrigger;
+import org.apache.solr.cloud.autoscaling.TriggerActionBase;
+import org.apache.solr.cloud.autoscaling.TriggerEvent;
+import org.apache.solr.cloud.autoscaling.TriggerEventQueue;
+import org.apache.solr.cloud.autoscaling.TriggerListenerBase;
+import org.apache.solr.cloud.autoscaling.CapturedEvent;
+import org.apache.solr.cloud.autoscaling.TriggerValidationException;
+import org.apache.solr.common.cloud.LiveNodesListener;
+import org.apache.solr.common.cloud.ZkStateReader;
+import org.apache.solr.common.params.CollectionAdminParams;
+import org.apache.solr.common.util.NamedList;
+import org.apache.solr.common.util.TimeSource;
+import org.apache.solr.core.SolrResourceLoader;
+import org.apache.solr.util.LogLevel;
+import org.apache.solr.util.TimeOut;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import static org.apache.solr.cloud.autoscaling.AutoScalingHandlerTest.createAutoScalingRequest;
+import static org.apache.solr.cloud.autoscaling.ScheduledTriggers.DEFAULT_SCHEDULED_TRIGGER_DELAY_SECONDS;
+
+/**
+ * An end-to-end integration test for triggers
+ */
+@LogLevel("org.apache.solr.cloud.autoscaling=DEBUG")
+public class TestSimTriggerIntegration extends SimSolrCloudTestCase {
+ private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
+
+ public static final int SPEED = 50;
+
+ private static CountDownLatch actionConstructorCalled;
+ private static CountDownLatch actionInitCalled;
+ private static CountDownLatch triggerFiredLatch;
+ private static int waitForSeconds = 1;
+ private static CountDownLatch actionStarted;
+ private static CountDownLatch actionInterrupted;
+ private static CountDownLatch actionCompleted;
+ private static CountDownLatch triggerStartedLatch;
+ private static CountDownLatch triggerFinishedLatch;
+ private static AtomicInteger triggerStartedCount;
+ private static AtomicInteger triggerFinishedCount;
+ private static AtomicBoolean triggerFired;
+ private static Set<TriggerEvent> events = ConcurrentHashMap.newKeySet();
+
+ private static final long WAIT_FOR_DELTA_NANOS = TimeUnit.MILLISECONDS.toNanos(5);
+
+ @BeforeClass
+ public static void setupCluster() throws Exception {
+ configureCluster(2, TimeSource.get("simTime:" + SPEED));
+ }
+
+ private static CountDownLatch getTriggerFiredLatch() {
+ return triggerFiredLatch;
+ }
+
+ private static CountDownLatch getActionStarted() {
+ return actionStarted;
+ }
+
+ private static CountDownLatch getActionInterrupted() {
+ return actionInterrupted;
+ }
+
+ private static CountDownLatch getActionCompleted() {
+ return actionCompleted;
+ }
+
+ @Before
+ public void setupTest() throws Exception {
+ // disable .scheduled_maintenance
+ String suspendTriggerCommand = "{" +
+ "'suspend-trigger' : {'name' : '.scheduled_maintenance'}" +
+ "}";
+ SolrRequest req = createAutoScalingRequest(SolrRequest.METHOD.POST, suspendTriggerCommand);
+ SolrClient solrClient = cluster.simGetSolrClient();
+ NamedList<Object> response = solrClient.request(req);
+ assertEquals(response.get("result").toString(), "success");
+
+ waitForSeconds = 1 + random().nextInt(3);
+ actionConstructorCalled = new CountDownLatch(1);
+ actionInitCalled = new CountDownLatch(1);
+ triggerFiredLatch = new CountDownLatch(1);
+ triggerFired = new AtomicBoolean(false);
+ actionStarted = new CountDownLatch(1);
+ actionInterrupted = new CountDownLatch(1);
+ actionCompleted = new CountDownLatch(1);
+ triggerStartedLatch = new CountDownLatch(1);
+ triggerFinishedLatch = new CountDownLatch(1);
+ triggerStartedCount = new AtomicInteger();
+ triggerFinishedCount = new AtomicInteger();
+ events.clear();
+ listenerEvents.clear();
+ while (cluster.getClusterStateProvider().getLiveNodes().size() < 2) {
+ // perhaps a test stopped a node but didn't start it back
+ // lets start a node
+ cluster.simAddNode();
+ }
+ // do this in advance if missing
+ if (!cluster.getSimClusterStateProvider().simListCollections().contains(CollectionAdminParams.SYSTEM_COLL)) {
+ cluster.getSimClusterStateProvider().createSystemCollection();
+ CloudTestUtils.waitForState(cluster, CollectionAdminParams.SYSTEM_COLL, 120, TimeUnit.SECONDS,
+ CloudTestUtils.clusterShape(1, 1, false, true));
+ }
+ }
+
+ @Test
+ @LuceneTestCase.BadApple(bugUrl="https://issues.apache.org/jira/browse/SOLR-12028") // 2018-06-18
+ public void testTriggerThrottling() throws Exception {
+ // for this test we want to create two triggers so we must assert that the actions were created twice
+ actionInitCalled = new CountDownLatch(2);
+ // similarly we want both triggers to fire
+ triggerFiredLatch = new CountDownLatch(2);
+
+ SolrClient solrClient = cluster.simGetSolrClient();
+
+ // first trigger
+ String setTriggerCommand = "{" +
+ "'set-trigger' : {" +
+ "'name' : 'node_added_trigger1'," +
+ "'event' : 'nodeAdded'," +
+ "'waitFor' : '0s'," +
+ "'enabled' : true," +
+ "'actions' : [{'name':'test','class':'" + ThrottlingTesterAction.class.getName() + "'}]" +
+ "}}";
+ SolrRequest req = createAutoScalingRequest(SolrRequest.METHOD.POST, setTriggerCommand);
+ NamedList<Object> response = solrClient.request(req);
+ assertEquals(response.get("result").toString(), "success");
+
+ // second trigger
+ setTriggerCommand = "{" +
+ "'set-trigger' : {" +
+ "'name' : 'node_added_trigger2'," +
+ "'event' : 'nodeAdded'," +
+ "'waitFor' : '0s'," +
+ "'enabled' : true," +
+ "'actions' : [{'name':'test','class':'" + ThrottlingTesterAction.class.getName() + "'}]" +
+ "}}";
+ req = createAutoScalingRequest(SolrRequest.METHOD.POST, setTriggerCommand);
+ response = solrClient.request(req);
+ assertEquals(response.get("result").toString(), "success");
+
+ // wait until the two instances of action are created
+ if (!actionInitCalled.await(3000 / SPEED, TimeUnit.MILLISECONDS)) {
+ fail("Two TriggerAction instances should have been created by now");
+ }
+
+ String newNode = cluster.simAddNode();
+
+ if (!triggerFiredLatch.await(20000 / SPEED, TimeUnit.MILLISECONDS)) {
+ fail("Both triggers should have fired by now");
+ }
+
+ // reset shared state
+ lastActionExecutedAt.set(0);
+ TestSimTriggerIntegration.actionInitCalled = new CountDownLatch(2);
+ triggerFiredLatch = new CountDownLatch(2);
+
+ setTriggerCommand = "{" +
+ "'set-trigger' : {" +
+ "'name' : 'node_lost_trigger1'," +
+ "'event' : 'nodeLost'," +
+ "'waitFor' : '0s'," +
+ "'enabled' : true," +
+ "'actions' : [{'name':'test','class':'" + ThrottlingTesterAction.class.getName() + "'}]" +
+ "}}";
+ req = createAutoScalingRequest(SolrRequest.METHOD.POST, setTriggerCommand);
+ response = solrClient.request(req);
+ assertEquals(response.get("result").toString(), "success");
+
+ setTriggerCommand = "{" +
+ "'set-trigger' : {" +
+ "'name' : 'node_lost_trigger2'," +
+ "'event' : 'nodeLost'," +
+ "'waitFor' : '0s'," +
+ "'enabled' : true," +
+ "'actions' : [{'name':'test','class':'" + ThrottlingTesterAction.class.getName() + "'}]" +
+ "}}";
+ req = createAutoScalingRequest(SolrRequest.METHOD.POST, setTriggerCommand);
+ response = solrClient.request(req);
+ assertEquals(response.get("result").toString(), "success");
+
+ // wait until the two instances of action are created
+ if (!actionInitCalled.await(3000 / SPEED, TimeUnit.MILLISECONDS)) {
+ fail("Two TriggerAction instances should have been created by now");
+ }
+
+ // stop the node we had started earlier
+ cluster.simRemoveNode(newNode, false);
+
+ if (!triggerFiredLatch.await(20000 / SPEED, TimeUnit.MILLISECONDS)) {
+ fail("Both triggers should have fired by now");
+ }
+ }
+
+ static AtomicLong lastActionExecutedAt = new AtomicLong(0);
+ static ReentrantLock lock = new ReentrantLock();
+ public static class ThrottlingTesterAction extends TestTriggerAction {
+ // nanos are very precise so we need a delta for comparison with ms
+ private static final long DELTA_MS = 2;
+
+ // sanity check that an action instance is only invoked once
+ private final AtomicBoolean onlyOnce = new AtomicBoolean(false);
+
+ @Override
+ public void process(TriggerEvent event, ActionContext actionContext) {
+ boolean locked = lock.tryLock();
+ if (!locked) {
+ log.info("We should never have a tryLock fail because actions are never supposed to be executed concurrently");
+ return;
+ }
+ try {
+ if (lastActionExecutedAt.get() != 0) {
+ log.info("last action at " + lastActionExecutedAt.get() + " time = " + cluster.getTimeSource().getTimeNs());
+ if (TimeUnit.NANOSECONDS.toMillis(cluster.getTimeSource().getTimeNs() - lastActionExecutedAt.get()) <
+ TimeUnit.SECONDS.toMillis(ScheduledTriggers.DEFAULT_ACTION_THROTTLE_PERIOD_SECONDS) - DELTA_MS) {
+ log.info("action executed again before minimum wait time from {}", event.getSource());
+ fail("TriggerListener was fired before the throttling period");
+ }
+ }
+ if (onlyOnce.compareAndSet(false, true)) {
+ log.info("action executed from {}", event.getSource());
+ lastActionExecutedAt.set(cluster.getTimeSource().getTimeNs());
+ getTriggerFiredLatch().countDown();
+ } else {
+ log.info("action executed more than once from {}", event.getSource());
+ fail("Trigger should not have fired more than once!");
+ }
+ } finally {
+ lock.unlock();
+ }
+ }
+ }
+
+ @Test
+ // commented 20-July-2018 @BadApple(bugUrl="https://issues.apache.org/jira/browse/SOLR-12028")
+ @BadApple(bugUrl="https://issues.apache.org/jira/browse/SOLR-12028") // added 09-Aug-2018
+ public void testNodeLostTriggerRestoreState() throws Exception {
+ // for this test we want to update the trigger so we must assert that the actions were created twice
+ TestSimTriggerIntegration.actionInitCalled = new CountDownLatch(2);
+
+ // start a new node
+ String nodeName = cluster.simAddNode();
+
+ SolrClient solrClient = cluster.simGetSolrClient();
+ waitForSeconds = 5;
+ String setTriggerCommand = "{" +
+ "'set-trigger' : {" +
+ "'name' : 'node_lost_restore_trigger'," +
+ "'event' : 'nodeLost'," +
+ "'waitFor' : '5s'," + // should be enough for us to update the trigger
+ "'enabled' : true," +
+ "'actions' : [{'name':'test','class':'" + TestTriggerAction.class.getName() + "'}]" +
+ "}}";
+ SolrRequest req = createAutoScalingRequest(SolrRequest.METHOD.POST, setTriggerCommand);
+ NamedList<Object> response = solrClient.request(req);
+ assertEquals(response.get("result").toString(), "success");
+
+ TimeOut timeOut = new TimeOut(2, TimeUnit.SECONDS, cluster.getTimeSource());
+ while (actionInitCalled.getCount() == 0 && !timeOut.hasTimedOut()) {
+ timeOut.sleep(200);
+ }
+ assertTrue("The action specified in node_lost_restore_trigger was not instantiated even after 2 seconds", actionInitCalled.getCount() > 0);
+
+ cluster.simRemoveNode(nodeName, false);
+
+ // ensure that the old trigger sees the stopped node, todo find a better way to do this
+ timeOut.sleep(500 + TimeUnit.SECONDS.toMillis(DEFAULT_SCHEDULED_TRIGGER_DELAY_SECONDS));
+
+ waitForSeconds = 0;
+ setTriggerCommand = "{" +
+ "'set-trigger' : {" +
+ "'name' : 'node_lost_restore_trigger'," +
+ "'event' : 'nodeLost'," +
+ "'waitFor' : '0s'," + // update a property so that it replaces the old trigger, also we want it to fire immediately
+ "'enabled' : true," +
+ "'actions' : [{'name':'test','class':'" + TestTriggerAction.class.getName() + "'}]" +
+ "}}";
+ req = createAutoScalingRequest(SolrRequest.METHOD.POST, setTriggerCommand);
+ response = solrClient.request(req);
+ assertEquals(response.get("result").toString(), "success");
+
+ // wait until the second instance of action is created
+ if (!actionInitCalled.await(3000 / SPEED, TimeUnit.MILLISECONDS)) {
+ fail("Two TriggerAction instances should have been created by now");
+ }
+
+ boolean await = triggerFiredLatch.await(5000 / SPEED, TimeUnit.MILLISECONDS);
+ assertTrue("The trigger did not fire at all", await);
+ assertTrue(triggerFired.get());
+ NodeLostTrigger.NodeLostEvent nodeLostEvent = (NodeLostTrigger.NodeLostEvent) events.iterator().next();
+ assertNotNull(nodeLostEvent);
+ List<String> nodeNames = (List<String>)nodeLostEvent.getProperty(TriggerEvent.NODE_NAMES);
+ assertTrue(nodeNames.contains(nodeName));
+ }
+
+ @Test
+ @BadApple(bugUrl="https://issues.apache.org/jira/browse/SOLR-12028") // 09-Apr-2018
+ public void testNodeAddedTriggerRestoreState() throws Exception {
+ // for this test we want to update the trigger so we must assert that the actions were created twice
+ TestSimTriggerIntegration.actionInitCalled = new CountDownLatch(2);
+
+ SolrClient solrClient = cluster.simGetSolrClient();
+ waitForSeconds = 5;
+ String setTriggerCommand = "{" +
+ "'set-trigger' : {" +
+ "'name' : 'node_added_restore_trigger'," +
+ "'event' : 'nodeAdded'," +
+ "'waitFor' : '5s'," + // should be enough for us to update the trigger
+ "'enabled' : true," +
+ "'actions' : [{'name':'test','class':'" + TestTriggerAction.class.getName() + "'}]" +
+ "}}";
+ SolrRequest req = createAutoScalingRequest(SolrRequest.METHOD.POST, setTriggerCommand);
+ NamedList<Object> response = solrClient.request(req);
+ assertEquals(response.get("result").toString(), "success");
+
+ TimeOut timeOut = new TimeOut(2, TimeUnit.SECONDS, cluster.getTimeSource());
+ while (actionInitCalled.getCount() == 0 && !timeOut.hasTimedOut()) {
+ timeOut.sleep(200);
+ }
+ assertTrue("The action specified in node_added_restore_trigger was not instantiated even after 2 seconds", actionInitCalled.getCount() > 0);
+
+ // start a new node
+ String newNode = cluster.simAddNode();
+
+ // ensure that the old trigger sees the new node, todo find a better way to do this
+ cluster.getTimeSource().sleep(500 + TimeUnit.SECONDS.toMillis(DEFAULT_SCHEDULED_TRIGGER_DELAY_SECONDS));
+
+ waitForSeconds = 0;
+ setTriggerCommand = "{" +
+ "'set-trigger' : {" +
+ "'name' : 'node_added_restore_trigger'," +
+ "'event' : 'nodeAdded'," +
+ "'waitFor' : '0s'," + // update a property so that it replaces the old trigger, also we want it to fire immediately
+ "'enabled' : true," +
+ "'actions' : [{'name':'test','class':'" + TestTriggerAction.class.getName() + "'}]" +
+ "}}";
+ req = createAutoScalingRequest(SolrRequest.METHOD.POST, setTriggerCommand);
+ response = solrClient.request(req);
+ assertEquals(response.get("result").toString(), "success");
+
+ // wait until the second instance of action is created
+ if (!actionInitCalled.await(3000 / SPEED, TimeUnit.MILLISECONDS)) {
+ fail("Two TriggerAction instances should have been created by now");
+ }
+
+ boolean await = triggerFiredLatch.await(5000 / SPEED, TimeUnit.MILLISECONDS);
+ assertTrue("The trigger did not fire at all", await);
+ assertTrue(triggerFired.get());
+ TriggerEvent nodeAddedEvent = events.iterator().next();
+ assertNotNull(nodeAddedEvent);
+ List<String> nodeNames = (List<String>)nodeAddedEvent.getProperty(TriggerEvent.NODE_NAMES);
+ assertTrue(nodeNames.toString(), nodeNames.contains(newNode));
+ }
+
+ @Test
+ @LuceneTestCase.BadApple(bugUrl="https://issues.apache.org/jira/browse/SOLR-12028") // 2018-06-18
+ public void testNodeAddedTrigger() throws Exception {
+ SolrClient solrClient = cluster.simGetSolrClient();
+ String setTriggerCommand = "{" +
+ "'set-trigger' : {" +
+ "'name' : 'node_added_trigger'," +
+ "'event' : 'nodeAdded'," +
+ "'waitFor' : '" + waitForSeconds + "s'," +
+ "'enabled' : true," +
+ "'actions' : [{'name':'test','class':'" + TestTriggerAction.class.getName() + "'}]" +
+ "}}";
+ SolrRequest req = createAutoScalingRequest(SolrRequest.METHOD.POST, setTriggerCommand);
+ NamedList<Object> response = solrClient.request(req);
+ assertEquals(response.get("result").toString(), "success");
+
+ if (!actionInitCalled.await(5000 / SPEED, TimeUnit.MILLISECONDS)) {
+ fail("The TriggerAction should have been created by now");
+ }
+
+ String newNode = cluster.simAddNode();
+ boolean await = triggerFiredLatch.await(20000 / SPEED, TimeUnit.MILLISECONDS);
+ assertTrue("The trigger did not fire at all", await);
+ assertTrue(triggerFired.get());
+ TriggerEvent nodeAddedEvent = events.iterator().next();
+ assertNotNull(nodeAddedEvent);
+ List<String> nodeNames = (List<String>)nodeAddedEvent.getProperty(TriggerEvent.NODE_NAMES);
+ assertTrue(nodeAddedEvent.toString(), nodeNames.contains(newNode));
+
+ // reset
+ actionConstructorCalled = new CountDownLatch(1);
+ actionInitCalled = new CountDownLatch(1);
+
+ // update the trigger with exactly the same data
+ setTriggerCommand = "{" +
+ "'set-trigger' : {" +
+ "'name' : 'node_added_trigger'," +
+ "'event' : 'nodeAdded'," +
+ "'waitFor' : '" + waitForSeconds + "s'," +
+ "'enabled' : true," +
+ "'actions' : [{'name':'test','class':'" + TestTriggerAction.class.getName() + "'}]" +
+ "}}";
+ req = createAutoScalingRequest(SolrRequest.METHOD.POST, setTriggerCommand);
+ response = solrClient.request(req);
+ assertEquals(response.get("result").toString(), "success");
+
+ // this should be a no-op so the action should have been created but init should not be called
+ if (!actionConstructorCalled.await(3000 / SPEED, TimeUnit.MILLISECONDS)) {
+ fail("The TriggerAction should have been created by now");
+ }
+
+ assertFalse(actionInitCalled.await(2000 / SPEED, TimeUnit.MILLISECONDS));
+ }
+
+ @Test
+ @BadApple(bugUrl="https://issues.apache.org/jira/browse/SOLR-12028") // 26-Mar-2018
+ public void testNodeLostTrigger() throws Exception {
+ SolrClient solrClient = cluster.simGetSolrClient();
+ String setTriggerCommand = "{" +
+ "'set-trigger' : {" +
+ "'name' : 'node_lost_trigger'," +
+ "'event' : 'nodeLost'," +
+ "'waitFor' : '" + waitForSeconds + "s'," +
+ "'enabled' : true," +
+ "'actions' : [{'name':'test','class':'" + TestTriggerAction.class.getName() + "'}]" +
+ "}}";
+ SolrRequest req = createAutoScalingRequest(SolrRequest.METHOD.POST, setTriggerCommand);
+ NamedList<Object> response = solrClient.request(req);
+ assertEquals(response.get("result").toString(), "success");
+
+ if (!actionInitCalled.await(5000 / SPEED, TimeUnit.MILLISECONDS)) {
+ fail("The TriggerAction should have been created by now");
+ }
+
+ String lostNodeName = cluster.getSimClusterStateProvider().simGetRandomNode(random());
+ cluster.simRemoveNode(lostNodeName, false);
+ boolean await = triggerFiredLatch.await(20000 / SPEED, TimeUnit.MILLISECONDS);
+ assertTrue("The trigger did not fire at all", await);
+ assertTrue(triggerFired.get());
+ TriggerEvent nodeLostEvent = events.iterator().next();
+ assertNotNull(nodeLostEvent);
+ List<String> nodeNames = (List<String>)nodeLostEvent.getProperty(TriggerEvent.NODE_NAMES);
+ assertTrue(nodeNames.contains(lostNodeName));
+
+ // reset
+ actionConstructorCalled = new CountDownLatch(1);
+ actionInitCalled = new CountDownLatch(1);
+
+ // update the trigger with exactly the same data
+ setTriggerCommand = "{" +
+ "'set-trigger' : {" +
+ "'name' : 'node_lost_trigger'," +
+ "'event' : 'nodeLost'," +
+ "'waitFor' : '" + waitForSeconds + "s'," +
+ "'enabled' : true," +
+ "'actions' : [{'name':'test','class':'" + TestTriggerAction.class.getName() + "'}]" +
+ "}}";
+ req = createAutoScalingRequest(SolrRequest.METHOD.POST, setTriggerCommand);
+ response = solrClient.request(req);
+ assertEquals(response.get("result").toString(), "success");
+
+ // this should be a no-op so the action should have been created but init should not be called
+ if (!actionConstructorCalled.await(3000 / SPEED, TimeUnit.MILLISECONDS)) {
+ fail("The TriggerAction should have been created by now");
+ }
+
+ assertFalse(actionInitCalled.await(2000 / SPEED, TimeUnit.MILLISECONDS));
+ }
+
+ // simulator doesn't support overseer functionality yet
+ /*
+ @Test
+ public void testContinueTriggersOnOverseerRestart() throws Exception {
+ CollectionAdminRequest.OverseerStatus status = new CollectionAdminRequest.OverseerStatus();
+ CloudSolrClient solrClient = cluster.getSolrClient();
+ CollectionAdminResponse adminResponse = status.process(solrClient);
+ NamedList<Object> response = adminResponse.getResponse();
+ String leader = (String) response.get("leader");
+ JettySolrRunner overseerNode = null;
+ int index = -1;
+ List<JettySolrRunner> jettySolrRunners = cluster.getJettySolrRunners();
+ for (int i = 0; i < jettySolrRunners.size(); i++) {
+ JettySolrRunner runner = jettySolrRunners.get(i);
+ if (runner.getNodeName().equals(leader)) {
+ overseerNode = runner;
+ index = i;
+ break;
+ }
+ }
+ assertNotNull(overseerNode);
+
+ String setTriggerCommand = "{" +
+ "'set-trigger' : {" +
+ "'name' : 'node_added_trigger'," +
+ "'event' : 'nodeAdded'," +
+ "'waitFor' : '" + waitForSeconds + "s'," +
+ "'enabled' : true," +
+ "'actions' : [{'name':'test','class':'" + TestTriggerAction.class.getName() + "'}]" +
+ "}}";
+ SolrRequest req = createAutoScalingRequest(SolrRequest.METHOD.POST, setTriggerCommand);
+ response = solrClient.request(req);
+ assertEquals(response.get("result").toString(), "success");
+ if (!actionInitCalled.await(3, TimeUnit.SECONDS)) {
+ fail("The TriggerAction should have been created by now");
+ }
+
+ // stop the overseer, somebody else will take over as the overseer
+ cluster.stopJettySolrRunner(index);
+ Thread.sleep(10000);
+ JettySolrRunner newNode = cluster.startJettySolrRunner();
+ boolean await = triggerFiredLatch.await(20, TimeUnit.SECONDS);
+ assertTrue("The trigger did not fire at all", await);
+ assertTrue(triggerFired.get());
+ NodeAddedTrigger.NodeAddedEvent nodeAddedEvent = (NodeAddedTrigger.NodeAddedEvent) events.iterator().next();
+ assertNotNull(nodeAddedEvent);
+ List<String> nodeNames = (List<String>)nodeAddedEvent.getProperty(TriggerEvent.NODE_NAMES);
+ assertTrue(nodeNames.contains(newNode.getNodeName()));
+ }
+
+*/
+
+ public static class TestTriggerAction extends TriggerActionBase {
+
+ public TestTriggerAction() {
+ actionConstructorCalled.countDown();
+ }
+
+ @Override
+ public void process(TriggerEvent event, ActionContext actionContext) {
+ try {
+ if (triggerFired.compareAndSet(false, true)) {
+ events.add(event);
+ long currentTimeNanos = cluster.getTimeSource().getTimeNs();
+ long eventTimeNanos = event.getEventTime();
+ long waitForNanos = TimeUnit.NANOSECONDS.convert(waitForSeconds, TimeUnit.SECONDS) - WAIT_FOR_DELTA_NANOS;
+ if (currentTimeNanos - eventTimeNanos <= waitForNanos) {
+ fail(event.getSource() + " was fired before the configured waitFor period");
+ }
+ getTriggerFiredLatch().countDown();
+ } else {
+ fail(event.getSource() + " was fired more than once!");
+ }
+ } catch (Throwable t) {
+ log.debug("--throwable", t);
+ throw t;
+ }
+ }
+
+ @Override
+ public void init() throws Exception {
+ log.info("TestTriggerAction init");
+ super.init();
+ actionInitCalled.countDown();
+ }
+
+ }
+
+ public static class TestEventQueueAction extends TriggerActionBase {
+
+ public TestEventQueueAction() {
+ log.info("TestEventQueueAction instantiated");
+ }
+
+ @Override
+ public void process(TriggerEvent event, ActionContext actionContext) {
+ log.info("-- event: " + event);
+ events.add(event);
+ getActionStarted().countDown();
+ try {
+ Thread.sleep(eventQueueActionWait);
+ triggerFired.compareAndSet(false, true);
+ getActionCompleted().countDown();
+ } catch (InterruptedException e) {
+ getActionInterrupted().countDown();
+ return;
+ }
+ }
+
+ @Override
+ public void configure(SolrResourceLoader loader, SolrCloudManager cloudManager, Map<String, Object> args) throws TriggerValidationException {
+ log.debug("TestTriggerAction init");
+ actionInitCalled.countDown();
+ super.configure(loader, cloudManager, args);
+ }
+ }
+
+ public static long eventQueueActionWait = 5000;
+
+ @Test
+ @BadApple(bugUrl="https://issues.apache.org/jira/browse/SOLR-12028") // 16-Apr-2018
+ public void testEventQueue() throws Exception {
+ waitForSeconds = 1;
+ SolrClient solrClient = cluster.simGetSolrClient();
+ String setTriggerCommand = "{" +
+ "'set-trigger' : {" +
+ "'name' : 'node_added_trigger1'," +
+ "'event' : 'nodeAdded'," +
+ "'waitFor' : '" + waitForSeconds + "s'," +
+ "'enabled' : true," +
+ "'actions' : [{'name':'test','class':'" + TestEventQueueAction.class.getName() + "'}]" +
+ "}}";
+
+ String overseerLeader = cluster.getSimClusterStateProvider().simGetRandomNode(random());
+
+ SolrRequest req = createAutoScalingRequest(SolrRequest.METHOD.POST, setTriggerCommand);
+ NamedList<Object> response = solrClient.request(req);
+ assertEquals(response.get("result").toString(), "success");
+
+ if (!actionInitCalled.await(3000 / SPEED, TimeUnit.MILLISECONDS)) {
+ fail("The TriggerAction should have been created by now");
+ }
+
+ // add node to generate the event
+ String newNode = cluster.simAddNode();
+ boolean await = actionStarted.await(60000 / SPEED, TimeUnit.MILLISECONDS);
+ assertTrue("action did not start", await);
+ // event should be there
+ TriggerEvent nodeAddedEvent = events.iterator().next();
+ assertNotNull(nodeAddedEvent);
+ // but action did not complete yet so the event is still enqueued
+ assertFalse(triggerFired.get());
+ events.clear();
+ actionStarted = new CountDownLatch(1);
+ eventQueueActionWait = 1;
+ // kill overseer
+ cluster.simRestartOverseer(overseerLeader);
+ cluster.getTimeSource().sleep(5000);
+ // new overseer leader should be elected and run triggers
+ await = actionInterrupted.await(3000 / SPEED, TimeUnit.MILLISECONDS);
+ assertTrue("action wasn't interrupted", await);
+ // it should fire again from enqueued event
+ await = actionStarted.await(60000 / SPEED, TimeUnit.MILLISECONDS);
+ assertTrue("action wasn't started", await);
+ TriggerEvent replayedEvent = events.iterator().next();
+ assertTrue(replayedEvent.getProperty(TriggerEventQueue.ENQUEUE_TIME) != null);
+ assertTrue(events + "\n" + replayedEvent.toString(), replayedEvent.getProperty(TriggerEventQueue.DEQUEUE_TIME) != null);
+ await = actionCompleted.await(10000 / SPEED, TimeUnit.MILLISECONDS);
+ assertTrue("action wasn't completed", await);
+ assertTrue(triggerFired.get());
+ }
+
+ @Test
+ @BadApple(bugUrl="https://issues.apache.org/jira/browse/SOLR-12028") //2018-03-10
+ public void testEventFromRestoredState() throws Exception {
+ SolrClient solrClient = cluster.simGetSolrClient();
+ String setTriggerCommand = "{" +
+ "'set-trigger' : {" +
+ "'name' : 'node_added_trigger'," +
+ "'event' : 'nodeAdded'," +
+ "'waitFor' : '10s'," +
+ "'enabled' : true," +
+ "'actions' : [{'name':'test','class':'" + TestTriggerAction.class.getName() + "'}]" +
+ "}}";
+ SolrRequest req = createAutoScalingRequest(SolrRequest.METHOD.POST, setTriggerCommand);
+ NamedList<Object> response = solrClient.request(req);
+ assertEquals(response.get("result").toString(), "success");
+
+ if (!actionInitCalled.await(10000 / SPEED, TimeUnit.MILLISECONDS)) {
+ fail("The TriggerAction should have been created by now");
+ }
+
+ events.clear();
+
+ String newNode = cluster.simAddNode();
+ boolean await = triggerFiredLatch.await(20000 / SPEED, TimeUnit.MILLISECONDS);
+ assertTrue("The trigger did not fire at all", await);
+ assertTrue(triggerFired.get());
+ // reset
+ triggerFired.set(false);
+ triggerFiredLatch = new CountDownLatch(1);
+ TriggerEvent nodeAddedEvent = events.iterator().next();
+ assertNotNull(nodeAddedEvent);
+ List<String> nodeNames = (List<String>)nodeAddedEvent.getProperty(TriggerEvent.NODE_NAMES);
+ assertTrue(nodeNames.contains(newNode));
+ // add a second node - state of the trigger will change but it won't fire for waitFor sec.
+ String newNode2 = cluster.simAddNode();
+ cluster.getTimeSource().sleep(10000);
+ // kill overseer
+ cluster.simRestartOverseer(null);
+ await = triggerFiredLatch.await(20000 / SPEED, TimeUnit.MILLISECONDS);
+ assertTrue("The trigger did not fire at all", await);
+ assertTrue(triggerFired.get());
+ }
+
+ private static class TestLiveNodesListener implements LiveNodesListener {
+ Set<String> lostNodes = new HashSet<>();
+ Set<String> addedNodes = new HashSet<>();
+ CountDownLatch onChangeLatch = new CountDownLatch(1);
+
+ public void reset() {
+ lostNodes.clear();
+ addedNodes.clear();
+ onChangeLatch = new CountDownLatch(1);
+ }
+
+ @Override
+ public void onChange(SortedSet<String> oldLiveNodes, SortedSet<String> newLiveNodes) {
+ onChangeLatch.countDown();
+ Set<String> old = new HashSet<>(oldLiveNodes);
+ old.removeAll(newLiveNodes);
+ if (!old.isEmpty()) {
+ lostNodes.addAll(old);
+ }
+ newLiveNodes.removeAll(oldLiveNodes);
+ if (!newLiveNodes.isEmpty()) {
+ addedNodes.addAll(newLiveNodes);
+ }
+ }
+ }
+
+ private TestLiveNodesListener registerLiveNodesListener() {
+ TestLiveNodesListener listener = new TestLiveNodesListener();
+ cluster.getLiveNodesSet().registerLiveNodesListener(listener);
+ return listener;
+ }
+
+ public static class TestEventMarkerAction extends TriggerActionBase {
+
+ public TestEventMarkerAction() {
+ actionConstructorCalled.countDown();
+ }
+
+ @Override
+ public void process(TriggerEvent event, ActionContext actionContext) {
+ boolean locked = lock.tryLock();
+ if (!locked) {
+ log.info("We should never have a tryLock fail because actions are never supposed to be executed concurrently");
+ return;
+ }
+ try {
+ events.add(event);
+ getTriggerFiredLatch().countDown();
+ } catch (Throwable t) {
+ log.debug("--throwable", t);
+ throw t;
+ } finally {
+ lock.unlock();
+ }
+ }
+
+ @Override
+ public void configure(SolrResourceLoader loader, SolrCloudManager cloudManager, Map<String, Object> args) throws TriggerValidationException {
+ log.info("TestEventMarkerAction init");
+ actionInitCalled.countDown();
+ super.configure(loader, cloudManager, args);
+ }
+ }
+
+ @Test
+ @BadApple(bugUrl="https://issues.apache.org/jira/browse/SOLR-12028")
+ public void testNodeMarkersRegistration() throws Exception {
+ // for this test we want to create two triggers so we must assert that the actions were created twice
+ actionInitCalled = new CountDownLatch(2);
+ // similarly we want both triggers to fire
+ triggerFiredLatch = new CountDownLatch(2);
+ TestLiveNodesListener listener = registerLiveNodesListener();
+
+ SolrClient solrClient = cluster.simGetSolrClient();
+
+ // pick overseer node
+ String overseerLeader = cluster.getSimClusterStateProvider().simGetRandomNode(random());
+
+ // add a node
+ String node = cluster.simAddNode();
+ if (!listener.onChangeLatch.await(10000 / SPEED, TimeUnit.MILLISECONDS)) {
+ fail("onChange listener didn't execute on cluster change");
+ }
+ assertEquals(1, listener.addedNodes.size());
+ assertEquals(node, listener.addedNodes.iterator().next());
+ // verify that a znode doesn't exist (no trigger)
+ String pathAdded = ZkStateReader.SOLR_AUTOSCALING_NODE_ADDED_PATH + "/" + node;
+ assertFalse("Path " + pathAdded + " was created but there are no nodeAdded triggers",
+ cluster.getDistribStateManager().hasData(pathAdded));
+ listener.reset();
+ // stop overseer
+ log.info("====== KILL OVERSEER 1");
+ cluster.simRestartOverseer(overseerLeader);
+ if (!listener.onChangeLatch.await(10000 / SPEED, TimeUnit.MILLISECONDS)) {
+ fail("onChange listener didn't execute on cluster change");
+ }
+ assertEquals(1, listener.lostNodes.size());
+ assertEquals(overseerLeader, listener.lostNodes.iterator().next());
+ assertEquals(0, listener.addedNodes.size());
+ // wait until the new overseer is up
+ cluster.getTimeSource().sleep(5000);
+ // verify that a znode does NOT exist - there's no nodeLost trigger,
+ // so the new overseer cleaned up existing nodeLost markers
+ String pathLost = ZkStateReader.SOLR_AUTOSCALING_NODE_LOST_PATH + "/" + overseerLeader;
+ assertFalse("Path " + pathLost + " exists", cluster.getDistribStateManager().hasData(pathLost));
+
+ listener.reset();
+
+ // set up triggers
+
+ log.info("====== ADD TRIGGERS");
+ String setTriggerCommand = "{" +
+ "'set-trigger' : {" +
+ "'name' : 'node_added_trigger'," +
+ "'event' : 'nodeAdded'," +
+ "'waitFor' : '1s'," +
+ "'enabled' : true," +
+ "'actions' : [{'name':'test','class':'" + TestEventMarkerAction.class.getName() + "'}]" +
+ "}}";
+ SolrRequest req = createAutoScalingRequest(SolrRequest.METHOD.POST, setTriggerCommand);
+ NamedList<Object> response = solrClient.request(req);
+ assertEquals(response.get("result").toString(), "success");
+
+ setTriggerCommand = "{" +
+ "'set-trigger' : {" +
+ "'name' : 'node_lost_trigger'," +
+ "'event' : 'nodeLost'," +
+ "'waitFor' : '1s'," +
+ "'enabled' : true," +
+ "'actions' : [{'name':'test','class':'" + TestEventMarkerAction.class.getName() + "'}]" +
+ "}}";
+ req = createAutoScalingRequest(SolrRequest.METHOD.POST, setTriggerCommand);
+ response = solrClient.request(req);
+ assertEquals(response.get("result").toString(), "success");
+
+ overseerLeader = cluster.getSimClusterStateProvider().simGetRandomNode(random());
+
+ // create another node
+ log.info("====== ADD NODE 1");
+ String node1 = cluster.simAddNode();
+ if (!listener.onChangeLatch.await(10000 / SPEED, TimeUnit.MILLISECONDS)) {
+ fail("onChange listener didn't execute on cluster change");
+ }
+ assertEquals(1, listener.addedNodes.size());
+ assertEquals(node1, listener.addedNodes.iterator().next());
+ // verify that a znode exists
+ pathAdded = ZkStateReader.SOLR_AUTOSCALING_NODE_ADDED_PATH + "/" + node1;
+ assertTrue("Path " + pathAdded + " wasn't created", cluster.getDistribStateManager().hasData(pathAdded));
+
+ cluster.getTimeSource().sleep(5000);
+ // nodeAdded marker should be consumed now by nodeAdded trigger
+ assertFalse("Path " + pathAdded + " should have been deleted",
+ cluster.getDistribStateManager().hasData(pathAdded));
+
+ listener.reset();
+ events.clear();
+ triggerFiredLatch = new CountDownLatch(1);
+ // kill overseer again
+ log.info("====== KILL OVERSEER 2");
+ cluster.simRestartOverseer(overseerLeader);
+ if (!listener.onChangeLatch.await(10000 / SPEED, TimeUnit.MILLISECONDS)) {
+ fail("onChange listener didn't execute on cluster change");
+ }
+
+
+ if (!triggerFiredLatch.await(20000 / SPEED, TimeUnit.MILLISECONDS)) {
+ fail("Trigger should have fired by now");
+ }
+ assertEquals(1, events.size());
+ TriggerEvent ev = events.iterator().next();
+ List<String> nodeNames = (List<String>)ev.getProperty(TriggerEvent.NODE_NAMES);
+ assertTrue(nodeNames.contains(overseerLeader));
+ assertEquals(TriggerEventType.NODELOST, ev.getEventType());
+ }
+
+ static Map<String, List<CapturedEvent>> listenerEvents = new ConcurrentHashMap<>();
+ static List<CapturedEvent> allListenerEvents = new ArrayList<>();
+ static CountDownLatch listenerCreated = new CountDownLatch(1);
+ static boolean failDummyAction = false;
+
+ public static class TestTriggerListener extends TriggerListenerBase {
+ @Override
+ public void configure(SolrResourceLoader loader, SolrCloudManager cloudManager, AutoScalingConfig.TriggerListenerConfig config) throws TriggerValidationException {
+ super.configure(loader, cloudManager, config);
+ listenerCreated.countDown();
+ }
+
+ @Override
+ public synchronized void onEvent(TriggerEvent event, TriggerEventProcessorStage stage, String actionName,
+ ActionContext context, Throwable error, String message) {
+ List<CapturedEvent> lst = listenerEvents.computeIfAbsent(config.name, s -> new ArrayList<>());
+ CapturedEvent ev = new CapturedEvent(cluster.getTimeSource().getTimeNs(), context, config, stage, actionName, event, message);
+ lst.add(ev);
+ allListenerEvents.add(ev);
+ }
+ }
+
+ public static class TestDummyAction extends TriggerActionBase {
+
+ @Override
+ public void process(TriggerEvent event, ActionContext context) {
+ if (failDummyAction) {
+ throw new RuntimeException("failure");
+ }
+
+ }
+ }
+
+ @Test
+ @BadApple(bugUrl="https://issues.apache.org/jira/browse/SOLR-12028")
+ public void testListeners() throws Exception {
+ SolrClient solrClient = cluster.simGetSolrClient();
+ String setTriggerCommand = "{" +
+ "'set-trigger' : {" +
+ "'name' : 'node_added_trigger'," +
+ "'event' : 'nodeAdded'," +
+ "'waitFor' : '" + waitForSeconds + "s'," +
+ "'enabled' : true," +
+ "'actions' : [" +
+ "{'name':'test','class':'" + TestTriggerAction.class.getName() + "'}," +
+ "{'name':'test1','class':'" + TestDummyAction.class.getName() + "'}," +
+ "]" +
+ "}}";
+ SolrRequest req = createAutoScalingRequest(SolrRequest.METHOD.POST, setTriggerCommand);
+ NamedList<Object> response = solrClient.request(req);
+ assertEquals(response.get("result").toString(), "success");
+
+ if (!actionInitCalled.await(3000 / SPEED, TimeUnit.MILLISECONDS)) {
+ fail("The TriggerAction should have been created by now");
+ }
+
+ String setListenerCommand = "{" +
+ "'set-listener' : " +
+ "{" +
+ "'name' : 'foo'," +
+ "'trigger' : 'node_added_trigger'," +
+ "'stage' : ['STARTED','ABORTED','SUCCEEDED', 'FAILED']," +
+ "'beforeAction' : 'test'," +
+ "'afterAction' : ['test', 'test1']," +
+ "'class' : '" + TestTriggerListener.class.getName() + "'" +
+ "}" +
+ "}";
+ req = createAutoScalingRequest(SolrRequest.METHOD.POST, setListenerCommand);
+ response = solrClient.request(req);
+ assertEquals(response.get("result").toString(), "success");
+
+ String setListenerCommand1 = "{" +
+ "'set-listener' : " +
+ "{" +
+ "'name' : 'bar'," +
+ "'trigger' : 'node_added_trigger'," +
+ "'stage' : ['FAILED','SUCCEEDED']," +
+ "'beforeAction' : ['test', 'test1']," +
+ "'afterAction' : 'test'," +
+ "'class' : '" + TestTriggerListener.class.getName() + "'" +
+ "}" +
+ "}";
+ req = createAutoScalingRequest(SolrRequest.METHOD.POST, setListenerCommand1);
+ response = solrClient.request(req);
+ assertEquals(response.get("result").toString(), "success");
+
+ listenerEvents.clear();
+ failDummyAction = false;
+
+ String newNode = cluster.simAddNode();
+ boolean await = triggerFiredLatch.await(20000 / SPEED, TimeUnit.MILLISECONDS);
+ assertTrue("The trigger did not fire at all", await);
+ assertTrue(triggerFired.get());
+
+ assertEquals("both listeners should have fired", 2, listenerEvents.size());
+
+ cluster.getTimeSource().sleep(2000);
+
+ // check foo events
+ List<CapturedEvent> testEvents = listenerEvents.get("foo");
+ assertNotNull("foo events: " + testEvents, testEvents);
+ assertEquals("foo events: " + testEvents, 5, testEvents.size());
+
+ assertEquals(TriggerEventProcessorStage.STARTED, testEvents.get(0).stage);
+
+ assertEquals(TriggerEventProcessorStage.BEFORE_ACTION, testEvents.get(1).stage);
+ assertEquals("test", testEvents.get(1).actionName);
+
+ assertEquals(TriggerEventProcessorStage.AFTER_ACTION, testEvents.get(2).stage);
+ assertEquals("test", testEvents.get(2).actionName);
+
+ assertEquals(TriggerEventProcessorStage.AFTER_ACTION, testEvents.get(3).stage);
+ assertEquals("test1", testEvents.get(3).actionName);
+
+ assertEquals(TriggerEventProcessorStage.SUCCEEDED, testEvents.get(4).stage);
+
+ // check bar events
+ testEvents = listenerEvents.get("bar");
+ assertNotNull("bar events", testEvents);
+ assertEquals("bar events", 4, testEvents.size());
+
+ assertEquals(TriggerEventProcessorStage.BEFORE_ACTION, testEvents.get(0).stage);
+ assertEquals("test", testEvents.get(0).actionName);
+
+ assertEquals(TriggerEventProcessorStage.AFTER_ACTION, testEvents.get(1).stage);
+ assertEquals("test", testEvents.get(1).actionName);
+
+ assertEquals(TriggerEventProcessorStage.BEFORE_ACTION, testEvents.get(2).stage);
+ assertEquals("test1", testEvents.get(2).actionName);
+
+ assertEquals(TriggerEventProcessorStage.SUCCEEDED, testEvents.get(3).stage);
+
+ // check global ordering of events (SOLR-12668)
+ int fooIdx = -1;
+ int barIdx = -1;
+ for (int i = 0; i < allListenerEvents.size(); i++) {
+ CapturedEvent ev = allListenerEvents.get(i);
+ if (ev.stage == TriggerEventProcessorStage.BEFORE_ACTION && ev.actionName.equals("test")) {
+ if (ev.config.name.equals("foo")) {
+ fooIdx = i;
+ } else if (ev.config.name.equals("bar")) {
+ barIdx = i;
+ }
+ }
+ }
+ assertTrue("fooIdx not found", fooIdx != -1);
+ assertTrue("barIdx not found", barIdx != -1);
+ assertTrue("foo fired later than bar: fooIdx=" + fooIdx + ", barIdx=" + barIdx, fooIdx < barIdx);
+
+ // reset
+ triggerFired.set(false);
+ triggerFiredLatch = new CountDownLatch(1);
+ listenerEvents.clear();
+ failDummyAction = true;
+
+ newNode = cluster.simAddNode();
+ await = triggerFiredLatch.await(20000 / SPEED, TimeUnit.MILLISECONDS);
+ assertTrue("The trigger did not fire at all", await);
+
+ cluster.getTimeSource().sleep(2000);
+
+ // check foo events
+ testEvents = listenerEvents.get("foo");
+ assertNotNull("foo events: " + testEvents, testEvents);
+ assertEquals("foo events: " + testEvents, 4, testEvents.size());
+
+ assertEquals(TriggerEventProcessorStage.STARTED, testEvents.get(0).stage);
+
+ assertEquals(TriggerEventProcessorStage.BEFORE_ACTION, testEvents.get(1).stage);
+ assertEquals("test", testEvents.get(1).actionName);
+
+ assertEquals(TriggerEventProcessorStage.AFTER_ACTION, testEvents.get(2).stage);
+ assertEquals("test", testEvents.get(2).actionName);
+
+ assertEquals(TriggerEventProcessorStage.FAILED, testEvents.get(3).stage);
+ assertEquals("test1", testEvents.get(3).actionName);
+
+ // check bar events
+ testEvents = listenerEvents.get("bar");
+ assertNotNull("bar events", testEvents);
+ assertEquals("bar events", 4, testEvents.size());
+
+ assertEquals(TriggerEventProcessorStage.BEFORE_ACTION, testEvents.get(0).stage);
+ assertEquals("test", testEvents.get(0).actionName);
+
+ assertEquals(TriggerEventProcessorStage.AFTER_ACTION, testEvents.get(1).stage);
+ assertEquals("test", testEvents.get(1).actionName);
+
+ assertEquals(TriggerEventProcessorStage.BEFORE_ACTION, testEvents.get(2).stage);
+ assertEquals("test1", testEvents.get(2).actionName);
+
+ assertEquals(TriggerEventProcessorStage.FAILED, testEvents.get(3).stage);
+ assertEquals("test1", testEvents.get(3).actionName);
+ }
+
+ @Test
+ @BadApple(bugUrl="https://issues.apache.org/jira/browse/SOLR-12028")
+ public void testCooldown() throws Exception {
+ SolrClient solrClient = cluster.simGetSolrClient();
+ failDummyAction = false;
+ waitForSeconds = 1;
+ String setTriggerCommand = "{" +
+ "'set-trigger' : {" +
+ "'name' : 'node_added_cooldown_trigger'," +
+ "'event' : 'nodeAdded'," +
+ "'waitFor' : '" + waitForSeconds + "s'," +
+ "'enabled' : true," +
+ "'actions' : [" +
+ "{'name':'test','class':'" + TestTriggerAction.class.getName() + "'}" +
+ "]" +
+ "}}";
+ SolrRequest req = createAutoScalingRequest(SolrRequest.METHOD.POST, setTriggerCommand);
+ NamedList<Object> response = solrClient.request(req);
+ assertEquals(response.get("result").toString(), "success");
+
+ String setListenerCommand1 = "{" +
+ "'set-listener' : " +
+ "{" +
+ "'name' : 'bar'," +
+ "'trigger' : 'node_added_cooldown_trigger'," +
+ "'stage' : ['FAILED','SUCCEEDED', 'IGNORED']," +
+ "'class' : '" + TestTriggerListener.class.getName() + "'" +
+ "}" +
+ "}";
+ req = createAutoScalingRequest(SolrRequest.METHOD.POST, setListenerCommand1);
+ response = solrClient.request(req);
+ assertEquals(response.get("result").toString(), "success");
+
+ listenerCreated = new CountDownLatch(1);
+ listenerEvents.clear();
+
+ String newNode = cluster.simAddNode();
+ boolean await = triggerFiredLatch.await(20000 / SPEED, TimeUnit.MILLISECONDS);
+ assertTrue("The trigger did not fire at all", await);
+ assertTrue(triggerFired.get());
+ // wait for listener to capture the SUCCEEDED stage
+ cluster.getTimeSource().sleep(5000);
+
+ List<CapturedEvent> capturedEvents = listenerEvents.get("bar");
+ assertNotNull("no events for 'bar'!", capturedEvents);
+ // we may get a few IGNORED events if other tests caused events within cooldown period
+ assertTrue(capturedEvents.toString(), capturedEvents.size() > 0);
+ long prevTimestamp = capturedEvents.get(capturedEvents.size() - 1).timestamp;
+
+ // reset the trigger and captured events
+ listenerEvents.clear();
+ triggerFiredLatch = new CountDownLatch(1);
+ triggerFired.compareAndSet(true, false);
+
+ String newNode2 = cluster.simAddNode();
+ await = triggerFiredLatch.await(20000 / SPEED, TimeUnit.MILLISECONDS);
+ assertTrue("The trigger did not fire at all", await);
+ // wait for listener to capture the SUCCEEDED stage
+ cluster.getTimeSource().sleep(2000);
+
+ // there must be exactly one SUCCEEDED event
+ capturedEvents = listenerEvents.get("bar");
+ assertTrue(capturedEvents.toString(), capturedEvents.size() >= 1);
+ CapturedEvent ev = capturedEvents.get(capturedEvents.size() - 1);
+ assertEquals(ev.toString(), TriggerEventProcessorStage.SUCCEEDED, ev.stage);
+ // the difference between timestamps of the first SUCCEEDED and the last SUCCEEDED
+ // must be larger than cooldown period
+ assertTrue("timestamp delta is less than default cooldown period", ev.timestamp - prevTimestamp > TimeUnit.SECONDS.toNanos(ScheduledTriggers.DEFAULT_COOLDOWN_PERIOD_SECONDS));
+ }
+
+ public static class TestSearchRateAction extends TriggerActionBase {
+
+ @Override
+ public void process(TriggerEvent event, ActionContext context) throws Exception {
+ try {
+ events.add(event);
+ long currentTimeNanos = cluster.getTimeSource().getTimeNs();
+ long eventTimeNanos = event.getEventTime();
+ long waitForNanos = TimeUnit.NANOSECONDS.convert(waitForSeconds, TimeUnit.SECONDS) - WAIT_FOR_DELTA_NANOS;
+ if (currentTimeNanos - eventTimeNanos <= waitForNanos) {
+ fail(event.getSource() + " was fired before the configured waitFor period");
+ }
+ getTriggerFiredLatch().countDown();
+ } catch (Throwable t) {
+ log.debug("--throwable", t);
+ throw t;
+ }
+ }
+ }
+
+ public static class FinishTriggerAction extends TriggerActionBase {
+ @Override
+ public void process(TriggerEvent event, ActionContext context) throws Exception {
+ triggerFinishedCount.incrementAndGet();
+ triggerFinishedLatch.countDown();
+ }
+ }
+
+ public static class StartTriggerAction extends TriggerActionBase {
+ @Override
+ public void process(TriggerEvent event, ActionContext context) throws Exception {
+ triggerStartedLatch.countDown();
+ triggerStartedCount.incrementAndGet();
+ }
+ }
+
+
+
+ @Test
+ //@BadApple(bugUrl="https://issues.apache.org/jira/browse/SOLR-12028")
+ public void testSearchRate() throws Exception {
+ SolrClient solrClient = cluster.simGetSolrClient();
+ String COLL1 = "collection1";
+ CollectionAdminRequest.Create create = CollectionAdminRequest.createCollection(COLL1,
+ "conf", 1, 2);
+ create.process(solrClient);
+ CloudTestUtils.waitForState(cluster, COLL1, 10, TimeUnit.SECONDS, CloudTestUtils.clusterShape(1, 2, false, true));
+
+ String setTriggerCommand = "{" +
+ "'set-trigger' : {" +
+ "'name' : 'search_rate_trigger'," +
+ "'event' : 'searchRate'," +
+ "'waitFor' : '" + waitForSeconds + "s'," +
+ "'enabled' : true," +
+ "'aboveRate' : 1.0," +
+ "'aboveNodeRate' : 1.0," +
+ "'actions' : [" +
+ "{'name':'start','class':'" + StartTriggerAction.class.getName() + "'}," +
+ "{'name':'compute','class':'" + ComputePlanAction.class.getName() + "'}," +
+ "{'name':'execute','class':'" + ExecutePlanAction.class.getName() + "'}," +
+ "{'name':'test','class':'" + TestSearchRateAction.class.getName() + "'}" +
+ "{'name':'finish','class':'" + FinishTriggerAction.class.getName() + "'}," +
+ "]" +
+ "}}";
+ SolrRequest req = createAutoScalingRequest(SolrRequest.METHOD.POST, setTriggerCommand);
+ NamedList<Object> response = solrClient.request(req);
+ assertEquals(response.get("result").toString(), "success");
+
+ String setListenerCommand1 = "{" +
+ "'set-listener' : " +
+ "{" +
+ "'name' : 'srt'," +
+ "'trigger' : 'search_rate_trigger'," +
+ "'stage' : ['FAILED','SUCCEEDED']," +
+ "'afterAction': ['compute', 'execute', 'test']," +
+ "'class' : '" + TestTriggerListener.class.getName() + "'" +
+ "}" +
+ "}";
+ req = createAutoScalingRequest(SolrRequest.METHOD.POST, setListenerCommand1);
+ response = solrClient.request(req);
+ assertEquals(response.get("result").toString(), "success");
+
+// SolrParams query = params(CommonParams.Q, "*:*");
+// for (int i = 0; i < 500; i++) {
+// solrClient.query(COLL1, query);
+// }
+
+ cluster.getSimClusterStateProvider().simSetCollectionValue(COLL1, "QUERY./select.requestTimes:1minRate", 500, false, true);
+
+ boolean await = triggerStartedLatch.await(20000 / SPEED, TimeUnit.MILLISECONDS);
+ assertTrue("The trigger did not start in time", await);
+ await = triggerFinishedLatch.await(60000 / SPEED, TimeUnit.MILLISECONDS);
+ assertTrue("The trigger did not finish in time", await);
+ // wait for listener to capture the SUCCEEDED stage
+ cluster.getTimeSource().sleep(5000);
+ List<CapturedEvent> events = listenerEvents.get("srt");
+
+ assertEquals(listenerEvents.toString(), 4, events.size());
+ assertEquals("AFTER_ACTION", events.get(0).stage.toString());
+ assertEquals("compute", events.get(0).actionName);
+ assertEquals("AFTER_ACTION", events.get(1).stage.toString());
+ assertEquals("execute", events.get(1).actionName);
+ assertEquals("AFTER_ACTION", events.get(2).stage.toString());
+ assertEquals("test", events.get(2).actionName);
+ assertEquals("SUCCEEDED", events.get(3).stage.toString());
+ assertNull(events.get(3).actionName);
+
+ CapturedEvent ev = events.get(0);
+ long now = cluster.getTimeSource().getTimeNs();
+ // verify waitFor
+ assertTrue(TimeUnit.SECONDS.convert(waitForSeconds, TimeUnit.NANOSECONDS) - WAIT_FOR_DELTA_NANOS <= now - ev.event.getEventTime());
+ Map<String, Double> nodeRates = (Map<String, Double>)ev.event.getProperties().get(SearchRateTrigger.HOT_NODES);
+ assertNotNull("nodeRates", nodeRates);
+ assertTrue(nodeRates.toString(), nodeRates.size() > 0);
+ AtomicDouble totalNodeRate = new AtomicDouble();
+ nodeRates.forEach((n, r) -> totalNodeRate.addAndGet(r));
+ List<ReplicaInfo> replicaRates = (List<ReplicaInfo>)ev.event.getProperties().get(SearchRateTrigger.HOT_REPLICAS);
+ assertNotNull("replicaRates", replicaRates);
+ assertTrue(replicaRates.toString(), replicaRates.size() > 0);
+ AtomicDouble totalReplicaRate = new AtomicDouble();
+ replicaRates.forEach(r -> {
+ assertTrue(r.toString(), r.getVariable("rate") != null);
+ totalReplicaRate.addAndGet((Double)r.getVariable("rate"));
+ });
+ Map<String, Object> shardRates = (Map<String, Object>)ev.event.getProperties().get(SearchRateTrigger.HOT_SHARDS);
+ assertNotNull("shardRates", shardRates);
+ assertEquals(shardRates.toString(), 1, shardRates.size());
+ shardRates = (Map<String, Object>)shardRates.get(COLL1);
+ assertNotNull("shardRates", shardRates);
+ assertEquals(shardRates.toString(), 1, shardRates.size());
+ AtomicDouble totalShardRate = new AtomicDouble();
+ shardRates.forEach((s, r) -> totalShardRate.addAndGet((Double)r));
+ Map<String, Double> collectionRates = (Map<String, Double>)ev.event.getProperties().get(SearchRateTrigger.HOT_COLLECTIONS);
+ assertNotNull("collectionRates", collectionRates);
+ assertEquals(collectionRates.toString(), 1, collectionRates.size());
+ Double collectionRate = collectionRates.get(COLL1);
+ assertNotNull(collectionRate);
+ assertTrue(collectionRate > 100.0);
+ assertTrue(totalNodeRate.get() > 100.0);
+ assertTrue(totalShardRate.get() > 100.0);
+ assertTrue(totalReplicaRate.get() > 100.0);
+
+ // check operations
+ List<Map<String, Object>> ops = (List<Map<String, Object>>)ev.context.get("properties.operations");
+ assertNotNull(ops);
+ assertTrue(ops.size() > 1);
+ for (Map<String, Object> m : ops) {
+ assertEquals("ADDREPLICA", m.get("params.action"));
+ }
+ }
+}
[5/6] lucene-solr:branch_7x: SOLR-12669: Rename tests that use the
autoscaling simulation framework.
Posted by ab...@apache.org.
http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/b949f57f/solr/core/src/test/org/apache/solr/cloud/autoscaling/sim/TestLargeCluster.java
----------------------------------------------------------------------
diff --git a/solr/core/src/test/org/apache/solr/cloud/autoscaling/sim/TestLargeCluster.java b/solr/core/src/test/org/apache/solr/cloud/autoscaling/sim/TestLargeCluster.java
deleted file mode 100644
index 93f92ec..0000000
--- a/solr/core/src/test/org/apache/solr/cloud/autoscaling/sim/TestLargeCluster.java
+++ /dev/null
@@ -1,727 +0,0 @@
-/*
- * 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.solr.cloud.autoscaling.sim;
-
-import java.lang.invoke.MethodHandles;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Locale;
-import java.util.Map;
-import java.util.Set;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.atomic.AtomicInteger;
-
-import com.carrotsearch.randomizedtesting.annotations.ThreadLeakLingering;
-import com.carrotsearch.randomizedtesting.annotations.TimeoutSuite;
-import org.apache.commons.math3.stat.descriptive.SummaryStatistics;
-import org.apache.lucene.util.LuceneTestCase;
-import org.apache.solr.client.solrj.SolrClient;
-import org.apache.solr.client.solrj.SolrRequest;
-import org.apache.solr.client.solrj.cloud.autoscaling.AutoScalingConfig;
-import org.apache.solr.client.solrj.cloud.SolrCloudManager;
-import org.apache.solr.client.solrj.cloud.autoscaling.Suggester;
-import org.apache.solr.client.solrj.cloud.autoscaling.TriggerEventProcessorStage;
-import org.apache.solr.client.solrj.cloud.autoscaling.TriggerEventType;
-import org.apache.solr.client.solrj.request.CollectionAdminRequest;
-import org.apache.solr.cloud.CloudTestUtils;
-import org.apache.solr.cloud.autoscaling.ActionContext;
-import org.apache.solr.cloud.autoscaling.ComputePlanAction;
-import org.apache.solr.cloud.autoscaling.ExecutePlanAction;
-import org.apache.solr.cloud.autoscaling.SearchRateTrigger;
-import org.apache.solr.cloud.autoscaling.TriggerActionBase;
-import org.apache.solr.cloud.autoscaling.TriggerEvent;
-import org.apache.solr.cloud.autoscaling.TriggerListenerBase;
-import org.apache.solr.cloud.autoscaling.CapturedEvent;
-import org.apache.solr.cloud.autoscaling.TriggerValidationException;
-import org.apache.solr.common.SolrInputDocument;
-import org.apache.solr.common.cloud.Replica;
-import org.apache.solr.common.params.CollectionAdminParams;
-import org.apache.solr.common.params.CollectionParams;
-import org.apache.solr.common.util.NamedList;
-import org.apache.solr.common.util.Pair;
-import org.apache.solr.common.util.TimeSource;
-import org.apache.solr.core.SolrResourceLoader;
-import org.apache.solr.util.LogLevel;
-import org.apache.solr.util.TimeOut;
-import org.junit.Before;
-import org.junit.BeforeClass;
-import org.junit.Test;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import static org.apache.solr.cloud.autoscaling.AutoScalingHandlerTest.createAutoScalingRequest;
-
-/**
- *
- */
-@TimeoutSuite(millis = 4 * 3600 * 1000)
-@LogLevel("org.apache.solr.cloud.autoscaling=DEBUG")
-@ThreadLeakLingering(linger = 20000) // ComputePlanAction may take significant time to complete
-//05-Jul-2018 @LuceneTestCase.BadApple(bugUrl = "https://issues.apache.org/jira/browse/SOLR-12075")
-public class TestLargeCluster extends SimSolrCloudTestCase {
- private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
-
- public static final int SPEED = 50;
-
- public static final int NUM_NODES = 100;
-
- static Map<String, List<CapturedEvent>> listenerEvents = new ConcurrentHashMap<>();
- static AtomicInteger triggerFinishedCount = new AtomicInteger();
- static AtomicInteger triggerStartedCount = new AtomicInteger();
- static CountDownLatch triggerStartedLatch;
- static CountDownLatch triggerFinishedLatch;
- static int waitForSeconds;
-
- @BeforeClass
- public static void setupCluster() throws Exception {
- configureCluster(NUM_NODES, TimeSource.get("simTime:" + SPEED));
- }
-
- @Before
- public void setupTest() throws Exception {
- waitForSeconds = 5;
- triggerStartedCount.set(0);
- triggerFinishedCount.set(0);
- triggerStartedLatch = new CountDownLatch(1);
- triggerFinishedLatch = new CountDownLatch(1);
- listenerEvents.clear();
- // disable .scheduled_maintenance and .auto_add_replicas
- String suspendTriggerCommand = "{" +
- "'suspend-trigger' : {'name' : '.scheduled_maintenance'}" +
- "}";
- SolrRequest req = createAutoScalingRequest(SolrRequest.METHOD.POST, suspendTriggerCommand);
- SolrClient solrClient = cluster.simGetSolrClient();
- NamedList<Object> response;
- try {
- response = solrClient.request(req);
- assertEquals(response.get("result").toString(), "success");
- } catch (Exception e) {
- if (!e.toString().contains("No trigger exists")) {
- throw e;
- }
- }
- suspendTriggerCommand = "{" +
- "'suspend-trigger' : {'name' : '.auto_add_replicas'}" +
- "}";
- req = createAutoScalingRequest(SolrRequest.METHOD.POST, suspendTriggerCommand);
- try {
- response = solrClient.request(req);
- assertEquals(response.get("result").toString(), "success");
- } catch (Exception e) {
- if (!e.toString().contains("No trigger exists")) {
- throw e;
- }
- }
-
- // do this in advance if missing
- if (!cluster.getSimClusterStateProvider().simListCollections().contains(CollectionAdminParams.SYSTEM_COLL)) {
- cluster.getSimClusterStateProvider().createSystemCollection();
- CloudTestUtils.waitForState(cluster, CollectionAdminParams.SYSTEM_COLL, 120, TimeUnit.SECONDS,
- CloudTestUtils.clusterShape(1, 1, false, true));
- }
- }
-
- public static class TestTriggerListener extends TriggerListenerBase {
- @Override
- public void configure(SolrResourceLoader loader, SolrCloudManager cloudManager, AutoScalingConfig.TriggerListenerConfig config) throws TriggerValidationException {
- super.configure(loader, cloudManager, config);
- }
-
- @Override
- public synchronized void onEvent(TriggerEvent event, TriggerEventProcessorStage stage, String actionName,
- ActionContext context, Throwable error, String message) {
- List<CapturedEvent> lst = listenerEvents.computeIfAbsent(config.name, s -> new ArrayList<>());
- lst.add(new CapturedEvent(cluster.getTimeSource().getTimeNs(), context, config, stage, actionName, event, message));
- }
- }
-
- public static class FinishTriggerAction extends TriggerActionBase {
- @Override
- public void process(TriggerEvent event, ActionContext context) throws Exception {
- triggerFinishedCount.incrementAndGet();
- triggerFinishedLatch.countDown();
- }
- }
-
- public static class StartTriggerAction extends TriggerActionBase {
- @Override
- public void process(TriggerEvent event, ActionContext context) throws Exception {
- triggerStartedLatch.countDown();
- triggerStartedCount.incrementAndGet();
- }
- }
-
- @Test
- @LuceneTestCase.BadApple(bugUrl="https://issues.apache.org/jira/browse/SOLR-12028") // 2018-06-18
- public void testBasic() throws Exception {
- SolrClient solrClient = cluster.simGetSolrClient();
- String setTriggerCommand = "{" +
- "'set-trigger' : {" +
- "'name' : 'node_lost_trigger1'," +
- "'event' : 'nodeLost'," +
- "'waitFor' : '" + waitForSeconds + "s'," +
- "'enabled' : true," +
- "'actions' : [" +
- "{'name':'start','class':'" + StartTriggerAction.class.getName() + "'}," +
- "{'name':'compute','class':'" + ComputePlanAction.class.getName() + "'}," +
- "{'name':'execute','class':'" + ExecutePlanAction.class.getName() + "'}," +
- "{'name':'test','class':'" + FinishTriggerAction.class.getName() + "'}" +
- "]" +
- "}}";
- SolrRequest req = createAutoScalingRequest(SolrRequest.METHOD.POST, setTriggerCommand);
- NamedList<Object> response = solrClient.request(req);
- assertEquals(response.get("result").toString(), "success");
-
- String setListenerCommand = "{" +
- "'set-listener' : " +
- "{" +
- "'name' : 'foo'," +
- "'trigger' : 'node_lost_trigger1'," +
- "'stage' : ['STARTED','ABORTED','SUCCEEDED', 'FAILED']," +
- "'beforeAction' : ['compute', 'execute']," +
- "'afterAction' : ['compute', 'execute']," +
- "'class' : '" + TestTriggerListener.class.getName() + "'" +
- "}" +
- "}";
- req = createAutoScalingRequest(SolrRequest.METHOD.POST, setListenerCommand);
- response = solrClient.request(req);
- assertEquals(response.get("result").toString(), "success");
-
- cluster.getTimeSource().sleep(5000);
-
- // pick a few random nodes
- List<String> nodes = new ArrayList<>();
- int limit = 75;
- for (String node : cluster.getClusterStateProvider().getLiveNodes()) {
- nodes.add(node);
- if (nodes.size() > limit) {
- break;
- }
- }
- Collections.shuffle(nodes, random());
- // create collection on these nodes
- String collectionName = "testBasic";
- CollectionAdminRequest.Create create = CollectionAdminRequest.createCollection(collectionName,
- "conf", 5, 5, 5, 5);
- create.setMaxShardsPerNode(1);
- create.setAutoAddReplicas(false);
- create.setCreateNodeSet(String.join(",", nodes));
- create.process(solrClient);
-
- log.info("Ready after " + CloudTestUtils.waitForState(cluster, collectionName, 30 * nodes.size(), TimeUnit.SECONDS,
- CloudTestUtils.clusterShape(5, 15, false, true)) + "ms");
-
- int KILL_NODES = 8;
- // kill off a number of nodes
- for (int i = 0; i < KILL_NODES; i++) {
- cluster.simRemoveNode(nodes.get(i), false);
- }
- // should fully recover
- log.info("Ready after " + CloudTestUtils.waitForState(cluster, collectionName, 90 * KILL_NODES, TimeUnit.SECONDS,
- CloudTestUtils.clusterShape(5, 15, false, true)) + "ms");
-
- log.info("OP COUNTS: " + cluster.simGetOpCounts());
- long moveReplicaOps = cluster.simGetOpCount(CollectionParams.CollectionAction.MOVEREPLICA.name());
-
- // simulate a number of flaky nodes
- int FLAKY_NODES = 10;
- int flakyReplicas = 0;
- for (int cnt = 0; cnt < 10; cnt++) {
- for (int i = KILL_NODES; i < KILL_NODES + FLAKY_NODES; i++) {
- flakyReplicas += cluster.getSimClusterStateProvider().simGetReplicaInfos(nodes.get(i))
- .stream().filter(r -> r.getState().equals(Replica.State.ACTIVE)).count();
- cluster.simRemoveNode(nodes.get(i), false);
- }
- cluster.getTimeSource().sleep(TimeUnit.SECONDS.toMillis(waitForSeconds) * 2);
- for (int i = KILL_NODES; i < KILL_NODES + FLAKY_NODES; i++) {
- final String nodeId = nodes.get(i);
- cluster.submit(() -> cluster.getSimClusterStateProvider().simRestoreNode(nodeId));
- }
- }
-
- // wait until started == finished
- TimeOut timeOut = new TimeOut(20 * waitForSeconds * NUM_NODES, TimeUnit.SECONDS, cluster.getTimeSource());
- while (!timeOut.hasTimedOut()) {
- if (triggerStartedCount.get() == triggerFinishedCount.get()) {
- break;
- }
- timeOut.sleep(1000);
- }
- if (timeOut.hasTimedOut()) {
- fail("did not finish processing all events in time: started=" + triggerStartedCount.get() + ", finished=" + triggerFinishedCount.get());
- }
-
-
- log.info("Ready after " + CloudTestUtils.waitForState(cluster, collectionName, 30 * nodes.size(), TimeUnit.SECONDS,
- CloudTestUtils.clusterShape(5, 15, false, true)) + "ms");
- long newMoveReplicaOps = cluster.simGetOpCount(CollectionParams.CollectionAction.MOVEREPLICA.name());
- log.info("==== Flaky replicas: {}. Additional MOVEREPLICA count: {}", flakyReplicas, (newMoveReplicaOps - moveReplicaOps));
- // flaky nodes lead to a number of MOVEREPLICA that is non-zero but lower than the number of flaky replicas
- assertTrue("there should be new MOVERPLICA ops", newMoveReplicaOps - moveReplicaOps > 0);
- assertTrue("there should be less than flakyReplicas=" + flakyReplicas + " MOVEREPLICA ops",
- newMoveReplicaOps - moveReplicaOps < flakyReplicas);
- }
-
- @Test
- @LuceneTestCase.BadApple(bugUrl="https://issues.apache.org/jira/browse/SOLR-12028") // 28-June-2018
- public void testAddNode() throws Exception {
- SolrClient solrClient = cluster.simGetSolrClient();
- String setTriggerCommand = "{" +
- "'set-trigger' : {" +
- "'name' : 'node_added_trigger2'," +
- "'event' : 'nodeAdded'," +
- "'waitFor' : '" + waitForSeconds + "s'," +
- "'enabled' : true," +
- "'actions' : [" +
- "{'name':'start','class':'" + StartTriggerAction.class.getName() + "'}," +
- "{'name':'compute','class':'" + ComputePlanAction.class.getName() + "'}," +
- "{'name':'execute','class':'" + ExecutePlanAction.class.getName() + "'}," +
- "{'name':'test','class':'" + FinishTriggerAction.class.getName() + "'}" +
- "]" +
- "}}";
- SolrRequest req = createAutoScalingRequest(SolrRequest.METHOD.POST, setTriggerCommand);
- NamedList<Object> response = solrClient.request(req);
- assertEquals(response.get("result").toString(), "success");
-
- // create a collection with more than 1 replica per node
- String collectionName = "testNodeAdded";
- CollectionAdminRequest.Create create = CollectionAdminRequest.createCollection(collectionName,
- "conf", NUM_NODES / 10, NUM_NODES / 8, NUM_NODES / 8, NUM_NODES / 8);
- create.setMaxShardsPerNode(5);
- create.setAutoAddReplicas(false);
- create.process(solrClient);
-
- log.info("Ready after " + CloudTestUtils.waitForState(cluster, collectionName, 20 * NUM_NODES, TimeUnit.SECONDS,
- CloudTestUtils.clusterShape(NUM_NODES / 10, NUM_NODES / 8 * 3, false, true)) + " ms");
-
- // start adding nodes
- int numAddNode = NUM_NODES / 5;
- List<String> addNodesList = new ArrayList<>(numAddNode);
- for (int i = 0; i < numAddNode; i++) {
- addNodesList.add(cluster.simAddNode());
- cluster.getTimeSource().sleep(5000);
- }
- // wait until at least one event is generated
- boolean await = triggerStartedLatch.await(20000 / SPEED, TimeUnit.MILLISECONDS);
- assertTrue("trigger did not fire", await);
-
- // wait until started == finished
- TimeOut timeOut = new TimeOut(20 * waitForSeconds * NUM_NODES, TimeUnit.SECONDS, cluster.getTimeSource());
- while (!timeOut.hasTimedOut()) {
- if (triggerStartedCount.get() == triggerFinishedCount.get()) {
- break;
- }
- timeOut.sleep(1000);
- }
- if (timeOut.hasTimedOut()) {
- fail("did not finish processing all events in time: started=" + triggerStartedCount.get() + ", finished=" + triggerFinishedCount.get());
- }
-
- List<SolrInputDocument> systemColl = cluster.simGetSystemCollection();
- int startedEventPos = -1;
- for (int i = 0; i < systemColl.size(); i++) {
- SolrInputDocument d = systemColl.get(i);
- if (!"node_added_trigger2".equals(d.getFieldValue("event.source_s"))) {
- continue;
- }
- if ("NODEADDED".equals(d.getFieldValue("event.type_s")) &&
- "STARTED".equals(d.getFieldValue("stage_s"))) {
- startedEventPos = i;
- break;
- }
- }
- assertTrue("no STARTED event", startedEventPos > -1);
- SolrInputDocument startedEvent = systemColl.get(startedEventPos);
- int lastIgnoredPos = startedEventPos;
- // make sure some replicas have been moved
- assertTrue("no MOVEREPLICA ops?", cluster.simGetOpCount("MOVEREPLICA") > 0);
-
- log.info("Ready after " + CloudTestUtils.waitForState(cluster, collectionName, 20 * NUM_NODES, TimeUnit.SECONDS,
- CloudTestUtils.clusterShape(NUM_NODES / 10, NUM_NODES / 8 * 3, false, true)) + " ms");
-
- int count = 50;
- SolrInputDocument finishedEvent = null;
- long lastNumOps = cluster.simGetOpCount("MOVEREPLICA");
- while (count-- > 0) {
- cluster.getTimeSource().sleep(10000);
- long currentNumOps = cluster.simGetOpCount("MOVEREPLICA");
- if (currentNumOps == lastNumOps) {
- int size = systemColl.size() - 1;
- for (int i = size; i > lastIgnoredPos; i--) {
- SolrInputDocument d = systemColl.get(i);
- if (!"node_added_trigger2".equals(d.getFieldValue("event.source_s"))) {
- continue;
- }
- if ("SUCCEEDED".equals(d.getFieldValue("stage_s"))) {
- finishedEvent = d;
- break;
- }
- }
- break;
- } else {
- lastNumOps = currentNumOps;
- }
- }
-
- assertTrue("did not finish processing changes", finishedEvent != null);
- long delta = (Long)finishedEvent.getFieldValue("event.time_l") - (Long)startedEvent.getFieldValue("event.time_l");
- log.info("#### System stabilized after " + TimeUnit.NANOSECONDS.toMillis(delta) + " ms");
- assertTrue("unexpected number of MOVEREPLICA ops", cluster.simGetOpCount("MOVEREPLICA") > 1);
- }
-
- @Test
- @LuceneTestCase.BadApple(bugUrl="https://issues.apache.org/jira/browse/SOLR-12028") // 2018-06-18
- public void testNodeLost() throws Exception {
- doTestNodeLost(waitForSeconds, 5000, 0);
- }
-
- // Renard R5 series - evenly covers a log10 range
- private static final int[] renard5 = new int[] {
- 1, 2, 3, 4, 6,
- 10
- };
- private static final int[] renard5x = new int[] {
- 1, 2, 3, 4, 6,
- 10, 16, 25, 40, 63,
- 100
- };
- private static final int[] renard5xx = new int[] {
- 1, 2, 3, 4, 6,
- 10, 16, 25, 40, 63,
- 100, 158, 251, 398, 631,
- 1000, 1585, 2512, 3981, 6310,
- 10000
- };
- // Renard R10 series
- private static final double[] renard10 = new double[] {
- 1, 1.3, 1.6, 2, 2.5, 3.2, 4, 5, 6.3, 7.9,
- 10
- };
- private static final double[] renard10x = new double[] {
- 1, 1.3, 1.6, 2, 2.5, 3.2, 4, 5, 6.3, 7.9,
- 10, 12.6, 15.8, 20, 25.1, 31.6, 39.8, 50.1, 63.1, 79.4,
- 100
- };
-
- private static final AtomicInteger ZERO = new AtomicInteger(0);
-
- //@Test
- public void benchmarkNodeLost() throws Exception {
- List<String> results = new ArrayList<>();
- for (int wait : renard5x) {
- for (int delay : renard5x) {
- SummaryStatistics totalTime = new SummaryStatistics();
- SummaryStatistics ignoredOurEvents = new SummaryStatistics();
- SummaryStatistics ignoredOtherEvents = new SummaryStatistics();
- SummaryStatistics startedOurEvents = new SummaryStatistics();
- SummaryStatistics startedOtherEvents = new SummaryStatistics();
- for (int i = 0; i < 5; i++) {
- if (cluster != null) {
- cluster.close();
- }
- setupCluster();
- setUp();
- setupTest();
- long total = doTestNodeLost(wait, delay * 1000, 0);
- totalTime.addValue(total);
- // get event counts
- Map<String, Map<String, AtomicInteger>> counts = cluster.simGetEventCounts();
- Map<String, AtomicInteger> map = counts.remove("node_lost_trigger");
- startedOurEvents.addValue(map.getOrDefault("STARTED", ZERO).get());
- ignoredOurEvents.addValue(map.getOrDefault("IGNORED", ZERO).get());
- int otherStarted = 0;
- int otherIgnored = 0;
- for (Map<String, AtomicInteger> m : counts.values()) {
- otherStarted += m.getOrDefault("STARTED", ZERO).get();
- otherIgnored += m.getOrDefault("IGNORED", ZERO).get();
- }
- startedOtherEvents.addValue(otherStarted);
- ignoredOtherEvents.addValue(otherIgnored);
- }
- results.add(String.format(Locale.ROOT, "%d\t%d\t%4.0f\t%4.0f\t%4.0f\t%4.0f\t%6.0f\t%6.0f\t%6.0f\t%6.0f\t%6.0f",
- wait, delay, startedOurEvents.getMean(), ignoredOurEvents.getMean(),
- startedOtherEvents.getMean(), ignoredOtherEvents.getMean(),
- totalTime.getMin(), totalTime.getMax(), totalTime.getMean(), totalTime.getStandardDeviation(), totalTime.getVariance()));
- }
- }
- log.info("===== RESULTS ======");
- log.info("waitFor\tdelay\tSTRT\tIGN\toSTRT\toIGN\tmin\tmax\tmean\tstdev\tvar");
- results.forEach(s -> log.info(s));
- }
-
- private long doTestNodeLost(int waitFor, long killDelay, int minIgnored) throws Exception {
- SolrClient solrClient = cluster.simGetSolrClient();
- String setTriggerCommand = "{" +
- "'set-trigger' : {" +
- "'name' : 'node_lost_trigger3'," +
- "'event' : 'nodeLost'," +
- "'waitFor' : '" + waitFor + "s'," +
- "'enabled' : true," +
- "'actions' : [" +
- "{'name':'start','class':'" + StartTriggerAction.class.getName() + "'}," +
- "{'name':'compute','class':'" + ComputePlanAction.class.getName() + "'}," +
- "{'name':'execute','class':'" + ExecutePlanAction.class.getName() + "'}," +
- "{'name':'test','class':'" + FinishTriggerAction.class.getName() + "'}" +
- "]" +
- "}}";
- SolrRequest req = createAutoScalingRequest(SolrRequest.METHOD.POST, setTriggerCommand);
- NamedList<Object> response = solrClient.request(req);
- assertEquals(response.get("result").toString(), "success");
-
- String setListenerCommand = "{" +
- "'set-listener' : " +
- "{" +
- "'name' : 'failures'," +
- "'trigger' : 'node_lost_trigger3'," +
- "'stage' : ['FAILED']," +
- "'class' : '" + TestTriggerListener.class.getName() + "'" +
- "}" +
- "}";
- req = createAutoScalingRequest(SolrRequest.METHOD.POST, setListenerCommand);
- response = solrClient.request(req);
- assertEquals(response.get("result").toString(), "success");
-
-
- // create a collection with 1 replica per node
- String collectionName = "testNodeLost";
- CollectionAdminRequest.Create create = CollectionAdminRequest.createCollection(collectionName,
- "conf", NUM_NODES / 5, NUM_NODES / 10);
- create.setMaxShardsPerNode(5);
- create.setAutoAddReplicas(false);
- create.process(solrClient);
-
- log.info("Ready after " + CloudTestUtils.waitForState(cluster, collectionName, 20 * NUM_NODES, TimeUnit.SECONDS,
- CloudTestUtils.clusterShape(NUM_NODES / 5, NUM_NODES / 10, false, true)) + " ms");
-
- // start killing nodes
- int numNodes = NUM_NODES / 5;
- List<String> nodes = new ArrayList<>(cluster.getLiveNodesSet().get());
- for (int i = 0; i < numNodes; i++) {
- // this may also select a node where a replica is moved to, so the total number of
- // MOVEREPLICA may vary
- cluster.simRemoveNode(nodes.get(i), false);
- cluster.getTimeSource().sleep(killDelay);
- }
- // wait for the trigger to fire and complete at least once
- boolean await = triggerFinishedLatch.await(20 * waitFor * 1000 / SPEED, TimeUnit.MILLISECONDS);
- assertTrue("trigger did not fire within timeout, " +
- "waitFor=" + waitFor + ", killDelay=" + killDelay + ", minIgnored=" + minIgnored,
- await);
- List<SolrInputDocument> systemColl = cluster.simGetSystemCollection();
- int startedEventPos = -1;
- for (int i = 0; i < systemColl.size(); i++) {
- SolrInputDocument d = systemColl.get(i);
- if (!"node_lost_trigger3".equals(d.getFieldValue("event.source_s"))) {
- continue;
- }
- if ("NODELOST".equals(d.getFieldValue("event.type_s")) &&
- "STARTED".equals(d.getFieldValue("stage_s"))) {
- startedEventPos = i;
- break;
- }
- }
- assertTrue("no STARTED event: " + systemColl + ", " +
- "waitFor=" + waitFor + ", killDelay=" + killDelay + ", minIgnored=" + minIgnored,
- startedEventPos > -1);
- SolrInputDocument startedEvent = systemColl.get(startedEventPos);
- // we can expect some failures when target node in MOVEREPLICA has been killed
- // between when the event processing started and the actual moment of MOVEREPLICA execution
- // wait until started == (finished + failed)
- TimeOut timeOut = new TimeOut(20 * waitFor * NUM_NODES, TimeUnit.SECONDS, cluster.getTimeSource());
- while (!timeOut.hasTimedOut()) {
- if (triggerStartedCount.get() == triggerFinishedCount.get()) {
- break;
- }
- log.debug("started=" + triggerStartedCount.get() + ", finished=" + triggerFinishedCount.get() +
- ", failed=" + listenerEvents.size());
- timeOut.sleep(1000);
- }
- if (timeOut.hasTimedOut()) {
- if (triggerStartedCount.get() > triggerFinishedCount.get() + listenerEvents.size()) {
- fail("did not finish processing all events in time: started=" + triggerStartedCount.get() + ", finished=" + triggerFinishedCount.get() +
- ", failed=" + listenerEvents.size());
- }
- }
- int ignored = 0;
- int lastIgnoredPos = startedEventPos;
- for (int i = startedEventPos + 1; i < systemColl.size(); i++) {
- SolrInputDocument d = systemColl.get(i);
- if (!"node_lost_trigger3".equals(d.getFieldValue("event.source_s"))) {
- continue;
- }
- if ("NODELOST".equals(d.getFieldValue("event.type_s"))) {
- if ("IGNORED".equals(d.getFieldValue("stage_s"))) {
- ignored++;
- lastIgnoredPos = i;
- }
- }
- }
- assertTrue("should be at least " + minIgnored + " IGNORED events, " +
- "waitFor=" + waitFor + ", killDelay=" + killDelay + ", minIgnored=" + minIgnored,
- ignored >= minIgnored);
- // make sure some replicas have been moved
- assertTrue("no MOVEREPLICA ops? " +
- "waitFor=" + waitFor + ", killDelay=" + killDelay + ", minIgnored=" + minIgnored,
- cluster.simGetOpCount("MOVEREPLICA") > 0);
-
- if (listenerEvents.isEmpty()) {
- // no failed movements - verify collection shape
- log.info("Ready after " + CloudTestUtils.waitForState(cluster, collectionName, 20 * NUM_NODES, TimeUnit.SECONDS,
- CloudTestUtils.clusterShape(NUM_NODES / 5, NUM_NODES / 10, false, true)) + " ms");
- } else {
- cluster.getTimeSource().sleep(NUM_NODES * 100);
- }
-
- int count = 50;
- SolrInputDocument finishedEvent = null;
- long lastNumOps = cluster.simGetOpCount("MOVEREPLICA");
- while (count-- > 0) {
- cluster.getTimeSource().sleep(waitFor * 10000);
- long currentNumOps = cluster.simGetOpCount("MOVEREPLICA");
- if (currentNumOps == lastNumOps) {
- int size = systemColl.size() - 1;
- for (int i = size; i > lastIgnoredPos; i--) {
- SolrInputDocument d = systemColl.get(i);
- if (!"node_lost_trigger3".equals(d.getFieldValue("event.source_s"))) {
- continue;
- }
- if ("SUCCEEDED".equals(d.getFieldValue("stage_s"))) {
- finishedEvent = d;
- break;
- }
- }
- break;
- } else {
- lastNumOps = currentNumOps;
- }
- }
-
- assertTrue("did not finish processing changes, " +
- "waitFor=" + waitFor + ", killDelay=" + killDelay + ", minIgnored=" + minIgnored,
- finishedEvent != null);
- long delta = (Long)finishedEvent.getFieldValue("event.time_l") - (Long)startedEvent.getFieldValue("event.time_l");
- delta = TimeUnit.NANOSECONDS.toMillis(delta);
- log.info("#### System stabilized after " + delta + " ms");
- long ops = cluster.simGetOpCount("MOVEREPLICA");
- long expectedMinOps = 40;
- if (!listenerEvents.isEmpty()) {
- expectedMinOps = 20;
- }
- assertTrue("unexpected number (" + expectedMinOps + ") of MOVEREPLICA ops: " + ops + ", " +
- "waitFor=" + waitFor + ", killDelay=" + killDelay + ", minIgnored=" + minIgnored,
- ops >= expectedMinOps);
- return delta;
- }
-
- @Test
- //commented 2-Aug-2018 @LuceneTestCase.BadApple(bugUrl="https://issues.apache.org/jira/browse/SOLR-12028") // 2018-06-18
- public void testSearchRate() throws Exception {
- SolrClient solrClient = cluster.simGetSolrClient();
- String collectionName = "testSearchRate";
- CollectionAdminRequest.Create create = CollectionAdminRequest.createCollection(collectionName,
- "conf", 2, 10);
- create.process(solrClient);
-
- log.info("Ready after " + CloudTestUtils.waitForState(cluster, collectionName, 300, TimeUnit.SECONDS,
- CloudTestUtils.clusterShape(2, 10, false, true)) + " ms");
-
- // collect the node names for shard1
- Set<String> nodes = new HashSet<>();
- cluster.getSimClusterStateProvider().getClusterState().getCollection(collectionName)
- .getSlice("shard1")
- .getReplicas()
- .forEach(r -> nodes.add(r.getNodeName()));
-
- String metricName = "QUERY./select.requestTimes:1minRate";
- // simulate search traffic
- cluster.getSimClusterStateProvider().simSetShardValue(collectionName, "shard1", metricName, 40, false, true);
-
- // now define the trigger. doing it earlier may cause partial events to be generated (where only some
- // nodes / replicas exceeded the threshold).
- String setTriggerCommand = "{" +
- "'set-trigger' : {" +
- "'name' : 'search_rate_trigger'," +
- "'event' : 'searchRate'," +
- "'waitFor' : '" + waitForSeconds + "s'," +
- "'aboveRate' : 1.0," +
- "'aboveNodeRate' : 1.0," +
- "'enabled' : true," +
- "'actions' : [" +
- "{'name':'compute','class':'" + ComputePlanAction.class.getName() + "'}," +
- "{'name':'execute','class':'" + ExecutePlanAction.class.getName() + "'}," +
- "{'name':'test','class':'" + FinishTriggerAction.class.getName() + "'}" +
- "]" +
- "}}";
- SolrRequest req = createAutoScalingRequest(SolrRequest.METHOD.POST, setTriggerCommand);
- NamedList<Object> response = solrClient.request(req);
- assertEquals(response.get("result").toString(), "success");
- String setListenerCommand1 = "{" +
- "'set-listener' : " +
- "{" +
- "'name' : 'srt'," +
- "'trigger' : 'search_rate_trigger'," +
- "'stage' : ['FAILED','SUCCEEDED']," +
- "'class' : '" + TestTriggerListener.class.getName() + "'" +
- "}" +
- "}";
- req = createAutoScalingRequest(SolrRequest.METHOD.POST, setListenerCommand1);
- response = solrClient.request(req);
- assertEquals(response.get("result").toString(), "success");
-
-
- boolean await = triggerFinishedLatch.await(waitForSeconds * 20000 / SPEED, TimeUnit.MILLISECONDS);
- assertTrue("The trigger did not fire at all", await);
- // wait for listener to capture the SUCCEEDED stage
- cluster.getTimeSource().sleep(2000);
- assertEquals(listenerEvents.toString(), 1, listenerEvents.get("srt").size());
- CapturedEvent ev = listenerEvents.get("srt").get(0);
- assertEquals(TriggerEventType.SEARCHRATE, ev.event.getEventType());
- Map<String, Number> m = (Map<String, Number>)ev.event.getProperty(SearchRateTrigger.HOT_NODES);
- assertNotNull(m);
- assertEquals(nodes.size(), m.size());
- assertEquals(nodes, m.keySet());
- m.forEach((k, v) -> assertEquals(4.0, v.doubleValue(), 0.01));
- List<TriggerEvent.Op> ops = (List<TriggerEvent.Op>)ev.event.getProperty(TriggerEvent.REQUESTED_OPS);
- assertNotNull(ops);
- assertEquals(ops.toString(), 1, ops.size());
- ops.forEach(op -> {
- assertEquals(CollectionParams.CollectionAction.ADDREPLICA, op.getAction());
- assertEquals(1, op.getHints().size());
- Object o = op.getHints().get(Suggester.Hint.COLL_SHARD);
- // this may be a pair or a HashSet of pairs with size 1
- Pair<String, String> hint = null;
- if (o instanceof Pair) {
- hint = (Pair<String, String>)o;
- } else if (o instanceof Set) {
- assertEquals("unexpected number of hints: " + o, 1, ((Set)o).size());
- o = ((Set)o).iterator().next();
- assertTrue("unexpected hint: " + o, o instanceof Pair);
- hint = (Pair<String, String>)o;
- } else {
- fail("unexpected hints: " + o);
- }
- assertNotNull(hint);
- assertEquals(collectionName, hint.first());
- assertEquals("shard1", hint.second());
- });
- }
-}
http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/b949f57f/solr/core/src/test/org/apache/solr/cloud/autoscaling/sim/TestNodeAddedTrigger.java
----------------------------------------------------------------------
diff --git a/solr/core/src/test/org/apache/solr/cloud/autoscaling/sim/TestNodeAddedTrigger.java b/solr/core/src/test/org/apache/solr/cloud/autoscaling/sim/TestNodeAddedTrigger.java
deleted file mode 100644
index 04e8c1d..0000000
--- a/solr/core/src/test/org/apache/solr/cloud/autoscaling/sim/TestNodeAddedTrigger.java
+++ /dev/null
@@ -1,327 +0,0 @@
-/*
- * 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.solr.cloud.autoscaling.sim;
-
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.atomic.AtomicBoolean;
-import java.util.concurrent.atomic.AtomicInteger;
-import java.util.concurrent.atomic.AtomicReference;
-
-import org.apache.solr.client.solrj.cloud.SolrCloudManager;
-import org.apache.solr.cloud.autoscaling.ActionContext;
-import org.apache.solr.cloud.autoscaling.AutoScaling;
-import org.apache.solr.cloud.autoscaling.NodeAddedTrigger;
-import org.apache.solr.cloud.autoscaling.TriggerAction;
-import org.apache.solr.cloud.autoscaling.TriggerEvent;
-import org.apache.solr.cloud.autoscaling.TriggerValidationException;
-import org.apache.solr.common.util.TimeSource;
-import org.apache.solr.core.SolrResourceLoader;
-import org.junit.Before;
-import org.junit.BeforeClass;
-import org.junit.Test;
-
-/**
- * Test for {@link NodeAddedTrigger}
- */
-public class TestNodeAddedTrigger extends SimSolrCloudTestCase {
- private static AtomicBoolean actionConstructorCalled = new AtomicBoolean(false);
- private static AtomicBoolean actionInitCalled = new AtomicBoolean(false);
- private static AtomicBoolean actionCloseCalled = new AtomicBoolean(false);
-
- private AutoScaling.TriggerEventProcessor noFirstRunProcessor = event -> {
- fail("Did not expect the listener to fire on first run!");
- return true;
- };
-
- private static int SPEED = 50;
-
- // currentTimeMillis is not as precise so to avoid false positives while comparing time of fire, we add some delta
- private static final long WAIT_FOR_DELTA_NANOS = TimeUnit.MILLISECONDS.toNanos(2);
-
- private static TimeSource timeSource;
-
- @BeforeClass
- public static void setupCluster() throws Exception {
- configureCluster(1, TimeSource.get("simTime:" + SPEED));
- timeSource = cluster.getTimeSource();
- }
-
- @Before
- public void beforeTest() throws Exception {
- actionConstructorCalled = new AtomicBoolean(false);
- actionInitCalled = new AtomicBoolean(false);
- actionCloseCalled = new AtomicBoolean(false);
- }
-
- @Test
- public void testTrigger() throws Exception {
- long waitForSeconds = 1 + random().nextInt(5);
- Map<String, Object> props = createTriggerProps(waitForSeconds);
-
- try (NodeAddedTrigger trigger = new NodeAddedTrigger("node_added_trigger")) {
- trigger.configure(cluster.getLoader(), cluster, props);
- trigger.init();
- trigger.setProcessor(noFirstRunProcessor);
- trigger.run();
-
- String newNode1 = cluster.simAddNode();
- String newNode2 = cluster.simAddNode();
- AtomicBoolean fired = new AtomicBoolean(false);
- AtomicReference<TriggerEvent> eventRef = new AtomicReference<>();
- trigger.setProcessor(event -> {
- if (fired.compareAndSet(false, true)) {
- eventRef.set(event);
- long currentTimeNanos = timeSource.getTimeNs();
- long eventTimeNanos = event.getEventTime();
- long waitForNanos = TimeUnit.NANOSECONDS.convert(waitForSeconds, TimeUnit.SECONDS) - WAIT_FOR_DELTA_NANOS;
- if (currentTimeNanos - eventTimeNanos <= waitForNanos) {
- fail("NodeAddedListener was fired before the configured waitFor period: currentTimeNanos=" + currentTimeNanos + ", eventTimeNanos=" + eventTimeNanos + ",waitForNanos=" + waitForNanos);
- }
- } else {
- fail("NodeAddedTrigger was fired more than once!");
- }
- return true;
- });
- int counter = 0;
- do {
- trigger.run();
- timeSource.sleep(1000);
- if (counter++ > 10) {
- fail("Newly added node was not discovered by trigger even after 10 seconds");
- }
- } while (!fired.get());
-
- TriggerEvent nodeAddedEvent = eventRef.get();
- assertNotNull(nodeAddedEvent);
- List<String> nodeNames = (List<String>)nodeAddedEvent.getProperty(TriggerEvent.NODE_NAMES);
- assertTrue(nodeNames.contains(newNode1));
- assertTrue(nodeNames.contains(newNode2));
- }
-
- // add a new node but remove it before the waitFor period expires
- // and assert that the trigger doesn't fire at all
- try (NodeAddedTrigger trigger = new NodeAddedTrigger("node_added_trigger")) {
- trigger.configure(cluster.getLoader(), cluster, props);
- trigger.init();
- final long waitTime = 2;
- props.put("waitFor", waitTime);
- trigger.setProcessor(noFirstRunProcessor);
- trigger.run();
-
- String newNode = cluster.simAddNode();
- AtomicBoolean fired = new AtomicBoolean(false);
- trigger.setProcessor(event -> {
- if (fired.compareAndSet(false, true)) {
- long currentTimeNanos = timeSource.getTimeNs();
- long eventTimeNanos = event.getEventTime();
- long waitForNanos = TimeUnit.NANOSECONDS.convert(waitForSeconds, TimeUnit.SECONDS) - WAIT_FOR_DELTA_NANOS;
- if (currentTimeNanos - eventTimeNanos <= waitForNanos) {
- fail("NodeAddedListener was fired before the configured waitFor period: currentTimeNanos=" + currentTimeNanos + ", eventTimeNanos=" + eventTimeNanos + ",waitForNanos=" + waitForNanos);
- }
- } else {
- fail("NodeAddedTrigger was fired more than once!");
- }
- return true;
- });
- trigger.run(); // first run should detect the new node
- cluster.simRemoveNode(newNode, false);
- int counter = 0;
- do {
- trigger.run();
- timeSource.sleep(1000);
- if (counter++ > waitTime + 1) { // run it a little more than the wait time
- break;
- }
- } while (true);
-
- // ensure the event was not fired
- assertFalse(fired.get());
- }
- }
-
- public void testActionLifecycle() throws Exception {
- Map<String, Object> props = createTriggerProps(0);
- List<Map<String, String>> actions = (List<Map<String, String>>) props.get("actions");
- Map<String, String> action = new HashMap<>(2);
- action.put("name", "testActionInit");
- action.put("class", TestNodeAddedTrigger.AssertInitTriggerAction.class.getName());
- actions.add(action);
- try (NodeAddedTrigger trigger = new NodeAddedTrigger("node_added_trigger")) {
- trigger.configure(cluster.getLoader(), cluster, props);
- assertEquals(true, actionConstructorCalled.get());
- assertEquals(false, actionInitCalled.get());
- assertEquals(false, actionCloseCalled.get());
- trigger.init();
- assertEquals(true, actionInitCalled.get());
- assertEquals(false, actionCloseCalled.get());
- }
- assertEquals(true, actionCloseCalled.get());
- }
-
- public static class AssertInitTriggerAction implements TriggerAction {
- public AssertInitTriggerAction() {
- actionConstructorCalled.set(true);
- }
-
- @Override
- public void configure(SolrResourceLoader loader, SolrCloudManager cloudManager, Map<String, Object> properties) throws TriggerValidationException {
-
- }
-
- @Override
- public void init() {
- actionInitCalled.compareAndSet(false, true);
- }
-
- @Override
- public String getName() {
- return "";
- }
-
- @Override
- public void process(TriggerEvent event, ActionContext actionContext) {
-
- }
-
- @Override
- public void close() throws IOException {
- actionCloseCalled.compareAndSet(false, true);
- }
- }
-
- @Test
- public void testListenerAcceptance() throws Exception {
- Map<String, Object> props = createTriggerProps(0);
- try (NodeAddedTrigger trigger = new NodeAddedTrigger("node_added_trigger")) {
- trigger.configure(cluster.getLoader(), cluster, props);
- trigger.init();
- trigger.setProcessor(noFirstRunProcessor);
- trigger.run(); // starts tracking live nodes
-
- String newNode = cluster.simAddNode();
- AtomicInteger callCount = new AtomicInteger(0);
- AtomicBoolean fired = new AtomicBoolean(false);
-
- trigger.setProcessor(event -> {
- if (callCount.incrementAndGet() < 2) {
- return false;
- } else {
- fired.compareAndSet(false, true);
- return true;
- }
- });
-
- trigger.run(); // first run should detect the new node and fire immediately but listener isn't ready
- assertEquals(1, callCount.get());
- assertFalse(fired.get());
- trigger.run(); // second run should again fire
- assertEquals(2, callCount.get());
- assertTrue(fired.get());
- trigger.run(); // should not fire
- assertEquals(2, callCount.get());
- }
- }
-
- @Test
- public void testRestoreState() throws Exception {
- long waitForSeconds = 1 + random().nextInt(5);
- Map<String, Object> props = createTriggerProps(waitForSeconds);
-
- // add a new node but update the trigger before the waitFor period expires
- // and assert that the new trigger still fires
- NodeAddedTrigger trigger = new NodeAddedTrigger("node_added_trigger");
- trigger.configure(cluster.getLoader(), cluster, props);
- trigger.init();
- trigger.setProcessor(noFirstRunProcessor);
- trigger.run();
-
- String newNode = cluster.simAddNode();
- trigger.run(); // this run should detect the new node
- trigger.close(); // close the old trigger
-
- try (NodeAddedTrigger newTrigger = new NodeAddedTrigger("some_different_name")) {
- newTrigger.configure(cluster.getLoader(), cluster, props);
- trigger.init();
- try {
- newTrigger.restoreState(trigger);
- fail("Trigger should only be able to restore state from an old trigger of the same name");
- } catch (AssertionError e) {
- // expected
- }
- }
-
- try (NodeAddedTrigger newTrigger = new NodeAddedTrigger("node_added_trigger")) {
- newTrigger.configure(cluster.getLoader(), cluster, props);
- newTrigger.init();
- AtomicBoolean fired = new AtomicBoolean(false);
- AtomicReference<TriggerEvent> eventRef = new AtomicReference<>();
- newTrigger.setProcessor(event -> {
- if (fired.compareAndSet(false, true)) {
- eventRef.set(event);
- long currentTimeNanos = timeSource.getTimeNs();
- long eventTimeNanos = event.getEventTime();
- long waitForNanos = TimeUnit.NANOSECONDS.convert(waitForSeconds, TimeUnit.SECONDS) - WAIT_FOR_DELTA_NANOS;
- if (currentTimeNanos - eventTimeNanos <= waitForNanos) {
- fail("NodeAddedListener was fired before the configured waitFor period: currentTimeNanos=" + currentTimeNanos + ", eventTimeNanos=" + eventTimeNanos + ",waitForNanos=" + waitForNanos);
- }
- } else {
- fail("NodeAddedTrigger was fired more than once!");
- }
- return true;
- });
- newTrigger.restoreState(trigger); // restore state from the old trigger
- int counter = 0;
- do {
- newTrigger.run();
- timeSource.sleep(1000);
- if (counter++ > 10) {
- fail("Newly added node was not discovered by trigger even after 10 seconds");
- }
- } while (!fired.get());
-
- // ensure the event was fired
- assertTrue(fired.get());
- TriggerEvent nodeAddedEvent = eventRef.get();
- assertNotNull(nodeAddedEvent);
- //TODO assertEquals("", newNode.getNodeName(), nodeAddedEvent.getProperty(NodeAddedTrigger.NodeAddedEvent.NODE_NAME));
- }
- }
-
- private Map<String, Object> createTriggerProps(long waitForSeconds) {
- Map<String, Object> props = new HashMap<>();
- props.put("event", "nodeLost");
- props.put("waitFor", waitForSeconds);
- props.put("enabled", true);
- List<Map<String, String>> actions = new ArrayList<>(3);
- Map<String, String> map = new HashMap<>(2);
- map.put("name", "compute_plan");
- map.put("class", "solr.ComputePlanAction");
- actions.add(map);
- map = new HashMap<>(2);
- map.put("name", "execute_plan");
- map.put("class", "solr.ExecutePlanAction");
- actions.add(map);
- props.put("actions", actions);
- return props;
- }
-}
http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/b949f57f/solr/core/src/test/org/apache/solr/cloud/autoscaling/sim/TestNodeLostTrigger.java
----------------------------------------------------------------------
diff --git a/solr/core/src/test/org/apache/solr/cloud/autoscaling/sim/TestNodeLostTrigger.java b/solr/core/src/test/org/apache/solr/cloud/autoscaling/sim/TestNodeLostTrigger.java
deleted file mode 100644
index 33c2efa..0000000
--- a/solr/core/src/test/org/apache/solr/cloud/autoscaling/sim/TestNodeLostTrigger.java
+++ /dev/null
@@ -1,346 +0,0 @@
-/*
- * 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.solr.cloud.autoscaling.sim;
-
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Map;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.atomic.AtomicBoolean;
-import java.util.concurrent.atomic.AtomicInteger;
-import java.util.concurrent.atomic.AtomicReference;
-
-import org.apache.solr.client.solrj.cloud.SolrCloudManager;
-import org.apache.solr.cloud.autoscaling.ActionContext;
-import org.apache.solr.cloud.autoscaling.AutoScaling;
-import org.apache.solr.cloud.autoscaling.NodeLostTrigger;
-import org.apache.solr.cloud.autoscaling.TriggerAction;
-import org.apache.solr.cloud.autoscaling.TriggerEvent;
-import org.apache.solr.cloud.autoscaling.TriggerValidationException;
-import org.apache.solr.common.util.TimeSource;
-import org.apache.solr.core.SolrResourceLoader;
-import org.junit.Before;
-import org.junit.BeforeClass;
-import org.junit.Test;
-
-/**
- * Test for {@link NodeLostTrigger}
- */
-public class TestNodeLostTrigger extends SimSolrCloudTestCase {
- private static AtomicBoolean actionConstructorCalled = new AtomicBoolean(false);
- private static AtomicBoolean actionInitCalled = new AtomicBoolean(false);
- private static AtomicBoolean actionCloseCalled = new AtomicBoolean(false);
-
- private AutoScaling.TriggerEventProcessor noFirstRunProcessor = event -> {
- fail("Did not expect the listener to fire on first run!");
- return true;
- };
-
- private static final int SPEED = 50;
- // use the same time source as the trigger
- private static TimeSource timeSource;
- // currentTimeMillis is not as precise so to avoid false positives while comparing time of fire, we add some delta
- private static final long WAIT_FOR_DELTA_NANOS = TimeUnit.MILLISECONDS.toNanos(5);
-
- @BeforeClass
- public static void setupCluster() throws Exception {
- configureCluster(5, TimeSource.get("simTime:" + SPEED));
- timeSource = cluster.getTimeSource();
- }
-
- @Before
- public void beforeTest() throws Exception {
- actionConstructorCalled = new AtomicBoolean(false);
- actionInitCalled = new AtomicBoolean(false);
- actionCloseCalled = new AtomicBoolean(false);
- }
-
- @Test
- public void testTrigger() throws Exception {
- long waitForSeconds = 1 + random().nextInt(5);
- Map<String, Object> props = createTriggerProps(waitForSeconds);
-
- try (NodeLostTrigger trigger = new NodeLostTrigger("node_lost_trigger")) {
- trigger.configure(cluster.getLoader(), cluster, props);
- trigger.setProcessor(noFirstRunProcessor);
- trigger.run();
- Iterator<String> it = cluster.getLiveNodesSet().get().iterator();
- String lostNodeName1 = it.next();
- String lostNodeName2 = it.next();
- cluster.simRemoveNode(lostNodeName1, false);
- cluster.simRemoveNode(lostNodeName2, false);
- timeSource.sleep(1000);
-
- AtomicBoolean fired = new AtomicBoolean(false);
- AtomicReference<TriggerEvent> eventRef = new AtomicReference<>();
- trigger.setProcessor(event -> {
- if (fired.compareAndSet(false, true)) {
- eventRef.set(event);
- long currentTimeNanos = timeSource.getTimeNs();
- long eventTimeNanos = event.getEventTime();
- long waitForNanos = TimeUnit.NANOSECONDS.convert(waitForSeconds, TimeUnit.SECONDS) - WAIT_FOR_DELTA_NANOS;
- if (currentTimeNanos - eventTimeNanos <= waitForNanos) {
- fail("NodeLostListener was fired before the configured waitFor period: currentTimeNanos=" + currentTimeNanos + ", eventTimeNanos=" + eventTimeNanos + ",waitForNanos=" + waitForNanos);
- }
- } else {
- fail("NodeLostListener was fired more than once!");
- }
- return true;
- });
- int counter = 0;
- do {
- trigger.run();
- timeSource.sleep(1000);
- if (counter++ > 10) {
- fail("Lost node was not discovered by trigger even after 10 seconds");
- }
- } while (!fired.get());
-
- TriggerEvent nodeLostEvent = eventRef.get();
- assertNotNull(nodeLostEvent);
- List<String> nodeNames = (List<String>)nodeLostEvent.getProperty(TriggerEvent.NODE_NAMES);
- assertTrue(nodeNames + " doesn't contain " + lostNodeName1, nodeNames.contains(lostNodeName1));
- assertTrue(nodeNames + " doesn't contain " + lostNodeName2, nodeNames.contains(lostNodeName2));
-
- }
-
- // remove a node but add it back before the waitFor period expires
- // and assert that the trigger doesn't fire at all
- try (NodeLostTrigger trigger = new NodeLostTrigger("node_lost_trigger")) {
- trigger.configure(cluster.getLoader(), cluster, props);
- final long waitTime = 2;
- props.put("waitFor", waitTime);
- trigger.setProcessor(noFirstRunProcessor);
- trigger.run();
-
- String lostNode = cluster.getSimClusterStateProvider().simGetRandomNode(random());
- cluster.simRemoveNode(lostNode, false);
- AtomicBoolean fired = new AtomicBoolean(false);
- trigger.setProcessor(event -> {
- if (fired.compareAndSet(false, true)) {
- long currentTimeNanos = timeSource.getTimeNs();
- long eventTimeNanos = event.getEventTime();
- long waitForNanos = TimeUnit.NANOSECONDS.convert(waitTime, TimeUnit.SECONDS) - WAIT_FOR_DELTA_NANOS;
- if (currentTimeNanos - eventTimeNanos <= waitForNanos) {
- fail("NodeLostListener was fired before the configured waitFor period: currentTimeNanos=" + currentTimeNanos + ", eventTimeNanos=" + eventTimeNanos + ",waitForNanos=" + waitForNanos);
- }
- } else {
- fail("NodeLostListener was fired more than once!");
- }
- return true;
- });
- trigger.run(); // first run should detect the lost node
- int counter = 0;
- do {
- if (cluster.getLiveNodesSet().get().size() == 2) {
- break;
- }
- timeSource.sleep(100);
- if (counter++ > 20) {
- fail("Live nodes not updated!");
- }
- } while (true);
- counter = 0;
- cluster.getSimClusterStateProvider().simRestoreNode(lostNode);
- do {
- trigger.run();
- timeSource.sleep(1000);
- if (counter++ > waitTime + 1) { // run it a little more than the wait time
- break;
- }
- } while (true);
-
- // ensure the event was not fired
- assertFalse(fired.get());
- }
- }
-
- public void testActionLifecycle() throws Exception {
- Map<String, Object> props = createTriggerProps(0);
- List<Map<String, String>> actions = (List<Map<String, String>>) props.get("actions");
- Map<String, String> action = new HashMap<>(2);
- action.put("name", "testActionInit");
- action.put("class", AssertInitTriggerAction.class.getName());
- actions.add(action);
- try (NodeLostTrigger trigger = new NodeLostTrigger("node_added_trigger")) {
- trigger.configure(cluster.getLoader(), cluster, props);
- assertEquals(true, actionConstructorCalled.get());
- assertEquals(false, actionInitCalled.get());
- assertEquals(false, actionCloseCalled.get());
- trigger.init();
- assertEquals(true, actionInitCalled.get());
- assertEquals(false, actionCloseCalled.get());
- }
- assertEquals(true, actionCloseCalled.get());
- }
-
- public static class AssertInitTriggerAction implements TriggerAction {
- public AssertInitTriggerAction() {
- actionConstructorCalled.set(true);
- }
-
- @Override
- public void configure(SolrResourceLoader loader, SolrCloudManager cloudManager, Map<String, Object> properties) throws TriggerValidationException {
-
- }
-
- @Override
- public void init() {
- actionInitCalled.compareAndSet(false, true);
- }
-
- @Override
- public String getName() {
- return "";
- }
-
- @Override
- public void process(TriggerEvent event, ActionContext actionContext) {
-
- }
-
- @Override
- public void close() throws IOException {
- actionCloseCalled.compareAndSet(false, true);
- }
- }
-
- @Test
- public void testListenerAcceptance() throws Exception {
- Map<String, Object> props = createTriggerProps(0);
- try (NodeLostTrigger trigger = new NodeLostTrigger("node_added_trigger")) {
- trigger.configure(cluster.getLoader(), cluster, props);
- trigger.setProcessor(noFirstRunProcessor);
-
- String newNode = cluster.simAddNode();
-
- trigger.run(); // starts tracking live nodes
-
- // stop the newly created node
- cluster.simRemoveNode(newNode, false);
-
- AtomicInteger callCount = new AtomicInteger(0);
- AtomicBoolean fired = new AtomicBoolean(false);
-
- trigger.setProcessor(event -> {
- if (callCount.incrementAndGet() < 2) {
- return false;
- } else {
- fired.compareAndSet(false, true);
- return true;
- }
- });
-
- trigger.run(); // first run should detect the lost node and fire immediately but listener isn't ready
- assertEquals(1, callCount.get());
- assertFalse(fired.get());
- trigger.run(); // second run should again fire
- assertEquals(2, callCount.get());
- assertTrue(fired.get());
- trigger.run(); // should not fire
- assertEquals(2, callCount.get());
- }
- }
-
- @Test
- public void testRestoreState() throws Exception {
- long waitForSeconds = 1 + random().nextInt(5);
- Map<String, Object> props = createTriggerProps(waitForSeconds);
-
- String newNode = cluster.simAddNode();
-
- // remove a node but update the trigger before the waitFor period expires
- // and assert that the new trigger still fires
-
- NodeLostTrigger trigger = new NodeLostTrigger("node_lost_trigger");
- trigger.configure(cluster.getLoader(), cluster, props);
- trigger.setProcessor(noFirstRunProcessor);
- trigger.run();
-
- // stop the newly created node
- cluster.simRemoveNode(newNode, false);
-
- trigger.run(); // this run should detect the lost node
- trigger.close(); // close the old trigger
-
- try (NodeLostTrigger newTrigger = new NodeLostTrigger("some_different_name")) {
- newTrigger.configure(cluster.getLoader(), cluster, props);
- try {
- newTrigger.restoreState(trigger);
- fail("Trigger should only be able to restore state from an old trigger of the same name");
- } catch (AssertionError e) {
- // expected
- }
- }
-
- try (NodeLostTrigger newTrigger = new NodeLostTrigger("node_lost_trigger")) {
- newTrigger.configure(cluster.getLoader(), cluster, props);
- AtomicBoolean fired = new AtomicBoolean(false);
- AtomicReference<TriggerEvent> eventRef = new AtomicReference<>();
- newTrigger.setProcessor(event -> {
- if (fired.compareAndSet(false, true)) {
- eventRef.set(event);
- long currentTimeNanos = timeSource.getTimeNs();
- long eventTimeNanos = event.getEventTime();
- long waitForNanos = TimeUnit.NANOSECONDS.convert(waitForSeconds, TimeUnit.SECONDS) - WAIT_FOR_DELTA_NANOS;
- if (currentTimeNanos - eventTimeNanos <= waitForNanos) {
- fail("NodeLostListener was fired before the configured waitFor period: currentTimeNanos=" + currentTimeNanos + ", eventTimeNanos=" + eventTimeNanos + ",waitForNanos=" + waitForNanos);
- }
- } else {
- fail("NodeLostListener was fired more than once!");
- }
- return true;
- });
- newTrigger.restoreState(trigger); // restore state from the old trigger
- int counter = 0;
- do {
- newTrigger.run();
- timeSource.sleep(1000);
- if (counter++ > 10) {
- fail("Lost node was not discovered by trigger even after 10 seconds");
- }
- } while (!fired.get());
-
- TriggerEvent nodeLostEvent = eventRef.get();
- assertNotNull(nodeLostEvent);
- List<String> nodeNames = (List<String>)nodeLostEvent.getProperty(TriggerEvent.NODE_NAMES);
- assertTrue(nodeNames.contains(newNode));
- }
- }
-
- private Map<String, Object> createTriggerProps(long waitForSeconds) {
- Map<String, Object> props = new HashMap<>();
- props.put("event", "nodeLost");
- props.put("waitFor", waitForSeconds);
- props.put("enabled", true);
- List<Map<String, String>> actions = new ArrayList<>(3);
- Map<String, String> map = new HashMap<>(2);
- map.put("name", "compute_plan");
- map.put("class", "solr.ComputePlanAction");
- actions.add(map);
- map = new HashMap<>(2);
- map.put("name", "execute_plan");
- map.put("class", "solr.ExecutePlanAction");
- actions.add(map);
- props.put("actions", actions);
- return props;
- }
-}
http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/b949f57f/solr/core/src/test/org/apache/solr/cloud/autoscaling/sim/TestPolicyCloud.java
----------------------------------------------------------------------
diff --git a/solr/core/src/test/org/apache/solr/cloud/autoscaling/sim/TestPolicyCloud.java b/solr/core/src/test/org/apache/solr/cloud/autoscaling/sim/TestPolicyCloud.java
deleted file mode 100644
index 3237639..0000000
--- a/solr/core/src/test/org/apache/solr/cloud/autoscaling/sim/TestPolicyCloud.java
+++ /dev/null
@@ -1,366 +0,0 @@
-/*
- * 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.solr.cloud.autoscaling.sim;
-
-import java.io.IOException;
-import java.lang.invoke.MethodHandles;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
-import java.util.Map;
-import java.util.concurrent.atomic.AtomicInteger;
-import java.util.function.BiConsumer;
-
-import org.apache.lucene.util.Constants;
-import org.apache.solr.client.solrj.SolrClient;
-import org.apache.solr.client.solrj.SolrRequest;
-import org.apache.solr.client.solrj.SolrServerException;
-import org.apache.solr.client.solrj.cloud.autoscaling.AutoScalingConfig;
-import org.apache.solr.client.solrj.cloud.autoscaling.Policy;
-import org.apache.solr.client.solrj.cloud.autoscaling.ReplicaInfo;
-import org.apache.solr.client.solrj.cloud.autoscaling.Row;
-import org.apache.solr.client.solrj.cloud.autoscaling.Variable.Type;
-import org.apache.solr.client.solrj.request.CollectionAdminRequest;
-import org.apache.solr.cloud.CloudTestUtils;
-import org.apache.solr.cloud.autoscaling.AutoScalingHandlerTest;
-import org.apache.solr.common.cloud.DocCollection;
-import org.apache.solr.common.cloud.Replica;
-import org.apache.solr.common.cloud.ZkStateReader;
-import org.apache.solr.common.cloud.rule.ImplicitSnitch;
-import org.apache.solr.common.util.NamedList;
-import org.apache.solr.common.util.TimeSource;
-import org.apache.solr.common.util.Utils;
-import org.apache.zookeeper.KeeperException;
-import org.junit.BeforeClass;
-import org.junit.rules.ExpectedException;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import static org.apache.solr.cloud.autoscaling.AutoScalingHandlerTest.createAutoScalingRequest;
-
-public class TestPolicyCloud extends SimSolrCloudTestCase {
-
- private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
- @org.junit.Rule
- public ExpectedException expectedException = ExpectedException.none();
-
- @BeforeClass
- public static void setupCluster() throws Exception {
- configureCluster(5, TimeSource.get("simTime:50"));
- }
-
- public void testDataProviderPerReplicaDetails() throws Exception {
- SolrClient solrClient = cluster.simGetSolrClient();
- CollectionAdminRequest.createCollection("perReplicaDataColl", "conf", 1, 5)
- .process(solrClient);
-
- CloudTestUtils.waitForState(cluster, "Timeout waiting for collection to become active", "perReplicaDataColl",
- CloudTestUtils.clusterShape(1, 5, false, true));
- DocCollection coll = getCollectionState("perReplicaDataColl");
- String autoScaleJson = "{" +
- " 'cluster-preferences': [" +
- " { maximize : freedisk , precision: 50}," +
- " { minimize : cores, precision: 2}" +
- " ]," +
- " 'cluster-policy': [" +
- " { replica : '0' , 'nodeRole': 'overseer'}," +
- " { 'replica': '<2', 'shard': '#ANY', 'node': '#ANY'" +
- " }" +
- " ]," +
- " 'policies': {" +
- " 'policy1': [" +
- " { 'replica': '<2', 'shard': '#EACH', 'node': '#ANY'}," +
- " { 'replica': '<2', 'shard': '#EACH', 'sysprop.rack': 'rack1'}" +
- " ]" +
- " }" +
- "}";
- AutoScalingConfig config = new AutoScalingConfig((Map<String, Object>) Utils.fromJSONString(autoScaleJson));
- Policy.Session session = config.getPolicy().createSession(cluster);
-
- AtomicInteger count = new AtomicInteger(0);
- for (Row row : session.getSortedNodes()) {
- row.collectionVsShardVsReplicas.forEach((c, shardVsReplicas) -> shardVsReplicas.forEach((s, replicaInfos) -> {
- for (ReplicaInfo replicaInfo : replicaInfos) {
- if (replicaInfo.getVariables().containsKey(Type.CORE_IDX.tagName)) count.incrementAndGet();
- }
- }));
- }
- assertTrue(count.get() > 0);
-
- CollectionAdminRequest.deleteCollection("perReplicaDataColl").process(solrClient);
-
- }
-
- public void testCreateCollectionAddReplica() throws Exception {
- SolrClient solrClient = cluster.simGetSolrClient();
- String nodeId = cluster.getSimClusterStateProvider().simGetRandomNode(random());
-
- int port = (Integer)cluster.getSimNodeStateProvider().simGetNodeValue(nodeId, ImplicitSnitch.PORT);
-
- String commands = "{set-policy :{c1 : [{replica:0 , shard:'#EACH', port: '!" + port + "'}]}}";
- solrClient.request(AutoScalingHandlerTest.createAutoScalingRequest(SolrRequest.METHOD.POST, commands));
-
- String collectionName = "testCreateCollectionAddReplica";
- CollectionAdminRequest.createCollection(collectionName, "conf", 1, 1)
- .setPolicy("c1")
- .process(solrClient);
- CloudTestUtils.waitForState(cluster, "Timeout waiting for collection to become active", collectionName,
- CloudTestUtils.clusterShape(1, 1, false, true));
-
- getCollectionState(collectionName).forEachReplica((s, replica) -> assertEquals(nodeId, replica.getNodeName()));
-
- CollectionAdminRequest.addReplicaToShard(collectionName, "shard1").process(solrClient);
- CloudTestUtils.waitForState(cluster, "Timed out waiting to see 2 replicas for collection: " + collectionName,
- collectionName, (liveNodes, collectionState) -> collectionState.getReplicas().size() == 2);
-
- getCollectionState(collectionName).forEachReplica((s, replica) -> assertEquals(nodeId, replica.getNodeName()));
- }
-
- public void testCreateCollectionSplitShard() throws Exception {
- SolrClient solrClient = cluster.simGetSolrClient();
- String firstNode = cluster.getSimClusterStateProvider().simGetRandomNode(random());
- int firstNodePort = (Integer)cluster.getSimNodeStateProvider().simGetNodeValue(firstNode, ImplicitSnitch.PORT);
-
- String secondNode;
- int secondNodePort;
- while (true) {
- secondNode = cluster.getSimClusterStateProvider().simGetRandomNode(random());
- secondNodePort = (Integer)cluster.getSimNodeStateProvider().simGetNodeValue(secondNode, ImplicitSnitch.PORT);
- if (secondNodePort != firstNodePort) break;
- }
-
- String commands = "{set-policy :{c1 : [{replica:1 , shard:'#EACH', port: '" + firstNodePort + "'}, {replica:1, shard:'#EACH', port:'" + secondNodePort + "'}]}}";
- NamedList<Object> response = solrClient.request(AutoScalingHandlerTest.createAutoScalingRequest(SolrRequest.METHOD.POST, commands));
- assertEquals("success", response.get("result"));
-
- String collectionName = "testCreateCollectionSplitShard";
- CollectionAdminRequest.createCollection(collectionName, "conf", 1, 2)
- .setPolicy("c1")
- .process(solrClient);
- CloudTestUtils.waitForState(cluster, "Timeout waiting for collection to become active", collectionName,
- CloudTestUtils.clusterShape(1, 2, false, true));
-
- DocCollection docCollection = getCollectionState(collectionName);
- List<Replica> list = docCollection.getReplicas(firstNode);
- int replicasOnNode1 = list != null ? list.size() : 0;
- list = docCollection.getReplicas(secondNode);
- int replicasOnNode2 = list != null ? list.size() : 0;
-
- assertEquals("Expected exactly one replica of collection on node with port: " + firstNodePort, 1, replicasOnNode1);
- assertEquals("Expected exactly one replica of collection on node with port: " + secondNodePort, 1, replicasOnNode2);
-
- CollectionAdminRequest.splitShard(collectionName).setShardName("shard1").process(solrClient);
-
- CloudTestUtils.waitForState(cluster, "Timed out waiting to see 6 replicas for collection: " + collectionName,
- collectionName, (liveNodes, collectionState) -> collectionState.getReplicas().size() == 6);
-
- docCollection = getCollectionState(collectionName);
- list = docCollection.getReplicas(firstNode);
- replicasOnNode1 = list != null ? list.size() : 0;
- list = docCollection.getReplicas(secondNode);
- replicasOnNode2 = list != null ? list.size() : 0;
-
- assertEquals("Expected exactly three replica of collection on node with port: " + firstNodePort, 3, replicasOnNode1);
- assertEquals("Expected exactly three replica of collection on node with port: " + secondNodePort, 3, replicasOnNode2);
- CollectionAdminRequest.deleteCollection(collectionName).process(solrClient);
-
- }
-
- public void testMetricsTag() throws Exception {
- SolrClient solrClient = cluster.simGetSolrClient();
- String setClusterPolicyCommand = "{" +
- " 'set-cluster-policy': [" +
- " {'cores':'<10', 'node':'#ANY'}," +
- " {'replica':'<2', 'shard': '#EACH', 'node': '#ANY'}," +
- " {'metrics:abc':'overseer', 'replica':0}" +
- " ]" +
- "}";
- SolrRequest req = createAutoScalingRequest(SolrRequest.METHOD.POST, setClusterPolicyCommand);
- try {
- solrClient.request(req);
- fail("expected exception");
- } catch (Exception e) {
- // expected
- assertTrue(e.toString().contains("Invalid metrics: param in"));
- }
- setClusterPolicyCommand = "{" +
- " 'set-cluster-policy': [" +
- " {'cores':'<10', 'node':'#ANY'}," +
- " {'replica':'<2', 'shard': '#EACH', 'node': '#ANY'}," +
- " {'metrics:solr.node:ADMIN./admin/authorization.clientErrors:count':'>58768765', 'replica':0}" +
- " ]" +
- "}";
- req = createAutoScalingRequest(SolrRequest.METHOD.POST, setClusterPolicyCommand);
- solrClient.request(req);
-
- //org.eclipse.jetty.server.handler.DefaultHandler.2xx-responses
- CollectionAdminRequest.createCollection("metricsTest", "conf", 1, 1)
- .process(solrClient);
- CloudTestUtils.waitForState(cluster, "Timeout waiting for collection to become active", "metricsTest",
- CloudTestUtils.clusterShape(1, 1));
-
- DocCollection collection = getCollectionState("metricsTest");
- List<String> tags = Arrays.asList("metrics:solr.node:ADMIN./admin/authorization.clientErrors:count",
- "metrics:solr.jvm:buffers.direct.Count");
- Map<String, Object> val = cluster.getNodeStateProvider().getNodeValues(collection.getReplicas().get(0).getNodeName(), tags);
- for (String tag : tags) {
- assertNotNull( "missing : "+ tag , val.get(tag));
- }
-
-
- }
-
- public void testCreateCollectionAddShardWithReplicaTypeUsingPolicy() throws Exception {
- SolrClient solrClient = cluster.simGetSolrClient();
- List<String> nodes = new ArrayList<>(cluster.getClusterStateProvider().getLiveNodes());
- String nrtNodeName = nodes.get(0);
- int nrtPort = (Integer)cluster.getSimNodeStateProvider().simGetNodeValue(nrtNodeName, ImplicitSnitch.PORT);
-
-
- String pullNodeName = nodes.get(1);
- int pullPort = (Integer)cluster.getSimNodeStateProvider().simGetNodeValue(pullNodeName, ImplicitSnitch.PORT);
-
- String tlogNodeName = nodes.get(1);
- int tlogPort = (Integer)cluster.getSimNodeStateProvider().simGetNodeValue(tlogNodeName, ImplicitSnitch.PORT);
- log.info("NRT {} PULL {} , TLOG {} ", nrtNodeName, pullNodeName, tlogNodeName);
-
- String commands = "{set-cluster-policy :[" +
- "{replica:0 , shard:'#EACH', type: NRT, port: '!" + nrtPort + "'}" +
- "{replica:0 , shard:'#EACH', type: PULL, port: '!" + pullPort + "'}" +
- "{replica:0 , shard:'#EACH', type: TLOG, port: '!" + tlogPort + "'}" +
- "]}";
-
-
- solrClient.request(AutoScalingHandlerTest.createAutoScalingRequest(SolrRequest.METHOD.POST, commands));
- Map<String, Object> json = Utils.getJson(cluster.getDistribStateManager(), ZkStateReader.SOLR_AUTOSCALING_CONF_PATH);
- assertEquals("full json:" + Utils.toJSONString(json), "!" + nrtPort,
- Utils.getObjectByPath(json, true, "cluster-policy[0]/port"));
- assertEquals("full json:" + Utils.toJSONString(json), "!" + pullPort,
- Utils.getObjectByPath(json, true, "cluster-policy[1]/port"));
- assertEquals("full json:" + Utils.toJSONString(json), "!" + tlogPort,
- Utils.getObjectByPath(json, true, "cluster-policy[2]/port"));
-
- CollectionAdminRequest.createCollectionWithImplicitRouter("policiesTest", "conf", "s1", 1, 1, 1)
- .process(solrClient);
- CloudTestUtils.waitForState(cluster, "Timeout waiting for collection to become active", "policiesTest",
- CloudTestUtils.clusterShape(1, 3, false, true));
-
- DocCollection coll = getCollectionState("policiesTest");
-
-
- BiConsumer<String, Replica> verifyReplicas = (s, replica) -> {
- switch (replica.getType()) {
- case NRT: {
- assertTrue("NRT replica should be in " + nrtNodeName, replica.getNodeName().equals(nrtNodeName));
- break;
- }
- case TLOG: {
- assertTrue("TLOG replica should be in " + tlogNodeName, replica.getNodeName().equals(tlogNodeName));
- break;
- }
- case PULL: {
- assertTrue("PULL replica should be in " + pullNodeName, replica.getNodeName().equals(pullNodeName));
- break;
- }
- }
-
- };
- coll.forEachReplica(verifyReplicas);
-
- CollectionAdminRequest.createShard("policiesTest", "s3").
- process(solrClient);
- coll = getCollectionState("policiesTest");
- assertEquals(3, coll.getSlice("s3").getReplicas().size());
- coll.forEachReplica(verifyReplicas);
- }
-
- public void testCreateCollectionAddShardUsingPolicy() throws Exception {
- SolrClient solrClient = cluster.simGetSolrClient();
- String nodeId = cluster.getSimClusterStateProvider().simGetRandomNode(random());
- int port = (Integer)cluster.getSimNodeStateProvider().simGetNodeValue(nodeId, ImplicitSnitch.PORT);
-
- String commands = "{set-policy :{c1 : [{replica:1 , shard:'#EACH', port: '" + port + "'}]}}";
- solrClient.request(AutoScalingHandlerTest.createAutoScalingRequest(SolrRequest.METHOD.POST, commands));
- Map<String, Object> json = Utils.getJson(cluster.getDistribStateManager(), ZkStateReader.SOLR_AUTOSCALING_CONF_PATH);
- assertEquals("full json:"+ Utils.toJSONString(json) , "#EACH",
- Utils.getObjectByPath(json, true, "/policies/c1[0]/shard"));
- CollectionAdminRequest.createCollectionWithImplicitRouter("policiesTest", "conf", "s1,s2", 1)
- .setPolicy("c1")
- .process(solrClient);
- CloudTestUtils.waitForState(cluster, "Timeout waiting for collection to become active", "policiesTest",
- CloudTestUtils.clusterShape(2, 1));
-
- DocCollection coll = getCollectionState("policiesTest");
- assertEquals("c1", coll.getPolicyName());
- assertEquals(2,coll.getReplicas().size());
- coll.forEachReplica((s, replica) -> assertEquals(nodeId, replica.getNodeName()));
- CollectionAdminRequest.createShard("policiesTest", "s3").process(solrClient);
- CloudTestUtils.waitForState(cluster, "Timeout waiting for collection to become active", "policiesTest",
- CloudTestUtils.clusterShape(3, 1));
-
- coll = getCollectionState("policiesTest");
- assertEquals(1, coll.getSlice("s3").getReplicas().size());
- coll.getSlice("s3").forEach(replica -> assertEquals(nodeId, replica.getNodeName()));
- }
-
- public void testDataProvider() throws IOException, SolrServerException, KeeperException, InterruptedException {
- SolrClient solrClient = cluster.simGetSolrClient();
- CollectionAdminRequest.createCollectionWithImplicitRouter("policiesTest", "conf", "shard1", 2)
- .process(solrClient);
- CloudTestUtils.waitForState(cluster, "Timeout waiting for collection to become active", "policiesTest",
- CloudTestUtils.clusterShape(1, 2, false, true));
- DocCollection rulesCollection = getCollectionState("policiesTest");
-
- Map<String, Object> val = cluster.getNodeStateProvider().getNodeValues(rulesCollection.getReplicas().get(0).getNodeName(), Arrays.asList(
- "freedisk",
- "cores",
- "heapUsage",
- "sysLoadAvg"));
- assertNotNull(val.get("freedisk"));
- assertNotNull(val.get("heapUsage"));
- assertNotNull(val.get("sysLoadAvg"));
- assertTrue(((Number) val.get("cores")).intValue() > 0);
- assertTrue("freedisk value is " + ((Number) val.get("freedisk")).doubleValue(), Double.compare(((Number) val.get("freedisk")).doubleValue(), 0.0d) > 0);
- assertTrue("heapUsage value is " + ((Number) val.get("heapUsage")).doubleValue(), Double.compare(((Number) val.get("heapUsage")).doubleValue(), 0.0d) > 0);
- if (!Constants.WINDOWS) {
- // the system load average metrics is not available on windows platform
- assertTrue("sysLoadAvg value is " + ((Number) val.get("sysLoadAvg")).doubleValue(), Double.compare(((Number) val.get("sysLoadAvg")).doubleValue(), 0.0d) > 0);
- }
- // simulator doesn't have Overseer, so just pick a random node
- String overseerNode = cluster.getSimClusterStateProvider().simGetRandomNode(random());
- solrClient.request(CollectionAdminRequest.addRole(overseerNode, "overseer"));
- for (int i = 0; i < 10; i++) {
- Map<String, Object> data = Utils.getJson(cluster.getDistribStateManager(), ZkStateReader.ROLES);
- if (i >= 9 && data.isEmpty()) {
- throw new RuntimeException("NO overseer node created");
- }
- cluster.getTimeSource().sleep(100);
- }
- val = cluster.getNodeStateProvider().getNodeValues(overseerNode, Arrays.asList(
- "nodeRole",
- "ip_1", "ip_2", "ip_3", "ip_4",
- "sysprop.java.version",
- "sysprop.java.vendor"));
- assertEquals("overseer", val.get("nodeRole"));
- assertNotNull(val.get("ip_1"));
- assertNotNull(val.get("ip_2"));
- assertNotNull(val.get("ip_3"));
- assertNotNull(val.get("ip_4"));
- assertNotNull(val.get("sysprop.java.version"));
- assertNotNull(val.get("sysprop.java.vendor"));
- }
-}
[3/6] lucene-solr:branch_7x: SOLR-12669: Rename tests that use the
autoscaling simulation framework.
Posted by ab...@apache.org.
http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/b949f57f/solr/core/src/test/org/apache/solr/cloud/autoscaling/sim/TestSimNodeAddedTrigger.java
----------------------------------------------------------------------
diff --git a/solr/core/src/test/org/apache/solr/cloud/autoscaling/sim/TestSimNodeAddedTrigger.java b/solr/core/src/test/org/apache/solr/cloud/autoscaling/sim/TestSimNodeAddedTrigger.java
new file mode 100644
index 0000000..c9e506c
--- /dev/null
+++ b/solr/core/src/test/org/apache/solr/cloud/autoscaling/sim/TestSimNodeAddedTrigger.java
@@ -0,0 +1,327 @@
+/*
+ * 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.solr.cloud.autoscaling.sim;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicReference;
+
+import org.apache.solr.client.solrj.cloud.SolrCloudManager;
+import org.apache.solr.cloud.autoscaling.ActionContext;
+import org.apache.solr.cloud.autoscaling.AutoScaling;
+import org.apache.solr.cloud.autoscaling.NodeAddedTrigger;
+import org.apache.solr.cloud.autoscaling.TriggerAction;
+import org.apache.solr.cloud.autoscaling.TriggerEvent;
+import org.apache.solr.cloud.autoscaling.TriggerValidationException;
+import org.apache.solr.common.util.TimeSource;
+import org.apache.solr.core.SolrResourceLoader;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+/**
+ * Test for {@link NodeAddedTrigger}
+ */
+public class TestSimNodeAddedTrigger extends SimSolrCloudTestCase {
+ private static AtomicBoolean actionConstructorCalled = new AtomicBoolean(false);
+ private static AtomicBoolean actionInitCalled = new AtomicBoolean(false);
+ private static AtomicBoolean actionCloseCalled = new AtomicBoolean(false);
+
+ private AutoScaling.TriggerEventProcessor noFirstRunProcessor = event -> {
+ fail("Did not expect the listener to fire on first run!");
+ return true;
+ };
+
+ private static int SPEED = 50;
+
+ // currentTimeMillis is not as precise so to avoid false positives while comparing time of fire, we add some delta
+ private static final long WAIT_FOR_DELTA_NANOS = TimeUnit.MILLISECONDS.toNanos(2);
+
+ private static TimeSource timeSource;
+
+ @BeforeClass
+ public static void setupCluster() throws Exception {
+ configureCluster(1, TimeSource.get("simTime:" + SPEED));
+ timeSource = cluster.getTimeSource();
+ }
+
+ @Before
+ public void beforeTest() throws Exception {
+ actionConstructorCalled = new AtomicBoolean(false);
+ actionInitCalled = new AtomicBoolean(false);
+ actionCloseCalled = new AtomicBoolean(false);
+ }
+
+ @Test
+ public void testTrigger() throws Exception {
+ long waitForSeconds = 1 + random().nextInt(5);
+ Map<String, Object> props = createTriggerProps(waitForSeconds);
+
+ try (NodeAddedTrigger trigger = new NodeAddedTrigger("node_added_trigger")) {
+ trigger.configure(cluster.getLoader(), cluster, props);
+ trigger.init();
+ trigger.setProcessor(noFirstRunProcessor);
+ trigger.run();
+
+ String newNode1 = cluster.simAddNode();
+ String newNode2 = cluster.simAddNode();
+ AtomicBoolean fired = new AtomicBoolean(false);
+ AtomicReference<TriggerEvent> eventRef = new AtomicReference<>();
+ trigger.setProcessor(event -> {
+ if (fired.compareAndSet(false, true)) {
+ eventRef.set(event);
+ long currentTimeNanos = timeSource.getTimeNs();
+ long eventTimeNanos = event.getEventTime();
+ long waitForNanos = TimeUnit.NANOSECONDS.convert(waitForSeconds, TimeUnit.SECONDS) - WAIT_FOR_DELTA_NANOS;
+ if (currentTimeNanos - eventTimeNanos <= waitForNanos) {
+ fail("NodeAddedListener was fired before the configured waitFor period: currentTimeNanos=" + currentTimeNanos + ", eventTimeNanos=" + eventTimeNanos + ",waitForNanos=" + waitForNanos);
+ }
+ } else {
+ fail("NodeAddedTrigger was fired more than once!");
+ }
+ return true;
+ });
+ int counter = 0;
+ do {
+ trigger.run();
+ timeSource.sleep(1000);
+ if (counter++ > 10) {
+ fail("Newly added node was not discovered by trigger even after 10 seconds");
+ }
+ } while (!fired.get());
+
+ TriggerEvent nodeAddedEvent = eventRef.get();
+ assertNotNull(nodeAddedEvent);
+ List<String> nodeNames = (List<String>)nodeAddedEvent.getProperty(TriggerEvent.NODE_NAMES);
+ assertTrue(nodeNames.contains(newNode1));
+ assertTrue(nodeNames.contains(newNode2));
+ }
+
+ // add a new node but remove it before the waitFor period expires
+ // and assert that the trigger doesn't fire at all
+ try (NodeAddedTrigger trigger = new NodeAddedTrigger("node_added_trigger")) {
+ trigger.configure(cluster.getLoader(), cluster, props);
+ trigger.init();
+ final long waitTime = 2;
+ props.put("waitFor", waitTime);
+ trigger.setProcessor(noFirstRunProcessor);
+ trigger.run();
+
+ String newNode = cluster.simAddNode();
+ AtomicBoolean fired = new AtomicBoolean(false);
+ trigger.setProcessor(event -> {
+ if (fired.compareAndSet(false, true)) {
+ long currentTimeNanos = timeSource.getTimeNs();
+ long eventTimeNanos = event.getEventTime();
+ long waitForNanos = TimeUnit.NANOSECONDS.convert(waitForSeconds, TimeUnit.SECONDS) - WAIT_FOR_DELTA_NANOS;
+ if (currentTimeNanos - eventTimeNanos <= waitForNanos) {
+ fail("NodeAddedListener was fired before the configured waitFor period: currentTimeNanos=" + currentTimeNanos + ", eventTimeNanos=" + eventTimeNanos + ",waitForNanos=" + waitForNanos);
+ }
+ } else {
+ fail("NodeAddedTrigger was fired more than once!");
+ }
+ return true;
+ });
+ trigger.run(); // first run should detect the new node
+ cluster.simRemoveNode(newNode, false);
+ int counter = 0;
+ do {
+ trigger.run();
+ timeSource.sleep(1000);
+ if (counter++ > waitTime + 1) { // run it a little more than the wait time
+ break;
+ }
+ } while (true);
+
+ // ensure the event was not fired
+ assertFalse(fired.get());
+ }
+ }
+
+ public void testActionLifecycle() throws Exception {
+ Map<String, Object> props = createTriggerProps(0);
+ List<Map<String, String>> actions = (List<Map<String, String>>) props.get("actions");
+ Map<String, String> action = new HashMap<>(2);
+ action.put("name", "testActionInit");
+ action.put("class", TestSimNodeAddedTrigger.AssertInitTriggerAction.class.getName());
+ actions.add(action);
+ try (NodeAddedTrigger trigger = new NodeAddedTrigger("node_added_trigger")) {
+ trigger.configure(cluster.getLoader(), cluster, props);
+ assertEquals(true, actionConstructorCalled.get());
+ assertEquals(false, actionInitCalled.get());
+ assertEquals(false, actionCloseCalled.get());
+ trigger.init();
+ assertEquals(true, actionInitCalled.get());
+ assertEquals(false, actionCloseCalled.get());
+ }
+ assertEquals(true, actionCloseCalled.get());
+ }
+
+ public static class AssertInitTriggerAction implements TriggerAction {
+ public AssertInitTriggerAction() {
+ actionConstructorCalled.set(true);
+ }
+
+ @Override
+ public void configure(SolrResourceLoader loader, SolrCloudManager cloudManager, Map<String, Object> properties) throws TriggerValidationException {
+
+ }
+
+ @Override
+ public void init() {
+ actionInitCalled.compareAndSet(false, true);
+ }
+
+ @Override
+ public String getName() {
+ return "";
+ }
+
+ @Override
+ public void process(TriggerEvent event, ActionContext actionContext) {
+
+ }
+
+ @Override
+ public void close() throws IOException {
+ actionCloseCalled.compareAndSet(false, true);
+ }
+ }
+
+ @Test
+ public void testListenerAcceptance() throws Exception {
+ Map<String, Object> props = createTriggerProps(0);
+ try (NodeAddedTrigger trigger = new NodeAddedTrigger("node_added_trigger")) {
+ trigger.configure(cluster.getLoader(), cluster, props);
+ trigger.init();
+ trigger.setProcessor(noFirstRunProcessor);
+ trigger.run(); // starts tracking live nodes
+
+ String newNode = cluster.simAddNode();
+ AtomicInteger callCount = new AtomicInteger(0);
+ AtomicBoolean fired = new AtomicBoolean(false);
+
+ trigger.setProcessor(event -> {
+ if (callCount.incrementAndGet() < 2) {
+ return false;
+ } else {
+ fired.compareAndSet(false, true);
+ return true;
+ }
+ });
+
+ trigger.run(); // first run should detect the new node and fire immediately but listener isn't ready
+ assertEquals(1, callCount.get());
+ assertFalse(fired.get());
+ trigger.run(); // second run should again fire
+ assertEquals(2, callCount.get());
+ assertTrue(fired.get());
+ trigger.run(); // should not fire
+ assertEquals(2, callCount.get());
+ }
+ }
+
+ @Test
+ public void testRestoreState() throws Exception {
+ long waitForSeconds = 1 + random().nextInt(5);
+ Map<String, Object> props = createTriggerProps(waitForSeconds);
+
+ // add a new node but update the trigger before the waitFor period expires
+ // and assert that the new trigger still fires
+ NodeAddedTrigger trigger = new NodeAddedTrigger("node_added_trigger");
+ trigger.configure(cluster.getLoader(), cluster, props);
+ trigger.init();
+ trigger.setProcessor(noFirstRunProcessor);
+ trigger.run();
+
+ String newNode = cluster.simAddNode();
+ trigger.run(); // this run should detect the new node
+ trigger.close(); // close the old trigger
+
+ try (NodeAddedTrigger newTrigger = new NodeAddedTrigger("some_different_name")) {
+ newTrigger.configure(cluster.getLoader(), cluster, props);
+ trigger.init();
+ try {
+ newTrigger.restoreState(trigger);
+ fail("Trigger should only be able to restore state from an old trigger of the same name");
+ } catch (AssertionError e) {
+ // expected
+ }
+ }
+
+ try (NodeAddedTrigger newTrigger = new NodeAddedTrigger("node_added_trigger")) {
+ newTrigger.configure(cluster.getLoader(), cluster, props);
+ newTrigger.init();
+ AtomicBoolean fired = new AtomicBoolean(false);
+ AtomicReference<TriggerEvent> eventRef = new AtomicReference<>();
+ newTrigger.setProcessor(event -> {
+ if (fired.compareAndSet(false, true)) {
+ eventRef.set(event);
+ long currentTimeNanos = timeSource.getTimeNs();
+ long eventTimeNanos = event.getEventTime();
+ long waitForNanos = TimeUnit.NANOSECONDS.convert(waitForSeconds, TimeUnit.SECONDS) - WAIT_FOR_DELTA_NANOS;
+ if (currentTimeNanos - eventTimeNanos <= waitForNanos) {
+ fail("NodeAddedListener was fired before the configured waitFor period: currentTimeNanos=" + currentTimeNanos + ", eventTimeNanos=" + eventTimeNanos + ",waitForNanos=" + waitForNanos);
+ }
+ } else {
+ fail("NodeAddedTrigger was fired more than once!");
+ }
+ return true;
+ });
+ newTrigger.restoreState(trigger); // restore state from the old trigger
+ int counter = 0;
+ do {
+ newTrigger.run();
+ timeSource.sleep(1000);
+ if (counter++ > 10) {
+ fail("Newly added node was not discovered by trigger even after 10 seconds");
+ }
+ } while (!fired.get());
+
+ // ensure the event was fired
+ assertTrue(fired.get());
+ TriggerEvent nodeAddedEvent = eventRef.get();
+ assertNotNull(nodeAddedEvent);
+ //TODO assertEquals("", newNode.getNodeName(), nodeAddedEvent.getProperty(NodeAddedTrigger.NodeAddedEvent.NODE_NAME));
+ }
+ }
+
+ private Map<String, Object> createTriggerProps(long waitForSeconds) {
+ Map<String, Object> props = new HashMap<>();
+ props.put("event", "nodeLost");
+ props.put("waitFor", waitForSeconds);
+ props.put("enabled", true);
+ List<Map<String, String>> actions = new ArrayList<>(3);
+ Map<String, String> map = new HashMap<>(2);
+ map.put("name", "compute_plan");
+ map.put("class", "solr.ComputePlanAction");
+ actions.add(map);
+ map = new HashMap<>(2);
+ map.put("name", "execute_plan");
+ map.put("class", "solr.ExecutePlanAction");
+ actions.add(map);
+ props.put("actions", actions);
+ return props;
+ }
+}
http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/b949f57f/solr/core/src/test/org/apache/solr/cloud/autoscaling/sim/TestSimNodeLostTrigger.java
----------------------------------------------------------------------
diff --git a/solr/core/src/test/org/apache/solr/cloud/autoscaling/sim/TestSimNodeLostTrigger.java b/solr/core/src/test/org/apache/solr/cloud/autoscaling/sim/TestSimNodeLostTrigger.java
new file mode 100644
index 0000000..4ad0623
--- /dev/null
+++ b/solr/core/src/test/org/apache/solr/cloud/autoscaling/sim/TestSimNodeLostTrigger.java
@@ -0,0 +1,346 @@
+/*
+ * 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.solr.cloud.autoscaling.sim;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicReference;
+
+import org.apache.solr.client.solrj.cloud.SolrCloudManager;
+import org.apache.solr.cloud.autoscaling.ActionContext;
+import org.apache.solr.cloud.autoscaling.AutoScaling;
+import org.apache.solr.cloud.autoscaling.NodeLostTrigger;
+import org.apache.solr.cloud.autoscaling.TriggerAction;
+import org.apache.solr.cloud.autoscaling.TriggerEvent;
+import org.apache.solr.cloud.autoscaling.TriggerValidationException;
+import org.apache.solr.common.util.TimeSource;
+import org.apache.solr.core.SolrResourceLoader;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+/**
+ * Test for {@link NodeLostTrigger}
+ */
+public class TestSimNodeLostTrigger extends SimSolrCloudTestCase {
+ private static AtomicBoolean actionConstructorCalled = new AtomicBoolean(false);
+ private static AtomicBoolean actionInitCalled = new AtomicBoolean(false);
+ private static AtomicBoolean actionCloseCalled = new AtomicBoolean(false);
+
+ private AutoScaling.TriggerEventProcessor noFirstRunProcessor = event -> {
+ fail("Did not expect the listener to fire on first run!");
+ return true;
+ };
+
+ private static final int SPEED = 50;
+ // use the same time source as the trigger
+ private static TimeSource timeSource;
+ // currentTimeMillis is not as precise so to avoid false positives while comparing time of fire, we add some delta
+ private static final long WAIT_FOR_DELTA_NANOS = TimeUnit.MILLISECONDS.toNanos(5);
+
+ @BeforeClass
+ public static void setupCluster() throws Exception {
+ configureCluster(5, TimeSource.get("simTime:" + SPEED));
+ timeSource = cluster.getTimeSource();
+ }
+
+ @Before
+ public void beforeTest() throws Exception {
+ actionConstructorCalled = new AtomicBoolean(false);
+ actionInitCalled = new AtomicBoolean(false);
+ actionCloseCalled = new AtomicBoolean(false);
+ }
+
+ @Test
+ public void testTrigger() throws Exception {
+ long waitForSeconds = 1 + random().nextInt(5);
+ Map<String, Object> props = createTriggerProps(waitForSeconds);
+
+ try (NodeLostTrigger trigger = new NodeLostTrigger("node_lost_trigger")) {
+ trigger.configure(cluster.getLoader(), cluster, props);
+ trigger.setProcessor(noFirstRunProcessor);
+ trigger.run();
+ Iterator<String> it = cluster.getLiveNodesSet().get().iterator();
+ String lostNodeName1 = it.next();
+ String lostNodeName2 = it.next();
+ cluster.simRemoveNode(lostNodeName1, false);
+ cluster.simRemoveNode(lostNodeName2, false);
+ timeSource.sleep(1000);
+
+ AtomicBoolean fired = new AtomicBoolean(false);
+ AtomicReference<TriggerEvent> eventRef = new AtomicReference<>();
+ trigger.setProcessor(event -> {
+ if (fired.compareAndSet(false, true)) {
+ eventRef.set(event);
+ long currentTimeNanos = timeSource.getTimeNs();
+ long eventTimeNanos = event.getEventTime();
+ long waitForNanos = TimeUnit.NANOSECONDS.convert(waitForSeconds, TimeUnit.SECONDS) - WAIT_FOR_DELTA_NANOS;
+ if (currentTimeNanos - eventTimeNanos <= waitForNanos) {
+ fail("NodeLostListener was fired before the configured waitFor period: currentTimeNanos=" + currentTimeNanos + ", eventTimeNanos=" + eventTimeNanos + ",waitForNanos=" + waitForNanos);
+ }
+ } else {
+ fail("NodeLostListener was fired more than once!");
+ }
+ return true;
+ });
+ int counter = 0;
+ do {
+ trigger.run();
+ timeSource.sleep(1000);
+ if (counter++ > 10) {
+ fail("Lost node was not discovered by trigger even after 10 seconds");
+ }
+ } while (!fired.get());
+
+ TriggerEvent nodeLostEvent = eventRef.get();
+ assertNotNull(nodeLostEvent);
+ List<String> nodeNames = (List<String>)nodeLostEvent.getProperty(TriggerEvent.NODE_NAMES);
+ assertTrue(nodeNames + " doesn't contain " + lostNodeName1, nodeNames.contains(lostNodeName1));
+ assertTrue(nodeNames + " doesn't contain " + lostNodeName2, nodeNames.contains(lostNodeName2));
+
+ }
+
+ // remove a node but add it back before the waitFor period expires
+ // and assert that the trigger doesn't fire at all
+ try (NodeLostTrigger trigger = new NodeLostTrigger("node_lost_trigger")) {
+ trigger.configure(cluster.getLoader(), cluster, props);
+ final long waitTime = 2;
+ props.put("waitFor", waitTime);
+ trigger.setProcessor(noFirstRunProcessor);
+ trigger.run();
+
+ String lostNode = cluster.getSimClusterStateProvider().simGetRandomNode(random());
+ cluster.simRemoveNode(lostNode, false);
+ AtomicBoolean fired = new AtomicBoolean(false);
+ trigger.setProcessor(event -> {
+ if (fired.compareAndSet(false, true)) {
+ long currentTimeNanos = timeSource.getTimeNs();
+ long eventTimeNanos = event.getEventTime();
+ long waitForNanos = TimeUnit.NANOSECONDS.convert(waitTime, TimeUnit.SECONDS) - WAIT_FOR_DELTA_NANOS;
+ if (currentTimeNanos - eventTimeNanos <= waitForNanos) {
+ fail("NodeLostListener was fired before the configured waitFor period: currentTimeNanos=" + currentTimeNanos + ", eventTimeNanos=" + eventTimeNanos + ",waitForNanos=" + waitForNanos);
+ }
+ } else {
+ fail("NodeLostListener was fired more than once!");
+ }
+ return true;
+ });
+ trigger.run(); // first run should detect the lost node
+ int counter = 0;
+ do {
+ if (cluster.getLiveNodesSet().get().size() == 2) {
+ break;
+ }
+ timeSource.sleep(100);
+ if (counter++ > 20) {
+ fail("Live nodes not updated!");
+ }
+ } while (true);
+ counter = 0;
+ cluster.getSimClusterStateProvider().simRestoreNode(lostNode);
+ do {
+ trigger.run();
+ timeSource.sleep(1000);
+ if (counter++ > waitTime + 1) { // run it a little more than the wait time
+ break;
+ }
+ } while (true);
+
+ // ensure the event was not fired
+ assertFalse(fired.get());
+ }
+ }
+
+ public void testActionLifecycle() throws Exception {
+ Map<String, Object> props = createTriggerProps(0);
+ List<Map<String, String>> actions = (List<Map<String, String>>) props.get("actions");
+ Map<String, String> action = new HashMap<>(2);
+ action.put("name", "testActionInit");
+ action.put("class", AssertInitTriggerAction.class.getName());
+ actions.add(action);
+ try (NodeLostTrigger trigger = new NodeLostTrigger("node_added_trigger")) {
+ trigger.configure(cluster.getLoader(), cluster, props);
+ assertEquals(true, actionConstructorCalled.get());
+ assertEquals(false, actionInitCalled.get());
+ assertEquals(false, actionCloseCalled.get());
+ trigger.init();
+ assertEquals(true, actionInitCalled.get());
+ assertEquals(false, actionCloseCalled.get());
+ }
+ assertEquals(true, actionCloseCalled.get());
+ }
+
+ public static class AssertInitTriggerAction implements TriggerAction {
+ public AssertInitTriggerAction() {
+ actionConstructorCalled.set(true);
+ }
+
+ @Override
+ public void configure(SolrResourceLoader loader, SolrCloudManager cloudManager, Map<String, Object> properties) throws TriggerValidationException {
+
+ }
+
+ @Override
+ public void init() {
+ actionInitCalled.compareAndSet(false, true);
+ }
+
+ @Override
+ public String getName() {
+ return "";
+ }
+
+ @Override
+ public void process(TriggerEvent event, ActionContext actionContext) {
+
+ }
+
+ @Override
+ public void close() throws IOException {
+ actionCloseCalled.compareAndSet(false, true);
+ }
+ }
+
+ @Test
+ public void testListenerAcceptance() throws Exception {
+ Map<String, Object> props = createTriggerProps(0);
+ try (NodeLostTrigger trigger = new NodeLostTrigger("node_added_trigger")) {
+ trigger.configure(cluster.getLoader(), cluster, props);
+ trigger.setProcessor(noFirstRunProcessor);
+
+ String newNode = cluster.simAddNode();
+
+ trigger.run(); // starts tracking live nodes
+
+ // stop the newly created node
+ cluster.simRemoveNode(newNode, false);
+
+ AtomicInteger callCount = new AtomicInteger(0);
+ AtomicBoolean fired = new AtomicBoolean(false);
+
+ trigger.setProcessor(event -> {
+ if (callCount.incrementAndGet() < 2) {
+ return false;
+ } else {
+ fired.compareAndSet(false, true);
+ return true;
+ }
+ });
+
+ trigger.run(); // first run should detect the lost node and fire immediately but listener isn't ready
+ assertEquals(1, callCount.get());
+ assertFalse(fired.get());
+ trigger.run(); // second run should again fire
+ assertEquals(2, callCount.get());
+ assertTrue(fired.get());
+ trigger.run(); // should not fire
+ assertEquals(2, callCount.get());
+ }
+ }
+
+ @Test
+ public void testRestoreState() throws Exception {
+ long waitForSeconds = 1 + random().nextInt(5);
+ Map<String, Object> props = createTriggerProps(waitForSeconds);
+
+ String newNode = cluster.simAddNode();
+
+ // remove a node but update the trigger before the waitFor period expires
+ // and assert that the new trigger still fires
+
+ NodeLostTrigger trigger = new NodeLostTrigger("node_lost_trigger");
+ trigger.configure(cluster.getLoader(), cluster, props);
+ trigger.setProcessor(noFirstRunProcessor);
+ trigger.run();
+
+ // stop the newly created node
+ cluster.simRemoveNode(newNode, false);
+
+ trigger.run(); // this run should detect the lost node
+ trigger.close(); // close the old trigger
+
+ try (NodeLostTrigger newTrigger = new NodeLostTrigger("some_different_name")) {
+ newTrigger.configure(cluster.getLoader(), cluster, props);
+ try {
+ newTrigger.restoreState(trigger);
+ fail("Trigger should only be able to restore state from an old trigger of the same name");
+ } catch (AssertionError e) {
+ // expected
+ }
+ }
+
+ try (NodeLostTrigger newTrigger = new NodeLostTrigger("node_lost_trigger")) {
+ newTrigger.configure(cluster.getLoader(), cluster, props);
+ AtomicBoolean fired = new AtomicBoolean(false);
+ AtomicReference<TriggerEvent> eventRef = new AtomicReference<>();
+ newTrigger.setProcessor(event -> {
+ if (fired.compareAndSet(false, true)) {
+ eventRef.set(event);
+ long currentTimeNanos = timeSource.getTimeNs();
+ long eventTimeNanos = event.getEventTime();
+ long waitForNanos = TimeUnit.NANOSECONDS.convert(waitForSeconds, TimeUnit.SECONDS) - WAIT_FOR_DELTA_NANOS;
+ if (currentTimeNanos - eventTimeNanos <= waitForNanos) {
+ fail("NodeLostListener was fired before the configured waitFor period: currentTimeNanos=" + currentTimeNanos + ", eventTimeNanos=" + eventTimeNanos + ",waitForNanos=" + waitForNanos);
+ }
+ } else {
+ fail("NodeLostListener was fired more than once!");
+ }
+ return true;
+ });
+ newTrigger.restoreState(trigger); // restore state from the old trigger
+ int counter = 0;
+ do {
+ newTrigger.run();
+ timeSource.sleep(1000);
+ if (counter++ > 10) {
+ fail("Lost node was not discovered by trigger even after 10 seconds");
+ }
+ } while (!fired.get());
+
+ TriggerEvent nodeLostEvent = eventRef.get();
+ assertNotNull(nodeLostEvent);
+ List<String> nodeNames = (List<String>)nodeLostEvent.getProperty(TriggerEvent.NODE_NAMES);
+ assertTrue(nodeNames.contains(newNode));
+ }
+ }
+
+ private Map<String, Object> createTriggerProps(long waitForSeconds) {
+ Map<String, Object> props = new HashMap<>();
+ props.put("event", "nodeLost");
+ props.put("waitFor", waitForSeconds);
+ props.put("enabled", true);
+ List<Map<String, String>> actions = new ArrayList<>(3);
+ Map<String, String> map = new HashMap<>(2);
+ map.put("name", "compute_plan");
+ map.put("class", "solr.ComputePlanAction");
+ actions.add(map);
+ map = new HashMap<>(2);
+ map.put("name", "execute_plan");
+ map.put("class", "solr.ExecutePlanAction");
+ actions.add(map);
+ props.put("actions", actions);
+ return props;
+ }
+}
http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/b949f57f/solr/core/src/test/org/apache/solr/cloud/autoscaling/sim/TestSimPolicyCloud.java
----------------------------------------------------------------------
diff --git a/solr/core/src/test/org/apache/solr/cloud/autoscaling/sim/TestSimPolicyCloud.java b/solr/core/src/test/org/apache/solr/cloud/autoscaling/sim/TestSimPolicyCloud.java
new file mode 100644
index 0000000..c964e44
--- /dev/null
+++ b/solr/core/src/test/org/apache/solr/cloud/autoscaling/sim/TestSimPolicyCloud.java
@@ -0,0 +1,368 @@
+/*
+ * 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.solr.cloud.autoscaling.sim;
+
+import java.io.IOException;
+import java.lang.invoke.MethodHandles;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.function.BiConsumer;
+
+import org.apache.lucene.util.Constants;
+import org.apache.solr.client.solrj.SolrClient;
+import org.apache.solr.client.solrj.SolrRequest;
+import org.apache.solr.client.solrj.SolrServerException;
+import org.apache.solr.client.solrj.cloud.autoscaling.AutoScalingConfig;
+import org.apache.solr.client.solrj.cloud.autoscaling.Policy;
+import org.apache.solr.client.solrj.cloud.autoscaling.ReplicaInfo;
+import org.apache.solr.client.solrj.cloud.autoscaling.Row;
+import org.apache.solr.client.solrj.cloud.autoscaling.Variable.Type;
+import org.apache.solr.client.solrj.request.CollectionAdminRequest;
+import org.apache.solr.cloud.CloudTestUtils;
+import org.apache.solr.cloud.autoscaling.AutoScalingHandlerTest;
+import org.apache.solr.common.cloud.DocCollection;
+import org.apache.solr.common.cloud.Replica;
+import org.apache.solr.common.cloud.ZkStateReader;
+import org.apache.solr.common.cloud.rule.ImplicitSnitch;
+import org.apache.solr.common.util.NamedList;
+import org.apache.solr.common.util.TimeSource;
+import org.apache.solr.common.util.Utils;
+import org.apache.solr.util.LogLevel;
+import org.apache.zookeeper.KeeperException;
+import org.junit.BeforeClass;
+import org.junit.rules.ExpectedException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import static org.apache.solr.cloud.autoscaling.AutoScalingHandlerTest.createAutoScalingRequest;
+
+@LogLevel("org.apache.solr.cloud.autoscaling=DEBUG")
+public class TestSimPolicyCloud extends SimSolrCloudTestCase {
+
+ private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
+ @org.junit.Rule
+ public ExpectedException expectedException = ExpectedException.none();
+
+ @BeforeClass
+ public static void setupCluster() throws Exception {
+ configureCluster(5, TimeSource.get("simTime:50"));
+ }
+
+ public void testDataProviderPerReplicaDetails() throws Exception {
+ SolrClient solrClient = cluster.simGetSolrClient();
+ CollectionAdminRequest.createCollection("perReplicaDataColl", "conf", 1, 5)
+ .process(solrClient);
+
+ CloudTestUtils.waitForState(cluster, "Timeout waiting for collection to become active", "perReplicaDataColl",
+ CloudTestUtils.clusterShape(1, 5, false, true));
+ DocCollection coll = getCollectionState("perReplicaDataColl");
+ String autoScaleJson = "{" +
+ " 'cluster-preferences': [" +
+ " { maximize : freedisk , precision: 50}," +
+ " { minimize : cores, precision: 2}" +
+ " ]," +
+ " 'cluster-policy': [" +
+ " { replica : '0' , 'nodeRole': 'overseer'}," +
+ " { 'replica': '<2', 'shard': '#ANY', 'node': '#ANY'" +
+ " }" +
+ " ]," +
+ " 'policies': {" +
+ " 'policy1': [" +
+ " { 'replica': '<2', 'shard': '#EACH', 'node': '#ANY'}," +
+ " { 'replica': '<2', 'shard': '#EACH', 'sysprop.rack': 'rack1'}" +
+ " ]" +
+ " }" +
+ "}";
+ AutoScalingConfig config = new AutoScalingConfig((Map<String, Object>) Utils.fromJSONString(autoScaleJson));
+ Policy.Session session = config.getPolicy().createSession(cluster);
+
+ AtomicInteger count = new AtomicInteger(0);
+ for (Row row : session.getSortedNodes()) {
+ row.collectionVsShardVsReplicas.forEach((c, shardVsReplicas) -> shardVsReplicas.forEach((s, replicaInfos) -> {
+ for (ReplicaInfo replicaInfo : replicaInfos) {
+ if (replicaInfo.getVariables().containsKey(Type.CORE_IDX.tagName)) count.incrementAndGet();
+ }
+ }));
+ }
+ assertTrue(count.get() > 0);
+
+ CollectionAdminRequest.deleteCollection("perReplicaDataColl").process(solrClient);
+
+ }
+
+ public void testCreateCollectionAddReplica() throws Exception {
+ SolrClient solrClient = cluster.simGetSolrClient();
+ String nodeId = cluster.getSimClusterStateProvider().simGetRandomNode(random());
+
+ int port = (Integer)cluster.getSimNodeStateProvider().simGetNodeValue(nodeId, ImplicitSnitch.PORT);
+
+ String commands = "{set-policy :{c1 : [{replica:0 , shard:'#EACH', port: '!" + port + "'}]}}";
+ solrClient.request(AutoScalingHandlerTest.createAutoScalingRequest(SolrRequest.METHOD.POST, commands));
+
+ String collectionName = "testCreateCollectionAddReplica";
+ CollectionAdminRequest.createCollection(collectionName, "conf", 1, 1)
+ .setPolicy("c1")
+ .process(solrClient);
+ CloudTestUtils.waitForState(cluster, "Timeout waiting for collection to become active", collectionName,
+ CloudTestUtils.clusterShape(1, 1, false, true));
+
+ getCollectionState(collectionName).forEachReplica((s, replica) -> assertEquals(nodeId, replica.getNodeName()));
+
+ CollectionAdminRequest.addReplicaToShard(collectionName, "shard1").process(solrClient);
+ CloudTestUtils.waitForState(cluster, "Timed out waiting to see 2 replicas for collection: " + collectionName,
+ collectionName, (liveNodes, collectionState) -> collectionState.getReplicas().size() == 2);
+
+ getCollectionState(collectionName).forEachReplica((s, replica) -> assertEquals(nodeId, replica.getNodeName()));
+ }
+
+ public void testCreateCollectionSplitShard() throws Exception {
+ SolrClient solrClient = cluster.simGetSolrClient();
+ String firstNode = cluster.getSimClusterStateProvider().simGetRandomNode(random());
+ int firstNodePort = (Integer)cluster.getSimNodeStateProvider().simGetNodeValue(firstNode, ImplicitSnitch.PORT);
+
+ String secondNode;
+ int secondNodePort;
+ while (true) {
+ secondNode = cluster.getSimClusterStateProvider().simGetRandomNode(random());
+ secondNodePort = (Integer)cluster.getSimNodeStateProvider().simGetNodeValue(secondNode, ImplicitSnitch.PORT);
+ if (secondNodePort != firstNodePort) break;
+ }
+
+ String commands = "{set-policy :{c1 : [{replica:1 , shard:'#EACH', port: '" + firstNodePort + "'}, {replica:1, shard:'#EACH', port:'" + secondNodePort + "'}]}}";
+ NamedList<Object> response = solrClient.request(AutoScalingHandlerTest.createAutoScalingRequest(SolrRequest.METHOD.POST, commands));
+ assertEquals("success", response.get("result"));
+
+ String collectionName = "testCreateCollectionSplitShard";
+ CollectionAdminRequest.createCollection(collectionName, "conf", 1, 2)
+ .setPolicy("c1")
+ .process(solrClient);
+ CloudTestUtils.waitForState(cluster, "Timeout waiting for collection to become active", collectionName,
+ CloudTestUtils.clusterShape(1, 2, false, true));
+
+ DocCollection docCollection = getCollectionState(collectionName);
+ List<Replica> list = docCollection.getReplicas(firstNode);
+ int replicasOnNode1 = list != null ? list.size() : 0;
+ list = docCollection.getReplicas(secondNode);
+ int replicasOnNode2 = list != null ? list.size() : 0;
+
+ assertEquals("Expected exactly one replica of collection on node with port: " + firstNodePort, 1, replicasOnNode1);
+ assertEquals("Expected exactly one replica of collection on node with port: " + secondNodePort, 1, replicasOnNode2);
+
+ CollectionAdminRequest.splitShard(collectionName).setShardName("shard1").process(solrClient);
+
+ CloudTestUtils.waitForState(cluster, "Timed out waiting to see 6 replicas for collection: " + collectionName,
+ collectionName, (liveNodes, collectionState) -> collectionState.getReplicas().size() == 6);
+
+ docCollection = getCollectionState(collectionName);
+ list = docCollection.getReplicas(firstNode);
+ replicasOnNode1 = list != null ? list.size() : 0;
+ list = docCollection.getReplicas(secondNode);
+ replicasOnNode2 = list != null ? list.size() : 0;
+
+ assertEquals("Expected exactly three replica of collection on node with port: " + firstNodePort, 3, replicasOnNode1);
+ assertEquals("Expected exactly three replica of collection on node with port: " + secondNodePort, 3, replicasOnNode2);
+ CollectionAdminRequest.deleteCollection(collectionName).process(solrClient);
+
+ }
+
+ public void testMetricsTag() throws Exception {
+ SolrClient solrClient = cluster.simGetSolrClient();
+ String setClusterPolicyCommand = "{" +
+ " 'set-cluster-policy': [" +
+ " {'cores':'<10', 'node':'#ANY'}," +
+ " {'replica':'<2', 'shard': '#EACH', 'node': '#ANY'}," +
+ " {'metrics:abc':'overseer', 'replica':0}" +
+ " ]" +
+ "}";
+ SolrRequest req = createAutoScalingRequest(SolrRequest.METHOD.POST, setClusterPolicyCommand);
+ try {
+ solrClient.request(req);
+ fail("expected exception");
+ } catch (Exception e) {
+ // expected
+ assertTrue(e.toString().contains("Invalid metrics: param in"));
+ }
+ setClusterPolicyCommand = "{" +
+ " 'set-cluster-policy': [" +
+ " {'cores':'<10', 'node':'#ANY'}," +
+ " {'replica':'<2', 'shard': '#EACH', 'node': '#ANY'}," +
+ " {'metrics:solr.node:ADMIN./admin/authorization.clientErrors:count':'>58768765', 'replica':0}" +
+ " ]" +
+ "}";
+ req = createAutoScalingRequest(SolrRequest.METHOD.POST, setClusterPolicyCommand);
+ solrClient.request(req);
+
+ //org.eclipse.jetty.server.handler.DefaultHandler.2xx-responses
+ CollectionAdminRequest.createCollection("metricsTest", "conf", 1, 1)
+ .process(solrClient);
+ CloudTestUtils.waitForState(cluster, "Timeout waiting for collection to become active", "metricsTest",
+ CloudTestUtils.clusterShape(1, 1));
+
+ DocCollection collection = getCollectionState("metricsTest");
+ List<String> tags = Arrays.asList("metrics:solr.node:ADMIN./admin/authorization.clientErrors:count",
+ "metrics:solr.jvm:buffers.direct.Count");
+ Map<String, Object> val = cluster.getNodeStateProvider().getNodeValues(collection.getReplicas().get(0).getNodeName(), tags);
+ for (String tag : tags) {
+ assertNotNull( "missing : "+ tag , val.get(tag));
+ }
+
+
+ }
+
+ public void testCreateCollectionAddShardWithReplicaTypeUsingPolicy() throws Exception {
+ SolrClient solrClient = cluster.simGetSolrClient();
+ List<String> nodes = new ArrayList<>(cluster.getClusterStateProvider().getLiveNodes());
+ String nrtNodeName = nodes.get(0);
+ int nrtPort = (Integer)cluster.getSimNodeStateProvider().simGetNodeValue(nrtNodeName, ImplicitSnitch.PORT);
+
+
+ String pullNodeName = nodes.get(1);
+ int pullPort = (Integer)cluster.getSimNodeStateProvider().simGetNodeValue(pullNodeName, ImplicitSnitch.PORT);
+
+ String tlogNodeName = nodes.get(1);
+ int tlogPort = (Integer)cluster.getSimNodeStateProvider().simGetNodeValue(tlogNodeName, ImplicitSnitch.PORT);
+ log.info("NRT {} PULL {} , TLOG {} ", nrtNodeName, pullNodeName, tlogNodeName);
+
+ String commands = "{set-cluster-policy :[" +
+ "{replica:0 , shard:'#EACH', type: NRT, port: '!" + nrtPort + "'}" +
+ "{replica:0 , shard:'#EACH', type: PULL, port: '!" + pullPort + "'}" +
+ "{replica:0 , shard:'#EACH', type: TLOG, port: '!" + tlogPort + "'}" +
+ "]}";
+
+
+ solrClient.request(AutoScalingHandlerTest.createAutoScalingRequest(SolrRequest.METHOD.POST, commands));
+ Map<String, Object> json = Utils.getJson(cluster.getDistribStateManager(), ZkStateReader.SOLR_AUTOSCALING_CONF_PATH);
+ assertEquals("full json:" + Utils.toJSONString(json), "!" + nrtPort,
+ Utils.getObjectByPath(json, true, "cluster-policy[0]/port"));
+ assertEquals("full json:" + Utils.toJSONString(json), "!" + pullPort,
+ Utils.getObjectByPath(json, true, "cluster-policy[1]/port"));
+ assertEquals("full json:" + Utils.toJSONString(json), "!" + tlogPort,
+ Utils.getObjectByPath(json, true, "cluster-policy[2]/port"));
+
+ CollectionAdminRequest.createCollectionWithImplicitRouter("policiesTest", "conf", "s1", 1, 1, 1)
+ .process(solrClient);
+ CloudTestUtils.waitForState(cluster, "Timeout waiting for collection to become active", "policiesTest",
+ CloudTestUtils.clusterShape(1, 3, false, true));
+
+ DocCollection coll = getCollectionState("policiesTest");
+
+
+ BiConsumer<String, Replica> verifyReplicas = (s, replica) -> {
+ switch (replica.getType()) {
+ case NRT: {
+ assertTrue("NRT replica should be in " + nrtNodeName, replica.getNodeName().equals(nrtNodeName));
+ break;
+ }
+ case TLOG: {
+ assertTrue("TLOG replica should be in " + tlogNodeName, replica.getNodeName().equals(tlogNodeName));
+ break;
+ }
+ case PULL: {
+ assertTrue("PULL replica should be in " + pullNodeName, replica.getNodeName().equals(pullNodeName));
+ break;
+ }
+ }
+
+ };
+ coll.forEachReplica(verifyReplicas);
+
+ CollectionAdminRequest.createShard("policiesTest", "s3").
+ process(solrClient);
+ coll = getCollectionState("policiesTest");
+ assertEquals(3, coll.getSlice("s3").getReplicas().size());
+ coll.forEachReplica(verifyReplicas);
+ }
+
+ public void testCreateCollectionAddShardUsingPolicy() throws Exception {
+ SolrClient solrClient = cluster.simGetSolrClient();
+ String nodeId = cluster.getSimClusterStateProvider().simGetRandomNode(random());
+ int port = (Integer)cluster.getSimNodeStateProvider().simGetNodeValue(nodeId, ImplicitSnitch.PORT);
+
+ String commands = "{set-policy :{c1 : [{replica:1 , shard:'#EACH', port: '" + port + "'}]}}";
+ solrClient.request(AutoScalingHandlerTest.createAutoScalingRequest(SolrRequest.METHOD.POST, commands));
+ Map<String, Object> json = Utils.getJson(cluster.getDistribStateManager(), ZkStateReader.SOLR_AUTOSCALING_CONF_PATH);
+ assertEquals("full json:"+ Utils.toJSONString(json) , "#EACH",
+ Utils.getObjectByPath(json, true, "/policies/c1[0]/shard"));
+ CollectionAdminRequest.createCollectionWithImplicitRouter("policiesTest", "conf", "s1,s2", 1)
+ .setPolicy("c1")
+ .process(solrClient);
+ CloudTestUtils.waitForState(cluster, "Timeout waiting for collection to become active", "policiesTest",
+ CloudTestUtils.clusterShape(2, 1));
+
+ DocCollection coll = getCollectionState("policiesTest");
+ assertEquals("c1", coll.getPolicyName());
+ assertEquals(2,coll.getReplicas().size());
+ coll.forEachReplica((s, replica) -> assertEquals(nodeId, replica.getNodeName()));
+ CollectionAdminRequest.createShard("policiesTest", "s3").process(solrClient);
+ CloudTestUtils.waitForState(cluster, "Timeout waiting for collection to become active", "policiesTest",
+ CloudTestUtils.clusterShape(3, 1));
+
+ coll = getCollectionState("policiesTest");
+ assertEquals(1, coll.getSlice("s3").getReplicas().size());
+ coll.getSlice("s3").forEach(replica -> assertEquals(nodeId, replica.getNodeName()));
+ }
+
+ public void testDataProvider() throws IOException, SolrServerException, KeeperException, InterruptedException {
+ SolrClient solrClient = cluster.simGetSolrClient();
+ CollectionAdminRequest.createCollectionWithImplicitRouter("policiesTest", "conf", "shard1", 2)
+ .process(solrClient);
+ CloudTestUtils.waitForState(cluster, "Timeout waiting for collection to become active", "policiesTest",
+ CloudTestUtils.clusterShape(1, 2, false, true));
+ DocCollection rulesCollection = getCollectionState("policiesTest");
+
+ Map<String, Object> val = cluster.getNodeStateProvider().getNodeValues(rulesCollection.getReplicas().get(0).getNodeName(), Arrays.asList(
+ "freedisk",
+ "cores",
+ "heapUsage",
+ "sysLoadAvg"));
+ assertNotNull(val.get("freedisk"));
+ assertNotNull(val.get("heapUsage"));
+ assertNotNull(val.get("sysLoadAvg"));
+ assertTrue(((Number) val.get("cores")).intValue() > 0);
+ assertTrue("freedisk value is " + ((Number) val.get("freedisk")).doubleValue(), Double.compare(((Number) val.get("freedisk")).doubleValue(), 0.0d) > 0);
+ assertTrue("heapUsage value is " + ((Number) val.get("heapUsage")).doubleValue(), Double.compare(((Number) val.get("heapUsage")).doubleValue(), 0.0d) > 0);
+ if (!Constants.WINDOWS) {
+ // the system load average metrics is not available on windows platform
+ assertTrue("sysLoadAvg value is " + ((Number) val.get("sysLoadAvg")).doubleValue(), Double.compare(((Number) val.get("sysLoadAvg")).doubleValue(), 0.0d) > 0);
+ }
+ // simulator doesn't have Overseer, so just pick a random node
+ String overseerNode = cluster.getSimClusterStateProvider().simGetRandomNode(random());
+ solrClient.request(CollectionAdminRequest.addRole(overseerNode, "overseer"));
+ for (int i = 0; i < 10; i++) {
+ Map<String, Object> data = Utils.getJson(cluster.getDistribStateManager(), ZkStateReader.ROLES);
+ if (i >= 9 && data.isEmpty()) {
+ throw new RuntimeException("NO overseer node created");
+ }
+ cluster.getTimeSource().sleep(100);
+ }
+ val = cluster.getNodeStateProvider().getNodeValues(overseerNode, Arrays.asList(
+ "nodeRole",
+ "ip_1", "ip_2", "ip_3", "ip_4",
+ "sysprop.java.version",
+ "sysprop.java.vendor"));
+ assertEquals("overseer", val.get("nodeRole"));
+ assertNotNull(val.get("ip_1"));
+ assertNotNull(val.get("ip_2"));
+ assertNotNull(val.get("ip_3"));
+ assertNotNull(val.get("ip_4"));
+ assertNotNull(val.get("sysprop.java.version"));
+ assertNotNull(val.get("sysprop.java.vendor"));
+ }
+}