vitastor-cli: add commands to control pools: pool-create, pool-ls, pool-modify, pool-rm

PR #59 - https://github.com/vitalif/vitastor/pull/58/commits

By MIND Software LLC

By submitting this pull request, I accept Vitastor CLA
master
idelson 2024-02-01 18:44:14 +03:00 committed by Vitaliy Filippov
parent 02d1f16bbd
commit dc92851322
19 changed files with 2700 additions and 263 deletions

View File

@ -145,7 +145,6 @@ add_library(vitastor_client SHARED
cli_status.cpp
cli_describe.cpp
cli_fix.cpp
cli_df.cpp
cli_ls.cpp
cli_create.cpp
cli_modify.cpp
@ -154,6 +153,11 @@ add_library(vitastor_client SHARED
cli_rm_data.cpp
cli_rm.cpp
cli_rm_osd.cpp
cli_pool_cfg.cpp
cli_pool_create.cpp
cli_pool_ls.cpp
cli_pool_modify.cpp
cli_pool_rm.cpp
)
set_target_properties(vitastor_client PROPERTIES PUBLIC_HEADER "vitastor_c.h")
target_link_libraries(vitastor_client

View File

@ -113,6 +113,105 @@ 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 create-pool <name> --scheme <scheme> -s <pg_size> --pg_minsize <pg_minsize> -n <pg_count> --parity_chunks <number> [OPTIONS]\n"
" Create a pool.\n"
" --scheme <scheme>\n"
" Redundancy scheme used for data in this pool. One of: \"replicated\", \"xor\", \"ec\" or \"jerasure\".\n"
" It's \"replicated\" by default.\n"
" --ec <N>+<K>\n"
" Shortcut for 'ec' scheme. scheme = ec, pg_size = N+K, parity_chunks = K.\n"
" -s|--pg_size <size>\n"
" Total number of disks for PGs of this pool - i.e., number of replicas for replicated pools and number of data plus parity disks for EC/XOR pools.\n"
" --pg_minsize <size>\n"
" Number of available live OSDs for PGs of this pool to remain active.\n"
" -n|--pg_count <count>\n"
" Number of PGs for this pool.\n"
" --parity_chunks <number>\n"
" Number of parity chunks for EC/XOR pools\n"
" -f|--force\n"
" Proceed without checking pool/OSD params (pg_size, block_size, bitmap_granularity, and immediate_commit).\n"
" --failure_domain <failure_domain>\n"
" Failure domain specification. Must be \"host\" or \"osd\" or refer to one of the placement tree levels, defined in placement_levels.\n"
" --max_osd_combinations <number>\n"
" This parameter specifies the maximum number of combinations to generate when optimising PG placement.\n"
" --block_size <size>\n"
" Block size for this pool.\n"
" --bitmap_granularity <granularity>\n"
" \"Sector\" size of virtual disks in this pool.\n"
" --immediate_commit <all|small|none>\n"
" Immediate commit setting for this pool. One of \"all\", \"small\" and \"none\".\n"
" --pg_stripe_size <size>\n"
" Specifies the stripe size for this pool according to which images are split into different PGs.\n"
" --root_node <node>\n"
" Specifies the root node of the OSD tree to restrict this pool OSDs to.\n"
" --osd_tags <tags>\n"
" Specifies OSD tags to restrict this pool to.\n"
" Example: --osd_tags tag0 or --osd_tags tag0,tag1\n"
" --primary_affinity_tags <tags>\n"
" Specifies OSD tags to prefer putting primary OSDs in this pool to.\n"
" Example: --primary_affinity_tags tag0 or --primary_affinity_tags tag0,tag1\n"
" --scrub_interval <time_interval>\n"
" Automatic scrubbing interval for this pool. Format: number + unit s/m/h/d/M/y.\n"
" Examples:\n"
" vitastor-cli create-pool test_x4 -s 4 -n 32\n"
" vitastor-cli create-pool test_ec42 --ec 4+2 -n 32\n"
"\n"
"vitastor-cli modify-pool <id|name> [--name <new_name>] [-s <pg_size>] [--pg_minsize <pg_minsize>] [-n <pg_count>] [OPTIONS]\n"
" Modify an existing pool.\n"
" --name <new_name>\n"
" Change name of this pool.\n"
" -s|--pg_size <size>\n"
" Total number of disks for PGs of this pool - i.e., number of replicas for replicated pools and number of data plus parity disks for EC/XOR pools.\n"
" --pg_minsize <size>\n"
" Number of available live OSDs for PGs of this pool to remain active.\n"
" -n|--pg_count <count>\n"
" Number of PGs for this pool.\n"
" -f|--force\n"
" Proceed without checking pool/OSD params (block_size, bitmap_granularity and immediate_commit).\n"
" --failure_domain <failure_domain>\n"
" Failure domain specification. Must be \"host\" or \"osd\" or refer to one of the placement tree levels, defined in placement_levels.\n"
" --max_osd_combinations <number>\n"
" This parameter specifies the maximum number of combinations to generate when optimising PG placement.\n"
" --block_size <size>\n"
" Block size for this pool.\n"
" --immediate_commit <all|small|none>\n"
" Immediate commit setting for this pool. One of \"all\", \"small\" and \"none\".\n"
" --pg_stripe_size <size>\n"
" Specifies the stripe size for this pool according to which images are split into different PGs.\n"
" --root_node <node>\n"
" Specifies the root node of the OSD tree to restrict this pool OSDs to.\n"
" --osd_tags <tags>\n"
" Specifies OSD tags to restrict this pool to.\n"
" Example: --osd_tags tag0 or --osd_tags tag0,tag1\n"
" --primary_affinity_tags <tags>\n"
" Specifies OSD tags to prefer putting primary OSDs in this pool to.\n"
" Example: --primary_affinity_tags tag0 or --primary_affinity_tags tag0,tag1\n"
" --scrub_interval <time_interval>\n"
" Automatic scrubbing interval for this pool. Format: number + unit s/m/h/d/M/y.\n"
" Examples:\n"
" vitastor-cli modify-pool pool_A -name pool_B\n"
" vitastor-cli modify-pool 2 -s 4 -n 128 --block_size 262144\n"
"\n"
"vitastor-cli rm-pool [--force] <id|name>\n"
" Remove existing pool from cluster.\n"
" Refuses to remove pools with related Image and/or Snapshot data without --force.\n"
" Examples:\n"
" vitastor-cli rm-pool test_pool\n"
" vitastor-cli rm-pool --force 2\n"
"\n"
"vitastor-cli ls-pool [-l] [-p POOL] [--sort FIELD] [-r] [-n N] [--stats] [<glob> ...]\n"
" List pool (only matching <glob> patterns if passed).\n"
" -p|--pool POOL Show in detail pool ID or name\n"
" -l|--long Show all available field\n"
" --sort FIELD Sort by specified field (id, name, pg_count, scheme_name, used_byte, total, max_available, used_pct, space_efficiency, status, restore, root_node, failure_domain, osd_tags, primary_affinity_tags)\n"
" -r|--reverse Sort in descending order\n"
" -n|--count N Only list first N items\n"
" --stats Performance statistics\n"
" Examples:\n"
" vitastor-cli ls-pool -l\n"
" vitastor-cli ls-pool -l --sort pool_name\n"
" vitastor-cli ls-pool -p 2\n"
"\n"
"Use vitastor-cli --help <command> for command details or vitastor-cli --help --all for all details.\n"
"\n"
"GLOBAL OPTIONS:\n"
@ -133,6 +232,8 @@ static json11::Json::object parse_args(int narg, const char *args[])
cfg["progress"] = "1";
for (int i = 1; i < narg; i++)
{
bool argHasValue = (!(i == narg-1) && (args[i+1][0] != '-'));
if (args[i][0] == '-' && args[i][1] == 'h' && args[i][2] == 0)
{
cfg["help"] = "1";
@ -143,15 +244,15 @@ static json11::Json::object parse_args(int narg, const char *args[])
}
else if (args[i][0] == '-' && args[i][1] == 'n' && args[i][2] == 0)
{
cfg["count"] = args[++i];
cfg["count"] = argHasValue ? args[++i] : "";
}
else if (args[i][0] == '-' && args[i][1] == 'p' && args[i][2] == 0)
{
cfg["pool"] = args[++i];
cfg["pool"] = argHasValue ? args[++i] : "";
}
else if (args[i][0] == '-' && args[i][1] == 's' && args[i][2] == 0)
{
cfg["size"] = args[++i];
cfg["size"] = argHasValue ? args[++i] : "";
}
else if (args[i][0] == '-' && args[i][1] == 'r' && args[i][2] == 0)
{
@ -164,7 +265,7 @@ static json11::Json::object parse_args(int narg, const char *args[])
else if (args[i][0] == '-' && args[i][1] == '-')
{
const char *opt = args[i]+2;
cfg[opt] = i == narg-1 || !strcmp(opt, "json") ||
if (!strcmp(opt, "json") ||
!strcmp(opt, "wait-list") || !strcmp(opt, "wait_list") ||
!strcmp(opt, "long") || !strcmp(opt, "del") ||
!strcmp(opt, "no-color") || !strcmp(opt, "no_color") ||
@ -172,9 +273,15 @@ static json11::Json::object parse_args(int narg, const char *args[])
!strcmp(opt, "force") || !strcmp(opt, "reverse") ||
!strcmp(opt, "allow-data-loss") || !strcmp(opt, "allow_data_loss") ||
!strcmp(opt, "dry-run") || !strcmp(opt, "dry_run") ||
!strcmp(opt, "help") || !strcmp(opt, "all") ||
(!strcmp(opt, "writers-stopped") || !strcmp(opt, "writers_stopped")) && strcmp("1", args[i+1]) != 0
? "1" : args[++i];
!strcmp(opt, "help") || !strcmp(opt, "all") || !strcmp(opt, "stats") ||
!strcmp(opt, "writers-stopped") || !strcmp(opt, "writers_stopped"))
{
cfg[opt] = "1";
}
else
{
cfg[opt] = argHasValue ? args[++i] : "";
}
}
else
{
@ -217,7 +324,8 @@ static int run(cli_tool_t *p, json11::Json::object cfg)
else if (cmd[0] == "df")
{
// Show pool space stats
action_cb = p->start_df(cfg);
cfg["dfformat"] = "1";
action_cb = p->start_pool_ls(cfg);
}
else if (cmd[0] == "ls")
{
@ -324,6 +432,43 @@ 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] == "create-pool")
{
// Create a new pool
if (cmd.size() > 1 && cfg["name"].is_null())
{
cfg["name"] = cmd[1];
}
action_cb = p->start_pool_create(cfg);
}
else if (cmd[0] == "modify-pool")
{
// Modify existing pool
if (cmd.size() > 1)
{
cfg["pool"] = cmd[1];
}
action_cb = p->start_pool_modify(cfg);
}
else if (cmd[0] == "rm-pool")
{
// Remove existing pool
if (cmd.size() > 1)
{
cfg["pool"] = cmd[1];
}
action_cb = p->start_pool_rm(cfg);
}
else if (cmd[0] == "ls-pool")
{
// Show pool list
if (cmd.size() > 1)
{
cmd.erase(cmd.begin(), cmd.begin()+1);
cfg["names"] = cmd;
}
action_cb = p->start_pool_ls(cfg);
}
else
{
result = { .err = EINVAL, .text = "unknown command: "+cmd[0].string_value() };

View File

@ -46,6 +46,7 @@ public:
json11::Json etcd_result;
void parse_config(json11::Json::object & cfg);
json11::Json parse_tags(std::string tags);
void change_parent(inode_t cur, inode_t new_parent, cli_result_t *result);
inode_config_t* get_inode_cfg(const std::string & name);
@ -58,7 +59,6 @@ public:
std::function<bool(cli_result_t &)> start_status(json11::Json);
std::function<bool(cli_result_t &)> start_describe(json11::Json);
std::function<bool(cli_result_t &)> start_fix(json11::Json);
std::function<bool(cli_result_t &)> start_df(json11::Json);
std::function<bool(cli_result_t &)> start_ls(json11::Json);
std::function<bool(cli_result_t &)> start_create(json11::Json);
std::function<bool(cli_result_t &)> start_modify(json11::Json);
@ -68,6 +68,10 @@ public:
std::function<bool(cli_result_t &)> start_rm(json11::Json);
std::function<bool(cli_result_t &)> start_rm_osd(json11::Json cfg);
std::function<bool(cli_result_t &)> start_alloc_osd(json11::Json cfg);
std::function<bool(cli_result_t &)> start_pool_create(json11::Json);
std::function<bool(cli_result_t &)> start_pool_modify(json11::Json);
std::function<bool(cli_result_t &)> start_pool_rm(json11::Json);
std::function<bool(cli_result_t &)> start_pool_ls(json11::Json);
// Should be called like loop_and_wait(start_status(), <completion callback>)
void loop_and_wait(std::function<bool(cli_result_t &)> loop_cb, std::function<void(const cli_result_t &)> complete_cb);

View File

@ -126,6 +126,32 @@ void cli_tool_t::parse_config(json11::Json::object & cfg)
list_first = cfg["wait_list"].uint64_value() ? true : false;
}
json11::Json cli_tool_t::parse_tags(std::string tags)
{
json11::Json json_tags;
// Format: "tag0" or "tag0,tag1,tag2"
if (tags.find(',') == std::string::npos)
{
json_tags = tags;
}
else
{
json11::Json::array json_tags_array;
while (tags.size())
{
auto pos = tags.find(',');
auto tag = tags.substr(0, pos);
if (tag != "")
{
json_tags_array.push_back(tag);
}
tags = pos == std::string::npos ? std::string("") : tags.substr(pos+1);
}
json_tags = json_tags_array;
}
return json_tags;
};
struct cli_result_looper_t
{
ring_consumer_t consumer;

View File

@ -183,7 +183,16 @@ resume_3:
// Save into inode_config for library users to be able to take it from there immediately
new_cfg.mod_revision = parent->etcd_result["responses"][0]["response_put"]["header"]["revision"].uint64_value();
parent->cli->st_cli.insert_inode_config(new_cfg);
result = (cli_result_t){ .err = 0, .text = "Image "+image_name+" created" };
result = (cli_result_t){
.err = 0,
.text = "Image "+image_name+" created",
.data = json11::Json::object {
{ "name", image_name },
{ "pool", new_pool_name },
{ "parent", new_parent },
{ "size", size },
}
};
state = 100;
}
@ -251,7 +260,16 @@ resume_4:
// Save into inode_config for library users to be able to take it from there immediately
new_cfg.mod_revision = parent->etcd_result["responses"][0]["response_put"]["header"]["revision"].uint64_value();
parent->cli->st_cli.insert_inode_config(new_cfg);
result = (cli_result_t){ .err = 0, .text = "Snapshot "+image_name+"@"+new_snap+" created" };
result = (cli_result_t){
.err = 0,
.text = "Snapshot "+image_name+"@"+new_snap+" created",
.data = json11::Json::object {
{ "name", image_name+"@"+new_snap },
{ "pool", (uint64_t)new_pool_id },
{ "parent", new_parent },
{ "size", size },
}
};
state = 100;
}

View File

@ -1,243 +0,0 @@
// Copyright (c) Vitaliy Filippov, 2019+
// License: VNPL-1.1 (see README.md for details)
#include "cli.h"
#include "cluster_client.h"
#include "str_util.h"
// List pools with space statistics
struct pool_lister_t
{
cli_tool_t *parent;
int state = 0;
json11::Json space_info;
cli_result_t result;
std::map<pool_id_t, json11::Json::object> pool_stats;
bool is_done()
{
return state == 100;
}
void get_stats()
{
if (state == 1)
goto resume_1;
// Space statistics - pool/stats/<pool>
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+"/pool/stats/"
) },
{ "range_end", base64_encode(
parent->cli->st_cli.etcd_prefix+"/pool/stats0"
) },
} },
},
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;
}
space_info = parent->etcd_result;
std::map<pool_id_t, uint64_t> osd_free;
for (auto & kv_item: space_info["responses"][0]["response_range"]["kvs"].array_items())
{
auto kv = parent->cli->st_cli.parse_etcd_kv(kv_item);
// pool ID
pool_id_t pool_id;
char null_byte = 0;
int scanned = sscanf(kv.key.substr(parent->cli->st_cli.etcd_prefix.length()).c_str(), "/pool/stats/%u%c", &pool_id, &null_byte);
if (scanned != 1 || !pool_id || pool_id >= POOL_ID_MAX)
{
fprintf(stderr, "Invalid key in etcd: %s\n", kv.key.c_str());
continue;
}
// pool/stats/<N>
pool_stats[pool_id] = kv.value.object_items();
}
for (auto & kv_item: space_info["responses"][1]["response_range"]["kvs"].array_items())
{
auto kv = parent->cli->st_cli.parse_etcd_kv(kv_item);
// osd ID
osd_num_t osd_num;
char null_byte = 0;
int scanned = sscanf(kv.key.substr(parent->cli->st_cli.etcd_prefix.length()).c_str(), "/osd/stats/%ju%c", &osd_num, &null_byte);
if (scanned != 1 || !osd_num || osd_num >= POOL_ID_MAX)
{
fprintf(stderr, "Invalid key in etcd: %s\n", kv.key.c_str());
continue;
}
// osd/stats/<N>::free
osd_free[osd_num] = kv.value["free"].uint64_value();
}
// Calculate max_avail for each pool
for (auto & pp: parent->cli->st_cli.pool_config)
{
auto & pool_cfg = pp.second;
uint64_t pool_avail = UINT64_MAX;
std::map<osd_num_t, uint64_t> pg_per_osd;
for (auto & pgp: pool_cfg.pg_config)
{
for (auto pg_osd: pgp.second.target_set)
{
if (pg_osd != 0)
{
pg_per_osd[pg_osd]++;
}
}
}
for (auto pg_per_pair: pg_per_osd)
{
uint64_t pg_free = osd_free[pg_per_pair.first] * pool_cfg.real_pg_count / pg_per_pair.second;
if (pool_avail > pg_free)
{
pool_avail = pg_free;
}
}
if (pool_avail == UINT64_MAX)
{
pool_avail = 0;
}
if (pool_cfg.scheme != POOL_SCHEME_REPLICATED)
{
pool_avail *= (pool_cfg.pg_size - pool_cfg.parity_chunks);
}
pool_stats[pool_cfg.id] = json11::Json::object {
{ "id", (uint64_t)pool_cfg.id },
{ "name", pool_cfg.name },
{ "pg_count", pool_cfg.pg_count },
{ "real_pg_count", pool_cfg.real_pg_count },
{ "scheme", pool_cfg.scheme == POOL_SCHEME_REPLICATED ? "replicated" : "ec" },
{ "scheme_name", pool_cfg.scheme == POOL_SCHEME_REPLICATED
? std::to_string(pool_cfg.pg_size)+"/"+std::to_string(pool_cfg.pg_minsize)
: "EC "+std::to_string(pool_cfg.pg_size-pool_cfg.parity_chunks)+"+"+std::to_string(pool_cfg.parity_chunks) },
{ "used_raw", (uint64_t)(pool_stats[pool_cfg.id]["used_raw_tb"].number_value() * ((uint64_t)1<<40)) },
{ "total_raw", (uint64_t)(pool_stats[pool_cfg.id]["total_raw_tb"].number_value() * ((uint64_t)1<<40)) },
{ "max_available", pool_avail },
{ "raw_to_usable", pool_stats[pool_cfg.id]["raw_to_usable"].number_value() },
{ "space_efficiency", pool_stats[pool_cfg.id]["space_efficiency"].number_value() },
{ "pg_real_size", pool_stats[pool_cfg.id]["pg_real_size"].uint64_value() },
{ "failure_domain", pool_cfg.failure_domain },
};
}
}
json11::Json::array to_list()
{
json11::Json::array list;
for (auto & kv: pool_stats)
{
list.push_back(kv.second);
}
return list;
}
void loop()
{
get_stats();
if (parent->waiting > 0)
return;
if (state == 100)
return;
if (parent->json_output)
{
// JSON output
result.data = to_list();
state = 100;
return;
}
// Table output: name, scheme_name, pg_count, total, used, max_avail, used%, efficiency
json11::Json::array cols;
cols.push_back(json11::Json::object{
{ "key", "name" },
{ "title", "NAME" },
});
cols.push_back(json11::Json::object{
{ "key", "scheme_name" },
{ "title", "SCHEME" },
});
cols.push_back(json11::Json::object{
{ "key", "pg_count_fmt" },
{ "title", "PGS" },
});
cols.push_back(json11::Json::object{
{ "key", "total_fmt" },
{ "title", "TOTAL" },
});
cols.push_back(json11::Json::object{
{ "key", "used_fmt" },
{ "title", "USED" },
});
cols.push_back(json11::Json::object{
{ "key", "max_avail_fmt" },
{ "title", "AVAILABLE" },
});
cols.push_back(json11::Json::object{
{ "key", "used_pct" },
{ "title", "USED%" },
});
cols.push_back(json11::Json::object{
{ "key", "eff_fmt" },
{ "title", "EFFICIENCY" },
});
json11::Json::array list;
for (auto & kv: pool_stats)
{
double raw_to = kv.second["raw_to_usable"].number_value();
if (raw_to < 0.000001 && raw_to > -0.000001)
raw_to = 1;
kv.second["pg_count_fmt"] = kv.second["real_pg_count"] == kv.second["pg_count"]
? kv.second["real_pg_count"].as_string()
: kv.second["real_pg_count"].as_string()+"->"+kv.second["pg_count"].as_string();
kv.second["total_fmt"] = format_size(kv.second["total_raw"].uint64_value() / raw_to);
kv.second["used_fmt"] = format_size(kv.second["used_raw"].uint64_value() / raw_to);
kv.second["max_avail_fmt"] = format_size(kv.second["max_available"].uint64_value());
kv.second["used_pct"] = format_q(kv.second["total_raw"].uint64_value()
? (100 - 100*kv.second["max_available"].uint64_value() *
kv.second["raw_to_usable"].number_value() / kv.second["total_raw"].uint64_value())
: 100)+"%";
kv.second["eff_fmt"] = format_q(kv.second["space_efficiency"].number_value()*100)+"%";
}
result.data = to_list();
result.text = print_table(result.data, cols, parent->color);
state = 100;
}
};
std::function<bool(cli_result_t &)> cli_tool_t::start_df(json11::Json cfg)
{
auto lister = new pool_lister_t();
lister->parent = this;
return [lister](cli_result_t & result)
{
lister->loop();
if (lister->is_done())
{
result = lister->result;
delete lister;
return true;
}
return false;
};
}

