You are viewing a plain text version of this content. The canonical link for it is here.
Posted to common-commits@hadoop.apache.org by ar...@apache.org on 2018/05/31 18:02:05 UTC

hadoop git commit: HDDS-116. Implement VolumeSet to manage disk volumes. Contributed by Hanisha Koneru.

Repository: hadoop
Updated Branches:
  refs/heads/HDDS-48 6cd19b45e -> 59777185f


HDDS-116. Implement VolumeSet to manage disk volumes. Contributed by Hanisha Koneru.


Project: http://git-wip-us.apache.org/repos/asf/hadoop/repo
Commit: http://git-wip-us.apache.org/repos/asf/hadoop/commit/59777185
Tree: http://git-wip-us.apache.org/repos/asf/hadoop/tree/59777185
Diff: http://git-wip-us.apache.org/repos/asf/hadoop/diff/59777185

Branch: refs/heads/HDDS-48
Commit: 59777185fc38b9f9b7428c5f3f7eb6c52796317d
Parents: 6cd19b4
Author: Arpit Agarwal <ar...@apache.org>
Authored: Thu May 31 10:29:25 2018 -0700
Committer: Arpit Agarwal <ar...@apache.org>
Committed: Thu May 31 10:29:25 2018 -0700

----------------------------------------------------------------------
 .../apache/hadoop/hdds/scm/ScmConfigKeys.java   |   1 +
 .../impl/RoundRobinVolumeChoosingPolicy.java    |  82 ++++++
 .../ozone/container/common/impl/VolumeInfo.java | 125 ++++++++++
 .../ozone/container/common/impl/VolumeSet.java  | 250 +++++++++++++++++++
 .../common/interfaces/VolumeChoosingPolicy.java |  46 ++++
 .../TestRoundRobinVolumeChoosingPolicy.java     | 100 ++++++++
 .../common/interfaces/TestVolumeSet.java        | 138 ++++++++++
 7 files changed, 742 insertions(+)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/hadoop/blob/59777185/hadoop-hdds/common/src/main/java/org/apache/hadoop/hdds/scm/ScmConfigKeys.java
