// Copyright (c) Vitaliy Filippov, 2019+ // License: VNPL-1.1 (see README.md for details) /** * CLI tool and also a library for administrative tasks */ #include #include #include "cli.h" #include "epoll_manager.h" #include "cluster_client.h" #include "pg_states.h" #include "str_util.h" static const char *exe_name = NULL; static const char* help_text = "Vitastor command-line tool\n" "(c) Vitaliy Filippov, 2019+ (VNPL-1.1)\n" "\n" "COMMANDS:\n" "\n" "vitastor-cli status\n" " Show cluster status\n" "\n" "vitastor-cli df\n" " Show pool space statistics\n" "\n" "vitastor-cli ls [-l] [-p POOL] [--sort FIELD] [-r] [-n N] [ ...]\n" " List images (only matching patterns if passed).\n" " -p|--pool POOL Filter images by pool ID or name\n" " -l|--long Also report allocated size and I/O statistics\n" " --del Also include delete operation statistics\n" " --sort FIELD Sort by specified field (name, size, used_size, _)\n" " -r|--reverse Sort in descending order\n" " -n|--count N Only list first N items\n" "\n" "vitastor-cli create -s|--size [-p|--pool ] [--parent [@]] \n" " Create an image. You may use K/M/G/T suffixes for . If --parent is specified,\n" " a copy-on-write image clone is created. Parent must be a snapshot (readonly image).\n" " Pool must be specified if there is more than one pool.\n" "\n" "vitastor-cli create --snapshot [-p|--pool ] \n" "vitastor-cli snap-create [-p|--pool ] @\n" " Create a snapshot of image . May be used live if only a single writer is active.\n" "\n" "vitastor-cli modify [--rename ] [--resize ] [--readonly | --readwrite] [-f|--force]\n" " Rename, resize image or change its readonly status. Images with children can't be made read-write.\n" " If the new size is smaller than the old size, extra data will be purged.\n" " You should resize file system in the image, if present, before shrinking it.\n" " -f|--force Proceed with shrinking or setting readwrite flag even if the image has children.\n" "\n" "vitastor-cli rm [] [--writers-stopped]\n" " Remove or all layers between and ( must be a child of ),\n" " rebasing all their children accordingly. --writers-stopped allows merging to be a bit\n" " more effective in case of a single 'slim' read-write child and 'fat' removed parent:\n" " the child is merged into parent and parent is renamed to child in that case.\n" " In other cases parent layers are always merged into children.\n" "\n" "vitastor-cli flatten \n" " Flatten a layer, i.e. merge data and detach it from parents.\n" "\n" "vitastor-cli rm-data --pool --inode [--wait-list] [--min-offset ]\n" " Remove inode data without changing metadata.\n" " --wait-list Retrieve full objects listings before starting to remove objects.\n" " Requires more memory, but allows to show correct removal progress.\n" " --min-offset Purge only data starting with specified offset.\n" "\n" "vitastor-cli merge-data [--target ]\n" " Merge layer data without changing metadata. Merge .. to .\n" " must be a child of and may be one of the layers between\n" " and , including and .\n" "\n" "vitastor-cli alloc-osd\n" " Allocate a new OSD number and reserve it by creating empty /osd/stats/ key.\n" "\n" "vitastor-cli rm-osd [--force] [--allow-data-loss] [--dry-run] [osd_id...]\n" " Remove metadata and configuration for specified OSD(s) from etcd.\n" " Refuses to remove OSDs with data without --force and --allow-data-loss.\n" " With --dry-run only checks if deletion is possible without data loss and\n" " redundancy degradation.\n" "\n" "Use vitastor-cli --help for command details or vitastor-cli --help --all for all details.\n" "\n" "GLOBAL OPTIONS:\n" " --etcd_address \n" " --iodepth N Send N operations in parallel to each OSD when possible (default 32)\n" " --parallel_osds M Work with M osds in parallel when possible (default 4)\n" " --progress 1|0 Report progress (default 1)\n" " --cas 1|0 Use CAS writes for flatten, merge, rm (default is decide automatically)\n" " --no-color Disable colored output\n" " --json JSON output\n" ; static json11::Json::object parse_args(int narg, const char *args[]) { json11::Json::object cfg; json11::Json::array cmd; cfg["progress"] = "1"; for (int i = 1; i < narg; i++) { if (args[i][0] == '-' && args[i][1] == 'h' && args[i][2] == 0) { cfg["help"] = "1"; } else if (args[i][0] == '-' && args[i][1] == 'l' && args[i][2] == 0) { cfg["long"] = "1"; } else if (args[i][0] == '-' && args[i][1] == 'n' && args[i][2] == 0) { cfg["count"] = args[++i]; } else if (args[i][0] == '-' && args[i][1] == 'p' && args[i][2] == 0) { cfg["pool"] = args[++i]; } else if (args[i][0] == '-' && args[i][1] == 's' && args[i][2] == 0) { cfg["size"] = args[++i]; } else if (args[i][0] == '-' && args[i][1] == 'r' && args[i][2] == 0) { cfg["reverse"] = "1"; } else if (args[i][0] == '-' && args[i][1] == 'f' && args[i][2] == 0) { cfg["force"] = "1"; } else if (args[i][0] == '-' && args[i][1] == '-') { const char *opt = args[i]+2; cfg[opt] = i == narg-1 || !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") || !strcmp(opt, "readonly") || !strcmp(opt, "readwrite") || !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]; } else { cmd.push_back(std::string(args[i])); } } if (!cmd.size()) { std::string exe(exe_name); if (exe.size() >= 11 && exe.substr(exe.size()-11) == "vitastor-rm") { cmd.push_back("rm-data"); } } if (!cmd.size() || cfg["help"].bool_value()) { print_help(help_text, "vitastor-cli", cmd.size() ? cmd[0].string_value() : "", cfg["all"].bool_value()); } cfg["command"] = cmd; return cfg; } static int run(cli_tool_t *p, json11::Json::object cfg) { cli_result_t result = {}; p->parse_config(cfg); json11::Json::array cmd = cfg["command"].array_items(); cfg.erase("command"); std::function action_cb; if (!cmd.size()) { result = { .err = EINVAL, .text = "command is missing" }; } else if (cmd[0] == "status") { // Show cluster status action_cb = p->start_status(cfg); } else if (cmd[0] == "df") { // Show pool space stats action_cb = p->start_df(cfg); } else if (cmd[0] == "ls") { // List images if (cmd.size() > 1) { cmd.erase(cmd.begin(), cmd.begin()+1); cfg["names"] = cmd; } action_cb = p->start_ls(cfg); } else if (cmd[0] == "snap-create") { // Create snapshot std::string name = cmd.size() > 1 ? cmd[1].string_value() : ""; int pos = name.find('@'); if (pos == std::string::npos || pos == name.length()-1) { result = (cli_result_t){ .err = EINVAL, .text = "Please specify new snapshot name after @" }; } else { cfg["image"] = name.substr(0, pos); cfg["snapshot"] = name.substr(pos + 1); action_cb = p->start_create(cfg); } } else if (cmd[0] == "create") { // Create image/snapshot if (cmd.size() > 1) { cfg["image"] = cmd[1]; } action_cb = p->start_create(cfg); } else if (cmd[0] == "modify") { // Modify image if (cmd.size() > 1) { cfg["image"] = cmd[1]; } action_cb = p->start_modify(cfg); } else if (cmd[0] == "rm-data") { // Delete inode data action_cb = p->start_rm_data(cfg); } else if (cmd[0] == "rm-osd") { // Delete OSD metadata from etcd if (cmd.size() > 1) { cmd.erase(cmd.begin(), cmd.begin()+1); cfg["osd_id"] = cmd; } action_cb = p->start_rm_osd(cfg); } else if (cmd[0] == "merge-data") { // Merge layer data without affecting metadata if (cmd.size() > 1) { cfg["from"] = cmd[1]; if (cmd.size() > 2) cfg["to"] = cmd[2]; } action_cb = p->start_merge(cfg); } else if (cmd[0] == "flatten") { // Merge layer data without affecting metadata if (cmd.size() > 1) { cfg["image"] = cmd[1]; } action_cb = p->start_flatten(cfg); } else if (cmd[0] == "rm") { // Remove multiple snapshots and rebase their children if (cmd.size() > 1) { cfg["from"] = cmd[1]; if (cmd.size() > 2) cfg["to"] = cmd[2]; } action_cb = p->start_rm(cfg); } else if (cmd[0] == "alloc-osd") { // Allocate a new OSD number action_cb = p->start_alloc_osd(cfg); } else { result = { .err = EINVAL, .text = "unknown command: "+cmd[0].string_value() }; } if (action_cb != NULL) { // Create client json11::Json cfg_j = cfg; p->ringloop = new ring_loop_t(512); p->epmgr = new epoll_manager_t(p->ringloop); p->cli = new cluster_client_t(p->ringloop, p->epmgr->tfd, cfg_j); // Smaller timeout by default for more interactiveness p->cli->st_cli.etcd_slow_timeout = p->cli->st_cli.etcd_quick_timeout; p->loop_and_wait(action_cb, [&](const cli_result_t & r) { result = r; action_cb = NULL; }); // Loop until it completes while (action_cb != NULL) { p->ringloop->loop(); if (action_cb != NULL) p->ringloop->wait(); } // Destroy the client delete p->cli; delete p->epmgr; delete p->ringloop; p->cli = NULL; p->epmgr = NULL; p->ringloop = NULL; } // Print result if (p->json_output && !result.data.is_null()) { printf("%s\n", result.data.dump().c_str()); } else if (p->json_output && result.err) { printf("%s\n", json11::Json(json11::Json::object { { "error_code", result.err }, { "error_text", result.text }, }).dump().c_str()); } else if (result.text != "") { fprintf(result.err ? stderr : stdout, result.text[result.text.size()-1] == '\n' ? "%s" : "%s\n", result.text.c_str()); } return result.err; } int main(int narg, const char *args[]) { setvbuf(stdout, NULL, _IONBF, 0); setvbuf(stderr, NULL, _IONBF, 0); exe_name = args[0]; cli_tool_t *p = new cli_tool_t(); int r = run(p, parse_args(narg, args)); delete p; return r; }