View File

@ -342,7 +342,11 @@ struct snap_merger_t
printf("\rOverwriting blocks: %ju/%ju\n", to_process, to_process);
}
// Done
result = (cli_result_t){ .text = "Done, layers from "+from_name+" to "+to_name+" merged into "+target_name };
result = (cli_result_t){ .text = "Done, layers from "+from_name+" to "+to_name+" merged into "+target_name, .data = json11::Json::object {
{ "from", from_name },
{ "to", to_name },
{ "into", target_name },
}};
state = 100;
resume_100:
return;

View File

@ -84,7 +84,10 @@ struct image_changer_t
(!new_size && !force_size || cfg.size == new_size || cfg.size >= new_size && inc_size) &&
(new_name == "" || new_name == image_name))
{
result = (cli_result_t){ .text = "No change" };
result = (cli_result_t){ .err = 0, .text = "No change", .data = json11::Json::object {
{ "error_code", 0 },
{ "error_text", "No change" },
}};
state = 100;
return;
}
@ -220,7 +223,16 @@ resume_2:
parent->cli->st_cli.inode_by_name.erase(image_name);
}
parent->cli->st_cli.insert_inode_config(cfg);
result = (cli_result_t){ .err = 0, .text = "Image "+image_name+" modified" };
result = (cli_result_t){
.err = 0,
.text = "Image "+image_name+" modified",
.data = json11::Json::object {
{ "name", image_name },
{ "inode", INODE_NO_POOL(inode_num) },
{ "pool", (uint64_t)INODE_POOL(inode_num) },
{ "size", new_size },
}
};
state = 100;
}
};

