Repository: incubator-ratis
Updated Branches:
  refs/heads/master 9b84d79cf -> 737db6543


RATIS-306. Support multiple storage directories.  Contributed by Nanda kumar


Project: http://git-wip-us.apache.org/repos/asf/incubator-ratis/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-ratis/commit/737db654
Tree: http://git-wip-us.apache.org/repos/asf/incubator-ratis/tree/737db654
Diff: http://git-wip-us.apache.org/repos/asf/incubator-ratis/diff/737db654

Branch: refs/heads/master
Commit: 737db65432f41de9f0931781f462ac3e2ee5a763
Parents: 9b84d79
Author: Tsz Wo Nicholas Sze <[email protected]>
Authored: Mon Oct 8 21:35:02 2018 +0800
Committer: Tsz Wo Nicholas Sze <[email protected]>
Committed: Mon Oct 8 21:35:02 2018 +0800

----------------------------------------------------------------------
 .../java/org/apache/ratis/conf/ConfUtils.java   |  15 +++
 .../org/apache/ratis/conf/RaftProperties.java   |  27 ++++
 .../ratis/examples/arithmetic/cli/Server.java   |   3 +-
 .../ratis/server/RaftServerConfigKeys.java      |  12 +-
 .../ratis/server/impl/RaftServerProxy.java      |  43 ++++---
 .../apache/ratis/server/impl/ServerState.java   |  38 +++++-
 .../java/org/apache/ratis/MiniRaftCluster.java  |   2 +-
 .../ratis/server/TestRaftServerConfigKeys.java  |  98 ++++++++++++++
 .../ratis/server/impl/TestServerState.java      | 127 +++++++++++++++++++
 .../ratis/server/storage/TestCacheEviction.java |   3 +-
 .../server/storage/TestRaftLogReadWrite.java    |   4 +-
 .../server/storage/TestRaftLogSegment.java      |   3 +-
 .../server/storage/TestSegmentedRaftLog.java    |   2 +-
 13 files changed, 343 insertions(+), 34 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-ratis/blob/737db654/ratis-common/src/main/java/org/apache/ratis/conf/ConfUtils.java
----------------------------------------------------------------------
diff --git a/ratis-common/src/main/java/org/apache/ratis/conf/ConfUtils.java 
b/ratis-common/src/main/java/org/apache/ratis/conf/ConfUtils.java
index 430dbf0..453ea0e 100644
--- a/ratis-common/src/main/java/org/apache/ratis/conf/ConfUtils.java
+++ b/ratis-common/src/main/java/org/apache/ratis/conf/ConfUtils.java
@@ -30,6 +30,7 @@ import java.lang.reflect.Field;
 import java.lang.reflect.Modifier;
 import java.net.InetSocketAddress;
 import java.util.Arrays;
+import java.util.List;
 import java.util.function.BiConsumer;
 import java.util.function.BiFunction;
 import java.util.function.Consumer;
@@ -132,6 +133,13 @@ public interface ConfUtils {
     return get(fileGetter, key, defaultValue, logger, assertions);
   }
 
