You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@lucene.apache.org by ro...@apache.org on 2015/03/09 21:51:28 UTC

svn commit: r1665353 - in /lucene/dev/trunk/solr: CHANGES.txt core/src/test/org/apache/solr/cloud/TestMiniSolrCloudCluster.java test-framework/src/java/org/apache/solr/cloud/MiniSolrCloudCluster.java

Author: romseygeek
Date: Mon Mar  9 20:51:28 2015
New Revision: 1665353

URL: http://svn.apache.org/r1665353
Log:
SOLR-7180: MiniSolrCloudCluster starts up and shuts down its jetties in parallel

Modified:
    lucene/dev/trunk/solr/CHANGES.txt
    lucene/dev/trunk/solr/core/src/test/org/apache/solr/cloud/TestMiniSolrCloudCluster.java
    lucene/dev/trunk/solr/test-framework/src/java/org/apache/solr/cloud/MiniSolrCloudCluster.java

Modified: lucene/dev/trunk/solr/CHANGES.txt
URL: http://svn.apache.org/viewvc/lucene/dev/trunk/solr/CHANGES.txt?rev=1665353&r1=1665352&r2=1665353&view=diff
==============================================================================
--- lucene/dev/trunk/solr/CHANGES.txt (original)
+++ lucene/dev/trunk/solr/CHANGES.txt Mon Mar  9 20:51:28 2015
@@ -285,6 +285,9 @@ Other Changes
 
 * SOLR-6804: Untangle SnapPuller and ReplicationHandler (Ramkumar Aiyengar)
 
+* SOLR-7180: MiniSolrCloudCluster will startup and shutdown its jetties in
+  parallel (Alan Woodward, Tomás Fernández Löbbe, Vamsee Yarlagadda)
+
 ==================  5.0.0 ==================
 
 Consult the LUCENE_CHANGES.txt file for additional, low level, changes in this release.

Modified: lucene/dev/trunk/solr/core/src/test/org/apache/solr/cloud/TestMiniSolrCloudCluster.java
URL: http://svn.apache.org/viewvc/lucene/dev/trunk/solr/core/src/test/org/apache/solr/cloud/TestMiniSolrCloudCluster.java?rev=1665353&r1=1665352&r2=1665353&view=diff
==============================================================================
--- lucene/dev/trunk/solr/core/src/test/org/apache/solr/cloud/TestMiniSolrCloudCluster.java (original)
+++ lucene/dev/trunk/solr/core/src/test/org/apache/solr/cloud/TestMiniSolrCloudCluster.java Mon Mar  9 20:51:28 2015
@@ -22,9 +22,9 @@ import org.apache.lucene.util.LuceneTest
 import org.apache.lucene.util.LuceneTestCase.SuppressSysoutChecks;
 import org.apache.solr.SolrTestCaseJ4;
 import org.apache.solr.client.solrj.SolrQuery;
+import org.apache.solr.client.solrj.embedded.JettyConfig;
 import org.apache.solr.client.solrj.embedded.JettySolrRunner;
 import org.apache.solr.client.solrj.impl.CloudSolrClient;
-import org.apache.solr.client.solrj.request.QueryRequest;
 import org.apache.solr.client.solrj.response.QueryResponse;
 import org.apache.solr.common.SolrInputDocument;
 import org.apache.solr.common.cloud.ClusterState;
@@ -34,8 +34,6 @@ import org.apache.solr.common.cloud.Solr
 import org.apache.solr.common.cloud.ZkStateReader;
 import org.apache.solr.core.CoreDescriptor;
 import org.apache.solr.util.RevertDefaultThreadHandlerRule;
-import org.junit.AfterClass;
-import org.junit.BeforeClass;
 import org.junit.ClassRule;
 import org.junit.Rule;
 import org.junit.Test;