473
src/cli_pool_cfg.cpp Normal file
View File

@ -0,0 +1,473 @@
/*
=========================================================================
Copyright (c) 2023 MIND Software LLC. All Rights Reserved.
This file is part of the Software-Defined Storage MIND UStor Project.
For more information about this product, please visit https://mindsw.io
or contact us directly at info@mindsw.io
=========================================================================
*/
#include "cli_pool_cfg.h"
bool pool_configurator_t::is_valid_scheme_string(std::string scheme_str)
{
if (scheme_str != "replicated" && scheme_str != "xor" && scheme_str != "ec")
{
error = "Coding scheme should be one of \"xor\", \"replicated\", \"ec\" or \"jerasure\"";
return false;
}
return true;
}
bool pool_configurator_t::is_valid_immediate_commit_string(std::string immediate_commit_str)
{
if (immediate_commit != "" && immediate_commit != "all" && immediate_commit != "small" && immediate_commit != "none")
{
error = "Immediate Commit should be one of \"all\", \"small\", or \"none\"";
return false;
}
return true;
}
std::string pool_configurator_t::get_error_string()
{
return error;
}
bool pool_configurator_t::parse(json11::Json cfg, bool new_pool)
{
if (new_pool) // New pool configuration
{
// Pool name (req)
name = cfg["name"].string_value();
if (name == "")
{
error = "Pool name must be given";
return false;
}
// Exclusive ec shortcut check
if (!cfg["ec"].is_null() &&
(!cfg["scheme"].is_null() || !cfg["size"].is_null() || !cfg["pg_size"].is_null() || !cfg["parity_chunks"].is_null()))
{
error = "You cannot use 'ec' shortcut together with PG size, parity chunks and scheme arguments";
return false;
}
// ec = N+K (opt)
if (cfg["ec"].is_string())
{
scheme = "ec";
// pg_size = N+K
// parity_chunks = K
int ret = sscanf(cfg["ec"].string_value().c_str(), "%lu+%lu", &pg_size, &parity_chunks);
if (ret != 2)
{
error = "Shortcut for 'ec' scheme has an invalid value. Format: --ec <N>+<K>";
return false;
}
if (!pg_size || !parity_chunks)
{
error = "<N>+<K> values for 'ec' scheme cannot be 0";
return false;
}
pg_size += parity_chunks;
}
// scheme (opt) + pg_size (req) + parity_chunks (req)
else
{
scheme = cfg["scheme"].is_string() ?
(cfg["scheme"].string_value() == "jerasure" ? "ec" : cfg["scheme"].string_value()) : "replicated";
if (!is_valid_scheme_string(scheme))
{
return false;
}
if (!cfg["size"].is_null() && !cfg["pg_size"].is_null() ||
!cfg["size"].is_null() && !cfg["size"].uint64_value() ||
!cfg["pg_size"].is_null() && !cfg["pg_size"].uint64_value())
{
error = "PG size has an invalid value";
return false;
}
pg_size = !cfg["size"].is_null() ? cfg["size"].uint64_value() : cfg["pg_size"].uint64_value();
if (!pg_size)
{
error = "PG size must be given with value >= 1";
return false;
}
if (!cfg["parity_chunks"].is_null() && !cfg["parity_chunks"].uint64_value())
{
error = "Parity chunks has an invalid value";
return false;
}
parity_chunks = cfg["parity_chunks"].uint64_value();
if (scheme == "xor" && !parity_chunks)
{
parity_chunks = 1;
}
if (!parity_chunks)
{
error = "Parity Chunks must be given with value >= 1";
return false;
}
}
// pg_minsize (opt)
if (cfg["pg_minsize"].uint64_value())
{
pg_minsize = cfg["pg_minsize"].uint64_value();
}
else
{
if (!cfg["pg_minsize"].is_null())
{
error = "PG minsize has an invalid value";
return false;
}
if (scheme == "replicated")
{
// pg_minsize = (N+K > 2) ? 2 : 1
pg_minsize = pg_size > 2 ? 2 : 1;
}
else // ec or xor
{
// pg_minsize = (K > 1) ? N + 1 : N
pg_minsize = pg_size - parity_chunks + (parity_chunks > 1 ? 1 : 0);
}
}
if (!pg_minsize)
{
error = "PG minsize must be given with value >= 1";
return false;
}
// pg_count (req)
if (!cfg["count"].is_null() && !cfg["pg_count"].is_null() ||
!cfg["count"].is_null() && !cfg["count"].uint64_value() ||
!cfg["pg_count"].is_null() && !cfg["pg_count"].uint64_value())
{
error = "PG count has an invalid value";
return false;
}
pg_count = !cfg["count"].is_null() ? cfg["count"].uint64_value() : cfg["pg_count"].uint64_value();
if (!pg_count)
{
error = "PG count must be given with value >= 1";
return false;
}
// Optional params
failure_domain = cfg["failure_domain"].string_value();
if (!cfg["max_osd_combinations"].is_null() && !cfg["max_osd_combinations"].uint64_value())
{
error = "Max OSD combinations has an invalid value";
return false;
}
max_osd_combinations = cfg["max_osd_combinations"].uint64_value();
if (!cfg["block_size"].is_null() && !cfg["block_size"].uint64_value())
{
error = "Block size has an invalid value";
return false;
}
block_size = cfg["block_size"].uint64_value();
if (!cfg["bitmap_granularity"].is_null() && !cfg["bitmap_granularity"].uint64_value())
{
error = "Bitmap granularity has an invalid value";
return false;
}
bitmap_granularity = cfg["bitmap_granularity"].uint64_value();
if (!is_valid_immediate_commit_string(cfg["immediate_commit"].string_value()))
{
return false;
}
immediate_commit = cfg["immediate_commit"].string_value();
if (!cfg["pg_stripe_size"].is_null() && !cfg["pg_stripe_size"].uint64_value())
{
error = "PG stripe size has an invalid value";
return false;
}
pg_stripe_size = cfg["pg_stripe_size"].uint64_value();
root_node = cfg["root_node"].string_value();
osd_tags = cfg["osd_tags"].string_value();
primary_affinity_tags = cfg["primary_affinity_tags"].string_value();
scrub_interval = cfg["scrub_interval"].string_value();
}
else // Modified pool configuration
{
bool has_changes = false;
// Unsupported parameters
if (!cfg["scheme"].is_null() || !cfg["parity_chunks"].is_null() || !cfg["ec"].is_null() || !cfg["bitmap_granularity"].is_null())
{
error = "Scheme, parity_chunks and bitmap_granularity parameters cannot be modified";
return false;
}
// Supported parameters
if (!cfg["name"].is_null())
{
name = cfg["name"].string_value();
has_changes = true;
}
if (!cfg["size"].is_null() || !cfg["pg_size"].is_null())
{
if (!cfg["size"].is_null() && !cfg["pg_size"].is_null())
{
error = "Cannot use both size and pg_size parameters at the same time.";
return false;
}
else if (!cfg["size"].is_null() && !cfg["size"].uint64_value() ||
!cfg["pg_size"].is_null() && !cfg["pg_size"].uint64_value())
{
error = "PG size has an invalid value";
return false;
}
pg_size = !cfg["size"].is_null() ? cfg["size"].uint64_value() : cfg["pg_size"].uint64_value();
has_changes = true;
}
if (!cfg["pg_minsize"].is_null())
{
if (!cfg["pg_minsize"].uint64_value())
{
error = "PG minsize has an invalid value";
return false;
}
pg_minsize = cfg["pg_minsize"].uint64_value();
has_changes = true;
}
if (!cfg["count"].is_null() || !cfg["pg_count"].is_null())
{
if (!cfg["count"].is_null() && !cfg["pg_count"].is_null())
{
error = "Cannot use both count and pg_count parameters at the same time.";
return false;
}
else if (!cfg["count"].is_null() && !cfg["count"].uint64_value() ||
!cfg["pg_count"].is_null() && !cfg["pg_count"].uint64_value())
{
error = "PG count has an invalid value";
return false;
}
pg_count = !cfg["count"].is_null() ? cfg["count"].uint64_value() : cfg["pg_count"].uint64_value();
has_changes = true;
}
if (!cfg["failure_domain"].is_null())
{
failure_domain = cfg["failure_domain"].string_value();
has_changes = true;
}
if (!cfg["max_osd_combinations"].is_null())
{
if (!cfg["max_osd_combinations"].uint64_value())
{
error = "Max OSD combinations has an invalid value";
return false;
}
max_osd_combinations = cfg["max_osd_combinations"].uint64_value();
has_changes = true;
}
if (!cfg["block_size"].is_null())
{
if (!cfg["block_size"].uint64_value())
{
error = "Block size has an invalid value";
return false;
}
block_size = cfg["block_size"].uint64_value();
has_changes = true;
}
if (!cfg["immediate_commit"].is_null())
{
if (!is_valid_immediate_commit_string(cfg["immediate_commit"].string_value()))
{
return false;
}
immediate_commit = cfg["immediate_commit"].string_value();
has_changes = true;
}
if (!cfg["pg_stripe_size"].is_null())
{
if (!cfg["pg_stripe_size"].uint64_value())
{
error = "PG stripe size has an invalid value";
return false;
}
pg_stripe_size = cfg["pg_stripe_size"].uint64_value();
has_changes = true;
}
if (!cfg["root_node"].is_null())
{
root_node = cfg["root_node"].string_value();
has_changes = true;
}
if (!cfg["osd_tags"].is_null())
{
osd_tags = cfg["osd_tags"].string_value();
has_changes = true;
}
if (!cfg["primary_affinity_tags"].is_null())
{
primary_affinity_tags = cfg["primary_affinity_tags"].string_value();
has_changes = true;
}
if (!cfg["scrub_interval"].is_null())
{
scrub_interval = cfg["scrub_interval"].string_value();
has_changes = true;
}
if (!has_changes)
{
error = "No changes were provided to modify pool";
return false;
}
}
return true;
}
bool pool_configurator_t::validate(etcd_state_client_t &st_cli, pool_config_t *pool_config, bool strict)
{
// Validate pool parameters
// Scheme
uint64_t p_scheme = (scheme != "" ?
(scheme == "xor" ? POOL_SCHEME_XOR : (scheme == "ec" ? POOL_SCHEME_EC : POOL_SCHEME_REPLICATED)) :
(pool_config ? pool_config->scheme : 0));
// PG size
uint64_t p_pg_size = (pg_size ? pg_size : (pool_config ? pool_config->pg_size : 0));
if (p_pg_size)
{
// Min PG size
if ((p_scheme == POOL_SCHEME_XOR || p_scheme == POOL_SCHEME_EC) && p_pg_size < 3)
{
error = "PG size cannot be less than 3 for XOR/EC pool";
return false;
}
// Max PG size
else if (p_pg_size > 256)
{
error = "PG size cannot be greater than 256";
return false;
}
}
// Parity Chunks
uint64_t p_parity_chunks = (parity_chunks ? parity_chunks : (pool_config ? pool_config->parity_chunks : 0));
if (p_parity_chunks)
{
if (p_scheme == POOL_SCHEME_XOR && p_parity_chunks > 1)
{
error = "Parity Chunks must be 1 for XOR pool";
return false;
}
if (p_scheme == POOL_SCHEME_EC && (p_parity_chunks < 1 || p_parity_chunks > p_pg_size-2))
{
error = "Parity Chunks must be between 1 and pg_size-2 for EC pool";
return false;
}
}
// PG minsize
uint64_t p_pg_minsize = (pg_minsize ? pg_minsize : (pool_config ? pool_config->pg_minsize : 0));
if (p_pg_minsize)
{
// Max PG minsize relative to PG size
if (p_pg_minsize > p_pg_size)
{
error = "PG minsize cannot be greater than "+std::to_string(p_pg_size)+" (PG size)";
return false;
}
// PG minsize relative to PG size and Parity Chunks
else if ((p_scheme == POOL_SCHEME_XOR || p_scheme == POOL_SCHEME_EC) && p_pg_minsize < (p_pg_size - p_parity_chunks))
{
error =
"PG minsize cannot be less than "+std::to_string(p_pg_size - p_parity_chunks)+" "
"(PG size - Parity Chunks) for XOR/EC pool";
return false;
}
}
// Max OSD Combinations (optional)
if (max_osd_combinations > 0 && max_osd_combinations < 100)
{
error = "Max OSD Combinations must be at least 100";
return false;
}
// Scrub interval (optional)
if (scrub_interval != "")
{
bool ok;
parse_time(scrub_interval, &ok);
if (!ok)
{
error = "Failed to parse scrub interval. Format: number + unit s/m/h/d/M/y";
return false;
}
}
// Additional checks (only if strict)
if (strict)
{
uint64_t p_block_size = block_size ? block_size :
(pool_config ? pool_config->data_block_size : st_cli.global_block_size);
uint64_t p_bitmap_granularity = bitmap_granularity ? bitmap_granularity :
(pool_config ? pool_config->bitmap_granularity : st_cli.global_bitmap_granularity);
// Block size value and range
if ((p_block_size & (p_block_size-1)) || p_block_size < MIN_DATA_BLOCK_SIZE || p_block_size > MAX_DATA_BLOCK_SIZE)
{
error =
"Data block size must be a power of two between "+std::to_string(MIN_DATA_BLOCK_SIZE)+" "
"and "+std::to_string(MAX_DATA_BLOCK_SIZE);
return false;
}
// Block size relative to bitmap granularity
if (p_block_size % p_bitmap_granularity)
{
error = "Data block size must be devisible by "+std::to_string(p_bitmap_granularity)+" (Bitmap Granularity)";
return false;
}
}
return true;
}

