diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 37aefa34..411fdf7b 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_flatten.cpp cli_merge.cpp cli_rm.cpp cli_snap_rm.cpp + cli.cpp cli_ls.cpp cli_create.cpp cli_set.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 598c776f..79c37d86 100644 --- a/src/cli.cpp +++ b/src/cli.cpp @@ -48,11 +48,16 @@ json11::Json::object cli_tool_t::parse_args(int narg, const char *args[]) { cfg["reverse"] = "1"; } + else if (args[i][0] == '-' && args[i][1] == 'f') + { + 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, "long") || !strcmp(opt, "del") || !strcmp(opt, "no-color") || + !strcmp(opt, "force") || !strcmp(opt, "writers-stopped") && strcmp("1", args[i+1]) != 0 ? "1" : args[++i]; } @@ -98,8 +103,11 @@ void cli_tool_t::help() "%s snap-create [-p|--pool ] @\n" " Create a snapshot of image . May be used live if only a single writer is active.\n" "\n" - "%s set [-s|--size ] [--readonly | --readwrite]\n" + "%s set [-s|--size ] [--readonly | --readwrite] [-f|--force]\n" " 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" "%s rm [] [--writers-stopped]\n" " Remove or all layers between and ( must be a child of ),\n" @@ -111,10 +119,11 @@ void cli_tool_t::help() "%s flatten \n" " Flatten a layer, i.e. merge data and detach it from parents.\n" "\n" - "%s rm-data --pool --inode [--wait-list]\n" + "%s rm-data --pool --inode [--wait-list] [--min-offset ]\n" " Remove inode data without changing metadata.\n" - " --wait-list means first retrieve objects listings and then remove it.\n" - " --wait-list requires more memory, but allows to show correct removal progress.\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" "%s merge-data [--target ]\n" " Merge layer data without changing metadata. Merge .. to .\n" diff --git a/src/cli.h b/src/cli.h index ff9cc82b..bb027622 100644 --- a/src/cli.h +++ b/src/cli.h @@ -52,6 +52,7 @@ public: std::function start_ls(json11::Json cfg); std::function start_create(json11::Json cfg); + std::function start_set(json11::Json cfg); std::function start_rm(json11::Json); std::function start_merge(json11::Json); std::function start_flatten(json11::Json); diff --git a/src/cli_create.cpp b/src/cli_create.cpp index ca771856..69e69e13 100644 --- a/src/cli_create.cpp +++ b/src/cli_create.cpp @@ -427,6 +427,7 @@ resume_3: exit(1); } this->result = res; + parent->ringloop->wakeup(); }); } }; @@ -478,6 +479,11 @@ std::function cli_tool_t::start_create(json11::Json cfg) fprintf(stderr, "Invalid syntax for size: %s\n", cfg["size"].string_value().c_str()); exit(1); } + if (size % 4096) + { + fprintf(stderr, "Image size should be a multiple of 4096\n"); + exit(1); + } image_creator->size = size; if (image_creator->new_snap != "") { diff --git a/src/cli_ls.cpp b/src/cli_ls.cpp index 3f4528e7..2129efd1 100644 --- a/src/cli_ls.cpp +++ b/src/cli_ls.cpp @@ -129,6 +129,7 @@ struct image_lister_t exit(1); } space_info = res; + parent->ringloop->wakeup(); }); state = 1; resume_1: diff --git a/src/cli_rm.cpp b/src/cli_rm.cpp index f22d28d7..81a8c210 100644 --- a/src/cli_rm.cpp +++ b/src/cli_rm.cpp @@ -14,7 +14,7 @@ struct rm_pg_t osd_num_t rm_osd_num; std::set objects; std::set::iterator obj_pos; - uint64_t obj_count = 0, obj_done = 0, obj_prev_done = 0; + uint64_t obj_count = 0, obj_done = 0; int state = 0; int in_flight = 0; }; @@ -23,6 +23,7 @@ struct rm_inode_t { uint64_t inode = 0; pool_id_t pool_id = 0; + uint64_t min_offset = 0; cli_tool_t *parent = NULL; inode_list_t *lister = NULL; @@ -43,8 +44,21 @@ struct rm_inode_t .objects = objects, .obj_count = objects.size(), .obj_done = 0, - .obj_prev_done = 0, }); + if (min_offset == 0) + { + total_count += objects.size(); + } + else + { + for (object_id oid: objects) + { + if (oid.stripe >= min_offset) + { + total_count++; + } + } + } rm->obj_pos = rm->objects.begin(); lists.push_back(rm); if (parent->list_first) @@ -78,38 +92,41 @@ struct rm_inode_t } while (cur_list->in_flight < parent->iodepth && cur_list->obj_pos != cur_list->objects.end()) { - osd_op_t *op = new osd_op_t(); - op->op_type = OSD_OP_OUT; - op->peer_fd = parent->cli->msgr.osd_peer_fds[cur_list->rm_osd_num]; - op->req = (osd_any_op_t){ - .rw = { - .header = { - .magic = SECONDARY_OSD_OP_MAGIC, - .id = parent->cli->next_op_id(), - .opcode = OSD_OP_DELETE, - }, - .inode = cur_list->obj_pos->inode, - .offset = cur_list->obj_pos->stripe, - .len = 0, - }, - }; - op->callback = [this, cur_list](osd_op_t *op) + if (cur_list->obj_pos->stripe >= min_offset) { - cur_list->in_flight--; - if (op->reply.hdr.retval < 0) + osd_op_t *op = new osd_op_t(); + op->op_type = OSD_OP_OUT; + op->peer_fd = parent->cli->msgr.osd_peer_fds[cur_list->rm_osd_num]; + op->req = (osd_any_op_t){ + .rw = { + .header = { + .magic = SECONDARY_OSD_OP_MAGIC, + .id = parent->cli->next_op_id(), + .opcode = OSD_OP_DELETE, + }, + .inode = cur_list->obj_pos->inode, + .offset = cur_list->obj_pos->stripe, + .len = 0, + }, + }; + op->callback = [this, cur_list](osd_op_t *op) { - fprintf(stderr, "Failed to remove object %lx:%lx from PG %u (OSD %lu) (retval=%ld)\n", - op->req.rw.inode, op->req.rw.offset, - cur_list->pg_num, cur_list->rm_osd_num, op->reply.hdr.retval); - } - delete op; - cur_list->obj_done++; - total_done++; - continue_delete(); - }; + cur_list->in_flight--; + if (op->reply.hdr.retval < 0) + { + fprintf(stderr, "Failed to remove object %lx:%lx from PG %u (OSD %lu) (retval=%ld)\n", + op->req.rw.inode, op->req.rw.offset, + cur_list->pg_num, cur_list->rm_osd_num, op->reply.hdr.retval); + } + delete op; + cur_list->obj_done++; + total_done++; + continue_delete(); + }; + cur_list->in_flight++; + parent->cli->msgr.outbox_push(op); + } cur_list->obj_pos++; - cur_list->in_flight++; - parent->cli->msgr.outbox_push(op); } } @@ -183,6 +200,7 @@ std::function cli_tool_t::start_rm(json11::Json cfg) fprintf(stderr, "pool is missing\n"); exit(1); } + remover->min_offset = cfg["min-offset"].uint64_value(); return [remover]() { if (remover->loop()) diff --git a/src/cli_set.cpp b/src/cli_set.cpp new file mode 100644 index 00000000..4a53d50a --- /dev/null +++ b/src/cli_set.cpp @@ -0,0 +1,182 @@ +// Copyright (c) Vitaliy Filippov, 2019+ +// License: VNPL-1.1 (see README.md for details) + +#include "cli.h" +#include "cluster_client.h" +#include "base64.h" + +// Resize image (purging extra data on shrink) or change its readonly status +struct image_changer_t +{ + cli_tool_t *parent; + + std::string image_name; + uint64_t new_size = 0; + bool set_readonly = false, set_readwrite = false, force = false; + // interval between fsyncs + int fsync_interval = 128; + + uint64_t inode_num = 0; + inode_config_t cfg; + std::string cur_cfg_key; + bool has_children = false; + + int state = 0; + std::function cb; + + bool is_done() + { + return state == 100; + } + + void loop() + { + if (state == 1) + goto resume_1; + else if (state == 2) + goto resume_2; + for (auto & ic: parent->cli->st_cli.inode_config) + { + if (ic.second.name == image_name) + { + inode_num = ic.first; + cfg = ic.second; + break; + } + } + if (!inode_num) + { + fprintf(stderr, "Image %s does not exist\n", image_name.c_str()); + exit(1); + } + for (auto & ic: parent->cli->st_cli.inode_config) + { + if (ic.second.parent_id == inode_num) + { + has_children = true; + break; + } + } + if (new_size != 0) + { + if (cfg.size >= new_size) + { + // Check confirmation if trimming an image with children + if (has_children && !force) + { + fprintf(stderr, "Image %s has children. Refusing to shrink it without --force", image_name.c_str()); + exit(1); + } + // Shrink the image first + cb = parent->start_rm(json11::Json::object { + { "inode", INODE_NO_POOL(inode_num) }, + { "pool", (uint64_t)INODE_POOL(inode_num) }, + { "fsync-interval", fsync_interval }, + { "min-offset", new_size }, + }); +resume_1: + while (!cb()) + { + state = 1; + return; + } + cb = NULL; + } + cfg.size = new_size; + } + if (set_readonly) + { + cfg.readonly = true; + } + if (set_readwrite) + { + cfg.readonly = false; + // Check confirmation if trimming an image with children + if (!force) + { + fprintf(stderr, "Image %s has children. Refusing to make it read-write without --force", image_name.c_str()); + exit(1); + } + } + cur_cfg_key = base64_encode(parent->cli->st_cli.etcd_prefix+ + "/config/inode/"+std::to_string(INODE_POOL(inode_num))+ + "/"+std::to_string(INODE_NO_POOL(inode_num))); + parent->waiting++; + parent->cli->st_cli.etcd_txn(json11::Json::object { + { "compare", json11::Json::array { + json11::Json::object { + { "target", "MOD" }, + { "key", cur_cfg_key }, + { "result", "LESS" }, + { "mod_revision", cfg.mod_revision+1 }, + }, + } }, + { "success", json11::Json::array { + json11::Json::object { + { "request_put", json11::Json::object { + { "key", cur_cfg_key }, + { "value", base64_encode(json11::Json( + parent->cli->st_cli.serialize_inode_cfg(&cfg) + ).dump()) }, + } } + }, + } }, + }, ETCD_SLOW_TIMEOUT, [this](std::string err, json11::Json res) + { + if (err != "") + { + fprintf(stderr, "Error changing %s: %s\n", image_name.c_str(), err.c_str()); + exit(1); + } + if (!res["succeeded"].bool_value()) + { + fprintf(stderr, "Image %s was modified by someone else, please repeat your request\n", image_name.c_str()); + exit(1); + } + parent->waiting--; + parent->ringloop->wakeup(); + }); + state = 2; +resume_2: + if (parent->waiting > 0) + return; + printf("Image %s changed\n", image_name.c_str()); + state = 100; + } +}; + +std::function cli_tool_t::start_set(json11::Json cfg) +{ + json11::Json::array cmd = cfg["command"].array_items(); + auto changer = new image_changer_t(); + changer->parent = this; + changer->image_name = cmd.size() > 1 ? cmd[1].string_value() : ""; + if (changer->image_name == "") + { + fprintf(stderr, "Image name is missing\n"); + exit(1); + } + changer->new_size = cfg["size"].uint64_value(); + if (changer->new_size != 0 && (changer->new_size % 4096)) + { + fprintf(stderr, "Image size should be a multiple of 4096\n"); + exit(1); + } + changer->force = cfg["force"].bool_value(); + changer->set_readonly = cfg["readonly"].bool_value(); + changer->set_readwrite = cfg["readwrite"].bool_value(); + changer->fsync_interval = cfg["fsync-interval"].uint64_value(); + if (!changer->fsync_interval) + changer->fsync_interval = 128; + // FIXME Check that the image doesn't have children when shrinking + return [changer]() + { + changer->loop(); + if (changer->is_done()) + { + delete changer; + return true; + } + return false; + }; +}