----------------------------------------------------------------------
diff --git a/hadoop-hdds/common/src/main/java/org/apache/hadoop/hdds/scm/ScmConfigKeys.java b/hadoop-hdds/common/src/main/java/org/apache/hadoop/hdds/scm/ScmConfigKeys.java
index 85407e6..b6b95eb 100644
--- a/hadoop-hdds/common/src/main/java/org/apache/hadoop/hdds/scm/ScmConfigKeys.java
+++ b/hadoop-hdds/common/src/main/java/org/apache/hadoop/hdds/scm/ScmConfigKeys.java
@@ -141,6 +141,7 @@ public final class ScmConfigKeys {
   public static final String HDDS_REST_HTTP_ADDRESS_KEY =
       "hdds.rest.http-address";
   public static final String HDDS_REST_HTTP_ADDRESS_DEFAULT = "0.0.0.0:9880";
+  public static final String HDDS_DATANODE_DIR_KEY = "hdds.datanode.dir";
   public static final String HDDS_REST_CSRF_ENABLED_KEY =
       "hdds.rest.rest-csrf.enabled";
   public static final boolean HDDS_REST_CSRF_ENABLED_DEFAULT = false;

http://git-wip-us.apache.org/repos/asf/hadoop/blob/59777185/hadoop-hdds/container-service/src/main/java/org/apache/hadoop/ozone/container/common/impl/RoundRobinVolumeChoosingPolicy.java
----------------------------------------------------------------------
diff --git a/hadoop-hdds/container-service/src/main/java/org/apache/hadoop/ozone/container/common/impl/RoundRobinVolumeChoosingPolicy.java b/hadoop-hdds/container-service/src/main/java/org/apache/hadoop/ozone/container/common/impl/RoundRobinVolumeChoosingPolicy.java
new file mode 100644
index 0000000..0a20bf2
--- /dev/null
+++ b/hadoop-hdds/container-service/src/main/java/org/apache/hadoop/ozone/container/common/impl/RoundRobinVolumeChoosingPolicy.java
@@ -0,0 +1,82 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.hadoop.ozone.container.common.impl;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.hadoop.ozone.container.common.interfaces.VolumeChoosingPolicy;
+import org.apache.hadoop.util.DiskChecker.DiskOutOfSpaceException;
+
+import java.io.IOException;
+import java.util.List;
+import java.util.concurrent.atomic.AtomicInteger;
+
+/**
+ * Choose volumes in round-robin order.
+ * Use fine-grained locks to synchronize volume choosing.
+ */
+public class RoundRobinVolumeChoosingPolicy implements VolumeChoosingPolicy {
+
+  public static final Log LOG = LogFactory.getLog(
+		RoundRobinVolumeChoosingPolicy.class);
+
+  // Stores the index of the next volume to be returned.
+  private AtomicInteger nextVolumeIndex = new AtomicInteger(0);
+
+  @Override
+  public VolumeInfo chooseVolume(List<VolumeInfo> volumes,
+      long maxContainerSize) throws IOException {
+
+    // No volumes available to choose from
+    if (volumes.size() < 1) {
+      throw new DiskOutOfSpaceException("No more available volumes");
+    }
+
+    // since volumes could've been removed because of the failure
+    // make sure we are not out of bounds
+    int nextIndex = nextVolumeIndex.get();
+    int currentVolumeIndex = nextIndex < volumes.size() ? nextIndex : 0;
+
+    int startVolumeIndex = currentVolumeIndex;
+    long maxAvailable = 0;
+
+    while (true) {
+      final VolumeInfo volume = volumes.get(currentVolumeIndex);
+      long availableVolumeSize = volume.getAvailable();
+
+      currentVolumeIndex = (currentVolumeIndex + 1) % volumes.size();
+
+      if (availableVolumeSize > maxContainerSize) {
+        nextVolumeIndex.compareAndSet(nextIndex, currentVolumeIndex);
+        return volume;
+      }
+
+      if (availableVolumeSize > maxAvailable) {
+        maxAvailable = availableVolumeSize;
+      }
+
+      if (currentVolumeIndex == startVolumeIndex) {
+        throw new DiskOutOfSpaceException("Out of space: "
+            + "The volume with the most available space (=" + maxAvailable
+            + " B) is less than the container size (=" + maxContainerSize
+            + " B).");
+      }
+
+    }
+  }
+}

http://git-wip-us.apache.org/repos/asf/hadoop/blob/59777185/hadoop-hdds/container-service/src/main/java/org/apache/hadoop/ozone/container/common/impl/VolumeInfo.java
----------------------------------------------------------------------
diff --git a/hadoop-hdds/container-service/src/main/java/org/apache/hadoop/ozone/container/common/impl/VolumeInfo.java b/hadoop-hdds/container-service/src/main/java/org/apache/hadoop/ozone/container/common/impl/VolumeInfo.java
new file mode 100644
index 0000000..1b5a7aa
--- /dev/null
+++ b/hadoop-hdds/container-service/src/main/java/org/apache/hadoop/ozone/container/common/impl/VolumeInfo.java
@@ -0,0 +1,125 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.hadoop.ozone.container.common.impl;
+
+import org.apache.hadoop.fs.Path;
+import org.apache.hadoop.fs.StorageType;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+import java.util.concurrent.atomic.AtomicLong;
+
+/**
+ * Stores information about a disk/volume.
+ */
+public class VolumeInfo {
+
+  private static final Logger LOG = LoggerFactory.getLogger(VolumeInfo.class);
+
+  private final Path rootDir;
+  private final StorageType storageType;
+  private VolumeState state;
+  // Capacity configured. This is useful when we want to
+  // limit the visible capacity for tests. If negative, then we just
+  // query from the filesystem.
+  private long configuredCapacity;
+  private volatile AtomicLong scmUsed = new AtomicLong(0);
+
+  public static class Builder {
+    private final Path rootDir;
+    private StorageType storageType;
+    private VolumeState state;
+    private long configuredCapacity;
+
+    public Builder(Path rootDir) {
+      this.rootDir = rootDir;
+    }
+
+    public Builder(String rootDirStr) {
+      this.rootDir = new Path(rootDirStr);
+    }
+
+    public Builder storageType(StorageType storageType) {
+      this.storageType = storageType;
+      return this;
+    }
+
+    public Builder volumeState(VolumeState state) {
+      this.state = state;
+      return this;
+    }
+
+    public Builder configuredCapacity(long configuredCapacity) {
+      this.configuredCapacity = configuredCapacity;
+      return this;
+    }
+
+    public VolumeInfo build() throws IOException {
+      return new VolumeInfo(this);
+    }
+  }
+
+  private VolumeInfo(Builder b) {
+
+    this.rootDir = b.rootDir;
+
+    this.storageType = (b.storageType != null ?
+        b.storageType : StorageType.DEFAULT);
+
+    this.configuredCapacity = (b.configuredCapacity != 0 ?
+        b.configuredCapacity : -1);
+
+    this.state = (b.state != null ? b.state : VolumeState.NOT_FORMATTED);
+
+    LOG.info("Creating Volume : " + rootDir + " of  storage type : " +
+        storageType + " and capacity : " + configuredCapacity);
+  }
+
+  public void addSpaceUsed(long spaceUsed) {
+    this.scmUsed.getAndAdd(spaceUsed);
+  }
+
+  public long getAvailable() {
+    return configuredCapacity - scmUsed.get();
+  }
+
+  public void setState(VolumeState state) {
+    this.state = state;
+  }
+
+  public boolean isFailed() {
+    return (state == VolumeState.FAILED);
+  }
+
+  public Path getRootDir() {
+    return this.rootDir;
+  }
+
+  public StorageType getStorageType() {
+    return this.storageType;
+  }
+
+  public enum VolumeState {
+    NORMAL,
+    FAILED,
+    NON_EXISTENT,
+    NOT_FORMATTED,
+  }
+}

http://git-wip-us.apache.org/repos/asf/hadoop/blob/59777185/hadoop-hdds/container-service/src/main/java/org/apache/hadoop/ozone/container/common/impl/VolumeSet.java
----------------------------------------------------------------------
diff --git a/hadoop-hdds/container-service/src/main/java/org/apache/hadoop/ozone/container/common/impl/VolumeSet.java b/hadoop-hdds/container-service/src/main/java/org/apache/hadoop/ozone/container/common/impl/VolumeSet.java
new file mode 100644
index 0000000..27fd657
--- /dev/null
+++ b/hadoop-hdds/container-service/src/main/java/org/apache/hadoop/ozone/container/common/impl/VolumeSet.java
@@ -0,0 +1,250 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.hadoop.ozone.container.common.impl;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import org.apache.hadoop.conf.Configuration;
+import org.apache.hadoop.fs.Path;
+import org.apache.hadoop.fs.StorageType;
+import static org.apache.hadoop.hdds.scm.ScmConfigKeys.HDDS_DATANODE_DIR_KEY;
+import org.apache.hadoop.hdfs.DFSConfigKeys;
+import static org.apache.hadoop.hdfs.DFSConfigKeys.DFS_DATANODE_DATA_DIR_KEY;
+import org.apache.hadoop.hdfs.server.datanode.StorageLocation;
+import org.apache.hadoop.ozone.container.common.impl.VolumeInfo.VolumeState;
+import org.apache.hadoop.ozone.container.common.interfaces.VolumeChoosingPolicy;
+import org.apache.hadoop.util.AutoCloseableLock;
+import org.apache.hadoop.util.DiskChecker.DiskOutOfSpaceException;
+import org.apache.hadoop.util.InstrumentedLock;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.EnumMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.locks.ReentrantLock;
+
+/**
+ * VolumeSet to manage volumes in a DataNode.
+ */
+public class VolumeSet {
+
+  private static final Logger LOG = LoggerFactory.getLogger(VolumeSet.class);
+
+  private Configuration conf;
+  /**
+   * {@link VolumeSet#volumeList} maintains a list of active volumes in the
+   * DataNode. Each volume has one-to-one mapping with a volumeInfo object.
+   */
+  private List<VolumeInfo> volumeList;
+  /**
+   * {@link VolumeSet#failedVolumeList} maintains a list of volumes which have
+   * failed. This list is mutually exclusive to {@link VolumeSet#volumeList}.
+   */
+  private List<VolumeInfo> failedVolumeList;
+  /**
+   * {@link VolumeSet#volumeMap} maintains a map of all volumes in the
+   * DataNode irrespective of their state.
+   */
+  private Map<Path, VolumeInfo> volumeMap;
+  /**
+   * {@link VolumeSet#volumeStateMap} maintains a list of volumes per
+   * StorageType.
+   */
+  private EnumMap<StorageType, List<VolumeInfo>> volumeStateMap;
+
+  /**
+   * Lock to synchronize changes to the VolumeSet. Any update to
+   * {@link VolumeSet#volumeList}, {@link VolumeSet#failedVolumeList},
+   * {@link VolumeSet#volumeMap} or {@link VolumeSet#volumeStateMap} should
+   * be done after acquiring this lock.
+   */
+  private final AutoCloseableLock volumeSetLock;
+
+  public VolumeSet(Configuration conf) throws DiskOutOfSpaceException {
+    this.conf = conf;
+    this.volumeSetLock = new AutoCloseableLock(
+        new InstrumentedLock(getClass().getName(), LOG,
+            new ReentrantLock(true),
+            conf.getTimeDuration(
+                DFSConfigKeys.DFS_LOCK_SUPPRESS_WARNING_INTERVAL_KEY,
+                DFSConfigKeys.DFS_LOCK_SUPPRESS_WARNING_INTERVAL_DEFAULT,
+                TimeUnit.MILLISECONDS),
+            300));
+
+    initializeVolumeSet();
+  }
+
+  // Add DN volumes configured through ConfigKeys to volumeMap.
+  private void initializeVolumeSet() throws DiskOutOfSpaceException {
+    volumeList = new ArrayList<>();
+    failedVolumeList = new ArrayList<>();
+    volumeMap = new ConcurrentHashMap<>();
+    volumeStateMap = new EnumMap<>(StorageType.class);
+
+    Collection<String> datanodeDirs = conf.getTrimmedStringCollection(
+        HDDS_DATANODE_DIR_KEY);
+    if (datanodeDirs.isEmpty()) {
+      datanodeDirs = conf.getTrimmedStringCollection(DFS_DATANODE_DATA_DIR_KEY);
+    }
+    if (datanodeDirs.isEmpty()) {
+      throw new IllegalArgumentException("No location configured in either "
+          + HDDS_DATANODE_DIR_KEY + " or " + DFS_DATANODE_DATA_DIR_KEY);
+    }
+
+    for (StorageType storageType : StorageType.values()) {
+      volumeStateMap.put(storageType, new ArrayList<VolumeInfo>());
+    }
+
+    for (String dir : datanodeDirs) {
+      try {
+        VolumeInfo volumeInfo = getVolumeInfo(dir);
+
+        volumeList.add(volumeInfo);
+        volumeMap.put(volumeInfo.getRootDir(), volumeInfo);
+        volumeStateMap.get(volumeInfo.getStorageType()).add(volumeInfo);
+      } catch (IOException e) {
+        LOG.error("Failed to parse the storage location: " + dir, e);
+      }
+    }
+
+    if (volumeList.size() == 0) {
+      throw new DiskOutOfSpaceException("No storage location configured");
+    }
+  }
+
+  public void acquireLock() {
+    volumeSetLock.acquire();
+  }
+
+  public void releaseLock() {
+    volumeSetLock.release();
+  }
+
+  private VolumeInfo getVolumeInfo(String rootDir) throws IOException {
+    StorageLocation location = StorageLocation.parse(rootDir);
+    StorageType storageType = location.getStorageType();
+
+    VolumeInfo.Builder volumeBuilder = new VolumeInfo.Builder(rootDir);
+    volumeBuilder.storageType(storageType);
+    return volumeBuilder.build();
+  }
+
+  // Add a volume to VolumeSet
+  public void addVolume(String dataDir) throws IOException {
+    Path dirPath = new Path(dataDir);
+
+    try (AutoCloseableLock lock = volumeSetLock.acquire()) {
+      if (volumeMap.containsKey(dirPath)) {
+        VolumeInfo volumeInfo = volumeMap.get(dirPath);
+        if (volumeInfo.isFailed()) {
+          volumeInfo.setState(VolumeState.NORMAL);
+          failedVolumeList.remove(volumeInfo);
+          volumeList.add(volumeInfo);
+        } else {
+          LOG.warn("Volume : " + volumeInfo.getRootDir() + " already " +
+              "exists in VolumeMap");
+        }
+      } else {
+        VolumeInfo volumeInfo = getVolumeInfo(dataDir);
+
+        volumeList.add(volumeInfo);
+        volumeMap.put(volumeInfo.getRootDir(), volumeInfo);
+        volumeStateMap.get(volumeInfo.getStorageType()).add(volumeInfo);
+      }
+    }
+  }
+
+  // Mark a volume as failed
+  public void failVolume(String dataDir) {
+    Path dirPath = new Path(dataDir);
+
+    try (AutoCloseableLock lock = volumeSetLock.acquire()) {
+      if (volumeMap.containsKey(dirPath)) {
+        VolumeInfo volumeInfo = volumeMap.get(dirPath);
+        if (!volumeInfo.isFailed()) {
+          volumeInfo.setState(VolumeState.FAILED);
+          volumeList.remove(volumeInfo);
+          failedVolumeList.add(volumeInfo);
+        }
+      } else {
+        LOG.warn("Volume : " + dataDir + " does not exist in VolumeMap");
+      }
+    }
+  }
+
+  // Remove a volume from the VolumeSet completely.
+  public void removeVolume(String dataDir) throws IOException {
+    Path dirPath = new Path(dataDir);
+
+    try (AutoCloseableLock lock = volumeSetLock.acquire()) {
+      if (volumeMap.containsKey(dirPath)) {
+        VolumeInfo volumeInfo = volumeMap.get(dirPath);
+        if (!volumeInfo.isFailed()) {
+          volumeList.remove(volumeInfo);
+        } else {
+          failedVolumeList.remove(volumeInfo);
+        }
+        volumeMap.remove(dirPath);
+        volumeStateMap.get(volumeInfo.getStorageType()).remove(volumeInfo);
+      } else {
+        LOG.warn("Volume: " + dataDir + " does not exist in " + "volumeMap.");
+      }
+    }
+  }
+
+  /**
+   * Return an iterator over {@link VolumeSet#volumeList}.
+   */
+  public Iterator<VolumeInfo> getIterator() {
+    return volumeList.iterator();
+  }
+
+  public VolumeInfo chooseVolume(long containerSize,
+      VolumeChoosingPolicy choosingPolicy) throws IOException {
+    return choosingPolicy.chooseVolume(volumeList, containerSize);
+  }
+
+  @VisibleForTesting
+  public List<VolumeInfo> getVolumesList() {
+    return ImmutableList.copyOf(volumeList);
+  }
+
+  @VisibleForTesting
+  public List<VolumeInfo> getFailedVolumesList() {
+    return ImmutableList.copyOf(failedVolumeList);
+  }
+
+  @VisibleForTesting
+  public Map<Path, VolumeInfo> getVolumeMap() {
+    return ImmutableMap.copyOf(volumeMap);
+  }
+
+  @VisibleForTesting
+  public Map<StorageType, List<VolumeInfo>> getVolumeStateMap() {
+    return ImmutableMap.copyOf(volumeStateMap);
+  }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/hadoop/blob/59777185/hadoop-hdds/container-service/src/main/java/org/apache/hadoop/ozone/container/common/interfaces/VolumeChoosingPolicy.java
----------------------------------------------------------------------
diff --git a/hadoop-hdds/container-service/src/main/java/org/apache/hadoop/ozone/container/common/interfaces/VolumeChoosingPolicy.java b/hadoop-hdds/container-service/src/main/java/org/apache/hadoop/ozone/container/common/interfaces/VolumeChoosingPolicy.java
new file mode 100644
index 0000000..b8cbcb6
--- /dev/null
+++ b/hadoop-hdds/container-service/src/main/java/org/apache/hadoop/ozone/container/common/interfaces/VolumeChoosingPolicy.java
@@ -0,0 +1,46 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.hadoop.ozone.container.common.interfaces;
+
+import org.apache.hadoop.classification.InterfaceAudience;
+import org.apache.hadoop.ozone.container.common.impl.VolumeInfo;
+
+import java.io.IOException;
+import java.util.List;
+
+/**
+ * This interface specifies the policy for choosing volumes to store replicas.
+ */
+@InterfaceAudience.Private
+public interface VolumeChoosingPolicy {
+
+  /**
+   * Choose a volume to place a container,
+   * given a list of volumes and the max container size sought for storage.
+   *
+   * The implementations of this interface must be thread-safe.
+   *
+   * @param volumes - a list of available volumes.
+   * @param maxContainerSize - the maximum size of the container for which a
+   *                         volume is sought.
+   * @return the chosen volume.
+   * @throws IOException when disks are unavailable or are full.
+   */
+  VolumeInfo chooseVolume(List<VolumeInfo> volumes, long maxContainerSize)
+      throws IOException;
+}

http://git-wip-us.apache.org/repos/asf/hadoop/blob/59777185/hadoop-hdds/container-service/src/test/java/org/apache/hadoop/ozone/container/common/impl/TestRoundRobinVolumeChoosingPolicy.java
----------------------------------------------------------------------
diff --git a/hadoop-hdds/container-service/src/test/java/org/apache/hadoop/ozone/container/common/impl/TestRoundRobinVolumeChoosingPolicy.java b/hadoop-hdds/container-service/src/test/java/org/apache/hadoop/ozone/container/common/impl/TestRoundRobinVolumeChoosingPolicy.java
new file mode 100644
index 0000000..409db57
--- /dev/null
+++ b/hadoop-hdds/container-service/src/test/java/org/apache/hadoop/ozone/container/common/impl/TestRoundRobinVolumeChoosingPolicy.java
@@ -0,0 +1,100 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.hadoop.ozone.container.common.impl;
+
+import org.apache.hadoop.util.DiskChecker.DiskOutOfSpaceException;
+import org.apache.hadoop.util.ReflectionUtils;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mockito;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Tests {@link RoundRobinVolumeChoosingPolicy}.
+ */
+public class TestRoundRobinVolumeChoosingPolicy {
+
+  private RoundRobinVolumeChoosingPolicy policy;
+
+  @Before
+  public void setup() {
+   policy = ReflectionUtils.newInstance(
+       RoundRobinVolumeChoosingPolicy.class, null);
+  }
+
+  @Test
+  public void testRRVolumeChoosingPolicy() throws Exception {
+    final List<VolumeInfo> volumes = new ArrayList<>();
+
+    // First volume, with 100 bytes of space.
+    volumes.add(Mockito.mock(VolumeInfo.class));
+    Mockito.when(volumes.get(0).getAvailable()).thenReturn(100L);
+
+    // Second volume, with 200 bytes of space.
+    volumes.add(Mockito.mock(VolumeInfo.class));
+    Mockito.when(volumes.get(1).getAvailable()).thenReturn(200L);
+
+    // Test two rounds of round-robin choosing
+    Assert.assertEquals(volumes.get(0), policy.chooseVolume(volumes, 0));
+    Assert.assertEquals(volumes.get(1), policy.chooseVolume(volumes, 0));
+    Assert.assertEquals(volumes.get(0), policy.chooseVolume(volumes, 0));
+    Assert.assertEquals(volumes.get(1), policy.chooseVolume(volumes, 0));
+
+    // The first volume has only 100L space, so the policy should
+    // choose the second one in case we ask for more.
+    Assert.assertEquals(volumes.get(1),
+        policy.chooseVolume(volumes, 150));
+
+    // Fail if no volume has enough space available
+    try {
+      policy.chooseVolume(volumes, Long.MAX_VALUE);
+      Assert.fail();
+    } catch (IOException e) {
+      // Passed.
+    }
+  }
+
+  @Test
+  public void testRRPolicyExceptionMessage() throws Exception {
+    final List<VolumeInfo> volumes = new ArrayList<>();
+
+    // First volume, with 100 bytes of space.
+    volumes.add(Mockito.mock(VolumeInfo.class));
+    Mockito.when(volumes.get(0).getAvailable()).thenReturn(100L);
+
+    // Second volume, with 200 bytes of space.
+    volumes.add(Mockito.mock(VolumeInfo.class));
+    Mockito.when(volumes.get(1).getAvailable()).thenReturn(200L);
+
+    int blockSize = 300;
+    try {
+      policy.chooseVolume(volumes, blockSize);
+      Assert.fail("expected to throw DiskOutOfSpaceException");
+    } catch(DiskOutOfSpaceException e) {
+      Assert.assertEquals("Not returnig the expected message",
+          "Out of space: The volume with the most available space (=" + 200
+              + " B) is less than the container size (=" + blockSize + " B).",
+          e.getMessage());
+    }
+  }
+}

http://git-wip-us.apache.org/repos/asf/hadoop/blob/59777185/hadoop-hdds/container-service/src/test/java/org/apache/hadoop/ozone/container/common/interfaces/TestVolumeSet.java
----------------------------------------------------------------------
diff --git a/hadoop-hdds/container-service/src/test/java/org/apache/hadoop/ozone/container/common/interfaces/TestVolumeSet.java b/hadoop-hdds/container-service/src/test/java/org/apache/hadoop/ozone/container/common/interfaces/TestVolumeSet.java
new file mode 100644
index 0000000..5a1bc79
--- /dev/null
+++ b/hadoop-hdds/container-service/src/test/java/org/apache/hadoop/ozone/container/common/interfaces/TestVolumeSet.java
@@ -0,0 +1,138 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.hadoop.ozone.container.common.interfaces;
+
+import org.apache.commons.logging.LogFactory;
+import org.apache.hadoop.fs.Path;
+import org.apache.hadoop.hdds.conf.OzoneConfiguration;
+import org.apache.hadoop.hdfs.DFSConfigKeys;
+import org.apache.hadoop.hdfs.MiniDFSCluster;
+import org.apache.hadoop.ozone.container.common.impl.VolumeInfo;
+import org.apache.hadoop.ozone.container.common.impl.VolumeSet;
+import org.apache.hadoop.test.GenericTestUtils.LogCapturer;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.Timeout;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Tests {@link VolumeSet} operations.
+ */
+public class TestVolumeSet {
+
+  private OzoneConfiguration conf;
+  protected VolumeSet volumeSet;
+  protected final String baseDir = MiniDFSCluster.getBaseDirectory();
+  protected final String volume1 = baseDir + "disk1";
+  protected final String volume2 = baseDir + "disk2";
+  private final List<String> volumes = new ArrayList<>();
+
+  private void initializeVolumeSet() throws Exception {
+    volumeSet = new VolumeSet(conf);
+  }
+
+  @Rule
+  public Timeout testTimeout = new Timeout(300_000);
+
+  @Before
+  public void setup() throws Exception {
+    conf = new OzoneConfiguration();
+    String dataDirKey = volume1 + "," + volume2;
+    volumes.add(volume1);
+    volumes.add(volume2);
+    conf.set(DFSConfigKeys.DFS_DATANODE_DATA_DIR_KEY, dataDirKey);
+    initializeVolumeSet();
+  }
+
+  @Test
+  public void testVolumeSetInitialization() throws Exception {
+
+    List<VolumeInfo> volumesList = volumeSet.getVolumesList();
+
+    // VolumeSet initialization should add volume1 and volume2 to VolumeSet
+    assertEquals("VolumeSet intialization is incorrect",
+        volumesList.size(), volumes.size());
+    assertEquals(volume1, volumesList.get(0).getRootDir().toString());
+    assertEquals(volume2, volumesList.get(1).getRootDir().toString());
+  }
+
+  @Test
+  public void testAddVolume() throws Exception {
+
+    List<VolumeInfo> volumesList = volumeSet.getVolumesList();
+    assertEquals(2, volumeSet.getVolumesList().size());
+
+    // Add a volume to VolumeSet
+    String volume3 = baseDir + "disk3";
+    volumeSet.addVolume(volume3);
+
+    assertEquals(3, volumeSet.getVolumesList().size());
+    assertEquals("AddVolume did not add requested volume to VolumeSet",
+        volume3,
+        volumeSet.getVolumesList().get(2).getRootDir().toString());
+  }
+
+  @Test
+  public void testFailVolume() throws Exception {
+
+    //Fail a volume
+    volumeSet.failVolume(volume1);
+
+    // Failed volume should not show up in the volumeList
+    assertEquals(1, volumeSet.getVolumesList().size());
+
+    // Failed volume should be added to FailedVolumeList
+    assertEquals("Failed volume not present in FailedVolumeList",
+        1, volumeSet.getFailedVolumesList().size());
+    assertEquals("Failed Volume list did not match", volume1,
+        volumeSet.getFailedVolumesList().get(0).getRootDir().toString());
+
+    // Failed volume should exist in VolumeMap with isFailed flag set to true
+    Path volume1Path = new Path(volume1);
+    assertTrue(volumeSet.getVolumeMap().containsKey(volume1Path));
+    assertTrue(volumeSet.getVolumeMap().get(volume1Path).isFailed());
+  }
+
+  @Test
+  public void testRemoveVolume() throws Exception {
+
+    List<VolumeInfo> volumesList = volumeSet.getVolumesList();
+    assertEquals(2, volumeSet.getVolumesList().size());
+
+    // Remove a volume from VolumeSet
+    volumeSet.removeVolume(volume1);
+    assertEquals(1, volumeSet.getVolumesList().size());
+
+    // Attempting to remove a volume which does not exist in VolumeSet should
+    // log a warning.
+    LogCapturer logs = LogCapturer.captureLogs(
+        LogFactory.getLog(VolumeSet.class));
+    volumeSet.removeVolume(volume1);
+    assertEquals(1, volumeSet.getVolumesList().size());
+    String expectedLogMessage = "Volume: " + volume1 + " does not exist in "
+        + "volumeMap.";
+    assertTrue("Log output does not contain expected log message: "
+        + expectedLogMessage, logs.getOutput().contains(expectedLogMessage));
+  }
+}


---------------------------------------------------------------------
To unsubscribe, e-mail: common-commits-unsubscribe@hadoop.apache.org
For additional commands, e-mail: common-commits-help@hadoop.apache.org