@Override
    public Future getBlobAndValidate(final String blobIdStr, final byte[] blobContent,
        final RouterErrorCode expectedErrorCode, final Callback callback, final MetricsCollector metricsCollector) {
      final FutureResult futureResult = new FutureResult();
      try {
        final Long startTimeGetBlobMs = SystemTime.getInstance().milliseconds();
        router.getBlob(blobIdStr, new GetBlobOptionsBuilder().build(), new Callback<GetBlobResult>() {
          @Override
          public void onCompletion(GetBlobResult getBlobResult, Exception exception) {
            long latencyPerBlob = SystemTime.getInstance().milliseconds() - startTimeGetBlobMs;
            logger.trace("Time taken to get blob {} in ms {}", blobIdStr, latencyPerBlob);
            Exception exceptionToReturn = null;
            if (getBlobResult != null) {
              logger.trace("OnCompletion returned for blob {} with non null result ", blobIdStr);
              Thread compareThread = new Thread(
                  new CompareRunnable(blobIdStr, blobContent, getBlobResult.getBlobDataChannel(), futureResult,
                      callback, metricsCollector, startTimeGetBlobMs));
              Utils.newThread(compareThread, true).start();
            } else {
              logger.trace("OnCompletion returned for blob {} with exception ", blobIdStr);
              if (exception instanceof RouterException) {
                RouterException routerException = (RouterException) exception;
                if (expectedErrorCode != null) {
                  if (!expectedErrorCode.equals(routerException.getErrorCode())) {
                    logger.error("Error code mismatch. Expected {}, actual {}", expectedErrorCode,
                        routerException.getErrorCode());
                    exceptionToReturn = exception;
                  }
                } else {
                  logger.trace("RouterException thrown with error code {} when no exception was expected for {}",
                      routerException.getErrorCode(), blobIdStr);
                  exceptionToReturn = exception;
                }
              } else {
                logger.trace("UnknownException thrown for {}", blobIdStr);
                exceptionToReturn = exception;
              }
              futureResult.done(null, exceptionToReturn);
              if (callback != null) {
                callback.onCompletion(null, exceptionToReturn);
              }
            }
          }
        }, QUOTA_CHARGE_EVENT_LISTENER);
      } catch (Exception e) {
        futureResult.done(null, e);
        if (callback != null) {
          callback.onCompletion(null, e);
        }
      }
      return futureResult;
    }
{@inheritDoc}
Request to fetch the blob from the server directly pertaining to the {@code blobId} passed in and verify for
its content
@param blobIdStr the (string representation of the) blobId of that blob that needs to be fetched
@param blobContent the content of the blob that needs to be verified against
@param expectedErrorCode the expected error code for the Get call
@param callback the {@link Callback} that will be called on completion
@param metricsCollector the {@link MetricsCollector} to collect and report metrics
ambry-tools/src/main/java/com/github/ambry/tools/admin/ConcurrencyTestTool.java