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());
 +  }
  }