Implement CLI set (resize, change readonly status) command

nbd-vmsplice
Vitaliy Filippov 2021-11-11 01:55:15 +03:00
parent 32614c5bc8
commit 2cb3e84882
7 changed files with 253 additions and 36 deletions

View File

@ -153,7 +153,7 @@ target_link_libraries(vitastor-nbd
# vitastor-cli # vitastor-cli
add_executable(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 target_link_libraries(vitastor-cli
vitastor_client vitastor_client

View File

@ -48,11 +48,16 @@ json11::Json::object cli_tool_t::parse_args(int narg, const char *args[])
{ {
cfg["reverse"] = "1"; cfg["reverse"] = "1";
} }
else if (args[i][0] == '-' && args[i][1] == 'f')
{
cfg["force"] = "1";
}
else if (args[i][0] == '-' && args[i][1] == '-') else if (args[i][0] == '-' && args[i][1] == '-')
{ {
const char *opt = args[i]+2; const char *opt = args[i]+2;
cfg[opt] = i == narg-1 || !strcmp(opt, "json") || !strcmp(opt, "wait-list") || cfg[opt] = i == narg-1 || !strcmp(opt, "json") || !strcmp(opt, "wait-list") ||
!strcmp(opt, "long") || !strcmp(opt, "del") || !strcmp(opt, "no-color") || !strcmp(opt, "long") || !strcmp(opt, "del") || !strcmp(opt, "no-color") ||
!strcmp(opt, "force") ||
!strcmp(opt, "writers-stopped") && strcmp("1", args[i+1]) != 0 !strcmp(opt, "writers-stopped") && strcmp("1", args[i+1]) != 0
? "1" : args[++i]; ? "1" : args[++i];
} }
@ -98,8 +103,11 @@ void cli_tool_t::help()
"%s snap-create [-p|--pool <id|name>] <image>@<snapshot>\n" "%s snap-create [-p|--pool <id|name>] <image>@<snapshot>\n"
" Create a snapshot of image <name>. May be used live if only a single writer is active.\n" " Create a snapshot of image <name>. May be used live if only a single writer is active.\n"
"\n" "\n"
"%s set <name> [-s|--size <size>] [--readonly | --readwrite]\n" "%s set <name> [-s|--size <size>] [--readonly | --readwrite] [-f|--force]\n"
" Resize image or change its readonly status. Images with children can't be made read-write.\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" "\n"
"%s rm <from> [<to>] [--writers-stopped]\n" "%s rm <from> [<to>] [--writers-stopped]\n"
" Remove <from> or all layers between <from> and <to> (<to> must be a child of <from>),\n" " Remove <from> or all layers between <from> and <to> (<to> must be a child of <from>),\n"
@ -111,10 +119,11 @@ void cli_tool_t::help()
"%s flatten <layer>\n" "%s flatten <layer>\n"
" Flatten a layer, i.e. merge data and detach it from parents.\n" " Flatten a layer, i.e. merge data and detach it from parents.\n"
"\n" "\n"
"%s rm-data --pool <pool> --inode <inode> [--wait-list]\n" "%s rm-data --pool <pool> --inode <inode> [--wait-list] [--min-offset <offset>]\n"
" Remove inode data without changing metadata.\n" " Remove inode data without changing metadata.\n"
" --wait-list means first retrieve objects listings and then remove it.\n" " --wait-list Retrieve full objects listings before starting to remove objects.\n"
" --wait-list requires more memory, but allows to show correct removal progress.\n" " Requires more memory, but allows to show correct removal progress.\n"
" --min-offset Purge only data starting with specified offset.\n"
"\n" "\n"
"%s merge-data <from> <to> [--target <target>]\n" "%s merge-data <from> <to> [--target <target>]\n"
" Merge layer data without changing metadata. Merge <from>..<to> to <target>.\n" " Merge layer data without changing metadata. Merge <from>..<to> to <target>.\n"

View File

@ -52,6 +52,7 @@ public:
std::function<bool(void)> start_ls(json11::Json cfg); std::function<bool(void)> start_ls(json11::Json cfg);
std::function<bool(void)> start_create(json11::Json cfg); std::function<bool(void)> start_create(json11::Json cfg);
std::function<bool(void)> start_set(json11::Json cfg);
std::function<bool(void)> start_rm(json11::Json); std::function<bool(void)> start_rm(json11::Json);
std::function<bool(void)> start_merge(json11::Json); std::function<bool(void)> start_merge(json11::Json);
std::function<bool(void)> start_flatten(json11::Json); std::function<bool(void)> start_flatten(json11::Json);

View File

@ -427,6 +427,7 @@ resume_3:
exit(1); exit(1);
} }
this->result = res; this->result = res;
parent->ringloop->wakeup();
}); });
} }
}; };
@ -478,6 +479,11 @@ std::function<bool(void)> cli_tool_t::start_create(json11::Json cfg)
fprintf(stderr, "Invalid syntax for size: %s\n", cfg["size"].string_value().c_str()); fprintf(stderr, "Invalid syntax for size: %s\n", cfg["size"].string_value().c_str());
exit(1); exit(1);
} }
if (size % 4096)
{
fprintf(stderr, "Image size should be a multiple of 4096\n");
exit(1);
}
image_creator->size = size; image_creator->size = size;
if (image_creator->new_snap != "") if (image_creator->new_snap != "")
{ {

View File

@ -129,6 +129,7 @@ struct image_lister_t
exit(1); exit(1);
} }
space_info = res; space_info = res;
parent->ringloop->wakeup();
}); });
state = 1; state = 1;
resume_1: resume_1:

