From 4c2328eb13f457ad9ed8b0dfacf5077fc9180985 Mon Sep 17 00:00:00 2001 From: Vitaliy Filippov Date: Mon, 17 Jun 2024 02:22:14 +0300 Subject: [PATCH] Implement ls-osd command --- src/cmd/CMakeLists.txt | 1 + src/cmd/cli.cpp | 17 +++ src/cmd/cli.h | 19 +-- src/cmd/cli_osd_tree.cpp | 322 +++++++++++++++++++++++++++++++++++++++ src/util/str_util.cpp | 21 ++- src/util/str_util.h | 2 +- 6 files changed, 367 insertions(+), 15 deletions(-) create mode 100644 src/cmd/cli_osd_tree.cpp diff --git a/src/cmd/CMakeLists.txt b/src/cmd/CMakeLists.txt index c81d54bd..fae01e6d 100644 --- a/src/cmd/CMakeLists.txt +++ b/src/cmd/CMakeLists.txt @@ -12,6 +12,7 @@ add_library(vitastor_cli STATIC cli_ls.cpp cli_create.cpp cli_modify.cpp + cli_osd_tree.cpp cli_flatten.cpp cli_merge.cpp cli_rm_data.cpp diff --git a/src/cmd/cli.cpp b/src/cmd/cli.cpp index dc58e0ac..0a04e2f0 100644 --- a/src/cmd/cli.cpp +++ b/src/cmd/cli.cpp @@ -118,6 +118,12 @@ static const char* help_text = " With --dry-run only checks if deletion is possible without data loss and\n" " redundancy degradation.\n" "\n" + "vitastor-cli osd-tree\n" + " Show current OSD tree.\n" + "\n" + "vitastor-cli osds|ls-osd|osd-ls\n" + " Show current OSDs as list.\n" + "\n" "vitastor-cli create-pool|pool-create (-s |--ec +) -n [OPTIONS]\n" " Create a pool. Required parameters:\n" " -s|--pg_size R Number of replicas for replicated pools\n" @@ -389,6 +395,17 @@ static int run(cli_tool_t *p, json11::Json::object cfg) // Allocate a new OSD number action_cb = p->start_alloc_osd(cfg); } + else if (cmd[0] == "osd-tree") + { + // Print OSD tree + action_cb = p->start_osd_tree(cfg); + } + else if (cmd[0] == "osds" || cmd[0] == "ls-osds" || cmd[0] == "ls-osd" || cmd[0] == "osd-ls") + { + // Print OSD list + cfg["flat"] = true; + action_cb = p->start_osd_tree(cfg); + } else if (cmd[0] == "create-pool" || cmd[0] == "pool-create") { // Create a new pool diff --git a/src/cmd/cli.h b/src/cmd/cli.h index a8598393..9f966bf9 100644 --- a/src/cmd/cli.h +++ b/src/cmd/cli.h @@ -57,22 +57,23 @@ public: friend struct snap_flattener_t; friend struct snap_remover_t; - std::function start_status(json11::Json); + std::function start_alloc_osd(json11::Json); + std::function start_create(json11::Json); std::function start_describe(json11::Json); std::function start_fix(json11::Json); - std::function start_ls(json11::Json); - std::function start_create(json11::Json); - std::function start_modify(json11::Json); - std::function start_rm_data(json11::Json); - std::function start_merge(json11::Json); std::function start_flatten(json11::Json); - std::function start_rm(json11::Json); - std::function start_rm_osd(json11::Json cfg); - std::function start_alloc_osd(json11::Json cfg); + std::function start_ls(json11::Json); + std::function start_merge(json11::Json); + std::function start_modify(json11::Json); + std::function start_osd_tree(json11::Json); std::function start_pool_create(json11::Json); std::function start_pool_modify(json11::Json); std::function start_pool_rm(json11::Json); std::function start_pool_ls(json11::Json); + std::function start_rm(json11::Json); + std::function start_rm_data(json11::Json); + std::function start_rm_osd(json11::Json); + std::function start_status(json11::Json); // Should be called like loop_and_wait(start_status(), ) void loop_and_wait(std::function loop_cb, std::function complete_cb); diff --git a/src/cmd/cli_osd_tree.cpp b/src/cmd/cli_osd_tree.cpp new file mode 100644 index 00000000..4a45610f --- /dev/null +++ b/src/cmd/cli_osd_tree.cpp @@ -0,0 +1,322 @@ +// Copyright (c) Vitaliy Filippov, 2024 +// License: VNPL-1.1 (see README.md for details) + +#include +#include "cli.h" +#include "cluster_client.h" +#include "epoll_manager.h" +#include "pg_states.h" +#include "str_util.h" + +struct placement_osd_t +{ + osd_num_t num; + std::string parent; + std::vector tags; + uint64_t size; + uint64_t free; + bool up; + double reweight; + uint32_t block_size, bitmap_granularity, immediate_commit; +}; + +struct placement_node_t +{ + std::string name; + std::string parent; + std::string level; + std::vector child_nodes; + std::vector child_osds; +}; + +struct placement_tree_t +{ + std::map nodes; + std::map osds; +}; + +struct osd_tree_printer_t +{ + cli_tool_t *parent; + json11::Json cfg; + bool flat = false; + + int state = 0; + cli_result_t result; + + std::shared_ptr placement_tree; + + bool is_done() { return state == 100; } + + void load_osd_tree() + { + if (state == 1) + goto resume_1; + parent->etcd_txn(json11::Json::object { + { "success", json11::Json::array { + json11::Json::object { + { "request_range", json11::Json::object { + { "key", base64_encode(parent->cli->st_cli.etcd_prefix+"/config/node_placement") }, + } }, + }, + json11::Json::object { + { "request_range", json11::Json::object { + { "key", base64_encode(parent->cli->st_cli.etcd_prefix+"/config/osd/") }, + { "range_end", base64_encode(parent->cli->st_cli.etcd_prefix+"/config/osd0") }, + } }, + }, + json11::Json::object { + { "request_range", json11::Json::object { + { "key", base64_encode(parent->cli->st_cli.etcd_prefix+"/osd/stats/") }, + { "range_end", base64_encode(parent->cli->st_cli.etcd_prefix+"/osd/stats0") }, + } }, + }, + } }, + }); + state = 1; +resume_1: + if (parent->waiting > 0) + return; + if (parent->etcd_err.err) + { + result = parent->etcd_err; + state = 100; + return; + } + json11::Json node_placement; + for (auto & item: parent->etcd_result["responses"][0]["response_range"]["kvs"].array_items()) + { + node_placement = parent->cli->st_cli.parse_etcd_kv(item).value; + } + std::map osd_config; + parent->iterate_kvs_1(parent->etcd_result["responses"][1]["response_range"]["kvs"], "/config/osd/", [&](uint64_t cur_osd, json11::Json value) + { + osd_config[cur_osd] = value; + }); + std::map osd_stats; + parent->iterate_kvs_1(parent->etcd_result["responses"][2]["response_range"]["kvs"], "/osd/stats/", [&](uint64_t cur_osd, json11::Json value) + { + osd_stats[cur_osd] = value; + }); + placement_tree = make_osd_tree(node_placement, osd_config, osd_stats); + } + + std::shared_ptr make_osd_tree(json11::Json node_placement_json, + std::map osd_config, std::map osd_stats) + { + auto node_placement = node_placement_json.object_items(); + auto tree = std::make_shared(); + tree->nodes[""] = (placement_node_t){}; + // Add non-OSD items + for (auto & kv: node_placement) + { + auto osd_num = stoull_full(kv.first); + if (!osd_num) + { + auto level = kv.second["level"].string_value(); + tree->nodes[kv.first] = (placement_node_t){ + .name = kv.first, + .parent = kv.second["parent"].string_value(), + .level = level == "" ? "unknown" : level, + }; + } + } + // Add OSDs + for (auto & kv: osd_stats) + { + auto & osd = tree->osds[kv.first] = (placement_osd_t){ + .num = kv.first, + .parent = kv.second["host"].string_value(), + .size = kv.second["size"].uint64_value(), + .free = kv.second["free"].uint64_value(), + .up = parent->cli->st_cli.peer_states.find(kv.first) != parent->cli->st_cli.peer_states.end(), + .reweight = 1, + .block_size = (uint32_t)kv.second["data_block_size"].uint64_value(), + .bitmap_granularity = (uint32_t)kv.second["bitmap_granularity"].uint64_value(), + .immediate_commit = etcd_state_client_t::parse_immediate_commit(kv.second["immediate_commit"].string_value()), + }; + if (tree->nodes.find(osd.parent) == tree->nodes.end()) + { + // Autocreate all hosts + tree->nodes[osd.parent] = (placement_node_t){ + .name = osd.parent, + .level = "host", + }; + } + auto cfg_it = osd_config.find(osd.num); + if (cfg_it != osd_config.end()) + { + auto & osd_cfg = cfg_it->second; + osd.reweight = osd_cfg["reweight"].is_number() ? osd_cfg["reweight"].number_value() : 1; + if (osd_cfg["tags"].is_array()) + { + for (auto & jtag: osd_cfg["tags"].array_items()) + osd.tags.push_back(jtag.string_value()); + } + } + auto np_it = node_placement.find(std::to_string(osd.num)); + if (np_it != node_placement.end()) + { + osd.parent = np_it->second["parent"].string_value(); + } + tree->nodes[osd.parent].child_osds.push_back(osd.num); + } + // Fill child_nodes + for (auto & ip: tree->nodes) + { + if (tree->nodes.find(ip.second.parent) == tree->nodes.end()) + { + ip.second.parent = ""; + } + if (ip.first != "") + { + tree->nodes[ip.second.parent].child_nodes.push_back(ip.first); + } + } + // FIXME: Maybe filter out loops here + return tree; + } + + std::string format_tree() + { + std::vector node_seq = { "" }; + std::vector indents = { -1 }; + std::map seen; + for (int i = 0; i < node_seq.size(); i++) + { + if (seen[node_seq[i]]) + { + continue; + } + seen[node_seq[i]] = true; + auto & child_nodes = placement_tree->nodes.at(node_seq[i]).child_nodes; + if (child_nodes.size()) + { + node_seq.insert(node_seq.begin()+i+1, child_nodes.begin(), child_nodes.end()); + indents.insert(indents.begin()+i+1, child_nodes.size(), indents[i]+1); + } + } + json11::Json::array fmt_items; + for (int i = 1; i < node_seq.size(); i++) + { + auto & node = placement_tree->nodes.at(node_seq[i]); + if (!flat) + { + fmt_items.push_back(json11::Json::object{ + { "type", str_repeat(" ", indents[i]) + node.level }, + { "name", node.name }, + }); + } + std::string parent = node.name; + if (flat) + { + auto cur = &placement_tree->nodes.at(node.name); + while (cur->parent != "" && cur->parent != node.name) + { + parent = cur->parent+"/"+parent; + cur = &placement_tree->nodes.at(cur->parent); + } + } + for (uint64_t osd_num: node.child_osds) + { + auto & osd = placement_tree->osds.at(osd_num); + fmt_items.push_back(json11::Json::object{ + { "type", (flat ? "osd" : str_repeat(" ", indents[i]+1) + "osd") }, + { "name", osd.num }, + { "parent", parent }, + { "up", osd.up ? "up" : "down" }, + { "size", format_size(osd.size, false, true) }, + { "used", format_q(100.0*(osd.size - osd.free)/osd.size)+" %" }, + { "reweight", format_q(osd.reweight) }, + { "tags", implode(",", osd.tags) }, + { "block", format_size(osd.block_size, false, true) }, + { "bitmap", format_size(osd.bitmap_granularity, false, true) }, + { "commit", osd.immediate_commit == IMMEDIATE_NONE ? "none" : (osd.immediate_commit == IMMEDIATE_ALL ? "all" : "small") }, + }); + } + } + json11::Json::array cols; + if (!flat) + { + cols.push_back(json11::Json::object{ + { "key", "type" }, + { "title", "TYPE" }, + }); + } + cols.push_back(json11::Json::object{ + { "key", "name" }, + { "title", flat ? "OSD" : "NAME" }, + }); + if (flat) + { + cols.push_back(json11::Json::object{ + { "key", "parent" }, + { "title", "PARENT" }, + }); + } + cols.push_back(json11::Json::object{ + { "key", "up" }, + { "title", "UP" }, + }); + cols.push_back(json11::Json::object{ + { "key", "size" }, + { "title", "SIZE" }, + }); + cols.push_back(json11::Json::object{ + { "key", "used" }, + { "title", "USED%" }, + }); + cols.push_back(json11::Json::object{ + { "key", "tags" }, + { "title", "TAGS" }, + }); + cols.push_back(json11::Json::object{ + { "key", "reweight" }, + { "title", "WEIGHT" }, + }); + cols.push_back(json11::Json::object{ + { "key", "block" }, + { "title", "BLOCK" }, + }); + cols.push_back(json11::Json::object{ + { "key", "bitmap" }, + { "title", "BITMAP" }, + }); + cols.push_back(json11::Json::object{ + { "key", "commit" }, + { "title", "IMM" }, + }); + return print_table(fmt_items, cols, parent->color); + } + + void loop() + { + if (state == 1) + goto resume_1; +resume_1: + load_osd_tree(); + if (parent->waiting > 0) + return; + result.text = format_tree(); + state = 100; + } +}; + +std::function cli_tool_t::start_osd_tree(json11::Json cfg) +{ + auto osd_tree_printer = new osd_tree_printer_t(); + osd_tree_printer->parent = this; + osd_tree_printer->cfg = cfg; + osd_tree_printer->flat = cfg["flat"].bool_value(); + return [osd_tree_printer](cli_result_t & result) + { + osd_tree_printer->loop(); + if (osd_tree_printer->is_done()) + { + result = osd_tree_printer->result; + delete osd_tree_printer; + return true; + } + return false; + }; +} diff --git a/src/util/str_util.cpp b/src/util/str_util.cpp index 0e6026ec..4505f2eb 100644 --- a/src/util/str_util.cpp +++ b/src/util/str_util.cpp @@ -151,10 +151,11 @@ static uint64_t size_thresh[] = { (uint64_t)1024*1024*1024*1024, (uint64_t)1024* static uint64_t size_thresh_d[] = { (uint64_t)1000000000000, (uint64_t)1000000000, (uint64_t)1000000, (uint64_t)1000, 0 }; static const int size_thresh_n = sizeof(size_thresh)/sizeof(size_thresh[0]); static const char *size_unit = "TGMKB"; +static const char *size_unit_ns = "TGMk "; -std::string format_size(uint64_t size, bool nobytes) +std::string format_size(uint64_t size, bool nobytes, bool nospace) { - uint64_t *thr = nobytes ? size_thresh_d : size_thresh; + uint64_t *thr = (nobytes ? size_thresh_d : size_thresh); char buf[256]; for (int i = 0; i < size_thresh_n; i++) { @@ -165,9 +166,19 @@ std::string format_size(uint64_t size, bool nobytes) assert(l < sizeof(buf)-2); if (buf[l-1] == '0') l -= 2; - buf[l] = i == size_thresh_n-1 && nobytes ? 0 : ' '; - buf[l+1] = i == size_thresh_n-1 && nobytes ? 0 : size_unit[i]; - buf[l+2] = 0; + if (i == size_thresh_n-1 && nobytes) + buf[l] = 0; + else if (nospace) + { + buf[l] = size_unit_ns[i]; + buf[l+1] = 0; + } + else + { + buf[l] = ' '; + buf[l+1] = size_unit[i]; + buf[l+2] = 0; + } break; } } diff --git a/src/util/str_util.h b/src/util/str_util.h index 9f89fdf2..b283e7ee 100644 --- a/src/util/str_util.h +++ b/src/util/str_util.h @@ -16,7 +16,7 @@ std::string strtolower(const std::string & in); std::string trim(const std::string & in, const char *rm_chars = " \n\r\t"); std::string str_replace(const std::string & in, const std::string & needle, const std::string & replacement); uint64_t stoull_full(const std::string & str, int base = 0); -std::string format_size(uint64_t size, bool nobytes = false); +std::string format_size(uint64_t size, bool nobytes = false, bool nospace = false); void print_help(const char *help_text, std::string exe_name, std::string cmd, bool all); uint64_t parse_time(std::string time_str, bool *ok = NULL); std::string read_all_fd(int fd);