@Override
  public List<Version> getVersions(final ByteArray key) {
    StoreUtils.assertValidKey(key);

    long startTimeMs = -1;
    long startTimeNs = -1;

    if (logger.isDebugEnabled()) {
      startTimeMs = System.currentTimeMillis();
      startTimeNs = System.nanoTime();
    }

    BasicPipelineData<List<Version>> pipelineData = new BasicPipelineData<List<Version>>();
    if (zoneRoutingEnabled) pipelineData.setZonesRequired(storeDef.getZoneCountReads());
    else pipelineData.setZonesRequired(null);
    pipelineData.setStats(stats);
    Pipeline pipeline =
        new Pipeline(
            Operation.GET_VERSIONS,
            timeoutConfig.getOperationTimeout(VoldemortOpCode.GET_VERSION_OP_CODE),
            TimeUnit.MILLISECONDS);

    StoreRequest<List<Version>> blockingStoreRequest =
        new StoreRequest<List<Version>>() {

          @Override
          public List<Version> request(Store<ByteArray, byte[], byte[]> store) {
            return store.getVersions(key);
          }
        };

    if (zoneAffinity.isGetVersionsOpZoneAffinityEnabled()) {
      pipeline.addEventAction(
          Event.STARTED,
          new ConfigureNodesLocalZoneOnly<List<Version>, BasicPipelineData<List<Version>>>(
              pipelineData,
              Event.CONFIGURED,
              failureDetector,
              storeDef.getRequiredReads(),
              routingStrategy,
              key,
              clientZone));
    } else {
      pipeline.addEventAction(
          Event.STARTED,
          new ConfigureNodes<List<Version>, BasicPipelineData<List<Version>>>(
              pipelineData,
              Event.CONFIGURED,
              failureDetector,
              storeDef.getRequiredReads(),
              routingStrategy,
              key,
              clientZone));
    }
    pipeline.addEventAction(
        Event.CONFIGURED,
        new PerformParallelRequests<List<Version>, BasicPipelineData<List<Version>>>(
            pipelineData,
            Event.COMPLETED,
            key,
            null,
            failureDetector,
            storeDef.getPreferredReads(),
            storeDef.getRequiredReads(),
            timeoutConfig.getOperationTimeout(VoldemortOpCode.GET_VERSION_OP_CODE),
            nonblockingStores,
            Event.INSUFFICIENT_SUCCESSES,
            Event.INSUFFICIENT_ZONES));

    pipeline.addEventAction(
        Event.INSUFFICIENT_SUCCESSES,
        new PerformSerialRequests<List<Version>, BasicPipelineData<List<Version>>>(
            pipelineData,
            Event.COMPLETED,
            key,
            failureDetector,
            innerStores,
            storeDef.getPreferredReads(),
            storeDef.getRequiredReads(),
            blockingStoreRequest,
            null));

    if (zoneRoutingEnabled)
      pipeline.addEventAction(
          Event.INSUFFICIENT_ZONES,
          new PerformZoneSerialRequests<List<Version>, BasicPipelineData<List<Version>>>(
              pipelineData,
              Event.COMPLETED,
              key,
              failureDetector,
              innerStores,
              blockingStoreRequest));

    pipeline.addEvent(Event.STARTED);
    if (logger.isDebugEnabled()) {
      logger.debug(
          "Operation  "
              + pipeline.getOperation().getSimpleName()
              + " Key "
              + ByteUtils.toHexString(key.get()));
    }
    try {
      pipeline.execute();
    } catch (VoldemortException e) {
      stats.reportException(e);
      throw e;
    }

    if (pipelineData.getFatalError() != null) throw pipelineData.getFatalError();

    List<Version> results = new ArrayList<Version>();

    for (Response<ByteArray, List<Version>> response : pipelineData.getResponses())
      results.addAll(response.getValue());

    if (logger.isDebugEnabled()) {
      logger.debug(
          "Finished "
              + pipeline.getOperation().getSimpleName()
              + " for key "
              + ByteUtils.toHexString(key.get())
              + " keyRef: "
              + System.identityHashCode(key)
              + "; started at "
              + startTimeMs
              + " took "
              + (System.nanoTime() - startTimeNs)
              + " values: "
              + formatNodeValuesFromGetVersions(pipelineData.getResponses()));
    }

    return results;
  }