You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@kudu.apache.org by mp...@apache.org on 2017/07/11 19:32:17 UTC

kudu git commit: mini_cluster: support multiple data dirs

Repository: kudu
Updated Branches:
  refs/heads/master 5b18be1a7 -> af39a2aa0


mini_cluster: support multiple data dirs

This patch gives MiniTabletServers and MiniMasters the ability to
start with multiple data dirs. A number of dirs is specified and
directory names are generated for the specified number. A WAL dir will
be generated with an appropriate suffix.

The setup for MiniMaster is also changed to more closely resemble
MiniTabletServer. MasterOptions are generated in the constructor and a
single Start() call will create the Master.

The original directory structure is kept as a default.

E.g. if the number of data dirs is 3, the following directories will
be generated for tservers (a similar one is created for masters):
/test_dir/test_tserver/wal
/test_dir/test_tserver/data-0
/test_dir/test_tserver/data-1
/test_dir/test_tserver/data-2

Original:
/test_dir/test_tserver

Tests are added to the new mini_tablet_server-test and mini_master-test
to exercise this.

Change-Id: I52c9352f1a3565d58149cf2c63d37246c6b39c23
Reviewed-on: http://gerrit.cloudera.org:8080/7211
Reviewed-by: Mike Percy <mp...@apache.org>
Tested-by: Mike Percy <mp...@apache.org>


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

Branch: refs/heads/master
Commit: af39a2aa0d1506856d763033b13675d347a7cb15
Parents: 5b18be1
Author: Andrew Wong <aw...@cloudera.com>
Authored: Fri Jun 16 09:25:37 2017 -0700
Committer: Mike Percy <mp...@apache.org>
Committed: Tue Jul 11 19:31:58 2017 +0000

----------------------------------------------------------------------
 .../integration-tests/internal_mini_cluster.cc  | 69 ++++++++-------
 .../integration-tests/internal_mini_cluster.h   | 16 ++--
 .../master_replication-itest.cc                 |  3 +-
 src/kudu/master/CMakeLists.txt                  |  1 +
 src/kudu/master/master-test.cc                  |  1 +
 src/kudu/master/mini_master-test.cc             | 47 ++++++++++
 src/kudu/master/mini_master.cc                  | 93 ++++++++++----------
 src/kudu/master/mini_master.h                   | 28 ++----
 src/kudu/tserver/CMakeLists.txt                 |  1 +
 src/kudu/tserver/mini_tablet_server-test.cc     | 48 ++++++++++
 src/kudu/tserver/mini_tablet_server.cc          | 27 ++++--
 src/kudu/tserver/mini_tablet_server.h           |  5 +-
 src/kudu/tserver/tablet_copy-test-base.h        |  2 +-
 src/kudu/tserver/tablet_server-stress-test.cc   |  2 +-
 src/kudu/tserver/tablet_server-test-base.h      | 10 ++-
 src/kudu/tserver/tablet_server-test.cc          |  2 +-
 16 files changed, 233 insertions(+), 122 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/kudu/blob/af39a2aa/src/kudu/integration-tests/internal_mini_cluster.cc
----------------------------------------------------------------------
diff --git a/src/kudu/integration-tests/internal_mini_cluster.cc b/src/kudu/integration-tests/internal_mini_cluster.cc
index 64c5d06..ec8ff70 100644
--- a/src/kudu/integration-tests/internal_mini_cluster.cc
+++ b/src/kudu/integration-tests/internal_mini_cluster.cc
@@ -51,18 +51,17 @@ using tserver::TabletServer;
 InternalMiniClusterOptions::InternalMiniClusterOptions()
   : num_masters(1),
     num_tablet_servers(1),
+    num_data_dirs(1),
     bind_mode(MiniCluster::kDefaultBindMode) {
 }
 
-InternalMiniCluster::InternalMiniCluster(Env* env, const InternalMiniClusterOptions& options)
+InternalMiniCluster::InternalMiniCluster(Env* env, InternalMiniClusterOptions options)
   : env_(env),
