712 lines
30 KiB
C++
712 lines
30 KiB
C++
// Copyright (c) Vitaliy Filippov, 2019+
|
|
// License: VNPL-1.1 (see README.md for details)
|
|
|
|
/**
|
|
* CLI tool and also a library for administrative tasks
|
|
*/
|
|
|
|
#include <vector>
|
|
#include <algorithm>
|
|
|
|
#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 " VITASTOR_VERSION "\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] [<glob> ...]\n"
|
|
" List images (only matching <glob> patterns if passed).\n"
|
|
" --exact Do not match glob patterns as names, select only exact name matches.\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, <read|write|delete>_<iops|bps|lat|queue>)\n"
|
|
" -r|--reverse Sort in descending order\n"
|
|
" -n|--count N Only list first N items\n"
|
|
" --ids ID1,ID2 Only list images with specified full IDs\n"
|
|
" --tree Show image snapshot/clone tree\n"
|
|
"\n"
|
|
"vitastor-cli create -s|--size SIZE [OPTIONS] <name>\n"
|
|
" Create an image. Options:\n"
|
|
" -s|--size SIZE New image size in bytes or with a K/M/G/T unit suffix.\n"
|
|
" -p|--pool POOL Specify pool for the new image (may be omitted if there is only 1 pool).\n"
|
|
" --parent PARENT Create a copy-on-write image clone based on PARENT (or PARENT@SNAPSHOT).\n"
|
|
" If parent is not a snapshot, it must be a read-only image.\n"
|
|
" --enc-key random Generate a new random AES-256-XTS encryption key for the new image.\n"
|
|
" --enc-key HEX Set a specified AES-256-XTS key (64 bytes in hex) for the new image.\n"
|
|
" --enc-key vault:ID Use an encryption key from an external Vault secret with specified ID.\n"
|
|
" --owner username Set owner (default is the current user from TLS certificate).\n"
|
|
" --owner_group name Set owner group name.\n"
|
|
" --reader_group rdr Set reader group name.\n"
|
|
"\n"
|
|
"vitastor-cli create --snapshot <snapshot> [OPTIONS] <image>\n"
|
|
"vitastor-cli snap-create [OPTIONS] <image>@<snapshot>\n"
|
|
" Create a snapshot of image <image>. May be used live if only a single writer is active.\n"
|
|
" Options:\n"
|
|
" -p|--pool POOL Move image to pool POOL, leaving the snapshot in the old pool.\n"
|
|
" --enc-key random Change image encryption key to a new random AES-256-XTS key.\n"
|
|
" --enc-key KEY Change image encryption key to a specified key, Vault key or to an empty key.\n"
|
|
" By default, the image retains its old key when taking a snapshot.\n"
|
|
"\n"
|
|
"vitastor-cli modify <name> [--rename <new-name>] [--resize <size>] [--readonly | --readwrite] [-f|--force] [--down-ok]\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"
|
|
" --deleted 1|0 Set/clear 'deleted image' flag (set automatically during unfinished deletes).\n"
|
|
" -f|--force Proceed with shrinking or setting readwrite flag even if the image has children.\n"
|
|
" --down-ok Proceed with shrinking even if some data will be left on unavailable OSDs.\n"
|
|
" --enc-key HEX Change image encryption key (allowed only with --force).\n"
|
|
" --owner username Change image owner.\n"
|
|
" --owner_group name Change image owner group name.\n"
|
|
" --reader_group rdr Change image reader group name.\n"
|
|
"\n"
|
|
"vitastor-cli dd [iimg=<image> | if=<file>] [oimg=<image> | of=<file>] [bs=1M]\n"
|
|
" [count=N] [seek/oseek=N] [skip/iseek=M] [iodepth=N] [status=progress]\n"
|
|
" [conv=nocreat,noerror,nofsync,trunc,nosparse] [iflag=direct] [oflag=direct,append]\n"
|
|
" Copy data between Vitastor images, files and pipes.\n"
|
|
" Options can be specified in classic dd style (key=value) or like usual (--key value).\n"
|
|
" iimg=<image> Copy from Vitastor image <image>\n"
|
|
" if=<file> Copy from file <file>\n"
|
|
" oimg=<image> Copy to Vitastor image <image>\n"
|
|
" of=<file> Copy to file <file>\n"
|
|
" bs=1M Set copy block size\n"
|
|
" count=N Copy only N input blocks. If N ends in B it counts bytes, not blocks\n"
|
|
" seek/oseek=N Skip N output blocks. If N ends in B it counts bytes, not blocks\n"
|
|
" skip/iseek=N Skip N input blocks. If N ends in B it counts bytes, not blocks\n"
|
|
" iodepth=N Send N reads or writes in parallel (default 4)\n"
|
|
" status=LEVEL The LEVEL of information to print to stderr: none/noxfer/progress\n"
|
|
" size=N Specify size for the created output file/image (defaults to input size)\n"
|
|
" iflag=direct For input files only: use direct I/O\n"
|
|
" oflag=direct For output files only: use direct I/O\n"
|
|
" oflag=append For files only: append to output file\n"
|
|
" conv=nocreat Do not create output file/image\n"
|
|
" conv=trunc Truncate output file/image\n"
|
|
" conv=noerror Continue copying after errors\n"
|
|
" conv=nofsync Do not call fsync before finishing (default behaviour is fsync)\n"
|
|
" conv=nosparse Write all output blocks including all-zero blocks\n"
|
|
"\n"
|
|
"vitastor-cli rm <from> [<to>]\n"
|
|
"vitastor-cli rm (--exact|--matching) <glob> ...\n"
|
|
" Remove layer(s) and rebase all their children accordingly.\n"
|
|
" In the first form, remove <from> or layers between <from> and its child <to>.\n"
|
|
" In the second form, remove all images with exact or pattern-matched names.\n"
|
|
" --writers-stopped allows optimised removal in case of a single 'slim' read-write\n"
|
|
" child and 'fat' removed parent: the child is merged into parent and parent is renamed\n"
|
|
" to child in that case. In other cases parent layers are always merged into children.\n"
|
|
" --exact Remove multiple images with names matching given glob patterns.\n"
|
|
" --matching Remove multiple images with given names\n"
|
|
" --writers-stopped Allow renaming inodes over their read/write children.\n"
|
|
" --down-ok Continue deletion/merging even if some data will be left on unavailable OSDs.\n"
|
|
"\n"
|
|
"vitastor-cli flatten <layer>\n"
|
|
" Flatten a layer, i.e. merge data and detach it from parents.\n"
|
|
"\n"
|
|
"vitastor-cli rm-data --pool <pool> --inode <inode> [--wait-list] [--min-offset <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"
|
|
" --max-offset Purge only data before specified offset.\n"
|
|
" --client_wait_up_timeout 16 Timeout for waiting until PGs are up in seconds.\n"
|
|
"\n"
|
|
"vitastor-cli merge-data <from> <to> [--target <target>]\n"
|
|
" Merge layer data without changing metadata. Merge <from>..<to> to <target>.\n"
|
|
" <to> must be a child of <from> and <target> may be one of the layers between\n"
|
|
" <from> and <to>, including <from> and <to>.\n"
|
|
"\n"
|
|
"vitastor-cli describe [OPTIONS]\n"
|
|
" Describe unclean object locations in the cluster. Options:\n"
|
|
" --osds <osds>\n"
|
|
" Only list objects from primary OSD(s) <osds>.\n"
|
|
" --object-state <states>\n"
|
|
" Only list objects in given state(s). State(s) may include:\n"
|
|
" degraded, misplaced, incomplete, corrupted, inconsistent.\n"
|
|
" --pool <pool name or number>\n"
|
|
" Only list objects in the given pool.\n"
|
|
" --pg <pg number>\n"
|
|
" Only list objects in the given PG of the pool.\n"
|
|
" --inode, --min-inode, --max-inode\n"
|
|
" Restrict listing to specific inode numbers.\n"
|
|
" --min-offset, --max-offset\n"
|
|
" Restrict listing to specific offsets inside inodes.\n"
|
|
"\n"
|
|
"vitastor-cli raw-ls [OPTIONS]\n"
|
|
" Find object(s) in the cluster using raw secondary listing operations. Options:\n"
|
|
" [--min_inode NUM] [--max_inode NUM] [--offset NUM] [--pg_num NUM] [--pg_count COUNT]\n"
|
|
" [--pg_stripe_size NUM] [--osds 1,2,3,...]\n"
|
|
"\n"
|
|
"vitastor-cli fix [--objects <objects>] [--bad-osds <osds>] [--part <part>] [--check no]\n"
|
|
" Fix inconsistent objects in the cluster by deleting some copies.\n"
|
|
" --objects <objects>\n"
|
|
" Objects to fix, either in plain text or JSON format. If not specified,\n"
|
|
" object list will be read from STDIN in one of the same formats.\n"
|
|
" Plain text format: 0x<inode>:0x<stripe> <any delimiter> 0x<inode>:0x<stripe> ...\n"
|
|
" JSON format: [{\"inode\":\"0x...\",\"stripe\":\"0x...\"},...]\n"
|
|
" --bad-osds <osds>\n"
|
|
" Remove inconsistent copies/parts of objects from these OSDs, effectively\n"
|
|
" marking them bad and allowing Vitastor to recover objects from other copies.\n"
|
|
" --part <number>\n"
|
|
" Only remove EC part <number> (from 0 to pg_size-1), required for extreme\n"
|
|
" edge cases where one OSD has multiple parts of a EC object.\n"
|
|
" --check no\n"
|
|
" Do not recheck that requested objects are actually inconsistent,\n"
|
|
" delete requested copies/parts anyway.\n"
|
|
"\n"
|
|
"vitastor-cli alloc-osd\n"
|
|
" Allocate a new OSD number and reserve it by creating empty /osd/stats/<n> key.\n"
|
|
"\n"
|
|
"vitastor-cli rm-osd [--force] [--allow-data-loss] [--dry-run] <osd_id> [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"
|
|
"vitastor-cli osd-tree [-l|--long]\n"
|
|
" Show current OSD tree, optionally with I/O statistics if -l is specified.\n"
|
|
"\n"
|
|
"vitastor-cli osds|ls-osd|osd-ls [-l|--long]\n"
|
|
" Show current OSDs as list, optionally with I/O statistics if -l is specified.\n"
|
|
"\n"
|
|
"vitastor-cli modify-osd [--tags tag1,tag2,...] [--reweight <number>] [--noout true/false] <osd_number>\n"
|
|
" Set OSD reweight, tags or noout flag.\n"
|
|
"\n"
|
|
"vitastor-cli pg-list|pg-ls|list-pg|ls-pg|ls-pgs|pgs [OPTIONS] [state1+state2] [^state3] [...]\n"
|
|
" List PGs with any of listed state filters (^ or ! in the beginning is negation). Options:\n"
|
|
" --pool <pool name or number> Only list PGs of the given pool.\n"
|
|
" --min <min pg number> Only list PGs with number >= min.\n"
|
|
" --max <max pg number> Only list PGs with number <= max.\n"
|
|
" --osd 1,2,... Only list PGs with some data on specified OSD(s).\n"
|
|
" Examples:\n"
|
|
" vitastor-cli pg-list active+degraded\n"
|
|
" vitastor-cli pg-list ^active\n"
|
|
"\n"
|
|
"vitastor-cli create-pool|pool-create <name> (-s <pg_size>|--ec <N>+<K>) -n <pg_count> [OPTIONS]\n"
|
|
" Create a pool. Required parameters:\n"
|
|
" -s|--pg_size R Number of replicas for replicated pools\n"
|
|
" --ec N+K Number of data (N) and parity (K) chunks for erasure-coded pools\n"
|
|
" -n|--pg_count N PG count for the new pool (start with 10*<OSD count>/pg_size rounded to a power of 2)\n"
|
|
" Optional parameters:\n"
|
|
" --pg_minsize <number> R or N+K minus number of failures to tolerate without downtime\n"
|
|
" --failure_domain host Failure domain: host, osd or a level from placement_levels. Default: host\n"
|
|
" --root_node <node> Put pool only on child OSDs of this placement tree node\n"
|
|
" --osd_tags <tag>[,<tag>]... Put pool only on OSDs tagged with all specified tags\n"
|
|
" --block_size 128k Put pool only on OSDs with this data block size\n"
|
|
" --bitmap_granularity 4k Put pool only on OSDs with this logical sector size\n"
|
|
" --immediate_commit all Put pool only on OSDs with this or larger immediate_commit (none < small < all)\n"
|
|
" --level_placement <rules> Use additional failure domain rules (example: \"dc=112233\")\n"
|
|
" --raw_placement <rules> Specify raw PG generation rules (see documentation for details)\n"
|
|
" --local_reads primary Local read policy for replicated pools: primary, nearest or random\n"
|
|
" --primary_affinity_tags tags Prefer to put primary copies on OSDs with all specified tags\n"
|
|
" --scrub_interval <time> Enable regular scrubbing for this pool. Format: number + unit s/m/h/d/M/y\n"
|
|
" --used_for_app fs:<name> Mark pool as used for VitastorFS with metadata in image <name>\n"
|
|
" --used_for_app s3:<name> Mark pool as used for S3 location with name <name>\n"
|
|
" --pg_stripe_size <number> Increase object grouping stripe\n"
|
|
" --max_osd_combinations 10000 Maximum number of random combinations for LP solver input\n"
|
|
" --creator_group <group> User group allowed to create images in this pool.\n"
|
|
" --wait Wait for the new pool to come online\n"
|
|
" -f|--force Do not check that cluster has enough OSDs to create the pool\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|pool-modify <id|name> [--name <new_name>] [PARAMETERS...]\n"
|
|
" Modify an existing pool. Modifiable parameters:\n"
|
|
" [-s|--pg_size <number>] [--pg_minsize <number>] [-n|--pg_count <count>]\n"
|
|
" [--failure_domain <level>] [--root_node <node>] [--osd_tags <tags>] [--used_for_app <type>:<name>]\n"
|
|
" [--max_osd_combinations <number>] [--primary_affinity_tags <tags>] [--scrub_interval <time>]\n"
|
|
" [--level_placement <rules>] [--raw_placement <rules>] [--creator_group <group>]\n"
|
|
" Non-modifiable parameters (changing them WILL lead to data loss):\n"
|
|
" [--block_size <size>] [--bitmap_granularity <size>]\n"
|
|
" [--immediate_commit <all|small|none>] [--pg_stripe_size <size>]\n"
|
|
" These, however, can still be modified with -f|--force.\n"
|
|
" See create-pool for parameter descriptions.\n"
|
|
" Examples:\n"
|
|
" vitastor-cli modify-pool pool_A --name pool_B\n"
|
|
" vitastor-cli modify-pool 2 --pg_size 4 -n 128\n"
|
|
"\n"
|
|
"vitastor-cli rm-pool|pool-rm [--force] <id|name>\n"
|
|
" Remove a pool. Refuses to remove pools with images without --force.\n"
|
|
"\n"
|
|
"vitastor-cli ls-pools|pool-ls|ls-pool|pools [-l] [--detail] [--sort FIELD] [-r] [-n N] [<glob> ...]\n"
|
|
" List pools (only matching <glob> patterns if passed).\n"
|
|
" -l|--long Also report I/O statistics\n"
|
|
" --detail Use list format (not table), show all details\n"
|
|
" --sort FIELD Sort by specified field (see fields in --json output)\n"
|
|
" -r|--reverse Sort in descending order\n"
|
|
" -n|--count N Only list first N items\n"
|
|
"\n"
|
|
"vitastor-cli ls-users|user-ls|ls-user|list-users [<name> ...]\n"
|
|
" List users (only with specified names if passed).\n"
|
|
"\n"
|
|
"vitastor-cli modify-user --type <type> --groups group1,group2,... <username>\n"
|
|
" Create or update user permissions. User names match CN of their certificates.\n"
|
|
" --type TYPE Set user type: client, admin, mon or osd. Default is client.\n"
|
|
" --groups GROUPS Set user's groups.\n"
|
|
"\n"
|
|
"vitastor-cli rm-user|remove-user|delete-user <username>\n"
|
|
" Remove a user.\n"
|
|
"\n"
|
|
"vitastor-cli cpubench [--json]\n"
|
|
" Run CPU crypto performance tests: AES-256-GCM, AES-256-XTS and xxhash3.\n"
|
|
"\n"
|
|
"vitastor-cli serve\n"
|
|
" Start HTTP server able to handle CLI commands over a REST API. Options:\n"
|
|
" --bind_address ADDR Specify server IP address or addresses, separated by space. Default is 127.0.0.1.\n"
|
|
" --port 8080 Specify server port.\n"
|
|
" --api_cert FILE Path to server TLS certificate file (PEM format).\n"
|
|
" --api_pkey FILE Path to server TLS private key file.\n"
|
|
" --client_ca FILE Path to file with TLS CA certificates used to validate client connections.\n"
|
|
"\n"
|
|
"Use vitastor-cli --help <command> for command details or vitastor-cli --help --all for all details.\n"
|
|
"\n"
|
|
"GLOBAL OPTIONS:\n"
|
|
" --config_path FILE Path to Vitastor configuration file\n"
|
|
" --etcd_address URL Etcd connection 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"
|
|
" --color 1|0 Enable/disable colored output and CR symbols (default 1 if stdout is a terminal)\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++)
|
|
{
|
|
bool argHasValue = (i < narg-1);
|
|
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"] = argHasValue ? args[++i] : "";
|
|
}
|
|
else if (args[i][0] == '-' && args[i][1] == 'p' && args[i][2] == 0)
|
|
{
|
|
cfg["pool"] = argHasValue ? args[++i] : "";
|
|
}
|
|
else if (args[i][0] == '-' && args[i][1] == 's' && args[i][2] == 0)
|
|
{
|
|
cfg["size"] = argHasValue ? 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;
|
|
if (!strcmp(opt, "json") || !strcmp(opt, "wait") ||
|
|
!strcmp(opt, "wait-list") || !strcmp(opt, "wait_list") ||
|
|
!strcmp(opt, "long") || !strcmp(opt, "detail") || !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, "allow-up") || !strcmp(opt, "allow_up") ||
|
|
!strcmp(opt, "down-ok") || !strcmp(opt, "down_ok") ||
|
|
!strcmp(opt, "dry-run") || !strcmp(opt, "dry_run") ||
|
|
!strcmp(opt, "help") || !strcmp(opt, "all") ||
|
|
!strcmp(opt, "exact") || !strcmp(opt, "matching") || !strcmp(opt, "tree") ||
|
|
!strcmp(opt, "writers-stopped") || !strcmp(opt, "writers_stopped"))
|
|
{
|
|
cfg[opt] = "1";
|
|
}
|
|
else
|
|
{
|
|
cfg[opt] = argHasValue ? 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;
|
|
}
|
|
|
|
std::function<bool(cli_result_t &)> cli_tool_t::start(json11::Json::object cfg, cli_result_t & result)
|
|
{
|
|
json11::Json::array cmd = cfg["command"].array_items();
|
|
cfg.erase("command");
|
|
std::function<bool(cli_result_t &)> action_cb;
|
|
if (!cmd.size())
|
|
{
|
|
result = { .err = EOPNOTSUPP, .text = "command is missing" };
|
|
}
|
|
else if (cmd[0] == "status")
|
|
{
|
|
// Show cluster status
|
|
action_cb = start_status(cfg);
|
|
}
|
|
else if (cmd[0] == "df")
|
|
{
|
|
// Show pool space stats
|
|
action_cb = start_pool_ls(cfg);
|
|
}
|
|
else if (cmd[0] == "ls")
|
|
{
|
|
// List images
|
|
if (cmd.size() > 1)
|
|
{
|
|
cmd.erase(cmd.begin(), cmd.begin()+1);
|
|
cfg["names"] = cmd;
|
|
}
|
|
action_cb = 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 = start_create(cfg);
|
|
}
|
|
}
|
|
else if (cmd[0] == "create")
|
|
{
|
|
// Create image/snapshot
|
|
if (cmd.size() > 1)
|
|
{
|
|
cfg["image"] = cmd[1];
|
|
}
|
|
action_cb = start_create(cfg);
|
|
}
|
|
else if (cmd[0] == "modify")
|
|
{
|
|
// Modify image
|
|
if (cmd.size() > 1)
|
|
{
|
|
cfg["image"] = cmd[1];
|
|
}
|
|
action_cb = start_modify(cfg);
|
|
}
|
|
else if (cmd[0] == "rm-data")
|
|
{
|
|
// Delete inode data
|
|
action_cb = 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 = 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 = start_merge(cfg);
|
|
}
|
|
else if (cmd[0] == "flatten")
|
|
{
|
|
// Merge layer data without affecting metadata
|
|
if (cmd.size() > 1)
|
|
{
|
|
cfg["image"] = cmd[1];
|
|
}
|
|
action_cb = start_flatten(cfg);
|
|
}
|
|
else if (cmd[0] == "dd")
|
|
{
|
|
// Read or write to/from cluster
|
|
for (int i = 0; i < cmd.size(); i++)
|
|
{
|
|
auto arg = cmd[i].string_value();
|
|
ssize_t p = arg.find("=");
|
|
if (p != std::string::npos)
|
|
{
|
|
cfg[arg.substr(0, p)] = arg.substr(p+1);
|
|
}
|
|
}
|
|
action_cb = start_dd(cfg);
|
|
}
|
|
else if (cmd[0] == "rm")
|
|
{
|
|
// Remove multiple snapshots and rebase their children
|
|
if (cfg["names"].is_array())
|
|
{
|
|
cfg["globs"] = cfg["names"];
|
|
cfg.erase("names");
|
|
cfg["exact"] = true;
|
|
cfg["matching"] = false;
|
|
action_cb = start_rm_wildcard(cfg);
|
|
}
|
|
else if (cfg["matching"].is_array())
|
|
{
|
|
cfg["globs"] = cfg["matching"];
|
|
cfg["exact"] = false;
|
|
cfg["matching"] = true;
|
|
action_cb = start_rm_wildcard(cfg);
|
|
}
|
|
else if (cfg["exact"].bool_value() || cfg["matching"].bool_value())
|
|
{
|
|
cmd.erase(cmd.begin(), cmd.begin()+1);
|
|
cfg["globs"] = cmd;
|
|
action_cb = start_rm_wildcard(cfg);
|
|
}
|
|
else
|
|
{
|
|
if (cmd.size() > 1)
|
|
{
|
|
cfg["from"] = cmd[1];
|
|
if (cmd.size() > 2)
|
|
cfg["to"] = cmd[2];
|
|
}
|
|
action_cb = start_rm(cfg);
|
|
}
|
|
}
|
|
else if (cmd[0] == "describe")
|
|
{
|
|
// Describe unclean objects
|
|
action_cb = start_describe(cfg);
|
|
}
|
|
else if (cmd[0] == "raw-ls")
|
|
{
|
|
// Run raw listings
|
|
action_cb = start_raw_ls(cfg);
|
|
}
|
|
else if (cmd[0] == "fix")
|
|
{
|
|
// Fix inconsistent objects (by deleting some copies)
|
|
action_cb = start_fix(cfg);
|
|
}
|
|
else if (cmd[0] == "alloc-osd")
|
|
{
|
|
// Allocate a new OSD number
|
|
action_cb = start_alloc_osd(cfg);
|
|
}
|
|
else if (cmd[0] == "osd-tree")
|
|
{
|
|
// Print OSD tree
|
|
cfg["as_tree"] = true;
|
|
action_cb = start_osd_tree(cfg);
|
|
}
|
|
else if (cmd[0] == "osds" || cmd[0] == "ls-osds" || cmd[0] == "ls-osd" || cmd[0] == "osd-ls")
|
|
{
|
|
// Print OSD list
|
|
action_cb = start_osd_tree(cfg);
|
|
}
|
|
else if (cmd[0] == "modify-osd")
|
|
{
|
|
// Modify OSD configuration
|
|
if (cmd.size() > 1)
|
|
cfg["osd_num"] = cmd[1];
|
|
action_cb = start_modify_osd(cfg);
|
|
}
|
|
else if (cmd[0] == "pg-list" || cmd[0] == "pg-ls" || cmd[0] == "list-pg" || cmd[0] == "ls-pg" || cmd[0] == "ls-pgs" || cmd[0] == "pgs")
|
|
{
|
|
// Modify OSD configuration
|
|
if (cmd.size() > 1)
|
|
{
|
|
cmd.erase(cmd.begin(), cmd.begin()+1);
|
|
cfg["pg_state"] = cmd;
|
|
}
|
|
action_cb = start_pg_list(cfg);
|
|
}
|
|
else if (cmd[0] == "create-pool" || cmd[0] == "pool-create")
|
|
{
|
|
// Create a new pool
|
|
if (cmd.size() > 1 && cfg["name"].is_null())
|
|
{
|
|
cfg["name"] = cmd[1];
|
|
}
|
|
action_cb = start_pool_create(cfg);
|
|
}
|
|
else if (cmd[0] == "modify-pool" || cmd[0] == "pool-modify")
|
|
{
|
|
// Modify existing pool
|
|
if (cmd.size() > 1)
|
|
{
|
|
cfg["pool"] = cmd[1];
|
|
}
|
|
action_cb = start_pool_modify(cfg);
|
|
}
|
|
else if (cmd[0] == "rm-pool" || cmd[0] == "pool-rm")
|
|
{
|
|
// Remove existing pool
|
|
if (cmd.size() > 1)
|
|
{
|
|
cfg["pool"] = cmd[1];
|
|
}
|
|
action_cb = start_pool_rm(cfg);
|
|
}
|
|
else if (cmd[0] == "ls-pool" || cmd[0] == "pool-ls" || cmd[0] == "ls-pools" || cmd[0] == "pools")
|
|
{
|
|
// Show pool list
|
|
cfg["show_recovery"] = 1;
|
|
if (cmd.size() > 1)
|
|
{
|
|
cmd.erase(cmd.begin(), cmd.begin()+1);
|
|
cfg["names"] = cmd;
|
|
}
|
|
action_cb = start_pool_ls(cfg);
|
|
}
|
|
else if (cmd[0] == "user-ls" || cmd[0] == "ls-user" || cmd[0] == "ls-users" || cmd[0] == "list-users")
|
|
{
|
|
// List users
|
|
if (cmd.size() > 1)
|
|
{
|
|
cmd.erase(cmd.begin(), cmd.begin()+1);
|
|
cfg["names"] = cmd;
|
|
}
|
|
action_cb = start_user_ls(cfg);
|
|
}
|
|
else if (cmd[0] == "modify-user" || cmd[0] == "user-modify")
|
|
{
|
|
// Create/update user
|
|
if (cmd.size() > 1)
|
|
{
|
|
cfg["name"] = cmd[1];
|
|
}
|
|
action_cb = start_modify_user(cfg);
|
|
}
|
|
else if (cmd[0] == "rm-user" || cmd[0] == "remove-user" || cmd[0] == "delete-user")
|
|
{
|
|
// Remove user
|
|
if (cmd.size() > 1)
|
|
{
|
|
cfg["name"] = cmd[1];
|
|
}
|
|
cfg["remove"] = true;
|
|
action_cb = start_modify_user(cfg);
|
|
}
|
|
else if (cmd[0] == "serve")
|
|
{
|
|
// Start HTTP server
|
|
action_cb = start_serve(cfg);
|
|
}
|
|
else if (cmd[0] == "cpubench")
|
|
{
|
|
action_cb = start_cpubench(cfg);
|
|
}
|
|
else
|
|
{
|
|
result = { .err = EOPNOTSUPP, .text = "unknown command: "+cmd[0].string_value() };
|
|
}
|
|
return action_cb;
|
|
}
|
|
|
|
static int run(cli_tool_t *p, json11::Json::object cfg)
|
|
{
|
|
cli_result_t result = {};
|
|
p->is_command_line = true;
|
|
p->parse_config(cfg);
|
|
auto action_cb = p->start(cfg, result);
|
|
if (action_cb != NULL)
|
|
{
|
|
// Create client
|
|
json11::Json cfg_j = cfg;
|
|
p->ringloop = new ring_loop_t(RINGLOOP_DEFAULT_SIZE);
|
|
p->epmgr = new epoll_manager_t(p->ringloop);
|
|
p->cli = cluster_client_t::create(p->ringloop, p->epmgr->tfd, cfg_j);
|
|
p->loop_and_wait(action_cb, [&](const cli_result_t & r)
|
|
{
|
|
result = r;
|
|
action_cb = NULL;
|
|
p->ringloop->submit();
|
|
});
|
|
// Loop until it completes
|
|
while (action_cb != NULL)
|
|
{
|
|
p->ringloop->loop();
|
|
if (action_cb != NULL)
|
|
p->ringloop->wait();
|
|
}
|
|
// Destroy the client
|
|
p->cli->flush();
|
|
delete p->cli;
|
|
delete p->epmgr;
|
|
delete p->ringloop;
|
|
p->cli = NULL;
|
|
p->epmgr = NULL;
|
|
p->ringloop = NULL;
|
|
}
|
|
// Print result
|
|
fflush(stderr);
|
|
fflush(stdout);
|
|
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;
|
|
}
|