View File

@ -14,7 +14,7 @@ struct rm_pg_t
osd_num_t rm_osd_num; osd_num_t rm_osd_num;
std::set<object_id> objects; std::set<object_id> objects;
std::set<object_id>::iterator obj_pos; std::set<object_id>::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 state = 0;
int in_flight = 0; int in_flight = 0;
}; };
@ -23,6 +23,7 @@ struct rm_inode_t
{ {
uint64_t inode = 0; uint64_t inode = 0;
pool_id_t pool_id = 0; pool_id_t pool_id = 0;
uint64_t min_offset = 0;
cli_tool_t *parent = NULL; cli_tool_t *parent = NULL;
inode_list_t *lister = NULL; inode_list_t *lister = NULL;
@ -43,8 +44,21 @@ struct rm_inode_t
.objects = objects, .objects = objects,
.obj_count = objects.size(), .obj_count = objects.size(),
.obj_done = 0, .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(); rm->obj_pos = rm->objects.begin();
lists.push_back(rm); lists.push_back(rm);
if (parent->list_first) 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()) while (cur_list->in_flight < parent->iodepth && cur_list->obj_pos != cur_list->objects.end())
{ {
osd_op_t *op = new osd_op_t(); if (cur_list->obj_pos->stripe >= min_offset)
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)
{ {
cur_list->in_flight--; osd_op_t *op = new osd_op_t();
if (op->reply.hdr.retval < 0) 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", cur_list->in_flight--;
op->req.rw.inode, op->req.rw.offset, if (op->reply.hdr.retval < 0)
cur_list->pg_num, cur_list->rm_osd_num, op->reply.hdr.retval); {
} fprintf(stderr, "Failed to remove object %lx:%lx from PG %u (OSD %lu) (retval=%ld)\n",
delete op; op->req.rw.inode, op->req.rw.offset,
cur_list->obj_done++; cur_list->pg_num, cur_list->rm_osd_num, op->reply.hdr.retval);
total_done++; }
continue_delete(); 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->obj_pos++;
cur_list->in_flight++;
parent->cli->msgr.outbox_push(op);
} }
} }
@ -183,6 +200,7 @@ std::function<bool(void)> cli_tool_t::start_rm(json11::Json cfg)
fprintf(stderr, "pool is missing\n"); fprintf(stderr, "pool is missing\n");
exit(1); exit(1);
} }
remover->min_offset = cfg["min-offset"].uint64_value();
return [remover]() return [remover]()
{ {
if (remover->loop()) if (remover->loop())

182
src/cli_set.cpp Normal file
View File

@ -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<bool(void)> 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<bool(void)> 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;
};
}