private List<Stage> filterParallelPerHostStages(List<Stage> firstStageInProgressPerRequest) {
    // if there's only 1 stage in progress in 1 request, simply return that stage
    if (firstStageInProgressPerRequest.size() == 1) {
      return firstStageInProgressPerRequest;
    }

    List<Stage> retVal = new ArrayList<>();

    // set the lower range (inclusive) of requests to limit the query a bit
    // since there can be a LOT of commands
    long lowerRequestIdInclusive = firstStageInProgressPerRequest.get(0).getRequestId();

    // determine if this stage can be scheduled in parallel with the other
    // stages from other requests
    for (Stage stage : firstStageInProgressPerRequest) {
      long requestId = stage.getRequestId();

      if (LOG.isTraceEnabled()) {
        LOG.trace("==> Processing stage: {}/{} ({}) for {}", requestId, stage.getStageId(), stage.getRequestContext());
      }

      boolean addStage = true;

      // there are at least 2 request in progress concurrently; determine which
      // hosts are affected
      HashSet<String> hostsInProgressForEarlierRequests = new HashSet<>(
          hostRoleCommandDAO.getBlockingHostsForRequest(lowerRequestIdInclusive, requestId));

      // Iterate over the relevant hosts for this stage to see if any intersect with the set of
      // hosts needed for previous stages.  If any intersection occurs, this stage may not be
      // executed in parallel.
      for (String host : stage.getHosts()) {
        LOG.trace("===> Processing Host {}", host);

        if (hostsInProgressForEarlierRequests.contains(host)) {
          if (LOG.isTraceEnabled()) {
            LOG.trace("===>  Skipping stage since it utilizes at least one host that a previous stage requires: {}/{} ({})", stage.getRequestId(), stage.getStageId(), stage.getRequestContext());
          }

          addStage = false;
          break;
        }
      }

      // add the stage is no other prior stages for prior requests intersect the
      // hosts in this stage
      if (addStage) {
        if (LOG.isTraceEnabled()) {
          LOG.trace("===>  Adding stage to return value: {}/{} ({})", stage.getRequestId(), stage.getStageId(), stage.getRequestContext());
        }

        retVal.add(stage);
      }
    }

    return retVal;
  }
Returns filtered list of stages such that the returned list is an ordered
list of stages that may be executed in parallel or in the order in which
they are presented.
<p/>
The specified stages must be ordered by request ID and may only contain the
next stage in progress per request (as returned by
{@link ActionDBAccessor#getFirstStageInProgressPerRequest()}. This is
because there is a requirement that within a request, no two stages may
ever run in parallel.
<p/>
The following rules will be applied to the list:
<ul>
<li>Stages are filtered such that the first stage in the list (assumed to
be the first pending stage from the earliest active request) has priority.
</li>
<li>No stage in any request may be executed before an earlier stage in the
same request. This requirement is automatically covered by virtue of the
supplied stages only being for the next stage in progress per request.</li>
<li>A stage in different request may be performed in parallel
if-and-only-if the relevant hosts for the stage in the later requests do
not intersect with the union of hosts from (pending) stages in earlier
requests. In order to accomplish this</li>
</ul>

@param firstStageInProgressPerRequest
         the stages to process, one stage per request
@return a list of stages that may be executed in parallel
ambari-server/src/main/java/org/apache/ambari/server/actionmanager/ActionScheduler.java