@Override
  protected void masterOperation(
      final SnapshotsStatusRequest request,
      final ClusterState state,
      final ActionListener<SnapshotsStatusResponse> listener)
      throws Exception {
    ImmutableList<SnapshotMetaData.Entry> currentSnapshots =
        snapshotsService.currentSnapshots(request.repository(), request.snapshots());

    if (currentSnapshots.isEmpty()) {
      listener.onResponse(buildResponse(request, currentSnapshots, null));
      return;
    }

    Set<String> nodesIds = newHashSet();
    for (SnapshotMetaData.Entry entry : currentSnapshots) {
      for (SnapshotMetaData.ShardSnapshotStatus status : entry.shards().values()) {
        if (status.nodeId() != null) {
          nodesIds.add(status.nodeId());
        }
      }
    }

    if (!nodesIds.isEmpty()) {
      // There are still some snapshots running - check their progress
      SnapshotId[] snapshotIds = new SnapshotId[currentSnapshots.size()];
      for (int i = 0; i < currentSnapshots.size(); i++) {
        snapshotIds[i] = currentSnapshots.get(i).snapshotId();
      }

      transportNodesSnapshotsStatus.status(
          nodesIds.toArray(new String[nodesIds.size()]),
          snapshotIds,
          request.masterNodeTimeout(),
          new ActionListener<TransportNodesSnapshotsStatus.NodesSnapshotStatus>() {
            @Override
            public void onResponse(
                TransportNodesSnapshotsStatus.NodesSnapshotStatus nodeSnapshotStatuses) {
              try {
                ImmutableList<SnapshotMetaData.Entry> currentSnapshots =
                    snapshotsService.currentSnapshots(request.repository(), request.snapshots());
                listener.onResponse(buildResponse(request, currentSnapshots, nodeSnapshotStatuses));
              } catch (Throwable e) {
                listener.onFailure(e);
              }
            }

            @Override
            public void onFailure(Throwable e) {
              listener.onFailure(e);
            }
          });
    } else {
      // We don't have any in-progress shards, just return current stats
      listener.onResponse(buildResponse(request, currentSnapshots, null));
    }
  }
  private SnapshotsStatusResponse buildResponse(
      SnapshotsStatusRequest request,
      ImmutableList<SnapshotMetaData.Entry> currentSnapshots,
      TransportNodesSnapshotsStatus.NodesSnapshotStatus nodeSnapshotStatuses)
      throws IOException {
    // First process snapshot that are currently processed
    ImmutableList.Builder<SnapshotStatus> builder = ImmutableList.builder();
    Set<SnapshotId> currentSnapshotIds = newHashSet();
    if (!currentSnapshots.isEmpty()) {
      Map<String, TransportNodesSnapshotsStatus.NodeSnapshotStatus> nodeSnapshotStatusMap;
      if (nodeSnapshotStatuses != null) {
        nodeSnapshotStatusMap = nodeSnapshotStatuses.getNodesMap();
      } else {
        nodeSnapshotStatusMap = newHashMap();
      }

      for (SnapshotMetaData.Entry entry : currentSnapshots) {
        currentSnapshotIds.add(entry.snapshotId());
        ImmutableList.Builder<SnapshotIndexShardStatus> shardStatusBuilder =
            ImmutableList.builder();
        for (ImmutableMap.Entry<ShardId, SnapshotMetaData.ShardSnapshotStatus> shardEntry :
            entry.shards().entrySet()) {
          SnapshotMetaData.ShardSnapshotStatus status = shardEntry.getValue();
          if (status.nodeId() != null) {
            // We should have information about this shard from the shard:
            TransportNodesSnapshotsStatus.NodeSnapshotStatus nodeStatus =
                nodeSnapshotStatusMap.get(status.nodeId());
            if (nodeStatus != null) {
              ImmutableMap<ShardId, SnapshotIndexShardStatus> shardStatues =
                  nodeStatus.status().get(entry.snapshotId());
              if (shardStatues != null) {
                SnapshotIndexShardStatus shardStatus = shardStatues.get(shardEntry.getKey());
                if (shardStatus != null) {
                  // We have full information about this shard
                  shardStatusBuilder.add(shardStatus);
                  continue;
                }
              }
            }
          }
          final SnapshotIndexShardStage stage;
          switch (shardEntry.getValue().state()) {
            case FAILED:
            case ABORTED:
            case MISSING:
              stage = SnapshotIndexShardStage.FAILURE;
              break;
            case INIT:
            case WAITING:
            case STARTED:
              stage = SnapshotIndexShardStage.STARTED;
              break;
            case SUCCESS:
              stage = SnapshotIndexShardStage.DONE;
              break;
            default:
              throw new ElasticsearchIllegalArgumentException(
                  "Unknown snapshot state " + shardEntry.getValue().state());
          }
          SnapshotIndexShardStatus shardStatus =
              new SnapshotIndexShardStatus(shardEntry.getKey(), stage);
          shardStatusBuilder.add(shardStatus);
        }
        builder.add(
            new SnapshotStatus(entry.snapshotId(), entry.state(), shardStatusBuilder.build()));
      }
    }
    // Now add snapshots on disk that are not currently running
    if (Strings.hasText(request.repository())) {
      if (request.snapshots() != null && request.snapshots().length > 0) {
        for (String snapshotName : request.snapshots()) {
          SnapshotId snapshotId = new SnapshotId(request.repository(), snapshotName);
          if (currentSnapshotIds.contains(snapshotId)) {
            // This is a snapshot the is currently running - skipping
            continue;
          }
          Snapshot snapshot = snapshotsService.snapshot(snapshotId);
          ImmutableList.Builder<SnapshotIndexShardStatus> shardStatusBuilder =
              ImmutableList.builder();
          if (snapshot.state().completed()) {
            ImmutableMap<ShardId, IndexShardSnapshotStatus> shardStatues =
                snapshotsService.snapshotShards(snapshotId);
            for (ImmutableMap.Entry<ShardId, IndexShardSnapshotStatus> shardStatus :
                shardStatues.entrySet()) {
              shardStatusBuilder.add(
                  new SnapshotIndexShardStatus(shardStatus.getKey(), shardStatus.getValue()));
            }
            final SnapshotMetaData.State state;
            switch (snapshot.state()) {
              case FAILED:
                state = SnapshotMetaData.State.FAILED;
                break;
              case SUCCESS:
              case PARTIAL:
                // Translating both PARTIAL and SUCCESS to SUCCESS for now
                // TODO: add the differentiation on the metadata level in the next major release
                state = SnapshotMetaData.State.SUCCESS;
                break;
              default:
                throw new ElasticsearchIllegalArgumentException(
                    "Unknown snapshot state " + snapshot.state());
            }
            builder.add(new SnapshotStatus(snapshotId, state, shardStatusBuilder.build()));
          }
        }
      }
    }

    return new SnapshotsStatusResponse(builder.build());
  }