diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 75daae77..8dc2ff91 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -153,7 +153,7 @@ target_link_libraries(vitastor-nbd # vitastor-cli add_executable(vitastor-cli - cli.cpp cli_ls.cpp cli_create.cpp cli_modify.cpp cli_flatten.cpp cli_merge.cpp cli_rm.cpp cli_snap_rm.cpp + cli.cpp cli_alloc_osd.cpp cli_ls.cpp cli_create.cpp cli_modify.cpp cli_flatten.cpp cli_merge.cpp cli_rm.cpp cli_snap_rm.cpp ) target_link_libraries(vitastor-cli vitastor_client diff --git a/src/cli.cpp b/src/cli.cpp index c2b2875e..8bc80cd5 100644 --- a/src/cli.cpp +++ b/src/cli.cpp @@ -130,6 +130,9 @@ void cli_tool_t::help() " must be a child of and may be one of the layers between\n" " and , including and .\n" "\n" + "%s alloc-osd\n" + " Allocate a new OSD number and reserve it by creating empty /osd/stats/ key.\n" + "\n" "GLOBAL OPTIONS:\n" " --etcd_address \n" " --iodepth N Send N operations in parallel to each OSD when possible (default 32)\n" @@ -139,7 +142,7 @@ void cli_tool_t::help() " --no-color Disable colored output\n" " --json JSON output\n" , - exe_name, exe_name, exe_name, exe_name, exe_name, exe_name, exe_name, exe_name, exe_name + exe_name, exe_name, exe_name, exe_name, exe_name, exe_name, exe_name, exe_name, exe_name, exe_name ); exit(0); } @@ -268,6 +271,11 @@ void cli_tool_t::run(json11::Json cfg) // Remove multiple snapshots and rebase their children action_cb = start_snap_rm(cfg); } + else if (cmd[0] == "alloc-osd") + { + // Allocate a new OSD number + action_cb = start_alloc_osd(cfg); + } else { fprintf(stderr, "unknown command: %s\n", cmd[0].string_value().c_str()); diff --git a/src/cli.h b/src/cli.h index 426069ec..413eb0cf 100644 --- a/src/cli.h +++ b/src/cli.h @@ -50,11 +50,12 @@ public: friend struct snap_flattener_t; friend struct snap_remover_t; - std::function start_ls(json11::Json cfg); - std::function start_create(json11::Json cfg); - std::function start_modify(json11::Json cfg); + std::function start_ls(json11::Json); + std::function start_create(json11::Json); + std::function start_modify(json11::Json); std::function start_rm(json11::Json); std::function start_merge(json11::Json); std::function start_flatten(json11::Json); std::function start_snap_rm(json11::Json); + std::function start_alloc_osd(json11::Json cfg, uint64_t *out = NULL); }; diff --git a/src/cli_alloc_osd.cpp b/src/cli_alloc_osd.cpp new file mode 100644 index 00000000..3c2bcb13 --- /dev/null +++ b/src/cli_alloc_osd.cpp @@ -0,0 +1,141 @@ +// Copyright (c) Vitaliy Filippov, 2019+ +// License: VNPL-1.1 (see README.md for details) + +#include +#include "cli.h" +#include "cluster_client.h" +#include "base64.h" + +#include + +// Safely allocate an OSD number +struct alloc_osd_t +{ + cli_tool_t *parent; + + json11::Json result; + uint64_t new_id = 1; + + int state = 0; + + bool is_done() + { + return state == 100; + } + + void loop() + { + if (state == 1) + goto resume_1; + while (true) + { + etcd_txn(json11::Json::object { + { "compare", json11::Json::array { + json11::Json::object { + { "target", "VERSION" }, + { "version", 0 }, + { "key", base64_encode( + parent->cli->st_cli.etcd_prefix+"/osd/stats/"+std::to_string(new_id) + ) }, + }, + } }, + { "success", json11::Json::array { + json11::Json::object { + { "request_put", json11::Json::object { + { "key", base64_encode( + parent->cli->st_cli.etcd_prefix+"/osd/stats/"+std::to_string(new_id) + ) }, + { "value", base64_encode("{}") }, + } }, + }, + } }, + { "failure", json11::Json::array { + 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") }, + { "keys_only", true }, + } }, + }, + } }, + }); + resume_1: + state = 1; + if (parent->waiting > 0) + return; + if (!result["succeeded"].bool_value()) + { + std::vector used; + for (auto kv: result["responses"][0]["response_range"]["kvs"].array_items()) + { + std::string key = base64_decode(kv["key"].string_value()); + osd_num_t cur_osd; + char null_byte = 0; + sscanf(key.c_str() + parent->cli->st_cli.etcd_prefix.length(), "/osd/stats/%lu%c", &cur_osd, &null_byte); + if (!cur_osd || null_byte != 0) + { + fprintf(stderr, "Invalid key in etcd: %s\n", key.c_str()); + continue; + } + used.push_back(cur_osd); + } + std::sort(used.begin(), used.end()); + if (used[used.size()-1] == used.size()) + { + new_id = used.size()+1; + } + else + { + int s = 0, e = used.size(); + while (e > s+1) + { + int c = (s+e)/2; + if (used[c] == c+1) + s = c; + else + e = c; + } + new_id = used[e-1]+1; + } + } + } + state = 100; + } + + void etcd_txn(json11::Json txn) + { + parent->waiting++; + parent->cli->st_cli.etcd_txn(txn, ETCD_SLOW_TIMEOUT, [this](std::string err, json11::Json res) + { + parent->waiting--; + if (err != "") + { + fprintf(stderr, "Error reading from etcd: %s\n", err.c_str()); + exit(1); + } + this->result = res; + parent->ringloop->wakeup(); + }); + } +}; + +std::function cli_tool_t::start_alloc_osd(json11::Json cfg, uint64_t *out) +{ + json11::Json::array cmd = cfg["command"].array_items(); + auto alloc_osd = new alloc_osd_t(); + alloc_osd->parent = this; + return [alloc_osd, &out]() + { + alloc_osd->loop(); + if (alloc_osd->is_done()) + { + if (out) + *out = alloc_osd->new_id; + else if (alloc_osd->new_id) + printf("%lu\n", alloc_osd->new_id); + delete alloc_osd; + return true; + } + return false; + }; +}