/**
  * Sets the tracking queue for all nodes participating in this cluster. Once this method returns,
  * all search and core admin requests distributed to shards will be submitted to the given queue.
  *
  * @param runners a list of {@link org.apache.solr.client.solrj.embedded.JettySolrRunner} nodes
  * @param queue an implementation of {@link java.util.Queue} which accepts {@link
  *     org.apache.solr.handler.component.TrackingShardHandlerFactory.ShardRequestAndParams}
  *     instances
  */
 public static void setTrackingQueue(
     List<JettySolrRunner> runners, Queue<ShardRequestAndParams> queue) {
   for (JettySolrRunner runner : runners) {
     CoreContainer container = runner.getCoreContainer();
     ShardHandlerFactory factory = container.getShardHandlerFactory();
     assert factory instanceof TrackingShardHandlerFactory
         : "not a TrackingShardHandlerFactory: " + factory.getClass();
     TrackingShardHandlerFactory trackingShardHandlerFactory =
         (TrackingShardHandlerFactory) factory;
     trackingShardHandlerFactory.setTrackingQueue(queue);
   }
 }
  @Test
  @BaseDistributedSearchTestCase.ShardsFixed(num = 2)
  public void testRequestTracking() throws Exception {
    String collectionName = "testTwoPhase";

    List<JettySolrRunner> runners = new ArrayList<>(jettys);
    runners.add(controlJetty);

    TrackingShardHandlerFactory.RequestTrackingQueue trackingQueue =
        new TrackingShardHandlerFactory.RequestTrackingQueue();
    TrackingShardHandlerFactory.setTrackingQueue(runners, trackingQueue);

    for (JettySolrRunner runner : runners) {
      CoreContainer container =
          ((SolrDispatchFilter) runner.getDispatchFilter().getFilter()).getCores();
      ShardHandlerFactory factory = container.getShardHandlerFactory();
      assert factory instanceof TrackingShardHandlerFactory;
      TrackingShardHandlerFactory trackingShardHandlerFactory =
          (TrackingShardHandlerFactory) factory;
      assertSame(trackingQueue, trackingShardHandlerFactory.getTrackingQueue());
    }

    createCollection(collectionName, 2, 1, 1);

    waitForRecoveriesToFinish(collectionName, true);

    List<TrackingShardHandlerFactory.ShardRequestAndParams> coreAdminRequests =
        trackingQueue.getCoreAdminRequests();
    assertNotNull(coreAdminRequests);
    assertEquals(
        "Unexpected number of core admin requests were found", 2, coreAdminRequests.size());

    CloudSolrClient client = cloudClient;

    client.setDefaultCollection(collectionName);
    /*
    hash of b is 95de7e03 high bits=2 shard=shard1
    hash of e is 656c4367 high bits=1 shard=shard2
     */
    for (int i = 0; i < 10; i++) {
      SolrInputDocument doc = new SolrInputDocument();
      doc.addField("id", (i % 2 == 0 ? "b!" : "e!") + i);
      doc.addField("a_i", i);
      doc.addField("a_t", "text_" + i);
      client.add(doc);
    }
    client.commit();

    client.query(new SolrQuery("*:*"));

    TrackingShardHandlerFactory.ShardRequestAndParams getTopIdsRequest =
        trackingQueue.getShardRequestByPurpose(
            client.getZkStateReader(), collectionName, "shard1", ShardRequest.PURPOSE_GET_TOP_IDS);
    assertNotNull(getTopIdsRequest);
    getTopIdsRequest =
        trackingQueue.getShardRequestByPurpose(
            client.getZkStateReader(), collectionName, "shard2", ShardRequest.PURPOSE_GET_TOP_IDS);
    assertNotNull(getTopIdsRequest);

    TrackingShardHandlerFactory.ShardRequestAndParams getFieldsRequest =
        trackingQueue.getShardRequestByPurpose(
            client.getZkStateReader(), collectionName, "shard1", ShardRequest.PURPOSE_GET_FIELDS);
    assertNotNull(getFieldsRequest);
    getFieldsRequest =
        trackingQueue.getShardRequestByPurpose(
            client.getZkStateReader(), collectionName, "shard2", ShardRequest.PURPOSE_GET_FIELDS);
    assertNotNull(getFieldsRequest);

    int numRequests = 0;
    Map<String, List<TrackingShardHandlerFactory.ShardRequestAndParams>> allRequests =
        trackingQueue.getAllRequests();
    for (Map.Entry<String, List<TrackingShardHandlerFactory.ShardRequestAndParams>> entry :
        allRequests.entrySet()) {
      numRequests += entry.getValue().size();
    }
    // 4 shard requests + 2 core admin requests (invoked by create collection API)
    assertEquals("Total number of requests do not match expected", 6, numRequests);

    // reset
    TrackingShardHandlerFactory.setTrackingQueue(runners, null);

    for (JettySolrRunner runner : runners) {
      CoreContainer container =
          ((SolrDispatchFilter) runner.getDispatchFilter().getFilter()).getCores();
      ShardHandlerFactory factory = container.getShardHandlerFactory();
      assert factory instanceof TrackingShardHandlerFactory;
      TrackingShardHandlerFactory trackingShardHandlerFactory =
          (TrackingShardHandlerFactory) factory;
      assertFalse(trackingShardHandlerFactory.isTracking());
    }

    // make another request and verify
    client.query(new SolrQuery("*:*"));
    numRequests = 0;
    allRequests = trackingQueue.getAllRequests();
    for (Map.Entry<String, List<TrackingShardHandlerFactory.ShardRequestAndParams>> entry :
        allRequests.entrySet()) {
      numRequests += entry.getValue().size();
    }
    // should still be 6
    assertEquals("Total number of shard requests do not match expected", 6, numRequests);
  }