+  @SafeVarargs
+  static List<File> getFiles(
+      BiFunction<String, List<File>, List<File>> fileGetter,
+      String key, List<File> defaultValue, Consumer<String> logger, 
BiConsumer<String, List<File>>... assertions) {
+    return get(fileGetter, key, defaultValue, logger, assertions);
+  }
+
 
   @SafeVarargs
   static SizeInBytes getSizeInBytes(
@@ -195,6 +203,13 @@ public interface ConfUtils {
   }
 
   @SafeVarargs
+  static void setFiles(
+      BiConsumer<String, List<File>> fileSetter, String key, List<File> value,
+      BiConsumer<String, List<File>>... assertions) {
+    set(fileSetter, key, value, assertions);
+  }
+
+  @SafeVarargs
   static void setSizeInBytes(
       BiConsumer<String, String> stringSetter, String key, SizeInBytes value,
       BiConsumer<String, Long>... assertions) {

http://git-wip-us.apache.org/repos/asf/incubator-ratis/blob/737db654/ratis-common/src/main/java/org/apache/ratis/conf/RaftProperties.java
----------------------------------------------------------------------
diff --git 
a/ratis-common/src/main/java/org/apache/ratis/conf/RaftProperties.java 
b/ratis-common/src/main/java/org/apache/ratis/conf/RaftProperties.java
index 03582b9..9b853d6 100644
--- a/ratis-common/src/main/java/org/apache/ratis/conf/RaftProperties.java
+++ b/ratis-common/src/main/java/org/apache/ratis/conf/RaftProperties.java
@@ -48,6 +48,7 @@ import java.util.function.BiFunction;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 import java.util.regex.PatternSyntaxException;
+import java.util.stream.Collectors;
 
 /** 
  * Provides access to configuration parameters. The current implementation is a
@@ -637,6 +638,26 @@ public class RaftProperties {
     return valueString == null? defaultValue: new File(valueString);
   }
 
+  /**
+   * Get the value of the <code>name</code> property as a list
+   * of <code>File</code>.
+   * The value of the property specifies a list of comma separated path names.
+   * If no such property is specified, then <code>defaultValue</code> is
+   * returned.
+   *
+   * @param name the property name.
+   * @param defaultValue default value.
+   * @return property value as a List of File, or <code>defaultValue</code>.
+   */
+  public List<File> getFiles(String name, List<File> defaultValue) {
+    String valueString = getRaw(name);
+    if (null == valueString) {
+      return defaultValue;
+    }
+    String[] paths = getTrimmedStrings(name);
+    return Arrays.stream(paths).map(File::new).collect(Collectors.toList());
+  }
+
   public void setFile(String name, File value) {
     try {
       set(name, value.getCanonicalPath());
@@ -646,6 +667,12 @@ public class RaftProperties {
     }
   }
 
+  public void setFiles(String name, List<File> value) {
+    String paths = value.stream().map(File::getAbsolutePath)
+        .collect(Collectors.joining(","));
+    set(name, paths);
+  }
+
   /** @return property value; if it is not set, return the default value. */
   public SizeInBytes getSizeInBytes(String name, SizeInBytes defaultValue) {
     final String valueString = getTrimmed(name);

http://git-wip-us.apache.org/repos/asf/incubator-ratis/blob/737db654/ratis-examples/src/main/java/org/apache/ratis/examples/arithmetic/cli/Server.java
----------------------------------------------------------------------
diff --git 
a/ratis-examples/src/main/java/org/apache/ratis/examples/arithmetic/cli/Server.java
 
b/ratis-examples/src/main/java/org/apache/ratis/examples/arithmetic/cli/Server.java
index 564dfcf..f5be3e1 100644
--- 
a/ratis-examples/src/main/java/org/apache/ratis/examples/arithmetic/cli/Server.java
+++ 
b/ratis-examples/src/main/java/org/apache/ratis/examples/arithmetic/cli/Server.java
@@ -33,6 +33,7 @@ import org.apache.ratis.statemachine.StateMachine;
 import org.apache.ratis.util.NetUtils;
 
 import java.io.File;
+import java.util.Collections;
 import java.util.Objects;
 
 /**
@@ -59,7 +60,7 @@ public class Server extends SubCommandBase {
     final int port = 
NetUtils.createSocketAddr(getPeer(peerId).getAddress()).getPort();
     GrpcConfigKeys.Server.setPort(properties, port);
     properties.setInt(GrpcConfigKeys.OutputStream.RETRY_TIMES_KEY, 
Integer.MAX_VALUE);
-    RaftServerConfigKeys.setStorageDir(properties, storageDir);
+    RaftServerConfigKeys.setStorageDirs(properties, 
Collections.singletonList(storageDir));
     StateMachine stateMachine = new ArithmeticStateMachine();
 
     final RaftGroup raftGroup = 
RaftGroup.valueOf(RaftGroupId.valueOf(ByteString.copyFromUtf8(raftGroupId)), 
peers);

http://git-wip-us.apache.org/repos/asf/incubator-ratis/blob/737db654/ratis-server/src/main/java/org/apache/ratis/server/RaftServerConfigKeys.java
----------------------------------------------------------------------
diff --git 
a/ratis-server/src/main/java/org/apache/ratis/server/RaftServerConfigKeys.java 
b/ratis-server/src/main/java/org/apache/ratis/server/RaftServerConfigKeys.java
index c878c8f..33662c9 100644
--- 
a/ratis-server/src/main/java/org/apache/ratis/server/RaftServerConfigKeys.java
+++ 
b/ratis-server/src/main/java/org/apache/ratis/server/RaftServerConfigKeys.java
@@ -25,6 +25,8 @@ import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 import java.io.File;
+import java.util.Collections;
+import java.util.List;
 import java.util.concurrent.TimeUnit;
 import java.util.function.Consumer;
 
@@ -39,12 +41,12 @@ public interface RaftServerConfigKeys {
   String PREFIX = "raft.server";
 
   String STORAGE_DIR_KEY = PREFIX + ".storage.dir";
-  File STORAGE_DIR_DEFAULT = new File("/tmp/raft-server/");
-  static File storageDir(RaftProperties properties) {
-    return getFile(properties::getFile, STORAGE_DIR_KEY, STORAGE_DIR_DEFAULT, 
getDefaultLog());
+  List<File> STORAGE_DIR_DEFAULT = Collections.singletonList(new 
File("/tmp/raft-server/"));
+  static List<File> storageDirs(RaftProperties properties) {
+    return getFiles(properties::getFiles, STORAGE_DIR_KEY, 
STORAGE_DIR_DEFAULT, getDefaultLog());
   }
-  static void setStorageDir(RaftProperties properties, File storageDir) {
-    setFile(properties::setFile, STORAGE_DIR_KEY, storageDir);
+  static void setStorageDirs(RaftProperties properties, List<File> storageDir) 
{
+    setFiles(properties::setFiles, STORAGE_DIR_KEY, storageDir);
   }
 
   /**

http://git-wip-us.apache.org/repos/asf/incubator-ratis/blob/737db654/ratis-server/src/main/java/org/apache/ratis/server/impl/RaftServerProxy.java
----------------------------------------------------------------------
diff --git 
a/ratis-server/src/main/java/org/apache/ratis/server/impl/RaftServerProxy.java 
b/ratis-server/src/main/java/org/apache/ratis/server/impl/RaftServerProxy.java
index f12905c..4622002 100644
--- 
a/ratis-server/src/main/java/org/apache/ratis/server/impl/RaftServerProxy.java
+++ 
b/ratis-server/src/main/java/org/apache/ratis/server/impl/RaftServerProxy.java
@@ -41,16 +41,19 @@ import java.io.File;
 import java.io.IOException;
 import java.net.InetSocketAddress;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collection;
 import java.util.List;
 import java.util.Map;
 import java.util.Objects;
+import java.util.Optional;
 import java.util.UUID;
 import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.CompletionException;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.ConcurrentMap;
 import java.util.stream.Collectors;
+import java.util.stream.Stream;
 
 public class RaftServerProxy implements RaftServer {
   public static final Logger LOG = 
LoggerFactory.getLogger(RaftServerProxy.class);
@@ -163,25 +166,27 @@ public class RaftServerProxy implements RaftServer {
 
   /** Check the storage dir and add groups*/
   void initGroups(RaftGroup group) {
-    final File dir = RaftServerConfigKeys.storageDir(properties);
-    if (dir.isDirectory()) {
-      for(File sub : dir.listFiles()) {
-        if (sub.isDirectory()) {
-          LOG.info("{}: found a subdirectory {}", getId(), sub);
-          try {
-            final RaftGroupId groupId = 
RaftGroupId.valueOf(UUID.fromString(sub.getName()));
-            if (group == null || !groupId.equals(group.getGroupId())) {
-              addGroup(RaftGroup.valueOf(groupId));
-            }
-          } catch(Throwable t) {
-            LOG.warn(getId() + ": Failed to initialize the group directory " + 
sub.getAbsolutePath() + ".  Ignoring it", t);
-          }
-        }
-      }
-    }
-    if (group != null) {
-      addGroup(group);
-    }
+
+    final Optional<RaftGroup> raftGroup = Optional.ofNullable(group);
+    final Optional<RaftGroupId> raftGroupId = 
raftGroup.map(RaftGroup::getGroupId);
+
+    RaftServerConfigKeys.storageDirs(properties).parallelStream()
+        .forEach((dir) -> Optional.ofNullable(dir.listFiles())
+            .map(Arrays::stream).orElse(Stream.empty())
+            .filter(File::isDirectory)
+            .forEach(sub -> {
+              try {
+                LOG.info("{}: found a subdirectory {}", getId(), sub);
+                final RaftGroupId groupId = 
RaftGroupId.valueOf(UUID.fromString(sub.getName()));
+                if (!raftGroupId.filter(groupId::equals).isPresent()) {
+                  addGroup(RaftGroup.valueOf(groupId));
+                }
+              } catch (Throwable t) {
+                LOG.warn(getId() + ": Failed to initialize the group directory 
"
+                    + sub.getAbsolutePath() + ".  Ignoring it", t);
+              }
+            }));
+    raftGroup.ifPresent(this::addGroup);
   }
 
   private CompletableFuture<RaftServerImpl> newRaftServerImpl(RaftGroup group) 
{

http://git-wip-us.apache.org/repos/asf/incubator-ratis/blob/737db654/ratis-server/src/main/java/org/apache/ratis/server/impl/ServerState.java
----------------------------------------------------------------------
diff --git 
a/ratis-server/src/main/java/org/apache/ratis/server/impl/ServerState.java 
b/ratis-server/src/main/java/org/apache/ratis/server/impl/ServerState.java
index 10afbfd..8fcc6b7 100644
--- a/ratis-server/src/main/java/org/apache/ratis/server/impl/ServerState.java
+++ b/ratis-server/src/main/java/org/apache/ratis/server/impl/ServerState.java
@@ -33,7 +33,14 @@ import org.apache.ratis.util.Timestamp;
 import java.io.Closeable;
 import java.io.File;
 import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
 import java.util.Objects;
+import java.util.Optional;
 import java.util.concurrent.TimeUnit;
 import java.util.function.Consumer;
 
@@ -90,10 +97,10 @@ public class ServerState implements Closeable {
     configurationManager = new ConfigurationManager(initialConf);
     LOG.info("{}: {}", id, configurationManager);
 
-    final File dir = RaftServerConfigKeys.storageDir(prop);
     // use full uuid string to create a subdirectory
-    storage = new RaftStorage(new File(dir, 
group.getGroupId().getUuid().toString()),
-        RaftServerConstants.StartupOption.REGULAR);
+    final File dir = chooseStorageDir(RaftServerConfigKeys.storageDirs(prop),
+        group.getGroupId().getUuid().toString());
+    storage = new RaftStorage(dir, RaftServerConstants.StartupOption.REGULAR);
     snapshotManager = new SnapshotManager(storage, id);
 
     long lastApplied = initStatemachine(stateMachine, group.getGroupId());
@@ -120,6 +127,31 @@ public class ServerState implements Closeable {
          lastApplied, prop);
   }
 
+
+  static File chooseStorageDir(List<File> volumes, String targetSubDir) throws 
IOException {
+    final Map<File, Integer> numberOfStorageDirPerVolume = new HashMap<>();
+    final File[] empty = {};
+    final List<File> resultList = new ArrayList<>();
+    volumes.stream().flatMap(volume -> {
+      final File[] dirs = 
Optional.ofNullable(volume.listFiles()).orElse(empty);
+      numberOfStorageDirPerVolume.put(volume, dirs.length);
+      return Arrays.stream(dirs);
+    }).filter(dir -> targetSubDir.equals(dir.getName()))
+        .forEach(resultList::add);
+
+    if (resultList.size() > 1) {
+      throw new IOException("More than one directories found for " + 
targetSubDir + ": " + resultList);
+    }
+    if (resultList.size() == 1) {
+      return resultList.get(0);
+    }
+    return numberOfStorageDirPerVolume.entrySet().stream()
+        .min(Comparator.comparing(Map.Entry::getValue))
+        .map(Map.Entry::getKey)
+        .map(v -> new File(v, targetSubDir))
+        .orElseThrow(() -> new IOException("No storage directory found."));
+  }
+
   private long initStatemachine(StateMachine sm, RaftGroupId groupId)
       throws IOException {
     sm.initialize(server.getProxy(), groupId, storage);

http://git-wip-us.apache.org/repos/asf/incubator-ratis/blob/737db654/ratis-server/src/test/java/org/apache/ratis/MiniRaftCluster.java
----------------------------------------------------------------------
diff --git a/ratis-server/src/test/java/org/apache/ratis/MiniRaftCluster.java 
b/ratis-server/src/test/java/org/apache/ratis/MiniRaftCluster.java
index 7db7685..da81fc3 100644
--- a/ratis-server/src/test/java/org/apache/ratis/MiniRaftCluster.java
+++ b/ratis-server/src/test/java/org/apache/ratis/MiniRaftCluster.java
@@ -237,7 +237,7 @@ public abstract class MiniRaftCluster implements Closeable {
         LOG.info("Formatted directory {}", dir);
       }
       final RaftProperties prop = new RaftProperties(properties);
-      RaftServerConfigKeys.setStorageDir(prop, dir);
+      RaftServerConfigKeys.setStorageDirs(prop, 
Collections.singletonList(dir));
       return newRaftServer(id, getStateMachineRegistry(properties), group, 
prop);
     } catch (IOException e) {
       throw new RuntimeException(e);

http://git-wip-us.apache.org/repos/asf/incubator-ratis/blob/737db654/ratis-server/src/test/java/org/apache/ratis/server/TestRaftServerConfigKeys.java
----------------------------------------------------------------------
diff --git 
a/ratis-server/src/test/java/org/apache/ratis/server/TestRaftServerConfigKeys.java
 
b/ratis-server/src/test/java/org/apache/ratis/server/TestRaftServerConfigKeys.java
new file mode 100644
index 0000000..53c8871
--- /dev/null
+++ 
b/ratis-server/src/test/java/org/apache/ratis/server/TestRaftServerConfigKeys.java
@@ -0,0 +1,98 @@
+/**
+ * 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.ratis.server;
+
+import org.apache.ratis.BaseTest;
+import org.apache.ratis.conf.RaftProperties;
+import org.apache.ratis.util.FileUtils;
+import org.apache.ratis.util.JavaUtils;
+import org.junit.AfterClass;
+import org.junit.Assert;
+import org.junit.Test;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.UUID;
+import java.util.concurrent.ThreadLocalRandom;
+import java.util.function.Supplier;
+import java.util.stream.Collectors;
+import java.util.stream.IntStream;
+
+/**
+ * Test cases to verify RaftServerConfigKeys.
+ */
+public class TestRaftServerConfigKeys {
+
+  private static final Supplier<File> rootTestDir = JavaUtils.memoize(
+      () -> new File(BaseTest.getRootTestDir(),
+          TestRaftServerConfigKeys.class.getSimpleName() +
+              Integer.toHexString(ThreadLocalRandom.current().nextInt())));
+
+  @AfterClass
+  public static void tearDown() throws IOException {
+    FileUtils.deleteFully(rootTestDir.get());
+  }
+
+  /**
+   * Sets the value to <code>raft.server.storage.dir</code> via
+   * RaftServerConfigKeys and verifies it by reading directly from property.
+   */
+  @Test
+  public void testStorageDirsProperty() {
+    final File testDir = new File(
+        rootTestDir.get(), UUID.randomUUID().toString());
+    final List<File> directories = new ArrayList<>();
+    final  RaftProperties properties = new RaftProperties();
+
+    IntStream.range(0, 10).mapToObj((i) -> new File(testDir,
+        Integer.toString(i))).forEach(directories::add);
+    RaftServerConfigKeys.setStorageDirs(properties, directories);
+
+    final String expected = directories.stream().map(File::getAbsolutePath)
+        .collect(Collectors.joining(","));
+    final String actual = properties.get(RaftServerConfigKeys.STORAGE_DIR_KEY);
+    Assert.assertEquals(expected, actual);
+  }
+
+  /**
+   * Sets the value to <code>raft.server.storage.dir</code> via
+   * RaftServerConfigKeys and also verifies the same via RaftServerConfigKeys.
+   */
+  @Test
+  public void testStorageDirs() {
+    final File testDir = new File(
+        rootTestDir.get(), UUID.randomUUID().toString());
+    final List<File> directories = new ArrayList<>();
+    IntStream.range(0, 10).mapToObj((i) -> new File(testDir,
+        Integer.toString(i))).forEach(directories::add);
+    RaftProperties properties = new RaftProperties();
+    RaftServerConfigKeys.setStorageDirs(properties, directories);
+
+    final List<File> storageDirs = 
RaftServerConfigKeys.storageDirs(properties);
+    final List<String> expectedDirs = directories.stream()
+        .map(File::getAbsolutePath).collect(Collectors.toList());
+    final List<String> actualDirs = storageDirs.stream()
+        .map(File::getAbsolutePath).collect(Collectors.toList());
+    actualDirs.removeAll(expectedDirs);
+    Assert.assertEquals(directories.size(), storageDirs.size());
+    Assert.assertEquals(0, actualDirs.size());
+  }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-ratis/blob/737db654/ratis-server/src/test/java/org/apache/ratis/server/impl/TestServerState.java
----------------------------------------------------------------------
diff --git 
a/ratis-server/src/test/java/org/apache/ratis/server/impl/TestServerState.java 
b/ratis-server/src/test/java/org/apache/ratis/server/impl/TestServerState.java
new file mode 100644
index 0000000..5801d2b
--- /dev/null
+++ 
b/ratis-server/src/test/java/org/apache/ratis/server/impl/TestServerState.java
@@ -0,0 +1,127 @@
+/**
+ * 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.ratis.server.impl;
+
+import org.apache.ratis.BaseTest;
+import org.apache.ratis.util.FileUtils;
+import org.apache.ratis.util.JavaUtils;
+import org.junit.AfterClass;
+import org.junit.Assert;
+import org.junit.Test;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.UUID;
+import java.util.concurrent.ThreadLocalRandom;
+import java.util.function.Supplier;
+import java.util.stream.IntStream;
+
+/**
+ * Test cases to verify ServerState.
+ */
+public class TestServerState {
+
+  private static final Supplier<File> rootTestDir = JavaUtils.memoize(
+      () -> new File(BaseTest.getRootTestDir(),
+          TestServerState.class.getSimpleName() +
+              Integer.toHexString(ThreadLocalRandom.current().nextInt())));
+
+  @AfterClass
+  public static void tearDown() throws IOException {
+    FileUtils.deleteFully(rootTestDir.get());
+  }
+
+  /**
+   * Tests choosing of storage directory when only one volume is configured.
+   *
+   * @throws IOException in case of exception.
+   */
+  @Test
+  public void testChooseStorageDirWithOneVolume() throws IOException {
+    File testDir = new File(rootTestDir.get(), UUID.randomUUID().toString());
+    List<File> directories = Collections.singletonList(testDir);
+    String subDirOne = UUID.randomUUID().toString();
+    String subDirTwo = UUID.randomUUID().toString();
+    File storageDirOne = ServerState.chooseStorageDir(directories, subDirOne);
+    File storageDirTwo = ServerState.chooseStorageDir(directories, subDirTwo);
+    File expectedOne = new File(testDir, subDirOne);
+    File expectedTwo = new File(testDir, subDirTwo);
+    Assert.assertEquals(expectedOne.getCanonicalPath(),
+        storageDirOne.getCanonicalPath());
+    Assert.assertEquals(expectedTwo.getCanonicalPath(),
+        storageDirTwo.getCanonicalPath());
+  }
+
+  /**
+   * Tests choosing of storage directory when multiple volumes are configured.
+   *
+   * @throws IOException in case of exception.
+   */
+  @Test
+  public void testChooseStorageDirWithMultipleVolumes() throws IOException {
+    File testDir = new File(rootTestDir.get(), UUID.randomUUID().toString());
+    List<File> directories = new ArrayList<>();
+    IntStream.range(0, 10).mapToObj((i) -> new File(testDir,
+        Integer.toString(i))).forEach((dir) -> {
+      try {
+        FileUtils.createDirectories(dir);
+        directories.add(dir);
+      } catch (IOException e) {
+        throw new RuntimeException(e);
+      }
+    });
+
+    directories.stream().filter((dir) -> Integer.parseInt(dir.getName()) != 6)
+        .forEach(
+            (dir) -> {
+              try {
+                FileUtils.createDirectories(
+                    new File(dir, UUID.randomUUID().toString()));
+              } catch (IOException e) {
+                throw new RuntimeException(e);
+              }
+            });
+    String subDir = UUID.randomUUID().toString();
+    File storageDirectory = ServerState.chooseStorageDir(directories, subDir);
+    File expected = new File(directories.get(6), subDir);
+    Assert.assertEquals(expected.getCanonicalPath(),
+        storageDirectory.getCanonicalPath());
+  }
+
+  /**
+   * Tests choosing of storage directory when only no volume is configured.
+   *
+   * @throws IOException in case of exception.
+   */
+  @Test
+  public void testChooseStorageDirWithNoVolume() {
+    try {
+      ServerState.chooseStorageDir(
+          Collections.emptyList(), UUID.randomUUID().toString());
+      Assert.fail();
+    } catch (IOException ex) {
+      String expectedErrMsg = "No storage directory found.";
+      Assert.assertEquals(expectedErrMsg, ex.getMessage());
+    }
+  }
+
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-ratis/blob/737db654/ratis-server/src/test/java/org/apache/ratis/server/storage/TestCacheEviction.java
----------------------------------------------------------------------
diff --git 
a/ratis-server/src/test/java/org/apache/ratis/server/storage/TestCacheEviction.java
 
b/ratis-server/src/test/java/org/apache/ratis/server/storage/TestCacheEviction.java
index a8f5fab..a883727 100644
--- 
a/ratis-server/src/test/java/org/apache/ratis/server/storage/TestCacheEviction.java
+++ 
b/ratis-server/src/test/java/org/apache/ratis/server/storage/TestCacheEviction.java
@@ -40,6 +40,7 @@ import org.mockito.Mockito;
 
 import java.io.File;
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.List;
 import java.util.concurrent.CompletableFuture;
 
@@ -156,7 +157,7 @@ public class TestCacheEviction extends BaseTest {
     final int maxCachedNum = 
RaftServerConfigKeys.Log.maxCachedSegmentNum(prop);
 
     File storageDir = getTestDir();
-    RaftServerConfigKeys.setStorageDir(prop, storageDir);
+    RaftServerConfigKeys.setStorageDirs(prop,  
Collections.singletonList(storageDir));
     RaftStorage storage = new RaftStorage(storageDir, 
RaftServerConstants.StartupOption.REGULAR);
 
     RaftServerImpl server = Mockito.mock(RaftServerImpl.class);

http://git-wip-us.apache.org/repos/asf/incubator-ratis/blob/737db654/ratis-server/src/test/java/org/apache/ratis/server/storage/TestRaftLogReadWrite.java
----------------------------------------------------------------------
diff --git 
a/ratis-server/src/test/java/org/apache/ratis/server/storage/TestRaftLogReadWrite.java
 
b/ratis-server/src/test/java/org/apache/ratis/server/storage/TestRaftLogReadWrite.java
index c218179..6ffb1bf 100644
--- 
a/ratis-server/src/test/java/org/apache/ratis/server/storage/TestRaftLogReadWrite.java
+++ 
b/ratis-server/src/test/java/org/apache/ratis/server/storage/TestRaftLogReadWrite.java
@@ -29,7 +29,6 @@ import 
org.apache.ratis.thirdparty.com.google.protobuf.CodedOutputStream;
 import org.apache.ratis.proto.RaftProtos.LogEntryProto;
 import org.apache.ratis.util.FileUtils;
 import org.apache.ratis.util.ProtoUtils;
-import org.apache.ratis.util.SizeInBytes;
 import org.junit.After;
 import org.junit.Assert;
 import org.junit.Before;
@@ -41,6 +40,7 @@ import java.io.IOException;
 import java.io.RandomAccessFile;
 import java.nio.ByteBuffer;
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.List;
 
 /**
@@ -59,7 +59,7 @@ public class TestRaftLogReadWrite extends BaseTest {
   public void setup() throws Exception {
     storageDir = getTestDir();
     RaftProperties properties = new RaftProperties();
-    RaftServerConfigKeys.setStorageDir(properties, storageDir);
+    RaftServerConfigKeys.setStorageDirs(properties,  
Collections.singletonList(storageDir));
     this.segmentMaxSize =
         RaftServerConfigKeys.Log.segmentSizeMax(properties).getSize();
     this.preallocatedSize =

http://git-wip-us.apache.org/repos/asf/incubator-ratis/blob/737db654/ratis-server/src/test/java/org/apache/ratis/server/storage/TestRaftLogSegment.java
----------------------------------------------------------------------
diff --git 
a/ratis-server/src/test/java/org/apache/ratis/server/storage/TestRaftLogSegment.java
 
b/ratis-server/src/test/java/org/apache/ratis/server/storage/TestRaftLogSegment.java
index ce5a92d..8d696b1 100644
--- 
a/ratis-server/src/test/java/org/apache/ratis/server/storage/TestRaftLogSegment.java
+++ 
b/ratis-server/src/test/java/org/apache/ratis/server/storage/TestRaftLogSegment.java
@@ -41,6 +41,7 @@ import java.nio.file.Files;
 import java.nio.file.Path;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collections;
 import java.util.List;
 
 import static 
org.apache.ratis.server.impl.RaftServerConstants.INVALID_LOG_INDEX;
@@ -62,7 +63,7 @@ public class TestRaftLogSegment extends BaseTest {
   public void setup() throws Exception {
     RaftProperties properties = new RaftProperties();
     storageDir = getTestDir();
-    RaftServerConfigKeys.setStorageDir(properties, storageDir);
+    RaftServerConfigKeys.setStorageDirs(properties,  
Collections.singletonList(storageDir));
     this.segmentMaxSize =
         RaftServerConfigKeys.Log.segmentSizeMax(properties).getSize();
     this.preallocatedSize =

http://git-wip-us.apache.org/repos/asf/incubator-ratis/blob/737db654/ratis-server/src/test/java/org/apache/ratis/server/storage/TestSegmentedRaftLog.java
----------------------------------------------------------------------
diff --git 
a/ratis-server/src/test/java/org/apache/ratis/server/storage/TestSegmentedRaftLog.java
 
b/ratis-server/src/test/java/org/apache/ratis/server/storage/TestSegmentedRaftLog.java
index 890f31b..0de9e8a 100644
--- 
a/ratis-server/src/test/java/org/apache/ratis/server/storage/TestSegmentedRaftLog.java
+++ 
b/ratis-server/src/test/java/org/apache/ratis/server/storage/TestSegmentedRaftLog.java
@@ -87,7 +87,7 @@ public class TestSegmentedRaftLog extends BaseTest {
   public void setup() throws Exception {
     storageDir = getTestDir();
     properties = new RaftProperties();
-    RaftServerConfigKeys.setStorageDir(properties, storageDir);
+    RaftServerConfigKeys.setStorageDirs(properties,  
Collections.singletonList(storageDir));
     storage = new RaftStorage(storageDir, 
RaftServerConstants.StartupOption.REGULAR);
     this.segmentMaxSize =
         RaftServerConfigKeys.Log.segmentSizeMax(properties).getSize();

Reply via email to