// Selects a dir with enough space (including space evictable) from all tiers
 private StorageDirView selectEvictableDirFromAnyTier(
     BlockMetadataManagerView view, long availableBytes) {
   for (StorageTierView tierView : view.getTierViews()) {
     for (StorageDirView dirView : tierView.getDirViews()) {
       if (canEvictBlocksFromDir(dirView, availableBytes)) {
         return dirView;
       }
     }
   }
   return null;
 }
 private StorageDirView selectAvailableDir(
     BlockMeta block,
     List<StorageTierView> candidateTiers,
     Map<StorageDirView, Long> pendingBytesInDir) {
   for (StorageTierView candidateTier : candidateTiers) {
     for (StorageDirView candidateDir : candidateTier.getDirViews()) {
       long pendingBytes = 0;
       if (pendingBytesInDir.containsKey(candidateDir)) {
         pendingBytes = pendingBytesInDir.get(candidateDir);
       }
       if (candidateDir.getAvailableBytes() - pendingBytes >= block.getBlockSize()) {
         return candidateDir;
       }
     }
   }
   return null;
 }
  @Override
  public EvictionPlan freeSpaceWithView(
      long availableBytes, BlockStoreLocation location, BlockMetadataManagerView view) {
    Preconditions.checkNotNull(location);
    Preconditions.checkNotNull(view);

    // 1. Select a StorageDirView that has enough capacity for required bytes.
    StorageDirView selectedDirView = null;
    if (location.equals(BlockStoreLocation.anyTier())) {
      selectedDirView = selectEvictableDirFromAnyTier(view, availableBytes);
    } else {
      String tierAlias = location.tierAlias();
      StorageTierView tierView = view.getTierView(tierAlias);
      if (location.equals(BlockStoreLocation.anyDirInTier(tierAlias))) {
        selectedDirView = selectEvictableDirFromTier(tierView, availableBytes);
      } else {
        int dirIndex = location.dir();
        StorageDirView dir = tierView.getDirView(dirIndex);
        if (canEvictBlocksFromDir(dir, availableBytes)) {
          selectedDirView = dir;
        }
      }
    }
    if (selectedDirView == null) {
      LOG.error(
          "Failed to freeSpace: No StorageDirView has enough capacity of {} bytes", availableBytes);
      return null;
    }

    // 2. Check if the selected StorageDirView already has enough space.
    List<BlockTransferInfo> toTransfer = new ArrayList<>();
    List<Pair<Long, BlockStoreLocation>> toEvict = new ArrayList<>();
    long bytesAvailableInDir = selectedDirView.getAvailableBytes();
    if (bytesAvailableInDir >= availableBytes) {
      // No need to evict anything, return an eviction plan with empty instructions.
      return new EvictionPlan(toTransfer, toEvict);
    }

    // 3. Collect victim blocks from the selected StorageDirView. They could either be evicted or
    // moved.
    List<BlockMeta> victimBlocks = new ArrayList<>();
    for (BlockMeta block : selectedDirView.getEvictableBlocks()) {
      victimBlocks.add(block);
      bytesAvailableInDir += block.getBlockSize();
      if (bytesAvailableInDir >= availableBytes) {
        break;
      }
    }

    // 4. Make best effort to transfer victim blocks to lower tiers rather than evict them.
    Map<StorageDirView, Long> pendingBytesInDir = new HashMap<>();
    for (BlockMeta block : victimBlocks) {
      // TODO(qifan): Should avoid calling getParentDir.
      String fromTierAlias = block.getParentDir().getParentTier().getTierAlias();
      List<StorageTierView> candidateTiers = view.getTierViewsBelow(fromTierAlias);
      StorageDirView dstDir = selectAvailableDir(block, candidateTiers, pendingBytesInDir);
      if (dstDir == null) {
        // Not possible to transfer
        toEvict.add(new Pair<>(block.getBlockId(), block.getBlockLocation()));
      } else {
        StorageTierView dstTier = dstDir.getParentTierView();
        toTransfer.add(
            new BlockTransferInfo(
                block.getBlockId(),
                block.getBlockLocation(),
                new BlockStoreLocation(dstTier.getTierViewAlias(), dstDir.getDirViewIndex())));
        if (pendingBytesInDir.containsKey(dstDir)) {
          pendingBytesInDir.put(dstDir, pendingBytesInDir.get(dstDir) + block.getBlockSize());
        } else {
          pendingBytesInDir.put(dstDir, block.getBlockSize());
        }
      }
    }
    return new EvictionPlan(toTransfer, toEvict);
  }