48
src/cli_pool_cfg.h Normal file
View File

@ -0,0 +1,48 @@
/*
=========================================================================
Copyright (c) 2023 MIND Software LLC. All Rights Reserved.
This file is part of the Software-Defined Storage MIND UStor Project.
For more information about this product, please visit https://mindsw.io
or contact us directly at info@mindsw.io
=========================================================================
*/
#pragma once
#include "json11/json11.hpp"
#include "etcd_state_client.h"
#include "str_util.h"
struct pool_configurator_t
{
protected:
std::string error;
bool is_valid_scheme_string(std::string scheme_str);
bool is_valid_immediate_commit_string(std::string immediate_commit_str);
public:
std::string name;
std::string scheme;
uint64_t pg_size, pg_minsize, pg_count;
uint64_t parity_chunks;
std::string immediate_commit;
std::string failure_domain;
std::string root_node;
uint64_t max_osd_combinations;
uint64_t block_size, bitmap_granularity;
uint64_t pg_stripe_size;
std::string osd_tags;
std::string primary_affinity_tags;
std::string scrub_interval;
std::string get_error_string();
bool parse(json11::Json cfg, bool new_pool);
bool validate(etcd_state_client_t &st_cli, pool_config_t *pool_config, bool strict);
};

708
src/cli_pool_create.cpp Normal file
View File

