public Node chooseRandomWithStorageType(final String scope,
      final Collection<Node> excludedNodes, StorageType type) {
    netlock.readLock().lock();
    try {
      if (scope.startsWith("~")) {
        return chooseRandomWithStorageType(
            NodeBase.ROOT, scope.substring(1), excludedNodes, type);
      } else {
        return chooseRandomWithStorageType(
            scope, null, excludedNodes, type);
      }
    } finally {
      netlock.readLock().unlock();
    }
  }
@Test
  public void testChooseRandomWithStorageTypeWithExcluded() throws Exception {
    Node n;
    DatanodeDescriptor dd;
    // below test choose random with exclude, for /l2/d3, every rack has exactly
    // one host
    // /l2/d3 has five racks r[1~5] but only r4 and r5 have ARCHIVE
    // host12 is the one under "/l2/d3/r4", host13 is the one under "/l2/d3/r5"
    n = CLUSTER.chooseRandomWithStorageType("/l2/d3/r4", null, null,
        StorageType.ARCHIVE);
    HashSet<Node> excluded = new HashSet<>();
    // exclude the host on r4 (since there is only one host, no randomness here)
    excluded.add(n);

    for (int i = 0; i < 10; i++) {
      n = CLUSTER.chooseRandomWithStorageType("/l2/d3", null, null,
          StorageType.ARCHIVE);
      assertTrue(n instanceof DatanodeDescriptor);
      dd = (DatanodeDescriptor) n;
      assertTrue(dd.getHostName().equals("host13") ||
          dd.getHostName().equals("host14"));
    }

    // test exclude nodes
    for (int i = 0; i < 10; i++) {
      n = CLUSTER.chooseRandomWithStorageType("/l2/d3", null, excluded,
          StorageType.ARCHIVE);
      assertTrue(n instanceof DatanodeDescriptor);
      dd = (DatanodeDescriptor) n;
      assertTrue(dd.getHostName().equals("host14"));
    }

    // test exclude scope
    for (int i = 0; i < 10; i++) {
      n = CLUSTER.chooseRandomWithStorageType("/l2/d3", "/l2/d3/r4", null,
          StorageType.ARCHIVE);
      assertTrue(n instanceof DatanodeDescriptor);
      dd = (DatanodeDescriptor) n;
      assertTrue(dd.getHostName().equals("host14"));
    }

    // test exclude scope + excluded node with expected null return node
    for (int i = 0; i < 10; i++) {
      n = CLUSTER.chooseRandomWithStorageType("/l2/d3", "/l2/d3/r5", excluded,
          StorageType.ARCHIVE);
      assertNull(n);
    }

    // test exclude scope + excluded node with expected non-null return node
    n = CLUSTER.chooseRandomWithStorageType("/l1/d2", null, null,
        StorageType.DISK);
    dd = (DatanodeDescriptor)n;
    assertEquals("host6", dd.getHostName());
    // exclude the host on r4 (since there is only one host, no randomness here)
    excluded.add(n);
    Set<String> expectedSet = new HashSet<>(Arrays.asList("host4", "host5"));
    for (int i = 0; i < 10; i++) {
      // under l1, there are four hosts with DISK:
      // /l1/d1/r1/host2, /l1/d1/r2/host4, /l1/d1/r2/host5 and /l1/d2/r3/host6
      // host6 is excludedNode, host2 is under excluded range scope /l1/d1/r1
      // so should always return r4 or r5
      n = CLUSTER.chooseRandomWithStorageType(
          "/l1", "/l1/d1/r1", excluded, StorageType.DISK);
      dd = (DatanodeDescriptor) n;
      assertTrue(expectedSet.contains(dd.getHostName()));
    }
  }