You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@kudu.apache.org by la...@apache.org on 2023/12/27 02:47:25 UTC
(kudu) branch master updated (1f6f7e5ba -> 9181f9ebe)
This is an automated email from the ASF dual-hosted git repository.
laiyingchun pushed a change to branch master
in repository https://gitbox.apache.org/repos/asf/kudu.git
from 1f6f7e5ba KUDU-3534 [compaction] Log timestamp of two matching DELETE REDO mutations.
new 0c8d2eee1 [rpc] extra test scenarios for exactly-once RPC logic
new 9181f9ebe [Tool] Find file path where the block is located
The 2 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails. The revisions
listed as "add" were already present in the repository and have only
been added to this reference.
Summary of changes:
src/kudu/fs/block_manager.h | 7 ++
src/kudu/fs/file_block_manager.h | 7 +-
src/kudu/fs/log_block_manager.cc | 15 +++
src/kudu/fs/log_block_manager.h | 2 +
src/kudu/rpc/exactly_once_rpc-test.cc | 203 +++++++++++++++++++++++++++++++---
src/kudu/tools/kudu-tool-test.cc | 64 +++++++++++
src/kudu/tools/tool_action_fs.cc | 45 ++++++++
7 files changed, 322 insertions(+), 21 deletions(-)
(kudu) 01/02: [rpc] extra test scenarios for exactly-once RPC logic
Posted by la...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
laiyingchun pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/kudu.git
commit 0c8d2eee1ee2b03d0de369d5975f153b00e26b36
Author: Alexey Serbin <al...@apache.org>
AuthorDate: Thu Dec 7 10:22:37 2023 -0800
[rpc] extra test scenarios for exactly-once RPC logic
In the context of troubleshooting one issue, it was necessary to know
how the exactly-once RPC logic handles the re-ordering of RPC requests,
so I added these extra scenarios to make sure the implementation behaves
as expected. I think it's worth it adding the new tests into the code
base to explicitly state how the system behaves in particular cases and
to catch possible regressions if the corresponding code modified
in the future.
Change-Id: I3eedeb786420175b604fc67a9de948cedba9c53a
Reviewed-on: http://gerrit.cloudera.org:8080/20831
Tested-by: Alexey Serbin <al...@apache.org>
Reviewed-by: Abhishek Chennaka <ac...@cloudera.com>
---
src/kudu/rpc/exactly_once_rpc-test.cc | 203 +++++++++++++++++++++++++++++++---
1 file changed, 187 insertions(+), 16 deletions(-)
diff --git a/src/kudu/rpc/exactly_once_rpc-test.cc b/src/kudu/rpc/exactly_once_rpc-test.cc
index 8308b66bb..e9a0a6d13 100644
--- a/src/kudu/rpc/exactly_once_rpc-test.cc
+++ b/src/kudu/rpc/exactly_once_rpc-test.cc
@@ -73,16 +73,24 @@ namespace {
const char* kClientId = "test-client";
-void AddRequestId(RpcController* controller,
- const std::string& client_id,
- ResultTracker::SequenceNumber sequence_number,
- int64_t attempt_no) {
- unique_ptr<RequestIdPB> request_id(new RequestIdPB());
+void AddRequestIdImpl(RpcController* ctl,
+ const std::string& client_id,
+ ResultTracker::SequenceNumber seq_num,
+ ResultTracker::SequenceNumber first_incomplete_seq_num,
+ int64_t attempt_num = 0) {
+ unique_ptr<RequestIdPB> request_id(new RequestIdPB);
request_id->set_client_id(client_id);
- request_id->set_seq_no(sequence_number);
- request_id->set_attempt_no(attempt_no);
- request_id->set_first_incomplete_seq_no(sequence_number);
- controller->SetRequestIdPB(std::move(request_id));
+ request_id->set_seq_no(seq_num);
+ request_id->set_first_incomplete_seq_no(first_incomplete_seq_num);
+ request_id->set_attempt_no(attempt_num);
+ ctl->SetRequestIdPB(std::move(request_id));
+}
+
+void AddRequestId(RpcController* ctl,
+ const std::string& client_id,
+ ResultTracker::SequenceNumber seq_num,
+ int64_t attempt_num = 0) {
+ return AddRequestIdImpl(ctl, client_id, seq_num, seq_num, attempt_num);
}
class TestServerPicker : public ServerPicker<CalculatorServiceProxy> {
@@ -248,13 +256,13 @@ class ExactlyOnceRpcTest : public RpcTestBase {
// and makes sure that only one of the calls was successful.
struct SimultaneousExactlyOnceAdder {
SimultaneousExactlyOnceAdder(CalculatorServiceProxy* p,
- ResultTracker::SequenceNumber sequence_number,
- int value,
- uint64_t client_sleep,
- uint64_t server_sleep,
- int64_t attempt_no)
- : proxy(p),
- client_sleep_for_ms(client_sleep) {
+ ResultTracker::SequenceNumber sequence_number,
+ int value,
+ uint64_t client_sleep,
+ uint64_t server_sleep,
+ int64_t attempt_no)
+ : proxy(p),
+ client_sleep_for_ms(client_sleep) {
req.set_value_to_add(value);
req.set_sleep_for_ms(server_sleep);
AddRequestId(&controller, kClientId, sequence_number, attempt_no);
@@ -614,6 +622,169 @@ TEST_F(ExactlyOnceRpcTest, TestExactlyOnceSemanticsGarbageCollectionStressTest)
NO_PENDING_FATALS();
}
+// Requests might be re-ordered due to rare network conditions if sent to
+// the server as separate WriteRequest RPCs or due to scheduling anomalies.
+// The latter can happen because the requests are dispatched to a multitude
+// of worker threads from a queue. This test scenario makes sure that if such
+// a situation occurs, the exactly-once logic behaves as expected.
+TEST_F(ExactlyOnceRpcTest, ReorderedRequestsOne) {
+ ASSERT_OK(StartServer());
+
+ // The request with ID 1 is dispatched first.
+ {
+ RpcController ctl;
+ ExactlyOnceResponsePB resp;
+ ExactlyOnceRequestPB req;
+ req.set_value_to_add(1);
+
+ AddRequestIdImpl(&ctl, kClientId,
+ /*seq_num=*/1, /*first_incomplete_seq_num=*/0, /*attempt_num=*/0);
+
+ ASSERT_OK(proxy_->AddExactlyOnce(req, &resp, &ctl));
+ ASSERT_EQ(1, resp.current_val());
+ }
+
+ // The processing of the request with ID 0 starts a bit later.
+ {
+ RpcController ctl;
+ ExactlyOnceResponsePB resp;
+ ExactlyOnceRequestPB req;
+ req.set_value_to_add(1);
+
+ AddRequestIdImpl(&ctl, kClientId,
+ /*seq_num=*/0, /*first_incomplete_seq_num=*/0, /*attempt_num=*/0);
+
+ ASSERT_OK(proxy_->AddExactlyOnce(req, &resp, &ctl));
+ ASSERT_EQ(2, resp.current_val());
+ }
+
+ // And here comes the retransmission of the request with ID 1
+ // (attempt_num = 1). The response should be the same as the server sent for
+ // attempt_num == 0, even if the current value of the counter is 2
+ // since the request with ID 0 has been already received and processed by
+ // the server.
+ {
+ RpcController ctl;
+ ExactlyOnceResponsePB resp;
+ ExactlyOnceRequestPB req;
+ req.set_value_to_add(1);
+
+ AddRequestIdImpl(&ctl, kClientId,
+ /*seq_num=*/1, /*first_incomplete_seq_num=*/0, /*attempt_num=*/1);
+
+ ASSERT_OK(proxy_->AddExactlyOnce(req, &resp, &ctl));
+ ASSERT_EQ(1, resp.current_val());
+ }
+
+ // Now send in a request whose ID is a gap away from the last request ID
+ // seen from this client, pretending the client has received responses
+ // for requests 0 and 1, and there are some requests in-flight the server
+ // hasn't seen yet.
+ {
+ RpcController ctl;
+ ExactlyOnceResponsePB resp;
+ ExactlyOnceRequestPB req;
+ req.set_value_to_add(10);
+
+ AddRequestIdImpl(&ctl, kClientId,
+ /*seq_num=*/10, /*first_incomplete_seq_num=*/8, /*attempt_num=*/0);
+
+ ASSERT_OK(proxy_->AddExactlyOnce(req, &resp, &ctl));
+ ASSERT_EQ(12, resp.current_val());
+ }
+}
+
+// This is similar to ReorderRequestsSimple above, but with more variatons.
+TEST_F(ExactlyOnceRpcTest, ReorderedRequestsTwo) {
+ ASSERT_OK(StartServer());
+
+ // The request with ID 1 is dispatched first.
+ {
+ RpcController ctl;
+ ExactlyOnceResponsePB resp;
+ ExactlyOnceRequestPB req;
+ req.set_value_to_add(2);
+
+ AddRequestIdImpl(&ctl, kClientId,
+ /*seq_num=*/1, /*first_incomplete_seq_num=*/0, /*attempt_num=*/0);
+
+ ASSERT_OK(proxy_->AddExactlyOnce(req, &resp, &ctl));
+ ASSERT_EQ(2, resp.current_val());
+ }
+
+ // The processing of the request with ID 0 starts a bit later.
+ {
+ RpcController ctl;
+ ExactlyOnceResponsePB resp;
+ ExactlyOnceRequestPB req;
+ req.set_value_to_add(1);
+
+ AddRequestIdImpl(&ctl, kClientId,
+ /*seq_num=*/0, /*first_incomplete_seq_num=*/0, /*attempt_num=*/0);
+
+ ASSERT_OK(proxy_->AddExactlyOnce(req, &resp, &ctl));
+ ASSERT_EQ(3, resp.current_val());
+ }
+
+ // However, the request with ID 0 is being responded first even if its
+ // processing started later because the worker thread that was processing
+ // the request with ID 1 had stuck for a while. So, request with ID 2
+ // has first_incomplete_seq_num == 1 on its attempt_num == 0.
+ // And with that, the two attempts of the request with ID 2 are re-ordered
+ // as well when the exactly-once logic receives them, so the request
+ // with attempt_num == 1 has first_incomplete_seq_num == 2.
+ {
+ RpcController ctl;
+ ExactlyOnceResponsePB resp;
+ ExactlyOnceRequestPB req;
+ req.set_value_to_add(3);
+
+ AddRequestIdImpl(&ctl, kClientId,
+ /*seq_num=*/2, /*first_incomplete_seq_num=*/2, /*attempt_num=*/1);
+
+ ASSERT_OK(proxy_->AddExactlyOnce(req, &resp, &ctl));
+ ASSERT_EQ(6, resp.current_val());
+ }
+ {
+ RpcController ctl;
+ ExactlyOnceResponsePB resp;
+ ExactlyOnceRequestPB req;
+ req.set_value_to_add(3);
+
+ AddRequestIdImpl(&ctl, kClientId,
+ /*seq_num=*/2, /*first_incomplete_seq_num=*/1, /*attempt_num=*/0);
+
+ ASSERT_OK(proxy_->AddExactlyOnce(req, &resp, &ctl));
+ ASSERT_EQ(6, resp.current_val());
+ }
+
+ // Send two more requests to make sure that the exactly-once state machine
+ // isn't screwed at this point.
+ {
+ RpcController ctl;
+ ExactlyOnceResponsePB resp;
+ ExactlyOnceRequestPB req;
+ req.set_value_to_add(4);
+
+ AddRequestIdImpl(&ctl, kClientId,
+ /*seq_num=*/3, /*first_incomplete_seq_num=*/2, /*attempt_num=*/0);
+
+ ASSERT_OK(proxy_->AddExactlyOnce(req, &resp, &ctl));
+ ASSERT_EQ(10, resp.current_val());
+ }
+ {
+ RpcController ctl;
+ ExactlyOnceResponsePB resp;
+ ExactlyOnceRequestPB req;
+ req.set_value_to_add(1);
+
+ AddRequestIdImpl(&ctl, kClientId,
+ /*seq_num=*/4, /*first_incomplete_seq_num=*/3, /*attempt_num=*/0);
+
+ ASSERT_OK(proxy_->AddExactlyOnce(req, &resp, &ctl));
+ ASSERT_EQ(11, resp.current_val());
+ }
+}
} // namespace rpc
} // namespace kudu
(kudu) 02/02: [Tool] Find file path where the block is located
Posted by la...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
laiyingchun pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/kudu.git
commit 9181f9ebef293229307dae0d84461e8616747c57
Author: xinghuayu007 <14...@qq.com>
AuthorDate: Wed Oct 18 16:46:41 2023 +0800
[Tool] Find file path where the block is located
Sometimes, a block is damaged because of the checksum is incorrect
or the disk is damaged. The log only prints the block id, but no
container id. We want to know the container where block is located.
If we know the container id, we can read the related metadata file
to get the status of the block: dead or alive. We can also truncate
the record from the metadata file to repair the container, although
it will cause some data loss.
This patch provides the tool to find the file path by a block id.
Change-Id: Ifaafd2bb634ed7fab923d9cf9eef40437442d187
Reviewed-on: http://gerrit.cloudera.org:8080/20594
Tested-by: Kudu Jenkins
Reviewed-by: Yingchun Lai <la...@apache.org>
---
src/kudu/fs/block_manager.h | 7 +++++
src/kudu/fs/file_block_manager.h | 7 ++---
src/kudu/fs/log_block_manager.cc | 15 ++++++++++
src/kudu/fs/log_block_manager.h | 2 ++
src/kudu/tools/kudu-tool-test.cc | 64 ++++++++++++++++++++++++++++++++++++++++
src/kudu/tools/tool_action_fs.cc | 45 ++++++++++++++++++++++++++++
6 files changed, 135 insertions(+), 5 deletions(-)
diff --git a/src/kudu/fs/block_manager.h b/src/kudu/fs/block_manager.h
index 382f68469..264d22507 100644
--- a/src/kudu/fs/block_manager.h
+++ b/src/kudu/fs/block_manager.h
@@ -258,6 +258,13 @@ class BlockManager : public RefCountedThreadSafe<BlockManager> {
virtual Status OpenBlock(const BlockId& block_id,
std::unique_ptr<ReadableBlock>* block) = 0;
+ // Get the data file where the block located.
+ //
+ // Find the data file according to the block id. The data file is the
+ // unique file where the block data is stored. On success, overwrites
+ // 'path' with the file's path.
+ virtual bool FindBlockPath(const BlockId& block_id, std::string* path) const = 0;
+
// Constructs a block creation transaction to group a set of block creation
// operations and closes the registered blocks together.
virtual std::unique_ptr<BlockCreationTransaction> NewCreationTransaction() = 0;
diff --git a/src/kudu/fs/file_block_manager.h b/src/kudu/fs/file_block_manager.h
index d770e2386..81ab17561 100644
--- a/src/kudu/fs/file_block_manager.h
+++ b/src/kudu/fs/file_block_manager.h
@@ -89,6 +89,8 @@ class FileBlockManager : public BlockManager {
Status CreateBlock(const CreateBlockOptions& opts,
std::unique_ptr<WritableBlock>* block) override;
+ bool FindBlockPath(const BlockId& block_id, std::string* path) const override;
+
Status OpenBlock(const BlockId& block_id,
std::unique_ptr<ReadableBlock>* block) override;
@@ -121,11 +123,6 @@ class FileBlockManager : public BlockManager {
// Synchronizes the metadata for a block with the given location.
Status SyncMetadata(const internal::FileBlockLocation& location);
- // Looks up the path of the file backing a particular block ID.
- //
- // On success, overwrites 'path' with the file's path.
- bool FindBlockPath(const BlockId& block_id, std::string* path) const;
-
Env* env() const { return env_; }
// For manipulating files.
diff --git a/src/kudu/fs/log_block_manager.cc b/src/kudu/fs/log_block_manager.cc
index 610758317..d8b43e742 100644
--- a/src/kudu/fs/log_block_manager.cc
+++ b/src/kudu/fs/log_block_manager.cc
@@ -2641,6 +2641,21 @@ Status LogBlockManager::OpenBlock(const BlockId& block_id,
return Status::OK();
}
+bool LogBlockManager::FindBlockPath(const BlockId& block_id, string* path) const {
+ LogBlockRefPtr lb;
+ {
+ auto index = block_id.id() & kBlockMapMask;
+ std::lock_guard<simple_spinlock> l(*managed_block_shards_[index].lock);
+ lb = FindPtrOrNull(*managed_block_shards_[index].blocks_by_block_id, block_id);
+ }
+ if (!lb) {
+ return false;
+ }
+ *path = StrCat(JoinPathSegments(lb->container()->data_dir()->dir(), lb->container()->id()),
+ LogBlockManager::kContainerDataFileSuffix);
+ return true;
+}
+
unique_ptr<BlockCreationTransaction> LogBlockManager::NewCreationTransaction() {
CHECK(!opts_.read_only);
return std::make_unique<internal::LogBlockCreationTransaction>();
diff --git a/src/kudu/fs/log_block_manager.h b/src/kudu/fs/log_block_manager.h
index 0cc8d4d25..f2d20bb57 100644
--- a/src/kudu/fs/log_block_manager.h
+++ b/src/kudu/fs/log_block_manager.h
@@ -191,6 +191,8 @@ class LogBlockManager : public BlockManager {
Status OpenBlock(const BlockId& block_id,
std::unique_ptr<ReadableBlock>* block) override;
+ bool FindBlockPath(const BlockId& block_id, std::string* path) const override;
+
std::unique_ptr<BlockCreationTransaction> NewCreationTransaction() override;
std::shared_ptr<BlockDeletionTransaction> NewDeletionTransaction() override;
diff --git a/src/kudu/tools/kudu-tool-test.cc b/src/kudu/tools/kudu-tool-test.cc
index 803c8bf17..ec311ea12 100644
--- a/src/kudu/tools/kudu-tool-test.cc
+++ b/src/kudu/tools/kudu-tool-test.cc
@@ -1366,6 +1366,7 @@ TEST_F(ToolTest, TestModeHelp) {
"dump.*Dump a Kudu filesystem",
"format.*new Kudu filesystem",
"list.*List metadata for on-disk tablets, rowsets",
+ "locate_block.*Find the file's path where the block locates",
"update_dirs.*Updates the set of data directories",
"upgrade_encryption_key.*Upgrade the encryption key info",
};
@@ -9303,6 +9304,69 @@ TEST_F(UnregisterTServerTest, TestUnregisterTServerNotPresumedDead) {
}
}
+class FindBlockPathTest : public ToolTest, public ::testing::WithParamInterface<string> {
+};
+
+static vector<string> BlockManagerType() {
+ return { "log", "file" };
+}
+
+INSTANTIATE_TEST_SUITE_P(, FindBlockPathTest, ::testing::ValuesIn(BlockManagerType()));
+
+TEST_P(FindBlockPathTest, TestFindBlockPath) {
+ FLAGS_block_manager = GetParam();
+
+ // Create replicas and fill some data.
+ NO_FATALS(StartMiniCluster());
+ NO_FATALS(CreateTableWithFlushedData("tablename", mini_cluster_.get()));
+
+ auto fs_manager = mini_cluster_->mini_tablet_server(0)->server()->fs_manager();
+ vector<BlockId> block_ids;
+ ASSERT_OK(fs_manager->block_manager()->GetAllBlockIds(&block_ids));
+ string fs_wal_dir = mini_cluster_->mini_tablet_server(0)->options()->fs_opts.wal_root;
+ string fs_data_dirs = JoinMapped(fs_manager->GetDataRootDirs(),
+ [] (const string& data_dir) {
+ return data_dir.substr(0, data_dir.length() - strlen("/data"));
+ }, ",");
+ string fs_paths = "--fs_wal_dir=" + fs_wal_dir + " "
+ "--fs_data_dirs=" + fs_data_dirs;
+
+ ASSERT_EQ(1, fs_manager->GetDataRootDirs().size());
+
+ // Case 1: block id exists.
+ string stdout;
+ NO_FATALS(RunActionStdoutString(
+ Substitute("fs locate_block $0 $1 --block_manager=$2",
+ block_ids[0].ToString(), fs_paths, FLAGS_block_manager),
+ &stdout));
+ ASSERT_STR_CONTAINS(stdout, Substitute("$0 | $1",
+ block_ids[0].ToString(), fs_data_dirs));
+
+ // Case 2: block id does not exists.
+ stdout.clear();
+ // 281474976710655 = (2^49 - 1). That is a very unique id, which will
+ // not exist in log_blog_manager or file_block_manager.
+ NO_FATALS(RunActionStdoutString(
+ Substitute("fs locate_block 281474976710655,$0 $1 --block_manager=$2",
+ block_ids[0].ToString(), fs_paths, FLAGS_block_manager),
+ &stdout));
+ ASSERT_STR_CONTAINS(stdout, Substitute("$0 | $1",
+ block_ids[0].ToString(), fs_data_dirs));
+ ASSERT_STR_CONTAINS(stdout, Substitute("$0 | $1",
+ block_ids[0].ToString(), fs_data_dirs));
+ ASSERT_STR_MATCHES(stdout, "281474976710655* | not found");
+
+ // case 3: block id is not integer.
+ string stderr;
+ NO_FATALS(RunActionStdoutString(
+ Substitute("fs locate_block 88uu88888,$0 $1 --block_manager=$2",
+ block_ids[0].ToString(), fs_paths, FLAGS_block_manager),
+ &stdout));
+ ASSERT_STR_CONTAINS(stdout, Substitute("$0 | $1",
+ block_ids[0].ToString(), fs_data_dirs));
+ ASSERT_STR_MATCHES(stdout, "88uu88888* | not an integer");
+}
+
TEST_F(ToolTest, TestLocalReplicaCopyLocal) {
SKIP_IF_SLOW_NOT_ALLOWED();
// TODO(abukor): Rewrite the test to make sure it works with encryption
diff --git a/src/kudu/tools/tool_action_fs.cc b/src/kudu/tools/tool_action_fs.cc
index 848064ffc..8085d821c 100644
--- a/src/kudu/tools/tool_action_fs.cc
+++ b/src/kudu/tools/tool_action_fs.cc
@@ -25,6 +25,7 @@
#include <string>
#include <type_traits>
#include <unordered_map>
+#include <unordered_set>
#include <utility>
#include <vector>
@@ -168,9 +169,13 @@ using std::shared_ptr;
using std::string;
using std::unique_ptr;
using std::unordered_map;
+using std::unordered_set;
using std::vector;
using strings::Substitute;
+constexpr const char* const kBlockIdsArg = "block_ids";
+constexpr const char* const kBlockIdsDesc = "Block ids existing in metadata files";
+
namespace kudu {
namespace tools {
@@ -439,6 +444,36 @@ Status DumpFsTree(const RunnerContext& /*context*/) {
return Status::OK();
}
+Status LocateBlock(const RunnerContext& context) {
+ const string& block_ids_str = FindOrDie(context.required_args, kBlockIdsArg);
+ vector<string> block_ids = strings::Split(block_ids_str, ",", strings::SkipEmpty());
+ if (block_ids.empty()) {
+ return Status::InvalidArgument("no block identifiers provided");
+ }
+ FsManagerOpts opts;
+ opts.read_only = true;
+ opts.update_instances = UpdateInstanceBehavior::DONT_UPDATE;
+ FsManager fs_manager(Env::Default(), std::move(opts));
+ RETURN_NOT_OK(fs_manager.Open());
+ DataTable output_table({ "block id", "path" });
+ for (const string& blkid_str : block_ids) {
+ uint64_t blkid = 0;
+ if (!safe_strtou64(blkid_str.c_str(), &blkid)) {
+ output_table.AddRow({blkid_str, "not an integer"});
+ continue;
+ }
+ BlockId block_id(blkid);
+ string path;
+ if (fs_manager.block_manager()->FindBlockPath(block_id, &path)) {
+ output_table.AddRow({blkid_str, path});
+ } else {
+ output_table.AddRow({blkid_str, "not found"});
+ }
+ }
+ RETURN_NOT_OK(output_table.PrintTo(cout));
+ return Status::OK();
+}
+
Status CheckForTabletsThatWillFailWithUpdate() {
FsManagerOpts opts;
opts.read_only = true;
@@ -1068,12 +1103,22 @@ unique_ptr<Mode> BuildFsMode() {
.AddOptionalParameter("fs_wal_dir")
.Build();
+ unique_ptr<Action> locate_block =
+ ActionBuilder("locate_block", &LocateBlock)
+ .Description("Find the file's path where the block locates")
+ .AddRequiredParameter({ kBlockIdsArg, kBlockIdsDesc })
+ .AddOptionalParameter("fs_data_dirs")
+ .AddOptionalParameter("fs_metadata_dir")
+ .AddOptionalParameter("fs_wal_dir")
+ .Build();
+
return ModeBuilder("fs")
.Description("Operate on a local Kudu filesystem")
.AddMode(BuildFsDumpMode())
.AddAction(std::move(check))
.AddAction(std::move(format))
.AddAction(std::move(list))
+ .AddAction(std::move(locate_block))
.AddAction(std::move(update))
.AddAction(std::move(upgrade_encryption_key))
.Build();