public boolean hasFailed() {
    if (routerOperation == RouterOperation.PutOperation && routerConfig.routerPutUseDynamicSuccessTarget) {
      return totalReplicaCount - failedCount < Math.max(totalReplicaCount - 1,
          routerConfig.routerPutSuccessTarget + disabledCount);
    } else {
      // If blob is not found in originating DC, we can terminate the operation early. Adding
      // routerConfig.routerOperationTrackerTerminateOnNotFoundEnabled as a safe guard to search replicas in all colos
      // before terminating the operation.
      if (routerConfig.routerOperationTrackerTerminateOnNotFoundEnabled && hasFailedOnNotFound()) {
        return true;
      }
      //For Delete and TtlUpdate continue to run as long as we have replicas.
      if (routerOperation == RouterOperation.DeleteOperation || routerOperation == RouterOperation.TtlUpdateOperation) {
        return replicaInPoolOrFlightCount <= 0;
      } else {
        return replicaInPoolOrFlightCount + replicaSuccessCount < replicaSuccessTarget;
      }
    }
  }
For get operations, if there are very few local replicas, then shuffle the pool with remote replicas so that the
few (or one) remaining local replicas don't get all the traffic. The threshold for minimum number of local replicas
is defined in {@link RouterConfig#routerGetOperationMinLocalReplicaCountToPrioritizeLocal}.
@param numLocalAndLiveReplicas the number of local and live replicas.
@param numRemoteOriginatingDcAndLiveReplicas the number of remote originating DC and live replicas.
ambry-router/src/main/java/com/github/ambry/router/SimpleOperationTracker.java