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 2017/05/25 11:58:53 UTC
[5/5] lucene-solr:jira/solr-10515: Merge branch 'feature/autoscaling'
into jira/solr-10515
Merge branch 'feature/autoscaling' into jira/solr-10515
Project: http://git-wip-us.apache.org/repos/asf/lucene-solr/repo
Commit: http://git-wip-us.apache.org/repos/asf/lucene-solr/commit/e4261d1c
Tree: http://git-wip-us.apache.org/repos/asf/lucene-solr/tree/e4261d1c
Diff: http://git-wip-us.apache.org/repos/asf/lucene-solr/diff/e4261d1c
Branch: refs/heads/jira/solr-10515
Commit: e4261d1cf71a9c3ceabd3ef682734636aac93438
Parents: 53172a7 b933b60
Author: Andrzej Bialecki <ab...@apache.org>
Authored: Thu May 25 13:58:08 2017 +0200
Committer: Andrzej Bialecki <ab...@apache.org>
Committed: Thu May 25 13:58:08 2017 +0200
----------------------------------------------------------------------
.../cloud/autoscaling/NodeAddedTriggerTest.java | 64 +++++++-
.../cloud/autoscaling/NodeLostTriggerTest.java | 62 ++++++++
.../autoscaling/TriggerIntegrationTest.java | 102 +++++++++----
.../cloud/autoscaling/AddReplicaSuggester.java | 21 ++-
.../apache/solr/cloud/autoscaling/Clause.java | 47 ++++--
.../apache/solr/cloud/autoscaling/Operand.java | 36 ++++-
.../apache/solr/cloud/autoscaling/Policy.java | 39 ++++-
.../solr/cloud/autoscaling/TestPolicy.java | 149 +++++++++++++------
8 files changed, 416 insertions(+), 104 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/e4261d1c/solr/core/src/test/org/apache/solr/cloud/autoscaling/NodeAddedTriggerTest.java
----------------------------------------------------------------------
diff --cc solr/core/src/test/org/apache/solr/cloud/autoscaling/NodeAddedTriggerTest.java
index f1e1089,ec06b23..9730c5b
--- a/solr/core/src/test/org/apache/solr/cloud/autoscaling/NodeAddedTriggerTest.java
+++ b/solr/core/src/test/org/apache/solr/cloud/autoscaling/NodeAddedTriggerTest.java
@@@ -29,7 -30,7 +30,8 @@@ import java.util.concurrent.atomic.Atom
import org.apache.solr.client.solrj.embedded.JettySolrRunner;
import org.apache.solr.cloud.SolrCloudTestCase;
import org.apache.solr.core.CoreContainer;
+import org.apache.solr.util.TimeSource;
+ import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
@@@ -37,8 -38,11 +39,11 @@@
* Test for {@link NodeAddedTrigger}
*/
public class NodeAddedTriggerTest extends SolrCloudTestCase {
+ private static AtomicBoolean actionConstructorCalled = new AtomicBoolean(false);
+ private static AtomicBoolean actionInitCalled = new AtomicBoolean(false);
+ private static AtomicBoolean actionCloseCalled = new AtomicBoolean(false);
- private AutoScaling.TriggerListener<NodeAddedTrigger.NodeAddedEvent> noFirstRunListener = event -> {
+ private AutoScaling.TriggerListener noFirstRunListener = event -> {
fail("Did not expect the listener to fire on first run!");
return true;
};
@@@ -85,9 -94,9 +97,9 @@@
}
} while (!fired.get());
- NodeAddedTrigger.NodeAddedEvent nodeAddedEvent = eventRef.get();
+ TriggerEvent nodeAddedEvent = eventRef.get();
assertNotNull(nodeAddedEvent);
- assertEquals("", newNode.getNodeName(), nodeAddedEvent.getProperty(NodeAddedTrigger.NodeAddedEvent.NODE_NAME));
- assertEquals("", newNode.getNodeName(), nodeAddedEvent.getNodeName());
++ assertEquals("", newNode.getNodeName(), nodeAddedEvent.getProperty(TriggerEvent.NODE_NAME));
}
// add a new node but remove it before the waitFor period expires
@@@ -126,6 -135,56 +138,56 @@@
}
}
+ public void testActionLifecycle() throws Exception {
+ CoreContainer container = cluster.getJettySolrRunners().get(0).getCoreContainer();
+ 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", NodeAddedTriggerTest.AssertInitTriggerAction.class.getName());
+ actions.add(action);
+ try (NodeAddedTrigger trigger = new NodeAddedTrigger("node_added_trigger", props, container)) {
+ 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 String getName() {
+ return "";
+ }
+
+ @Override
+ public String getClassName() {
+ return getClass().getName();
+ }
+
+ @Override
- public void process(AutoScaling.TriggerEvent event) {
++ public void process(TriggerEvent event) {
+
+ }
+
+ @Override
+ public void close() throws IOException {
+ actionCloseCalled.compareAndSet(false, true);
+ }
+
+ @Override
+ public void init(Map<String, String> args) {
+ actionInitCalled.compareAndSet(false, true);
+ }
+ }
+
@Test
public void testListenerAcceptance() throws Exception {
CoreContainer container = cluster.getJettySolrRunners().get(0).getCoreContainer();
http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/e4261d1c/solr/core/src/test/org/apache/solr/cloud/autoscaling/NodeLostTriggerTest.java
----------------------------------------------------------------------
diff --cc solr/core/src/test/org/apache/solr/cloud/autoscaling/NodeLostTriggerTest.java
index d83c49f,9baae0f..6e35467
--- a/solr/core/src/test/org/apache/solr/cloud/autoscaling/NodeLostTriggerTest.java
+++ b/solr/core/src/test/org/apache/solr/cloud/autoscaling/NodeLostTriggerTest.java
@@@ -28,8 -29,9 +29,9 @@@ import java.util.concurrent.atomic.Atom
import org.apache.solr.client.solrj.embedded.JettySolrRunner;
import org.apache.solr.cloud.SolrCloudTestCase;
-import org.apache.solr.common.util.Utils;
import org.apache.solr.core.CoreContainer;
+import org.apache.solr.util.TimeSource;
+ import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
@@@ -37,8 -39,11 +39,11 @@@
* Test for {@link NodeLostTrigger}
*/
public class NodeLostTriggerTest extends SolrCloudTestCase {
+ private static AtomicBoolean actionConstructorCalled = new AtomicBoolean(false);
+ private static AtomicBoolean actionInitCalled = new AtomicBoolean(false);
+ private static AtomicBoolean actionCloseCalled = new AtomicBoolean(false);
- private AutoScaling.TriggerListener<NodeLostTrigger.NodeLostEvent> noFirstRunListener = event -> {
+ private AutoScaling.TriggerListener noFirstRunListener = event -> {
fail("Did not expect the listener to fire on first run!");
return true;
};
@@@ -140,6 -149,56 +152,56 @@@
}
}
+ public void testActionLifecycle() throws Exception {
+ CoreContainer container = cluster.getJettySolrRunners().get(0).getCoreContainer();
+ 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", props, container)) {
+ 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 String getName() {
+ return "";
+ }
+
+ @Override
+ public String getClassName() {
+ return getClass().getName();
+ }
+
+ @Override
- public void process(AutoScaling.TriggerEvent event) {
++ public void process(TriggerEvent event) {
+
+ }
+
+ @Override
+ public void close() throws IOException {
+ actionCloseCalled.compareAndSet(false, true);
+ }
+
+ @Override
+ public void init(Map<String, String> args) {
+ actionInitCalled.compareAndSet(false, true);
+ }
+ }
+
@Test
public void testListenerAcceptance() throws Exception {
CoreContainer container = cluster.getJettySolrRunners().get(0).getCoreContainer();
http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/e4261d1c/solr/core/src/test/org/apache/solr/cloud/autoscaling/TriggerIntegrationTest.java
----------------------------------------------------------------------
diff --cc solr/core/src/test/org/apache/solr/cloud/autoscaling/TriggerIntegrationTest.java
index 768fb16,7850f33..1c189ae
--- a/solr/core/src/test/org/apache/solr/cloud/autoscaling/TriggerIntegrationTest.java
+++ b/solr/core/src/test/org/apache/solr/cloud/autoscaling/TriggerIntegrationTest.java
@@@ -57,14 -56,12 +57,15 @@@ import static org.apache.solr.common.cl
public class TriggerIntegrationTest extends SolrCloudTestCase {
private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
- private static CountDownLatch actionCreated;
+ 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 AtomicBoolean triggerFired;
- private static AtomicReference<AutoScaling.TriggerEvent> eventRef;
+ private static AtomicReference<TriggerEvent> eventRef;
private String path;
@@@ -78,35 -72,13 +79,32 @@@
.configure();
}
- private static CountDownLatch getActionCreated() {
- return actionCreated;
- }
-
+ 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 {
waitForSeconds = 1 + random().nextInt(3);
- actionCreated = new CountDownLatch(1);
+ 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);
eventRef = new AtomicReference<>();
// clear any persisted auto scaling configuration
Stat stat = zkClient().setData(SOLR_AUTOSCALING_CONF_PATH, Utils.toJSON(new ZkNodeProps()), true);
@@@ -124,7 -96,7 +122,7 @@@
@Test
public void testTriggerThrottling() throws Exception {
// for this test we want to create two triggers so we must assert that the actions were created twice
- actionCreated = new CountDownLatch(2);
- TriggerIntegrationTest.actionInitCalled = new CountDownLatch(2);
++ actionInitCalled = new CountDownLatch(2);
// similarly we want both triggers to fire
triggerFiredLatch = new CountDownLatch(2);
@@@ -408,7 -380,31 +406,31 @@@
NodeAddedTrigger.NodeAddedEvent nodeAddedEvent = (NodeAddedTrigger.NodeAddedEvent) eventRef.get();
assertNotNull(nodeAddedEvent);
assertEquals("The node added trigger was fired but for a different node",
- newNode.getNodeName(), nodeAddedEvent.getProperty(NodeAddedTrigger.NodeAddedEvent.NODE_NAME));
- newNode.getNodeName(), nodeAddedEvent.getNodeName());
++ newNode.getNodeName(), nodeAddedEvent.getProperty(TriggerEvent.NODE_NAME));
+
+ // 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 = new AutoScalingHandlerTest.AutoScalingRequest(SolrRequest.METHOD.POST, path, 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(3, TimeUnit.SECONDS)) {
+ fail("The TriggerAction should have been created by now");
+ }
+
+ assertFalse(actionInitCalled.await(2, TimeUnit.SECONDS));
}
@Test
@@@ -447,7 -443,31 +469,31 @@@
NodeLostTrigger.NodeLostEvent nodeLostEvent = (NodeLostTrigger.NodeLostEvent) eventRef.get();
assertNotNull(nodeLostEvent);
assertEquals("The node lost trigger was fired but for a different node",
- lostNodeName, nodeLostEvent.getProperty(NodeLostTrigger.NodeLostEvent.NODE_NAME));
- lostNodeName, nodeLostEvent.getNodeName());
++ lostNodeName, nodeLostEvent.getProperty(TriggerEvent.NODE_NAME));
+
+ // 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 = new AutoScalingHandlerTest.AutoScalingRequest(SolrRequest.METHOD.POST, path, 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(3, TimeUnit.SECONDS)) {
+ fail("The TriggerAction should have been created by now");
+ }
+
+ assertFalse(actionInitCalled.await(2, TimeUnit.SECONDS));
}
@Test
@@@ -540,155 -555,7 +586,155 @@@
@Override
public void init(Map<String, String> args) {
log.info("TestTriggerAction init");
- actionCreated.countDown();
+ actionInitCalled.countDown();
}
}
+
+ public static class TestEventQueueAction implements TriggerAction {
+
+ public TestEventQueueAction() {
+ log.info("TestEventQueueAction instantiated");
+ }
+
+ @Override
+ public String getName() {
+ return this.getClass().getSimpleName();
+ }
+
+ @Override
+ public String getClassName() {
+ return this.getClass().getName();
+ }
+
+ @Override
+ public void process(TriggerEvent event) {
+ eventRef.set(event);
+ getActionStarted().countDown();
+ try {
+ Thread.sleep(5000);
+ triggerFired.compareAndSet(false, true);
+ getActionCompleted().countDown();
+ } catch (InterruptedException e) {
+ getActionInterrupted().countDown();
+ return;
+ }
+ }
+
+ @Override
+ public void close() throws IOException {
+
+ }
+
+ @Override
+ public void init(Map<String, String> args) {
+ log.debug("TestTriggerAction init");
- getActionCreated().countDown();
++ actionInitCalled.countDown();
+ }
+ }
+
+ @Test
+ public void testEventQueue() throws Exception {
+ CloudSolrClient solrClient = cluster.getSolrClient();
+ String setTriggerCommand = "{" +
+ "'set-trigger' : {" +
+ "'name' : 'node_added_trigger1'," +
+ "'event' : 'nodeAdded'," +
+ "'waitFor' : '" + waitForSeconds + "s'," +
+ "'enabled' : true," +
+ "'actions' : [{'name':'test','class':'" + TestEventQueueAction.class.getName() + "'}]" +
+ "}}";
+ NamedList<Object> overSeerStatus = cluster.getSolrClient().request(CollectionAdminRequest.getOverseerStatus());
+ String overseerLeader = (String) overSeerStatus.get("leader");
+ int overseerLeaderIndex = 0;
+ for (int i = 0; i < cluster.getJettySolrRunners().size(); i++) {
+ JettySolrRunner jetty = cluster.getJettySolrRunner(i);
+ if (jetty.getNodeName().equals(overseerLeader)) {
+ overseerLeaderIndex = i;
+ break;
+ }
+ }
+ SolrRequest req = new AutoScalingHandlerTest.AutoScalingRequest(SolrRequest.METHOD.POST, path, setTriggerCommand);
+ NamedList<Object> response = solrClient.request(req);
+ assertEquals(response.get("result").toString(), "success");
+
- if (!actionCreated.await(3, TimeUnit.SECONDS)) {
++ if (!actionInitCalled.await(3, TimeUnit.SECONDS)) {
+ fail("The TriggerAction should have been created by now");
+ }
+
+ // add node to generate the event
+ JettySolrRunner newNode = cluster.startJettySolrRunner();
+ boolean await = actionStarted.await(60, TimeUnit.SECONDS);
+ assertTrue("action did not start", await);
+ // event should be there
+ NodeAddedTrigger.NodeAddedEvent nodeAddedEvent = (NodeAddedTrigger.NodeAddedEvent) eventRef.get();
+ assertNotNull(nodeAddedEvent);
+ // but action did not complete yet so the event is still enqueued
+ assertFalse(triggerFired.get());
+ actionStarted = new CountDownLatch(1);
+ // kill overseer leader
+ cluster.stopJettySolrRunner(overseerLeaderIndex);
+ Thread.sleep(5000);
+ await = actionInterrupted.await(3, TimeUnit.SECONDS);
+ assertTrue("action wasn't interrupted", await);
+ // new overseer leader should be elected and run triggers
+ newNode = cluster.startJettySolrRunner();
+ // it should fire again but not complete yet
+ await = actionStarted.await(60, TimeUnit.SECONDS);
+ TriggerEvent replayedEvent = eventRef.get();
+ assertTrue(replayedEvent.getProperty(TriggerEventQueue.ENQUEUE_TIME) != null);
+ assertTrue(replayedEvent.getProperty(TriggerEventQueue.DEQUEUE_TIME) != null);
+ await = actionCompleted.await(10, TimeUnit.SECONDS);
+ assertTrue(triggerFired.get());
+ }
+
+ @Test
+ public void testEventFromRestoredState() throws Exception {
+ CloudSolrClient solrClient = cluster.getSolrClient();
+ String setTriggerCommand = "{" +
+ "'set-trigger' : {" +
+ "'name' : 'node_added_trigger'," +
+ "'event' : 'nodeAdded'," +
+ "'waitFor' : '10s'," +
+ "'enabled' : true," +
+ "'actions' : [{'name':'test','class':'" + TestTriggerAction.class.getName() + "'}]" +
+ "}}";
+ SolrRequest req = new AutoScalingHandlerTest.AutoScalingRequest(SolrRequest.METHOD.POST, path, setTriggerCommand);
+ NamedList<Object> response = solrClient.request(req);
+ assertEquals(response.get("result").toString(), "success");
+
- if (!actionCreated.await(10, TimeUnit.SECONDS)) {
++ if (!actionInitCalled.await(10, TimeUnit.SECONDS)) {
+ fail("The TriggerAction should have been created by now");
+ }
+
+ NamedList<Object> overSeerStatus = cluster.getSolrClient().request(CollectionAdminRequest.getOverseerStatus());
+ String overseerLeader = (String) overSeerStatus.get("leader");
+ int overseerLeaderIndex = 0;
+ for (int i = 0; i < cluster.getJettySolrRunners().size(); i++) {
+ JettySolrRunner jetty = cluster.getJettySolrRunner(i);
+ if (jetty.getNodeName().equals(overseerLeader)) {
+ overseerLeaderIndex = i;
+ break;
+ }
+ }
+
+ JettySolrRunner newNode = cluster.startJettySolrRunner();
+ boolean await = triggerFiredLatch.await(20, TimeUnit.SECONDS);
+ assertTrue("The trigger did not fire at all", await);
+ assertTrue(triggerFired.get());
+ // reset
+ triggerFired.set(false);
+ triggerFiredLatch = new CountDownLatch(1);
+ NodeAddedTrigger.NodeAddedEvent nodeAddedEvent = (NodeAddedTrigger.NodeAddedEvent) eventRef.get();
+ assertNotNull(nodeAddedEvent);
+ assertEquals("The node added trigger was fired but for a different node",
+ newNode.getNodeName(), nodeAddedEvent.getProperty(NodeAddedTrigger.NodeAddedEvent.NODE_NAME));
+ // add a second node - state of the trigger will change but it won't fire for waitFor sec.
+ JettySolrRunner newNode2 = cluster.startJettySolrRunner();
+ Thread.sleep(10000);
+ // kill overseer leader
+ cluster.stopJettySolrRunner(overseerLeaderIndex);
+ await = triggerFiredLatch.await(20, TimeUnit.SECONDS);
+ assertTrue("The trigger did not fire at all", await);
+ assertTrue(triggerFired.get());
+ }
}