-    fs_root_(!options.data_root.empty() ? options.data_root :
-                JoinPathSegments(GetTestDataDirectory(), "minicluster-data")),
-    num_masters_initial_(options.num_masters),
-    num_ts_initial_(options.num_tablet_servers),
-    master_rpc_ports_(options.master_rpc_ports),
-    tserver_rpc_ports_(options.tserver_rpc_ports),
+    opts_(std::move(options)),
     running_(false) {
+  if (opts_.data_root.empty()) {
+    opts_.data_root = JoinPathSegments(GetTestDataDirectory(), "minicluster-data");
+  }
 }
 
 InternalMiniCluster::~InternalMiniCluster() {
@@ -70,31 +69,31 @@ InternalMiniCluster::~InternalMiniCluster() {
 }
 
 Status InternalMiniCluster::Start() {
-  CHECK(!fs_root_.empty()) << "No Fs root was provided";
+  CHECK(!opts_.data_root.empty()) << "No Fs root was provided";
   CHECK(!running_);
 
-  if (num_masters_initial_ > 1) {
-    CHECK_GE(master_rpc_ports_.size(), num_masters_initial_);
+  if (opts_.num_masters > 1) {
+    CHECK_GE(opts_.master_rpc_ports.size(), opts_.num_masters);
   }
 
-  if (!env_->FileExists(fs_root_)) {
-    RETURN_NOT_OK(env_->CreateDir(fs_root_));
+  if (!env_->FileExists(opts_.data_root)) {
+    RETURN_NOT_OK(env_->CreateDir(opts_.data_root));
   }
 
   // start the masters
-  if (num_masters_initial_ > 1) {
+  if (opts_.num_masters > 1) {
     RETURN_NOT_OK_PREPEND(StartDistributedMasters(),
                           "Couldn't start distributed masters");
   } else {
     RETURN_NOT_OK_PREPEND(StartSingleMaster(), "Couldn't start the single master");
   }
 
-  for (int i = 0; i < num_ts_initial_; i++) {
+  for (int i = 0; i < opts_.num_tablet_servers; i++) {
     RETURN_NOT_OK_PREPEND(AddTabletServer(),
                           Substitute("Error adding TS $0", i));
   }
 
-  RETURN_NOT_OK_PREPEND(WaitForTabletServerCount(num_ts_initial_),
+  RETURN_NOT_OK_PREPEND(WaitForTabletServerCount(opts_.num_tablet_servers),
                         "Waiting for tablet servers to start");
 
   RETURN_NOT_OK_PREPEND(rpc::MessengerBuilder("minicluster-messenger")
@@ -108,17 +107,18 @@ Status InternalMiniCluster::Start() {
 }
 
 Status InternalMiniCluster::StartDistributedMasters() {
-  CHECK_GE(master_rpc_ports_.size(), num_masters_initial_);
-  CHECK_GT(master_rpc_ports_.size(), 1);
+  CHECK_GT(opts_.num_data_dirs, 0);
+  CHECK_GE(opts_.master_rpc_ports.size(), opts_.num_masters);
+  CHECK_GT(opts_.master_rpc_ports.size(), 1);
 
   vector<HostPort> master_rpc_addrs = this->master_rpc_addrs();
   LOG(INFO) << "Creating distributed mini masters. Addrs: "
             << HostPort::ToCommaSeparatedString(master_rpc_addrs);
 
-  for (int i = 0; i < num_masters_initial_; i++) {
+  for (int i = 0; i < opts_.num_masters; i++) {
     shared_ptr<MiniMaster> mini_master(new MiniMaster(GetMasterFsRoot(i), master_rpc_addrs[i]));
-    RETURN_NOT_OK_PREPEND(mini_master->StartDistributedMaster(master_rpc_addrs),
-                          Substitute("Couldn't start follower $0", i));
+    mini_master->SetMasterAddresses(master_rpc_addrs);
+    RETURN_NOT_OK_PREPEND(mini_master->Start(), Substitute("Couldn't start follower $0", i));
     VLOG(1) << "Started MiniMaster with UUID " << mini_master->permanent_uuid()
             << " at index " << i;
     mini_masters_.push_back(std::move(mini_master));
@@ -144,17 +144,18 @@ Status InternalMiniCluster::StartSync() {
 }
 
 Status InternalMiniCluster::StartSingleMaster() {
-  CHECK_EQ(1, num_masters_initial_);
-  CHECK_LE(master_rpc_ports_.size(), 1);
+  CHECK_GT(opts_.num_data_dirs, 0);
+  CHECK_EQ(1, opts_.num_masters);
+  CHECK_LE(opts_.master_rpc_ports.size(), 1);
   uint16_t master_rpc_port = 0;
-  if (master_rpc_ports_.size() == 1) {
-    master_rpc_port = master_rpc_ports_[0];
+  if (opts_.master_rpc_ports.size() == 1) {
+    master_rpc_port = opts_.master_rpc_ports[0];
   }
 
   // start the master (we need the port to set on the servers).
   string bind_ip = GetBindIpForDaemon(MiniCluster::MASTER, /*index=*/ 0, opts_.bind_mode);
-  shared_ptr<MiniMaster> mini_master(
-      new MiniMaster(GetMasterFsRoot(0), HostPort(std::move(bind_ip), master_rpc_port)));
+  shared_ptr<MiniMaster> mini_master(new MiniMaster(GetMasterFsRoot(0),
+      HostPort(std::move(bind_ip), master_rpc_port), opts_.num_data_dirs));
   RETURN_NOT_OK_PREPEND(mini_master->Start(), "Couldn't start master");
   RETURN_NOT_OK(mini_master->master()->WaitUntilCatalogManagerIsLeaderAndReadyForTests(
       MonoDelta::FromSeconds(kMasterStartupWaitTimeSeconds)));
@@ -169,13 +170,13 @@ Status InternalMiniCluster::AddTabletServer() {
   int new_idx = mini_tablet_servers_.size();
 
   uint16_t ts_rpc_port = 0;
-  if (tserver_rpc_ports_.size() > new_idx) {
-    ts_rpc_port = tserver_rpc_ports_[new_idx];
+  if (opts_.tserver_rpc_ports.size() > new_idx) {
+    ts_rpc_port = opts_.tserver_rpc_ports[new_idx];
   }
 
   string bind_ip = GetBindIpForDaemon(MiniCluster::TSERVER, new_idx, opts_.bind_mode);
-  gscoped_ptr<MiniTabletServer> tablet_server(
-      new MiniTabletServer(GetTabletServerFsRoot(new_idx), HostPort(bind_ip, ts_rpc_port)));
+  gscoped_ptr<MiniTabletServer> tablet_server(new MiniTabletServer(GetTabletServerFsRoot(new_idx),
+      HostPort(bind_ip, ts_rpc_port), opts_.num_data_dirs));
 
   // set the master addresses
   tablet_server->options()->master_addresses.clear();
@@ -217,20 +218,20 @@ MiniTabletServer* InternalMiniCluster::mini_tablet_server(int idx) const {
 
 vector<HostPort> InternalMiniCluster::master_rpc_addrs() const {
   vector<HostPort> master_rpc_addrs;
-  for (int i = 0; i < master_rpc_ports_.size(); i++) {
+  for (int i = 0; i < opts_.master_rpc_ports.size(); i++) {
     master_rpc_addrs.emplace_back(
         GetBindIpForDaemon(MiniCluster::MASTER, i, opts_.bind_mode),
-        master_rpc_ports_[i]);
+        opts_.master_rpc_ports[i]);
   }
   return master_rpc_addrs;
 }
 
 string InternalMiniCluster::GetMasterFsRoot(int idx) const {
-  return JoinPathSegments(fs_root_, Substitute("master-$0-root", idx));
+  return JoinPathSegments(opts_.data_root, Substitute("master-$0-root", idx));
 }
 
 string InternalMiniCluster::GetTabletServerFsRoot(int idx) const {
-  return JoinPathSegments(fs_root_, Substitute("ts-$0-root", idx));
+  return JoinPathSegments(opts_.data_root, Substitute("ts-$0-root", idx));
 }
 
 Status InternalMiniCluster::WaitForTabletServerCount(int count) const {

http://git-wip-us.apache.org/repos/asf/kudu/blob/af39a2aa/src/kudu/integration-tests/internal_mini_cluster.h
----------------------------------------------------------------------
diff --git a/src/kudu/integration-tests/internal_mini_cluster.h b/src/kudu/integration-tests/internal_mini_cluster.h
index ec11fbc..29d655c 100644
--- a/src/kudu/integration-tests/internal_mini_cluster.h
+++ b/src/kudu/integration-tests/internal_mini_cluster.h
@@ -54,6 +54,10 @@ struct InternalMiniClusterOptions {
   // Default: 1
   int num_tablet_servers;
 
+  // Number of data dirs for each daemon.
+  // Default: 1 (this will place the wals in the same dir)
+  int num_data_dirs;
+
   // Directory in which to store data.
   // Default: "", which auto-generates a unique path for this cluster.
   // The default may only be used from a gtest unit test.
@@ -79,7 +83,7 @@ struct InternalMiniClusterOptions {
 // number of MiniTabletServers for use in tests.
 class InternalMiniCluster : public MiniCluster {
  public:
-  InternalMiniCluster(Env* env, const InternalMiniClusterOptions& options);
+  InternalMiniCluster(Env* env, InternalMiniClusterOptions options);
   virtual ~InternalMiniCluster();
 
   // Start a cluster with a Master and 'num_tablet_servers' TabletServers.
@@ -134,7 +138,7 @@ class InternalMiniCluster : public MiniCluster {
   }
 
   std::vector<uint16_t> master_rpc_ports() const override {
-    return master_rpc_ports_;
+    return opts_.master_rpc_ports;
   }
 
   std::vector<HostPort> master_rpc_addrs() const override;
@@ -188,15 +192,9 @@ class InternalMiniCluster : public MiniCluster {
     kMasterStartupWaitTimeSeconds = 30,
   };
 
-  const InternalMiniClusterOptions opts_;
-
   Env* const env_;
-  const std::string fs_root_;
-  const int num_masters_initial_;
-  const int num_ts_initial_;
 
-  const std::vector<uint16_t> master_rpc_ports_;
-  const std::vector<uint16_t> tserver_rpc_ports_;
+  InternalMiniClusterOptions opts_;
 
   bool running_;
 

http://git-wip-us.apache.org/repos/asf/kudu/blob/af39a2aa/src/kudu/integration-tests/master_replication-itest.cc
----------------------------------------------------------------------
diff --git a/src/kudu/integration-tests/master_replication-itest.cc b/src/kudu/integration-tests/master_replication-itest.cc
index 86f0616..0f3e22e 100644
--- a/src/kudu/integration-tests/master_replication-itest.cc
+++ b/src/kudu/integration-tests/master_replication-itest.cc
@@ -261,7 +261,8 @@ TEST_F(MasterReplicationTest, TestMasterPeerSetsDontMatch) {
   cluster_->mini_master(0)->Shutdown();
   vector<HostPort> master_rpc_addrs = cluster_->master_rpc_addrs();
   master_rpc_addrs.emplace_back("127.0.0.1", 55555);
-  ASSERT_OK(cluster_->mini_master(0)->StartDistributedMaster(master_rpc_addrs));
+  cluster_->mini_master(0)->SetMasterAddresses(master_rpc_addrs);
+  ASSERT_OK(cluster_->mini_master(0)->Start());
   Status s = cluster_->mini_master(0)->WaitForCatalogManagerInit();
   SCOPED_TRACE(s.ToString());
   ASSERT_TRUE(s.IsInvalidArgument());

http://git-wip-us.apache.org/repos/asf/kudu/blob/af39a2aa/src/kudu/master/CMakeLists.txt
----------------------------------------------------------------------
diff --git a/src/kudu/master/CMakeLists.txt b/src/kudu/master/CMakeLists.txt
index 029e5c1..ed72f58 100644
--- a/src/kudu/master/CMakeLists.txt
+++ b/src/kudu/master/CMakeLists.txt
@@ -72,6 +72,7 @@ set(KUDU_TEST_LINK_LIBS
 
 ADD_KUDU_TEST(catalog_manager-test)
 ADD_KUDU_TEST(master-test RESOURCE_LOCK "master-web-port")
+ADD_KUDU_TEST(mini_master-test RESOURCE_LOCK "master-web-port")
 ADD_KUDU_TEST(sys_catalog-test RESOURCE_LOCK "master-web-port")
 
 # Actual master executable

http://git-wip-us.apache.org/repos/asf/kudu/blob/af39a2aa/src/kudu/master/master-test.cc
----------------------------------------------------------------------
diff --git a/src/kudu/master/master-test.cc b/src/kudu/master/master-test.cc
index 9a6324f..a3585f1 100644
--- a/src/kudu/master/master-test.cc
+++ b/src/kudu/master/master-test.cc
@@ -40,6 +40,7 @@
 #include "kudu/security/token_verifier.h"
 #include "kudu/server/rpc_server.h"
 #include "kudu/util/curl_util.h"
+#include "kudu/util/path_util.h"
 #include "kudu/util/pb_util.h"
 #include "kudu/util/status.h"
 #include "kudu/util/test_util.h"

http://git-wip-us.apache.org/repos/asf/kudu/blob/af39a2aa/src/kudu/master/mini_master-test.cc
----------------------------------------------------------------------
diff --git a/src/kudu/master/mini_master-test.cc b/src/kudu/master/mini_master-test.cc
new file mode 100644
index 0000000..4cc708c
--- /dev/null
+++ b/src/kudu/master/mini_master-test.cc
@@ -0,0 +1,47 @@
+// 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.
+
+#include <gtest/gtest.h>
+
+#include "kudu/fs/fs_manager.h"
+#include "kudu/master/master.h"
+#include "kudu/master/mini_master.h"
+#include "kudu/util/path_util.h"
+#include "kudu/util/test_util.h"
+
+namespace kudu {
+namespace master {
+
+using std::unique_ptr;
+
+class MiniMasterTest : public KuduTest {};
+
+TEST_F(MiniMasterTest, TestMultiDirMaster) {
+  // Specifying the number of data directories will create subdirectories under the test root.
+  unique_ptr<MiniMaster> mini_master;
+  FsManager* fs_manager;
+
+  int kNumDataDirs = 3;
+  mini_master.reset(new MiniMaster(GetTestPath("Master"), HostPort("127.0.0.1", 0), kNumDataDirs));
+  ASSERT_OK(mini_master->Start());
+  fs_manager = mini_master->master()->fs_manager();
+  ASSERT_STR_CONTAINS(DirName(fs_manager->GetWalsRootDir()), "wal");
+  ASSERT_EQ(kNumDataDirs, fs_manager->GetDataRootDirs().size());
+}
+
+}  // namespace master
+}  // namespace kudu

http://git-wip-us.apache.org/repos/asf/kudu/blob/af39a2aa/src/kudu/master/mini_master.cc
----------------------------------------------------------------------
diff --git a/src/kudu/master/mini_master.cc b/src/kudu/master/mini_master.cc
index f8d1328..7a64f1d 100644
--- a/src/kudu/master/mini_master.cc
+++ b/src/kudu/master/mini_master.cc
@@ -1,3 +1,4 @@
+// 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
@@ -23,12 +24,12 @@
 #include <glog/logging.h>
 
 #include "kudu/fs/fs_manager.h"
-#include "kudu/gutil/gscoped_ptr.h"
 #include "kudu/gutil/strings/substitute.h"
-#include "kudu/master/master.h"
 #include "kudu/server/rpc_server.h"
 #include "kudu/server/webserver.h"
+#include "kudu/util/env_util.h"
 #include "kudu/util/net/sockaddr.h"
+#include "kudu/util/path_util.h"
 #include "kudu/util/status.h"
 
 using std::string;
@@ -42,35 +43,64 @@ DECLARE_bool(rpc_server_allow_ephemeral_ports);
 namespace kudu {
 namespace master {
 
-MiniMaster::MiniMaster(string fs_root, HostPort rpc_bind_addr)
-    : rpc_bind_addr_(std::move(rpc_bind_addr)),
-      fs_root_(std::move(fs_root)) {
+MiniMaster::MiniMaster(string fs_root, HostPort rpc_bind_addr, int num_data_dirs)
+    : fs_root_(std::move(fs_root)),
+      rpc_bind_addr_(std::move(rpc_bind_addr)) {
   // Disable minidump handler (we allow only one per process).
   FLAGS_enable_minidumps = false;
+  HostPort web_bind_addr(rpc_bind_addr_.host(), /*port=*/ 0);
+  opts_.rpc_opts.rpc_bind_addresses = rpc_bind_addr_.ToString();
+  opts_.webserver_opts.bind_interface = web_bind_addr.host();
+  opts_.webserver_opts.port = web_bind_addr.port();
+  if (num_data_dirs == 1) {
+    opts_.fs_opts.wal_path = fs_root_;
+    opts_.fs_opts.data_paths = { fs_root_ };
+  } else {
+    vector<string> fs_data_dirs;
+    for (int dir = 0; dir < num_data_dirs; dir++) {
+      fs_data_dirs.emplace_back(JoinPathSegments(fs_root_, Substitute("data-$0", dir)));
+    }
+    opts_.fs_opts.wal_path = JoinPathSegments(fs_root_, "wal");
+    opts_.fs_opts.data_paths = fs_data_dirs;
+  }
 }
 
 MiniMaster::~MiniMaster() {
   Shutdown();
 }
 
-Status MiniMaster::Start() {
-  FLAGS_rpc_server_allow_ephemeral_ports = true;
-  MasterOptions opts;
-  HostPort web_bind_addr(rpc_bind_addr_.host(), /*port=*/ 0);
-  RETURN_NOT_OK(StartOnAddrs(rpc_bind_addr_, web_bind_addr, &opts));
-  return master_->WaitForCatalogManagerInit();
+void MiniMaster::SetMasterAddresses(vector<HostPort> master_addrs) {
+  CHECK(!master_);
+  opts_.master_addresses = std::move(master_addrs);
 }
 
-Status MiniMaster::StartDistributedMaster(vector<HostPort> peer_addrs) {
-  HostPort web_bind_addr(rpc_bind_addr_.host(), /*port=*/ 0);
-  return StartDistributedMasterOnAddrs(rpc_bind_addr_, web_bind_addr,
-                                       std::move(peer_addrs));
+Status MiniMaster::Start() {
+  CHECK(!master_);
+  if (opts_.master_addresses.empty()) {
+    FLAGS_rpc_server_allow_ephemeral_ports = true;
+  }
+  // In case the wal dir and data dirs are subdirectories of the root directory,
+  // ensure the root directory exists.
+  RETURN_NOT_OK(env_util::CreateDirIfMissing(Env::Default(), fs_root_));
+  unique_ptr<Master> master(new Master(opts_));
+  RETURN_NOT_OK(master->Init());
+  RETURN_NOT_OK(master->StartAsync());
+  master_.swap(master);
+
+  // Wait for the catalog manager to be ready if we only have a single master.
+  if (opts_.master_addresses.empty()) {
+    return master_->WaitForCatalogManagerInit();
+  }
+  return Status::OK();
 }
 
 Status MiniMaster::Restart() {
-  MasterOptions opts;
-  RETURN_NOT_OK(StartOnAddrs(HostPort(bound_rpc_), HostPort(bound_http_), &opts));
-  return WaitForCatalogManagerInit();
+  CHECK(!master_);
+  opts_.rpc_opts.rpc_bind_addresses = bound_rpc_.ToString();
+  opts_.webserver_opts.bind_interface = bound_http_.host();
+  opts_.webserver_opts.port = bound_http_.port();
+  Shutdown();
+  return Start();
 }
 
 void MiniMaster::Shutdown() {
@@ -102,32 +132,5 @@ std::string MiniMaster::bound_rpc_addr_str() const {
   return bound_rpc_addr().ToString();
 }
 
-Status MiniMaster::StartOnAddrs(const HostPort& rpc_bind_addr,
-                                const HostPort& web_bind_addr,
-                                MasterOptions* opts) {
-  CHECK(!master_);
-
-  opts->rpc_opts.rpc_bind_addresses = rpc_bind_addr.ToString();
-  opts->webserver_opts.bind_interface = web_bind_addr.host();
-  opts->webserver_opts.port = web_bind_addr.port();
-  opts->fs_opts.wal_path = fs_root_;
-  opts->fs_opts.data_paths = { fs_root_ };
-
-  unique_ptr<Master> server(new Master(*opts));
-  RETURN_NOT_OK(server->Init());
-  RETURN_NOT_OK(server->StartAsync());
-  master_.swap(server);
-
-  return Status::OK();
-}
-
-Status MiniMaster::StartDistributedMasterOnAddrs(const HostPort& rpc_bind_addr,
-                                                 const HostPort& web_bind_addr,
-                                                 std::vector<HostPort> peer_addrs) {
-  MasterOptions opts;
-  opts.master_addresses = std::move(peer_addrs);
-  return StartOnAddrs(rpc_bind_addr, web_bind_addr, &opts);
-}
-
 } // namespace master
 } // namespace kudu

http://git-wip-us.apache.org/repos/asf/kudu/blob/af39a2aa/src/kudu/master/mini_master.h
----------------------------------------------------------------------
diff --git a/src/kudu/master/mini_master.h b/src/kudu/master/mini_master.h
index 77f80b2..e7dca14 100644
--- a/src/kudu/master/mini_master.h
+++ b/src/kudu/master/mini_master.h
@@ -21,9 +21,9 @@
 #include <string>
 #include <vector>
 
-#include "kudu/gutil/gscoped_ptr.h"
 #include "kudu/gutil/macros.h"
 #include "kudu/gutil/port.h"
+#include "kudu/master/master.h"
 #include "kudu/util/env.h"
 #include "kudu/util/net/net_util.h"
 #include "kudu/util/net/sockaddr.h"
@@ -33,16 +33,10 @@ namespace kudu {
 
 namespace master {
 
-class Master;
-struct MasterOptions;
-
 // An in-process Master meant for use in test cases.
-//
-// TODO: Store the distributed cluster configuration in the object, to avoid
-// having multiple Start methods.
 class MiniMaster {
  public:
-  MiniMaster(std::string fs_root, HostPort rpc_bind_addr);
+  MiniMaster(std::string fs_root, HostPort rpc_bind_addr, int num_data_dirs = 1);
   ~MiniMaster();
 
   // Start a master running on the address specified to the constructor.
@@ -50,12 +44,13 @@ class MiniMaster {
   // MiniMaster::bound_addr()
   Status Start();
 
-  Status StartDistributedMaster(std::vector<HostPort> peer_addrs);
-
   // Restart the master on the same ports as it was previously bound.
-  // Requires that the master is currently started.
+  // Requires that Shutdown() has already been called.
   Status Restart();
 
+  // Set the the addresses for distributed masters.
+  void SetMasterAddresses(std::vector<HostPort> master_addrs);
+
   void Shutdown();
 
   Status WaitForCatalogManagerInit() const;
@@ -74,18 +69,11 @@ class MiniMaster {
   std::string bound_rpc_addr_str() const;
 
  private:
-  Status StartOnAddrs(const HostPort& rpc_bind_addr,
-                      const HostPort& web_bind_addr,
-                      MasterOptions* opts);
-
-  Status StartDistributedMasterOnAddrs(const HostPort& rpc_bind_addr,
-                                       const HostPort& web_bind_addr,
-                                       std::vector<HostPort> peer_addrs);
-
-  const HostPort rpc_bind_addr_;
   const std::string fs_root_;
+  const HostPort rpc_bind_addr_;
   Sockaddr bound_rpc_;
   Sockaddr bound_http_;
+  MasterOptions opts_;
   std::unique_ptr<Master> master_;
 };
 

http://git-wip-us.apache.org/repos/asf/kudu/blob/af39a2aa/src/kudu/tserver/CMakeLists.txt
----------------------------------------------------------------------
diff --git a/src/kudu/tserver/CMakeLists.txt b/src/kudu/tserver/CMakeLists.txt
index 512988e..a64d027 100644
--- a/src/kudu/tserver/CMakeLists.txt
+++ b/src/kudu/tserver/CMakeLists.txt
@@ -167,6 +167,7 @@ set(KUDU_TEST_LINK_LIBS
   tserver
   tserver_test_util
   ${KUDU_MIN_TEST_LIBS})
+ADD_KUDU_TEST(mini_tablet_server-test)
 ADD_KUDU_TEST(tablet_copy_client-test)
 ADD_KUDU_TEST(tablet_copy_source_session-test)
 ADD_KUDU_TEST(tablet_copy_service-test)

http://git-wip-us.apache.org/repos/asf/kudu/blob/af39a2aa/src/kudu/tserver/mini_tablet_server-test.cc
----------------------------------------------------------------------
diff --git a/src/kudu/tserver/mini_tablet_server-test.cc b/src/kudu/tserver/mini_tablet_server-test.cc
new file mode 100644
index 0000000..7405830
--- /dev/null
+++ b/src/kudu/tserver/mini_tablet_server-test.cc
@@ -0,0 +1,48 @@
+// 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.
+
+#include <gtest/gtest.h>
+
+#include "kudu/fs/fs_manager.h"
+#include "kudu/tserver/mini_tablet_server.h"
+#include "kudu/tserver/tablet_server.h"
+#include "kudu/util/path_util.h"
+#include "kudu/util/test_util.h"
+
+namespace kudu {
+namespace tserver {
+
+using std::unique_ptr;
+
+class MiniTabletServerTest : public KuduTest {};
+
+TEST_F(MiniTabletServerTest, TestMultiDirServer) {
+  // Specifying the number of data directories will create subdirectories under the test root.
+  unique_ptr<MiniTabletServer> mini_server;
+  FsManager* fs_manager;
+
+  int kNumDataDirs = 3;
+  mini_server.reset(new MiniTabletServer(GetTestPath("TServer"),
+      HostPort("127.0.0.1", 0), kNumDataDirs));
+  ASSERT_OK(mini_server->Start());
+  fs_manager = mini_server->server()->fs_manager();
+  ASSERT_STR_CONTAINS(DirName(fs_manager->GetWalsRootDir()), "wal");
+  ASSERT_EQ(kNumDataDirs, fs_manager->GetDataRootDirs().size());
+}
+
+}  // namespace tserver
+}  // namespace kudu

http://git-wip-us.apache.org/repos/asf/kudu/blob/af39a2aa/src/kudu/tserver/mini_tablet_server.cc
----------------------------------------------------------------------
diff --git a/src/kudu/tserver/mini_tablet_server.cc b/src/kudu/tserver/mini_tablet_server.cc
index b13484d..1c77082 100644
--- a/src/kudu/tserver/mini_tablet_server.cc
+++ b/src/kudu/tserver/mini_tablet_server.cc
@@ -19,6 +19,7 @@
 
 #include <string>
 #include <utility>
+#include <vector>
 
 #include <gflags/gflags_declare.h>
 
@@ -27,7 +28,9 @@
 #include "kudu/tablet/tablet-test-util.h"
 #include "kudu/tserver/tablet_server.h"
 #include "kudu/tserver/ts_tablet_manager.h"
+#include "kudu/util/env_util.h"
 #include "kudu/util/net/sockaddr.h"
+#include "kudu/util/path_util.h"
 #include "kudu/util/status.h"
 
 DECLARE_bool(enable_minidumps);
@@ -38,13 +41,16 @@ using kudu::consensus::RaftPeerPB;
 using std::pair;
 using std::string;
 using std::unique_ptr;
+using std::vector;
 using strings::Substitute;
 
 namespace kudu {
 namespace tserver {
 
-MiniTabletServer::MiniTabletServer(const string& fs_root,
-                                   const HostPort& rpc_bind_addr) {
+MiniTabletServer::MiniTabletServer(string fs_root,
+                                   const HostPort& rpc_bind_addr,
+                                   int num_data_dirs)
+    : fs_root_(std::move(fs_root)) {
   // Disable minidump handler (we allow only one per process).
   FLAGS_enable_minidumps = false;
   // Start RPC server on loopback.
@@ -52,8 +58,17 @@ MiniTabletServer::MiniTabletServer(const string& fs_root,
   opts_.rpc_opts.rpc_bind_addresses = rpc_bind_addr.ToString();
   opts_.webserver_opts.bind_interface = rpc_bind_addr.host();
   opts_.webserver_opts.port = 0;
-  opts_.fs_opts.wal_path = fs_root;
-  opts_.fs_opts.data_paths = { fs_root };
+  if (num_data_dirs == 1) {
+    opts_.fs_opts.wal_path = fs_root_;
+    opts_.fs_opts.data_paths = { fs_root_ };
+  } else {
+    vector<string> fs_data_dirs;
+    for (int dir = 0; dir < num_data_dirs; dir++) {
+      fs_data_dirs.emplace_back(JoinPathSegments(fs_root_, Substitute("data-$0", dir)));
+    }
+    opts_.fs_opts.wal_path = JoinPathSegments(fs_root_, "wal");
+    opts_.fs_opts.data_paths = fs_data_dirs;
+  }
 }
 
 MiniTabletServer::~MiniTabletServer() {
@@ -62,7 +77,9 @@ MiniTabletServer::~MiniTabletServer() {
 
 Status MiniTabletServer::Start() {
   CHECK(!server_);
-
+  // In case the wal dir and data dirs are subdirectories of the root directory,
+  // ensure the root directory exists.
+  RETURN_NOT_OK(env_util::CreateDirIfMissing(Env::Default(), fs_root_));
   unique_ptr<TabletServer> server(new TabletServer(opts_));
   RETURN_NOT_OK(server->Init());
   RETURN_NOT_OK(server->Start());

http://git-wip-us.apache.org/repos/asf/kudu/blob/af39a2aa/src/kudu/tserver/mini_tablet_server.h
----------------------------------------------------------------------
diff --git a/src/kudu/tserver/mini_tablet_server.h b/src/kudu/tserver/mini_tablet_server.h
index 88d1289..f2f0231 100644
--- a/src/kudu/tserver/mini_tablet_server.h
+++ b/src/kudu/tserver/mini_tablet_server.h
@@ -42,7 +42,9 @@ class TabletServer;
 class MiniTabletServer {
  public:
   // Note: The host portion of 'rpc_bind_addr' is also used for the http service.
-  MiniTabletServer(const std::string& fs_root, const HostPort& rpc_bind_addr);
+  MiniTabletServer(std::string fs_root,
+                   const HostPort& rpc_bind_addr,
+                   int num_data_dirs = 1);
   ~MiniTabletServer();
 
   // Return the options which will be used to start the tablet server.
@@ -97,6 +99,7 @@ class MiniTabletServer {
   void FailHeartbeats();
 
  private:
+  const std::string fs_root_;
   TabletServerOptions opts_;
   std::unique_ptr<TabletServer> server_;
 };

http://git-wip-us.apache.org/repos/asf/kudu/blob/af39a2aa/src/kudu/tserver/tablet_copy-test-base.h
----------------------------------------------------------------------
diff --git a/src/kudu/tserver/tablet_copy-test-base.h b/src/kudu/tserver/tablet_copy-test-base.h
index 6e14d30..83cd554 100644
--- a/src/kudu/tserver/tablet_copy-test-base.h
+++ b/src/kudu/tserver/tablet_copy-test-base.h
@@ -42,7 +42,7 @@ class TabletCopyTest : public TabletServerTestBase {
  public:
   virtual void SetUp() OVERRIDE {
     NO_FATALS(TabletServerTestBase::SetUp());
-    NO_FATALS(StartTabletServer());
+    NO_FATALS(StartTabletServer(/* num_data_dirs */ 1));
     // Prevent logs from being deleted out from under us until / unless we want
     // to test that we are anchoring correctly. Since GenerateTestData() does a
     // Flush(), Log GC is allowed to eat the logs before we get around to

http://git-wip-us.apache.org/repos/asf/kudu/blob/af39a2aa/src/kudu/tserver/tablet_server-stress-test.cc
----------------------------------------------------------------------
diff --git a/src/kudu/tserver/tablet_server-stress-test.cc b/src/kudu/tserver/tablet_server-stress-test.cc
index 761c62b..96ab39b 100644
--- a/src/kudu/tserver/tablet_server-stress-test.cc
+++ b/src/kudu/tserver/tablet_server-stress-test.cc
@@ -61,7 +61,7 @@ class TSStressTest : public TabletServerTestBase {
 
   virtual void SetUp() OVERRIDE {
     TabletServerTestBase::SetUp();
-    NO_FATALS(StartTabletServer());
+    NO_FATALS(StartTabletServer(/* num_data_dirs */ 1));
 
     histogram_ = METRIC_insert_latency.Instantiate(ts_test_metric_entity_);
   }

http://git-wip-us.apache.org/repos/asf/kudu/blob/af39a2aa/src/kudu/tserver/tablet_server-test-base.h
----------------------------------------------------------------------
diff --git a/src/kudu/tserver/tablet_server-test-base.h b/src/kudu/tserver/tablet_server-test-base.h
index 2ea9de0..e174e4a 100644
--- a/src/kudu/tserver/tablet_server-test-base.h
+++ b/src/kudu/tserver/tablet_server-test-base.h
@@ -97,11 +97,13 @@ class TabletServerTestBase : public KuduTest {
     ASSERT_OK(bld.Build(&client_messenger_));
   }
 
-  virtual void StartTabletServer() {
+  virtual void StartTabletServer(int num_data_dirs) {
+    CHECK(!mini_server_);
+
     // Start server with an invalid master address, so it never successfully
     // heartbeats, even if there happens to be a master running on this machine.
     mini_server_.reset(new MiniTabletServer(GetTestPath("TabletServerTest-fsroot"),
-                                            HostPort("127.0.0.1", 0)));
+                                            HostPort("127.0.0.1", 0), num_data_dirs));
     mini_server_->options()->master_addresses.clear();
     mini_server_->options()->master_addresses.emplace_back("255.255.255.255", 1);
     ASSERT_OK(mini_server_->Start());
@@ -340,12 +342,12 @@ class TabletServerTestBase : public KuduTest {
     }
   }
 
-  Status ShutdownAndRebuildTablet() {
+  Status ShutdownAndRebuildTablet(int num_data_dirs = 1) {
     ShutdownTablet();
 
     // Start server.
     mini_server_.reset(new MiniTabletServer(GetTestPath("TabletServerTest-fsroot"),
-                                            HostPort("127.0.0.1", 0)));
+                                            HostPort("127.0.0.1", 0), num_data_dirs));
     mini_server_->options()->master_addresses.clear();
     mini_server_->options()->master_addresses.emplace_back("255.255.255.255", 1);
     // this should open the tablet created on StartTabletServer()

http://git-wip-us.apache.org/repos/asf/kudu/blob/af39a2aa/src/kudu/tserver/tablet_server-test.cc
----------------------------------------------------------------------
diff --git a/src/kudu/tserver/tablet_server-test.cc b/src/kudu/tserver/tablet_server-test.cc
index 1cedc97..0ae9c4f 100644
--- a/src/kudu/tserver/tablet_server-test.cc
+++ b/src/kudu/tserver/tablet_server-test.cc
@@ -78,7 +78,7 @@ class TabletServerTest : public TabletServerTestBase {
   // Starts the tablet server, override to start it later.
   virtual void SetUp() OVERRIDE {
     NO_FATALS(TabletServerTestBase::SetUp());
-    NO_FATALS(StartTabletServer());
+    NO_FATALS(StartTabletServer(/* num_data_dirs */ 1));
   }
 
   void DoOrderedScanTest(const Schema& projection, const string& expected_rows_as_string);