@ -0,0 +1,708 @@
/*
=========================================================================
Copyright (c) 2023 MIND Software LLC. All Rights Reserved.
This file is part of the Software-Defined Storage MIND UStor Project.
For more information about this product, please visit https://mindsw.io
or contact us directly at info@mindsw.io
=========================================================================
*/
#include <ctype.h>
#include "cli.h"
#include "cli_pool_cfg.h"
#include "cluster_client.h"
#include "epoll_manager.h"
#include "pg_states.h"
#include "str_util.h"
struct pool_creator_t
{
cli_tool_t *parent;
bool force;
pool_configurator_t *cfg;
int state = 0;
cli_result_t result;
struct {
uint32_t retries = 5;
uint32_t interval = 0;
bool passed = false;
} create_check;
uint64_t new_id = 1;
uint64_t new_pools_mod_rev;
json11::Json state_node_tree;
json11::Json new_pools;
json11::Json osd_tags_json;
json11::Json primary_affinity_tags_json;
bool is_done() { return state == 100; }
void loop()
{
if (state == 1)
goto resume_1;
else if (state == 2)
goto resume_2;
else if (state == 3)
goto resume_3;
else if (state == 4)
goto resume_4;
else if (state == 5)
goto resume_5;
else if (state == 6)
goto resume_6;
else if (state == 7)
goto resume_7;
else if (state == 8)
goto resume_8;
// Validate pool parameters
if (!cfg->validate(parent->cli->st_cli, NULL, !force))
{
result = (cli_result_t){ .err = EINVAL, .text = cfg->get_error_string() + "\n" };
state = 100;
return;
}
// OSD tags
if (cfg->osd_tags != "")
{
osd_tags_json = parent->parse_tags(cfg->osd_tags);
}
// Primary affinity tags
if (cfg->primary_affinity_tags != "")
{
primary_affinity_tags_json = parent->parse_tags(cfg->primary_affinity_tags);
}
state = 1;
resume_1:
// If not forced, check that we have enough osds for pg_size
if (!force)
{
// Get node_placement configuration from etcd
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") },
} }
},
} },
});
state = 2;
resume_2:
if (parent->waiting > 0)
return;
if (parent->etcd_err.err)
{
result = parent->etcd_err;
state = 100;
return;
}
// Get state_node_tree based on node_placement and osd peer states
{
auto kv = parent->cli->st_cli.parse_etcd_kv(parent->etcd_result["responses"][0]["response_range"]["kvs"][0]);
state_node_tree = get_state_node_tree(kv.value.object_items());
}
// Skip tag checks, if pool has none
if (!osd_tags_json.is_null())
{
// Get osd configs (for tags) of osds in state_node_tree
{
json11::Json::array osd_configs;
for (auto osd_num: state_node_tree["osds"].array_items())
{
osd_configs.push_back(json11::Json::object {
{ "request_range", json11::Json::object {
{ "key", base64_encode(parent->cli->st_cli.etcd_prefix+"/config/osd/"+osd_num.as_string()) },
} }
});
}
parent->etcd_txn(json11::Json::object { { "success", osd_configs, }, });
}
state = 3;
resume_3:
if (parent->waiting > 0)
return;
if (parent->etcd_err.err)
{
result = parent->etcd_err;
state = 100;
return;
}
// Filter out osds from state_node_tree based on pool/osd tags
{
std::vector<json11::Json> osd_configs;
for (auto & ocr: parent->etcd_result["responses"].array_items())
{
auto kv = parent->cli->st_cli.parse_etcd_kv(ocr["response_range"]["kvs"][0]);
osd_configs.push_back(kv.value);
}
state_node_tree = filter_state_node_tree_by_tags(state_node_tree, osd_configs);
}
}
// Get stats (for block_size, bitmap_granularity, ...) of osds in state_node_tree
{
json11::Json::array osd_stats;
for (auto osd_num: state_node_tree["osds"].array_items())
{
osd_stats.push_back(json11::Json::object {
{ "request_range", json11::Json::object {
{ "key", base64_encode(parent->cli->st_cli.etcd_prefix+"/osd/stats/"+osd_num.as_string()) },
} }
});
}
parent->etcd_txn(json11::Json::object { { "success", osd_stats, }, });
}
state = 4;
resume_4:
if (parent->waiting > 0)
return;
if (parent->etcd_err.err)
{
result = parent->etcd_err;
state = 100;
return;
}
// Filter osds from state_node_tree based on pool parameters and osd stats
{
std::vector<json11::Json> osd_stats;
for (auto & ocr: parent->etcd_result["responses"].array_items())
{
auto kv = parent->cli->st_cli.parse_etcd_kv(ocr["response_range&