@Override
  protected void masterOperation(
      final ClusterHealthRequest request,
      final ClusterState unusedState,
      final ActionListener<ClusterHealthResponse> listener)
      throws ElasticsearchException {
    long endTime = System.currentTimeMillis() + request.timeout().millis();

    if (request.waitForEvents() != null) {
      final CountDownLatch latch = new CountDownLatch(1);
      final AtomicReference<ElasticsearchException> failure = new AtomicReference<>();
      clusterService.submitStateUpdateTask(
          "cluster_health (wait_for_events [" + request.waitForEvents() + "])",
          request.waitForEvents(),
          new ProcessedClusterStateUpdateTask() {
            @Override
            public ClusterState execute(ClusterState currentState) {
              return currentState;
            }

            @Override
            public void clusterStateProcessed(
                String source, ClusterState oldState, ClusterState newState) {
              latch.countDown();
            }

            @Override
            public void onFailure(String source, Throwable t) {
              logger.error("unexpected failure during [{}]", t, source);
              failure.set(new ElasticsearchException("Error while waiting for events", t));
              latch.countDown();
            }

            @Override
            public boolean runOnlyOnMaster() {
              return !request.local();
            }
          });

      try {
        latch.await(request.timeout().millis(), TimeUnit.MILLISECONDS);
      } catch (InterruptedException e) {
        // ignore
      }
      if (failure.get() != null) {
        throw failure.get();
      }
    }

    int waitFor = 5;
    if (request.waitForStatus() == null) {
      waitFor--;
    }
    if (request.waitForRelocatingShards() == -1) {
      waitFor--;
    }
    if (request.waitForActiveShards() == -1) {
      waitFor--;
    }
    if (request.waitForNodes().isEmpty()) {
      waitFor--;
    }
    if (request.indices().length == 0) { // check that they actually exists in the meta data
      waitFor--;
    }
    if (waitFor == 0) {
      // no need to wait for anything
      ClusterState clusterState = clusterService.state();
      listener.onResponse(clusterHealth(request, clusterState));
      return;
    }
    while (true) {
      int waitForCounter = 0;
      ClusterState clusterState = clusterService.state();
      ClusterHealthResponse response = clusterHealth(request, clusterState);
      if (request.waitForStatus() != null
          && response.getStatus().value() <= request.waitForStatus().value()) {
        waitForCounter++;
      }
      if (request.waitForRelocatingShards() != -1
          && response.getRelocatingShards() <= request.waitForRelocatingShards()) {
        waitForCounter++;
      }
      if (request.waitForActiveShards() != -1
          && response.getActiveShards() >= request.waitForActiveShards()) {
        waitForCounter++;
      }
      if (request.indices().length > 0) {
        try {
          clusterState.metaData().concreteIndices(IndicesOptions.strictExpand(), request.indices());
          waitForCounter++;
        } catch (IndexMissingException e) {
          response.status = ClusterHealthStatus.RED; // no indices, make sure its RED
          // missing indices, wait a bit more...
        }
      }
      if (!request.waitForNodes().isEmpty()) {
        if (request.waitForNodes().startsWith(">=")) {
          int expected = Integer.parseInt(request.waitForNodes().substring(2));
          if (response.getNumberOfNodes() >= expected) {
            waitForCounter++;
          }
        } else if (request.waitForNodes().startsWith("ge(")) {
          int expected =
              Integer.parseInt(
                  request.waitForNodes().substring(3, request.waitForNodes().length() - 1));
          if (response.getNumberOfNodes() >= expected) {
            waitForCounter++;
          }
        } else if (request.waitForNodes().startsWith("<=")) {
          int expected = Integer.parseInt(request.waitForNodes().substring(2));
          if (response.getNumberOfNodes() <= expected) {
            waitForCounter++;
          }
        } else if (request.waitForNodes().startsWith("le(")) {
          int expected =
              Integer.parseInt(
                  request.waitForNodes().substring(3, request.waitForNodes().length() - 1));
          if (response.getNumberOfNodes() <= expected) {
            waitForCounter++;
          }
        } else if (request.waitForNodes().startsWith(">")) {
          int expected = Integer.parseInt(request.waitForNodes().substring(1));
          if (response.getNumberOfNodes() > expected) {
            waitForCounter++;
          }
        } else if (request.waitForNodes().startsWith("gt(")) {
          int expected =
              Integer.parseInt(
                  request.waitForNodes().substring(3, request.waitForNodes().length() - 1));
          if (response.getNumberOfNodes() > expected) {
            waitForCounter++;
          }
        } else if (request.waitForNodes().startsWith("<")) {
          int expected = Integer.parseInt(request.waitForNodes().substring(1));
          if (response.getNumberOfNodes() < expected) {
            waitForCounter++;
          }
        } else if (request.waitForNodes().startsWith("lt(")) {
          int expected =
              Integer.parseInt(
                  request.waitForNodes().substring(3, request.waitForNodes().length() - 1));
          if (response.getNumberOfNodes() < expected) {
            waitForCounter++;
          }
        } else {
          int expected = Integer.parseInt(request.waitForNodes());
          if (response.getNumberOfNodes() == expected) {
            waitForCounter++;
          }
        }
      }
      if (waitForCounter == waitFor) {
        listener.onResponse(response);
        return;
      }
      if (System.currentTimeMillis() > endTime) {
        response.timedOut = true;
        listener.onResponse(response);
        return;
      }
      try {
        Thread.sleep(200);
      } catch (InterruptedException e) {
        response.timedOut = true;
        listener.onResponse(response);
        return;
      }
    }
  }
 @Override
 protected ClusterHealthResponse masterOperation(ClusterHealthRequest request, ClusterState state)
     throws ElasticSearchException {
   int waitFor = 4;
   if (request.waitForStatus() == null) {
     waitFor--;
   }
   if (request.waitForRelocatingShards() == -1) {
     waitFor--;
   }
   if (request.waitForActiveShards() == -1) {
     waitFor--;
   }
   if (request.waitForNodes().isEmpty()) {
     waitFor--;
   }
   if (waitFor == 0) {
     // no need to wait for anything
     return clusterHealth(request);
   }
   long endTime = System.currentTimeMillis() + request.timeout().millis();
   while (true) {
     int waitForCounter = 0;
     ClusterHealthResponse response = clusterHealth(request);
     if (request.waitForStatus() != null
         && response.status().value() <= request.waitForStatus().value()) {
       waitForCounter++;
     }
     if (request.waitForRelocatingShards() != -1
         && response.relocatingShards() <= request.waitForRelocatingShards()) {
       waitForCounter++;
     }
     if (request.waitForActiveShards() != -1
         && response.activeShards() >= request.waitForActiveShards()) {
       waitForCounter++;
     }
     if (!request.waitForNodes().isEmpty()) {
       if (request.waitForNodes().startsWith(">=")) {
         int expected = Integer.parseInt(request.waitForNodes().substring(2));
         if (response.numberOfNodes() >= expected) {
           waitForCounter++;
         }
       } else if (request.waitForNodes().startsWith("ge(")) {
         int expected =
             Integer.parseInt(
                 request.waitForNodes().substring(3, request.waitForNodes().length() - 1));
         if (response.numberOfNodes() >= expected) {
           waitForCounter++;
         }
       } else if (request.waitForNodes().startsWith("<=")) {
         int expected = Integer.parseInt(request.waitForNodes().substring(2));
         if (response.numberOfNodes() <= expected) {
           waitForCounter++;
         }
       } else if (request.waitForNodes().startsWith("le(")) {
         int expected =
             Integer.parseInt(
                 request.waitForNodes().substring(3, request.waitForNodes().length() - 1));
         if (response.numberOfNodes() <= expected) {
           waitForCounter++;
         }
       } else if (request.waitForNodes().startsWith(">")) {
         int expected = Integer.parseInt(request.waitForNodes().substring(1));
         if (response.numberOfNodes() > expected) {
           waitForCounter++;
         }
       } else if (request.waitForNodes().startsWith("gt(")) {
         int expected =
             Integer.parseInt(
                 request.waitForNodes().substring(3, request.waitForNodes().length() - 1));
         if (response.numberOfNodes() > expected) {
           waitForCounter++;
         }
       } else if (request.waitForNodes().startsWith("<")) {
         int expected = Integer.parseInt(request.waitForNodes().substring(1));
         if (response.numberOfNodes() < expected) {
           waitForCounter++;
         }
       } else if (request.waitForNodes().startsWith("lt(")) {
         int expected =
             Integer.parseInt(
                 request.waitForNodes().substring(3, request.waitForNodes().length() - 1));
         if (response.numberOfNodes() < expected) {
           waitForCounter++;
         }
       } else {
         int expected = Integer.parseInt(request.waitForNodes());
         if (response.numberOfNodes() == expected) {
           waitForCounter++;
         }
       }
     }
     if (waitForCounter == waitFor) {
       return response;
     }
     if (timerService.estimatedTimeInMillis() > endTime) {
       response.timedOut = true;
       return response;
     }
     try {
       Thread.sleep(200);
     } catch (InterruptedException e) {
       response.timedOut = true;
       // we got interrupted, bail
       return response;
     }
   }
 }