@@ -45,10 +43,12 @@ import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 import java.io.File;
+import java.io.IOException;
 import java.util.Collection;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.concurrent.atomic.AtomicInteger;
 
 /**
  * Test of the MiniSolrCloudCluster functionality. Keep in mind, 
@@ -62,7 +62,6 @@ public class TestMiniSolrCloudCluster ex
   private static final int NUM_SERVERS = 5;
   private static final int NUM_SHARDS = 2;
   private static final int REPLICATION_FACTOR = 2;
-  private static MiniSolrCloudCluster miniCluster;
 
   @Rule
   public TestRule solrTestRules = RuleChain
@@ -73,99 +72,151 @@ public class TestMiniSolrCloudCluster ex
       new SystemPropertiesRestoreRule()).around(
       new RevertDefaultThreadHandlerRule());
 
-  @BeforeClass
-  public static void startup() throws Exception {
+  @Test
+  public void testBasics() throws Exception {
+
     File solrXml = new File(SolrTestCaseJ4.TEST_HOME(), "solr-no-core.xml");
-    miniCluster = new MiniSolrCloudCluster(NUM_SERVERS, null, createTempDir().toFile(), solrXml, null, null);
-  }
+    MiniSolrCloudCluster miniCluster = new MiniSolrCloudCluster(NUM_SERVERS, null, createTempDir().toFile(), solrXml, null, null);
 
-  @AfterClass
-  public static void shutdown() throws Exception {
-    if (miniCluster != null) {
+    try {
+      assertNotNull(miniCluster.getZkServer());
+      List<JettySolrRunner> jettys = miniCluster.getJettySolrRunners();
+      assertEquals(NUM_SERVERS, jettys.size());
+      for (JettySolrRunner jetty : jettys) {
+        assertTrue(jetty.isRunning());
+      }
+
+      // shut down a server
+      JettySolrRunner stoppedServer = miniCluster.stopJettySolrRunner(0);
+      assertTrue(stoppedServer.isStopped());
+      assertEquals(NUM_SERVERS - 1, miniCluster.getJettySolrRunners().size());
+
+      // create a server
+      JettySolrRunner startedServer = miniCluster.startJettySolrRunner(null, null, null);
+      assertTrue(startedServer.isRunning());
+      assertEquals(NUM_SERVERS, miniCluster.getJettySolrRunners().size());
+
+      // create collection
+      String collectionName = "testSolrCloudCollection";
+      String configName = "solrCloudCollectionConfig";
+      File configDir = new File(SolrTestCaseJ4.TEST_HOME() + File.separator + "collection1" + File.separator + "conf");
+      miniCluster.uploadConfigDir(configDir, configName);
+
+      Map<String, String> collectionProperties = new HashMap<>();
+      collectionProperties.put(CoreDescriptor.CORE_CONFIG, "solrconfig-tlog.xml");
+      collectionProperties.put("solr.tests.maxBufferedDocs", "100000");
+      collectionProperties.put("solr.tests.maxIndexingThreads", "-1");
+      collectionProperties.put("solr.tests.ramBufferSizeMB", "100");
+      // use non-test classes so RandomizedRunner isn't necessary
+      collectionProperties.put("solr.tests.mergePolicy", "org.apache.lucene.index.TieredMergePolicy");
+      collectionProperties.put("solr.tests.mergeScheduler", "org.apache.lucene.index.ConcurrentMergeScheduler");
+      collectionProperties.put("solr.directoryFactory", "solr.RAMDirectoryFactory");
+      miniCluster.createCollection(collectionName, NUM_SHARDS, REPLICATION_FACTOR, configName, collectionProperties);
+
+      try (SolrZkClient zkClient = new SolrZkClient
+          (miniCluster.getZkServer().getZkAddress(), AbstractZkTestCase.TIMEOUT, 45000, null)) {
+        ZkStateReader zkStateReader = new ZkStateReader(zkClient);
+        AbstractDistribZkTestBase.waitForRecoveriesToFinish(collectionName, zkStateReader, true, true, 330);
+
+        // modify/query collection
+        CloudSolrClient cloudSolrClient = miniCluster.getSolrClient();
+        cloudSolrClient.setDefaultCollection(collectionName);
+        SolrInputDocument doc = new SolrInputDocument();
+        doc.setField("id", "1");
+        cloudSolrClient.add(doc);
+        cloudSolrClient.commit();
+        SolrQuery query = new SolrQuery();
+        query.setQuery("*:*");
+        QueryResponse rsp = cloudSolrClient.query(query);
+        assertEquals(1, rsp.getResults().getNumFound());
+
+        // remove a server not hosting any replicas
+        zkStateReader.updateClusterState(true);
+        ClusterState clusterState = zkStateReader.getClusterState();
+        HashMap<String, JettySolrRunner> jettyMap = new HashMap<String, JettySolrRunner>();
+        for (JettySolrRunner jetty : miniCluster.getJettySolrRunners()) {
+          String key = jetty.getBaseUrl().toString().substring((jetty.getBaseUrl().getProtocol() + "://").length());
+          jettyMap.put(key, jetty);
+        }
+        Collection<Slice> slices = clusterState.getSlices(collectionName);
+        // track the servers not host repliacs
+        for (Slice slice : slices) {
+          jettyMap.remove(slice.getLeader().getNodeName().replace("_solr", "/solr"));
+          for (Replica replica : slice.getReplicas()) {
+            jettyMap.remove(replica.getNodeName().replace("_solr", "/solr"));
+          }
+        }
+        assertTrue("Expected to find a node without a replica", jettyMap.size() > 0);
+        JettySolrRunner jettyToStop = jettyMap.entrySet().iterator().next().getValue();
+        jettys = miniCluster.getJettySolrRunners();
+        for (int i = 0; i < jettys.size(); ++i) {
+          if (jettys.get(i).equals(jettyToStop)) {
+            miniCluster.stopJettySolrRunner(i);
+            assertEquals(NUM_SERVERS - 1, miniCluster.getJettySolrRunners().size());
+          }
+        }
+      }
+    }
+    finally {
       miniCluster.shutdown();
     }
-    miniCluster = null;
   }
 
   @Test
-  public void testBasics() throws Exception {
-    assertNotNull(miniCluster.getZkServer());
-    List<JettySolrRunner> jettys = miniCluster.getJettySolrRunners();
-    assertEquals(NUM_SERVERS, jettys.size());
-    for (JettySolrRunner jetty : jettys) {
-      assertTrue(jetty.isRunning());
-    }
-
-    // shut down a server
-    JettySolrRunner stoppedServer = miniCluster.stopJettySolrRunner(0);
-    assertTrue(stoppedServer.isStopped());
-    assertEquals(NUM_SERVERS - 1, miniCluster.getJettySolrRunners().size());
-
-    // create a server
-    JettySolrRunner startedServer = miniCluster.startJettySolrRunner(null, null, null);
-    assertTrue(startedServer.isRunning());
-    assertEquals(NUM_SERVERS, miniCluster.getJettySolrRunners().size());
-
-    // create collection
-    String collectionName = "testSolrCloudCollection";
-    String configName = "solrCloudCollectionConfig";
-    File configDir = new File(SolrTestCaseJ4.TEST_HOME() + File.separator + "collection1" + File.separator + "conf");
-    miniCluster.uploadConfigDir(configDir, configName);
-    
-    Map<String, String> collectionProperties = new HashMap<>();
-    collectionProperties.put(CoreDescriptor.CORE_CONFIG, "solrconfig-tlog.xml");
-    collectionProperties.put("solr.tests.maxBufferedDocs", "100000");
-    collectionProperties.put("solr.tests.maxIndexingThreads", "-1");
-    collectionProperties.put("solr.tests.ramBufferSizeMB", "100");
-    // use non-test classes so RandomizedRunner isn't necessary
-    collectionProperties.put("solr.tests.mergePolicy", "org.apache.lucene.index.TieredMergePolicy");
-    collectionProperties.put("solr.tests.mergeScheduler", "org.apache.lucene.index.ConcurrentMergeScheduler");
-    collectionProperties.put("solr.directoryFactory", "solr.RAMDirectoryFactory");
-    miniCluster.createCollection(collectionName, NUM_SHARDS, REPLICATION_FACTOR, configName, collectionProperties);
-    
-    try(SolrZkClient zkClient = new SolrZkClient
-        (miniCluster.getZkServer().getZkAddress(), AbstractZkTestCase.TIMEOUT, 45000, null)) {
-      ZkStateReader zkStateReader = new ZkStateReader(zkClient);
-      AbstractDistribZkTestBase.waitForRecoveriesToFinish(collectionName, zkStateReader, true, true, 330);
-      
-      // modify/query collection
-      CloudSolrClient cloudSolrClient = miniCluster.getSolrClient();
-      cloudSolrClient.setDefaultCollection(collectionName);
-      SolrInputDocument doc = new SolrInputDocument();
-      doc.setField("id", "1");
-      cloudSolrClient.add(doc);
-      cloudSolrClient.commit();
-      SolrQuery query = new SolrQuery();
-      query.setQuery("*:*");
-      QueryResponse rsp = cloudSolrClient.query(query);
-      assertEquals(1, rsp.getResults().getNumFound());
-
-      // remove a server not hosting any replicas
-      zkStateReader.updateClusterState(true);
-      ClusterState clusterState = zkStateReader.getClusterState();
-      HashMap<String, JettySolrRunner> jettyMap = new HashMap<String, JettySolrRunner>();
-      for (JettySolrRunner jetty : miniCluster.getJettySolrRunners()) {
-        String key = jetty.getBaseUrl().toString().substring((jetty.getBaseUrl().getProtocol() + "://").length());
-        jettyMap.put(key, jetty);
-      }
-      Collection<Slice> slices = clusterState.getSlices(collectionName);
-      // track the servers not host repliacs
-      for (Slice slice : slices) {
-        jettyMap.remove(slice.getLeader().getNodeName().replace("_solr", "/solr"));
-        for (Replica replica : slice.getReplicas()) {
-          jettyMap.remove(replica.getNodeName().replace("_solr", "/solr"));
+  public void testErrorsInStartup() throws Exception {
+
+    File solrXml = new File(SolrTestCaseJ4.TEST_HOME(), "solr-no-core.xml");
+    AtomicInteger jettyIndex = new AtomicInteger();
+
+    MiniSolrCloudCluster cluster = null;
+    try {
+      cluster = new MiniSolrCloudCluster(3, createTempDir().toFile(), solrXml, JettyConfig.builder().build()) {
+        @Override
+        public JettySolrRunner startJettySolrRunner(JettyConfig config) throws Exception {
+          if (jettyIndex.incrementAndGet() != 2)
+            return super.startJettySolrRunner(config);
+          throw new IOException("Fake exception on startup!");
         }
-      }
-      assertTrue("Expected to find a node without a replica", jettyMap.size() > 0);
-      JettySolrRunner jettyToStop = jettyMap.entrySet().iterator().next().getValue();
-      jettys = miniCluster.getJettySolrRunners();
-      for (int i = 0; i < jettys.size(); ++i) {
-        if (jettys.get(i).equals(jettyToStop)) {
-          miniCluster.stopJettySolrRunner(i);
-          assertEquals(NUM_SERVERS - 1, miniCluster.getJettySolrRunners().size());
+      };
+      fail("Expected an exception to be thrown from MiniSolrCloudCluster");
+    }
+    catch (Exception e) {
+      assertEquals("Error starting up MiniSolrCloudCluster", e.getMessage());
+      assertEquals("Expected one suppressed exception", 1, e.getSuppressed().length);
+      assertEquals("Fake exception on startup!", e.getSuppressed()[0].getMessage());
+    }
+    finally {
+      if (cluster != null)
+        cluster.shutdown();
+    }
+  }
+
+  @Test
+  public void testErrorsInShutdown() throws Exception {
+
+    File solrXml = new File(SolrTestCaseJ4.TEST_HOME(), "solr-no-core.xml");
+    AtomicInteger jettyIndex = new AtomicInteger();
+
+    MiniSolrCloudCluster cluster = new MiniSolrCloudCluster(3, createTempDir().toFile(), solrXml, JettyConfig.builder().build()) {
+        @Override
+        protected JettySolrRunner stopJettySolrRunner(JettySolrRunner jetty) throws Exception {
+          JettySolrRunner j = super.stopJettySolrRunner(jetty);
+          if (jettyIndex.incrementAndGet() == 2)
+            throw new IOException("Fake IOException on shutdown!");
+          return j;
         }
-      }
+      };
+
+    try {
+      cluster.shutdown();
+      fail("Expected an exception to be thrown on MiniSolrCloudCluster shutdown");
+    }
+    catch (Exception e) {
+      assertEquals("Error shutting down MiniSolrCloudCluster", e.getMessage());
+      assertEquals("Expected one suppressed exception", 1, e.getSuppressed().length);
+      assertEquals("Fake IOException on shutdown!", e.getSuppressed()[0].getMessage());
     }
+
   }
 
 }

Modified: lucene/dev/trunk/solr/test-framework/src/java/org/apache/solr/cloud/MiniSolrCloudCluster.java
URL: http://svn.apache.org/viewvc/lucene/dev/trunk/solr/test-framework/src/java/org/apache/solr/cloud/MiniSolrCloudCluster.java?rev=1665353&r1=1665352&r2=1665353&view=diff
==============================================================================
--- lucene/dev/trunk/solr/test-framework/src/java/org/apache/solr/cloud/MiniSolrCloudCluster.java (original)
+++ lucene/dev/trunk/solr/test-framework/src/java/org/apache/solr/cloud/MiniSolrCloudCluster.java Mon Mar  9 20:51:28 2015
@@ -31,6 +31,7 @@ import org.apache.solr.common.params.Col
 import org.apache.solr.common.params.CoreAdminParams;
 import org.apache.solr.common.params.ModifiableSolrParams;
 import org.apache.solr.common.util.NamedList;
+import org.apache.solr.common.util.SolrjNamedThreadFactory;
 import org.apache.zookeeper.KeeperException;
 import org.eclipse.jetty.servlet.ServletHolder;
 import org.slf4j.Logger;
@@ -39,30 +40,46 @@ import org.slf4j.LoggerFactory;
 import javax.servlet.Filter;
 import java.io.File;
 import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collection;
 import java.util.Collections;
 import java.util.LinkedList;
 import java.util.List;
 import java.util.Map;
 import java.util.SortedMap;
-
+import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * "Mini" SolrCloud cluster to be used for testing
+ */
 public class MiniSolrCloudCluster {
   
   private static Logger log = LoggerFactory.getLogger(MiniSolrCloudCluster.class);
 
   private final ZkTestServer zkServer;
-  private final List<JettySolrRunner> jettys;
+  private final List<JettySolrRunner> jettys = new LinkedList<>();
   private final File testDir;
   private final CloudSolrClient solrClient;
   private final JettyConfig jettyConfig;
 
+  private final ExecutorService executor = Executors.newCachedThreadPool(new SolrjNamedThreadFactory("jetty-launcher"));
+
   /**
-   * "Mini" SolrCloud cluster to be used for testing
+   * Create a MiniSolrCloudCluster
+   *
    * @param numServers number of Solr servers to start
    * @param hostContext context path of Solr servers used by Jetty
    * @param baseDir base directory that the mini cluster should be run from
    * @param solrXml solr.xml file to be uploaded to ZooKeeper
    * @param extraServlets Extra servlets to be started by Jetty
    * @param extraRequestFilters extra filters to be started by Jetty
+   *
+   * @throws Exception if there was an error starting the cluster
    */
   public MiniSolrCloudCluster(int numServers, String hostContext, File baseDir, File solrXml,
       SortedMap<ServletHolder, String> extraServlets,
@@ -71,7 +88,8 @@ public class MiniSolrCloudCluster {
   }
 
   /**
-   * "Mini" SolrCloud cluster to be used for testing
+   * Create a MiniSolrCloudCluster
+   *
    * @param numServers number of Solr servers to start
    * @param hostContext context path of Solr servers used by Jetty
    * @param baseDir base directory that the mini cluster should be run from
@@ -79,6 +97,8 @@ public class MiniSolrCloudCluster {
    * @param extraServlets Extra servlets to be started by Jetty
    * @param extraRequestFilters extra filters to be started by Jetty
    * @param sslConfig SSL configuration
+   *
+   * @throws Exception if there was an error starting the cluster
    */
   public MiniSolrCloudCluster(int numServers, String hostContext, File baseDir, File solrXml,
       SortedMap<ServletHolder, String> extraServlets,
@@ -92,6 +112,16 @@ public class MiniSolrCloudCluster {
         .build());
   }
 
+  /**
+   * Create a MiniSolrCloudCluster
+   *
+   * @param numServers number of Solr servers to start
+   * @param baseDir base directory that the mini cluster should be run from
+   * @param solrXml solr.xml file to be uploaded to ZooKeeper
+   * @param jettyConfig Jetty configuration
+   *
+   * @throws Exception if there was an error starting the cluster
+   */
   public MiniSolrCloudCluster(int numServers, File baseDir, File solrXml, JettyConfig jettyConfig) throws Exception {
 
     this.testDir = baseDir;
@@ -114,11 +144,28 @@ public class MiniSolrCloudCluster {
     System.setProperty("solr.solrxml.location","zookeeper");
     System.setProperty("zkHost", zkServer.getZkAddress());
 
-    jettys = new LinkedList<>();
+    List<Callable<JettySolrRunner>> startups = new ArrayList<>(numServers);
     for (int i = 0; i < numServers; ++i) {
-      startJettySolrRunner(jettyConfig);
+      startups.add(new Callable<JettySolrRunner>() {
+        @Override
+        public JettySolrRunner call() throws Exception {
+          return startJettySolrRunner(jettyConfig);
+        }
+      });
     }
-    
+
+    Collection<Future<JettySolrRunner>> futures = executor.invokeAll(startups);
+    Exception startupError = checkForExceptions("Error starting up MiniSolrCloudCluster", futures);
+    if (startupError != null) {
+      try {
+        this.shutdown();
+      }
+      catch (Throwable t) {
+        startupError.addSuppressed(t);
+      }
+      throw startupError;
+    }
+
     solrClient = buildSolrClient();
   }
 
@@ -220,6 +267,11 @@ public class MiniSolrCloudCluster {
     jettys.remove(index);
     return jetty;
   }
+
+  protected JettySolrRunner stopJettySolrRunner(JettySolrRunner jetty) throws Exception {
+    jetty.stop();
+    return jetty;
+  }
   
   public void uploadConfigDir(File configDir, String configName) throws IOException, KeeperException, InterruptedException {
     try(SolrZkClient zkClient = new SolrZkClient(zkServer.getZkAddress(),
@@ -254,11 +306,26 @@ public class MiniSolrCloudCluster {
    */
   public void shutdown() throws Exception {
     try {
-      solrClient.close();
-      for (int i = jettys.size() - 1; i >= 0; --i) {
-        stopJettySolrRunner(i);
+      if (solrClient != null)
+        solrClient.close();
+      List<Callable<JettySolrRunner>> shutdowns = new ArrayList<>(jettys.size());
+      for (final JettySolrRunner jetty : jettys) {
+        shutdowns.add(new Callable<JettySolrRunner>() {
+          @Override
+          public JettySolrRunner call() throws Exception {
+            return stopJettySolrRunner(jetty);
+          }
+        });
+      }
+      jettys.clear();
+      Collection<Future<JettySolrRunner>> futures = executor.invokeAll(shutdowns);
+      Exception shutdownError = checkForExceptions("Error shutting down MiniSolrCloudCluster", futures);
+      if (shutdownError != null) {
+        throw shutdownError;
       }
     } finally {
+      executor.shutdown();
+      executor.awaitTermination(2, TimeUnit.SECONDS);
       try {
         zkServer.shutdown();
       } finally {
@@ -282,4 +349,23 @@ public class MiniSolrCloudCluster {
     if (!ctx.startsWith("/")) ctx = "/" + ctx;
     return ctx;
   }
+
+  private Exception checkForExceptions(String message, Collection<Future<JettySolrRunner>> futures) throws InterruptedException {
+    Exception parsed = new Exception(message);
+    boolean ok = true;
+    for (Future<JettySolrRunner> future : futures) {
+      try {
+        future.get();
+      }
+      catch (ExecutionException e) {
+        parsed.addSuppressed(e.getCause());
+        ok = false;
+      }
+      catch (InterruptedException e) {
+        Thread.interrupted();
+        throw e;
+      }
+    }
+    return ok ? null : parsed;
+  }
 }