forked from vitalif/vitastor
Implement CLI set (resize, change readonly status) command
parent
32614c5bc8
commit
2cb3e84882
|
@ -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
|
||||||
|
|
17
src/cli.cpp
17
src/cli.cpp
|
@ -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"
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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 != "")
|
||||||
{
|
{
|
||||||
|
|
|
@ -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:
|
||||||
|
|
|
@ -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())
|
||||||
|
|
|
@ -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;
|
||||||
|
};
|
||||||
|
}
|
Loading…
Reference in New Issue