forked from vitalif/vitastor
Implement image/snapshot/clone creation and listing by pool
parent
b3dcee0d43
commit
404e07d365
|
@ -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_flatten.cpp cli_merge.cpp cli_rm.cpp cli_snap_rm.cpp
|
cli.cpp cli_ls.cpp cli_create.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
|
||||||
|
|
28
src/cli.cpp
28
src/cli.cpp
|
@ -40,6 +40,14 @@ json11::Json::object cli_tool_t::parse_args(int narg, const char *args[])
|
||||||
{
|
{
|
||||||
cfg["interactive"] = "1";
|
cfg["interactive"] = "1";
|
||||||
}
|
}
|
||||||
|
else if (args[i][0] == '-' && args[i][1] == 'p')
|
||||||
|
{
|
||||||
|
cfg["pool"] = args[++i];
|
||||||
|
}
|
||||||
|
else if (args[i][0] == '-' && args[i][1] == 's')
|
||||||
|
{
|
||||||
|
cfg["size"] = args[++i];
|
||||||
|
}
|
||||||
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;
|
||||||
|
@ -71,17 +79,20 @@ void cli_tool_t::help()
|
||||||
"(c) Vitaliy Filippov, 2019+ (VNPL-1.1)\n"
|
"(c) Vitaliy Filippov, 2019+ (VNPL-1.1)\n"
|
||||||
"\n"
|
"\n"
|
||||||
"USAGE:\n"
|
"USAGE:\n"
|
||||||
"%s ls [-l]\n"
|
"%s ls [-l] [--pool|-p <id|name>]\n"
|
||||||
" List existing images. Also report provisioned and allocated size if -l is specified.\n"
|
" List existing images from a specified pool or from all pools if not specified.\n"
|
||||||
|
" Also report allocated size if -l is specified.\n"
|
||||||
"\n"
|
"\n"
|
||||||
"%s create --size <size> [--parent <parent_name>[@<snapshot>]] <name>\n"
|
"%s create -s|--size <size> [--pool <id|name>] [--parent <parent_name>[@<snapshot>]] <name>\n"
|
||||||
" Create an image. You may use K/M/G/T suffixes for <size>. If --parent is specified,\n"
|
" Create an image. You may use K/M/G/T suffixes for <size>. If --parent is specified,\n"
|
||||||
" a copy-on-write image clone is created. Parent must be a snapshot (readonly image).\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"
|
"\n"
|
||||||
"%s snap-create <name>@<snapshot>\n"
|
"%s create --snapshot <snapshot> [--pool <id|name>] <image>\n"
|
||||||
|
"%s snap-create [--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> [--size <size>] [--readonly | --readwrite]\n"
|
"%s set <name> [-s|--size <size>] [--readonly | --readwrite]\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"
|
||||||
"\n"
|
"\n"
|
||||||
"%s top [-n <MAX_COUNT>] [-i]\n"
|
"%s top [-n <MAX_COUNT>] [-i]\n"
|
||||||
|
@ -115,7 +126,7 @@ void cli_tool_t::help()
|
||||||
" --cas 1|0 Use online CAS writes when possible (default auto)\n"
|
" --cas 1|0 Use online CAS writes when possible (default auto)\n"
|
||||||
" --json JSON output\n"
|
" --json JSON output\n"
|
||||||
,
|
,
|
||||||
exe_name, exe_name, exe_name, exe_name, exe_name, exe_name, exe_name, exe_name, exe_name
|
exe_name, exe_name, exe_name, exe_name, exe_name, exe_name, exe_name, exe_name, exe_name, exe_name
|
||||||
);
|
);
|
||||||
exit(0);
|
exit(0);
|
||||||
}
|
}
|
||||||
|
@ -214,6 +225,11 @@ void cli_tool_t::run(json11::Json cfg)
|
||||||
// List images
|
// List images
|
||||||
action_cb = start_ls(cfg);
|
action_cb = start_ls(cfg);
|
||||||
}
|
}
|
||||||
|
else if (cmd[0] == "create" || cmd[0] == "snap-create")
|
||||||
|
{
|
||||||
|
// Create image/snapshot
|
||||||
|
action_cb = start_create(cfg);
|
||||||
|
}
|
||||||
else if (cmd[0] == "rm-data")
|
else if (cmd[0] == "rm-data")
|
||||||
{
|
{
|
||||||
// Delete inode data
|
// Delete inode data
|
||||||
|
|
|
@ -51,6 +51,7 @@ public:
|
||||||
friend struct snap_remover_t;
|
friend struct snap_remover_t;
|
||||||
|
|
||||||
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_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);
|
||||||
|
|
|
@ -1,14 +1,17 @@
|
||||||
// Copyright (c) Vitaliy Filippov, 2019+
|
// Copyright (c) Vitaliy Filippov, 2019+
|
||||||
// License: VNPL-1.1 (see README.md for details)
|
// License: VNPL-1.1 (see README.md for details)
|
||||||
|
|
||||||
|
#include <ctype.h>
|
||||||
#include "cli.h"
|
#include "cli.h"
|
||||||
#include "cluster_client.h"
|
#include "cluster_client.h"
|
||||||
#include "base64.h"
|
#include "base64.h"
|
||||||
|
|
||||||
// Create an image, snapshot or clone
|
// Create an image, snapshot or clone
|
||||||
//
|
//
|
||||||
// Snapshot creation does a etcd transaction which:
|
// Snapshot creation performs a etcd transaction which:
|
||||||
// - Changes the name of old inode to the name of the snapshot (say, testimg -> testimg@0)
|
// - Checks that the image exists
|
||||||
|
// - Checks that the snapshot doesn't exist
|
||||||
|
// - Renames the inode to a new name with snapshot (say, testimg -> testimg@0)
|
||||||
// - Sets the readonly flag for the old inode
|
// - Sets the readonly flag for the old inode
|
||||||
// - Creates a new inode with the same name pointing to the old inode as parent
|
// - Creates a new inode with the same name pointing to the old inode as parent
|
||||||
// - Adjusts /index/image/*
|
// - Adjusts /index/image/*
|
||||||
|
@ -19,16 +22,412 @@ struct image_creator_t
|
||||||
{
|
{
|
||||||
cli_tool_t *parent;
|
cli_tool_t *parent;
|
||||||
|
|
||||||
|
pool_id_t new_pool_id = 0;
|
||||||
|
std::string new_pool_name;
|
||||||
|
std::string image_name, new_snap, new_parent;
|
||||||
|
uint64_t size;
|
||||||
|
|
||||||
|
pool_id_t old_pool_id = 0;
|
||||||
|
inode_t new_parent_id = 0;
|
||||||
|
inode_t new_id = 0, old_id = 0;
|
||||||
|
uint64_t max_id_mod_rev = 0, cfg_mod_rev = 0, idx_mod_rev = 0;
|
||||||
|
json11::Json result;
|
||||||
|
|
||||||
int state = 0;
|
int state = 0;
|
||||||
|
|
||||||
bool is_done()
|
bool is_done()
|
||||||
{
|
{
|
||||||
return true;
|
return state == 100;
|
||||||
}
|
}
|
||||||
|
|
||||||
void loop()
|
void loop()
|
||||||
{
|
{
|
||||||
return;
|
if (state >= 1)
|
||||||
|
goto resume_1;
|
||||||
|
if (new_pool_id)
|
||||||
|
{
|
||||||
|
auto & pools = parent->cli->st_cli.pool_config;
|
||||||
|
if (pools.find(new_pool_id) == pools.end())
|
||||||
|
{
|
||||||
|
new_pool_id = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (new_pool_name != "")
|
||||||
|
{
|
||||||
|
for (auto & ic: parent->cli->st_cli.pool_config)
|
||||||
|
{
|
||||||
|
if (ic.second.name == new_pool_name)
|
||||||
|
{
|
||||||
|
new_pool_id = ic.first;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (parent->cli->st_cli.pool_config.size() == 1)
|
||||||
|
{
|
||||||
|
auto it = parent->cli->st_cli.pool_config.begin();
|
||||||
|
new_pool_id = it->first;
|
||||||
|
}
|
||||||
|
if (!new_pool_id)
|
||||||
|
{
|
||||||
|
if (new_pool_name == "")
|
||||||
|
{
|
||||||
|
fprintf(stderr, "Pool name or ID is missing\n");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
fprintf(stderr, "Pool %s does not exist\n", new_pool_name.c_str());
|
||||||
|
}
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
state = 1;
|
||||||
|
resume_1:
|
||||||
|
if (new_snap == "")
|
||||||
|
create_image();
|
||||||
|
else
|
||||||
|
create_snapshot();
|
||||||
|
}
|
||||||
|
|
||||||
|
void create_image()
|
||||||
|
{
|
||||||
|
if (state == 2)
|
||||||
|
goto resume_2;
|
||||||
|
else if (state == 3)
|
||||||
|
goto resume_3;
|
||||||
|
for (auto & ic: parent->cli->st_cli.inode_config)
|
||||||
|
{
|
||||||
|
if (ic.second.name == image_name)
|
||||||
|
{
|
||||||
|
fprintf(stderr, "Image %s already exists\n", image_name.c_str());
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
do
|
||||||
|
{
|
||||||
|
etcd_txn(json11::Json::object {
|
||||||
|
{ "success", json11::Json::array { get_next_id() } }
|
||||||
|
});
|
||||||
|
state = 2;
|
||||||
|
resume_2:
|
||||||
|
if (parent->waiting > 0)
|
||||||
|
return;
|
||||||
|
extract_next_id(result["responses"][0]);
|
||||||
|
attempt_create();
|
||||||
|
state = 3;
|
||||||
|
resume_3:
|
||||||
|
if (parent->waiting > 0)
|
||||||
|
return;
|
||||||
|
if (!result["succeeded"].bool_value() &&
|
||||||
|
result["responses"][0]["response_range"]["kvs"].array_items().size() > 0)
|
||||||
|
{
|
||||||
|
fprintf(stderr, "Image %s already exists\n", image_name.c_str());
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
} while (!result["succeeded"].bool_value());
|
||||||
|
if (parent->progress)
|
||||||
|
{
|
||||||
|
printf("Image %s created\n", image_name.c_str());
|
||||||
|
}
|
||||||
|
state = 100;
|
||||||
|
}
|
||||||
|
|
||||||
|
void create_snapshot()
|
||||||
|
{
|
||||||
|
if (state == 2)
|
||||||
|
goto resume_2;
|
||||||
|
else if (state == 3)
|
||||||
|
goto resume_3;
|
||||||
|
else if (state == 4)
|
||||||
|
goto resume_4;
|
||||||
|
for (auto & ic: parent->cli->st_cli.inode_config)
|
||||||
|
{
|
||||||
|
if (ic.second.name == image_name+"@"+new_snap)
|
||||||
|
{
|
||||||
|
fprintf(stderr, "Snapshot %s@%s already exists\n", image_name.c_str(), new_snap.c_str());
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
do
|
||||||
|
{
|
||||||
|
// In addition to next_id, get: size, old_id, old_pool_id, new_parent, cfg_mod_rev, idx_mod_rev
|
||||||
|
resume_2:
|
||||||
|
resume_3:
|
||||||
|
get_image_details();
|
||||||
|
if (parent->waiting > 0)
|
||||||
|
return;
|
||||||
|
if (!old_id)
|
||||||
|
{
|
||||||
|
fprintf(stderr, "Image %s does not exist\n", image_name.c_str());
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
attempt_create();
|
||||||
|
state = 4;
|
||||||
|
resume_4:
|
||||||
|
if (parent->waiting > 0)
|
||||||
|
return;
|
||||||
|
if (!result["succeeded"].bool_value() &&
|
||||||
|
result["responses"][0]["response_range"]["kvs"].array_items().size() > 0)
|
||||||
|
{
|
||||||
|
fprintf(stderr, "Snapshot %s@%s already exists\n", image_name.c_str(), new_snap.c_str());
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
} while (!result["succeeded"].bool_value());
|
||||||
|
if (parent->progress)
|
||||||
|
{
|
||||||
|
printf("Snapshot %s@%s created\n", image_name.c_str(), new_snap.c_str());
|
||||||
|
}
|
||||||
|
state = 100;
|
||||||
|
}
|
||||||
|
|
||||||
|
json11::Json::object get_next_id()
|
||||||
|
{
|
||||||
|
return json11::Json::object {
|
||||||
|
{ "request_range", json11::Json::object {
|
||||||
|
{ "key", base64_encode(
|
||||||
|
parent->cli->st_cli.etcd_prefix+"/index/maxid/"+std::to_string(new_pool_id)
|
||||||
|
) },
|
||||||
|
} },
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
void extract_next_id(json11::Json response)
|
||||||
|
{
|
||||||
|
new_id = 1;
|
||||||
|
max_id_mod_rev = 0;
|
||||||
|
if (response["response_range"]["kvs"].array_items().size() > 0)
|
||||||
|
{
|
||||||
|
auto kv = parent->cli->st_cli.parse_etcd_kv(response["response_range"]["kvs"][0]);
|
||||||
|
new_id = 1+INODE_NO_POOL(kv.value.uint64_value());
|
||||||
|
max_id_mod_rev = kv.mod_revision;
|
||||||
|
}
|
||||||
|
auto ino_it = parent->cli->st_cli.inode_config.lower_bound(INODE_WITH_POOL(new_pool_id, 0));
|
||||||
|
if (ino_it != parent->cli->st_cli.inode_config.begin())
|
||||||
|
{
|
||||||
|
ino_it--;
|
||||||
|
if (INODE_POOL(ino_it->first) == new_pool_id && new_id < 1+INODE_NO_POOL(ino_it->first))
|
||||||
|
new_id = 1+INODE_NO_POOL(ino_it->first);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void get_image_details()
|
||||||
|
{
|
||||||
|
if (state == 2)
|
||||||
|
goto resume_2;
|
||||||
|
else if (state == 3)
|
||||||
|
goto resume_3;
|
||||||
|
etcd_txn(json11::Json::object { { "success", json11::Json::array {
|
||||||
|
get_next_id(),
|
||||||
|
json11::Json::object {
|
||||||
|
{ "request_range", json11::Json::object {
|
||||||
|
{ "key", base64_encode(
|
||||||
|
parent->cli->st_cli.etcd_prefix+"/index/image/"+image_name
|
||||||
|
) },
|
||||||
|
} },
|
||||||
|
},
|
||||||
|
} } });
|
||||||
|
state = 2;
|
||||||
|
resume_2:
|
||||||
|
if (parent->waiting > 0)
|
||||||
|
return;
|
||||||
|
extract_next_id(result["responses"][0]);
|
||||||
|
old_id = 0;
|
||||||
|
old_pool_id = 0;
|
||||||
|
cfg_mod_rev = idx_mod_rev = 0;
|
||||||
|
if (result["responses"][1]["response_range"]["kvs"].array_items().size() == 0)
|
||||||
|
{
|
||||||
|
for (auto & ic: parent->cli->st_cli.inode_config)
|
||||||
|
{
|
||||||
|
if (ic.second.name == image_name)
|
||||||
|
{
|
||||||
|
old_id = INODE_NO_POOL(ic.first);
|
||||||
|
old_pool_id = INODE_POOL(ic.first);
|
||||||
|
size = ic.second.size;
|
||||||
|
new_parent_id = ic.second.parent_id;
|
||||||
|
cfg_mod_rev = ic.second.mod_revision;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// FIXME: Parse kvs in etcd_state_client automatically
|
||||||
|
{
|
||||||
|
auto kv = parent->cli->st_cli.parse_etcd_kv(result["responses"][1]["response_range"]["kvs"][0]);
|
||||||
|
old_id = INODE_NO_POOL(kv.value["id"].uint64_value());
|
||||||
|
old_pool_id = (pool_id_t)kv.value["pool_id"].uint64_value();
|
||||||
|
idx_mod_rev = kv.mod_revision;
|
||||||
|
if (!old_id || !old_pool_id || old_pool_id >= POOL_ID_MAX)
|
||||||
|
{
|
||||||
|
fprintf(stderr, "Invalid pool or inode ID in etcd key %s\n", kv.key.c_str());
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
etcd_txn(json11::Json::object {
|
||||||
|
{ "success", json11::Json::array {
|
||||||
|
json11::Json::object {
|
||||||
|
{ "request_range", json11::Json::object {
|
||||||
|
{ "key", base64_encode(
|
||||||
|
parent->cli->st_cli.etcd_prefix+"/config/inode/"+
|
||||||
|
std::to_string(old_pool_id)+"/"+std::to_string(old_id)
|
||||||
|
) },
|
||||||
|
} },
|
||||||
|
},
|
||||||
|
} },
|
||||||
|
});
|
||||||
|
state = 3;
|
||||||
|
resume_3:
|
||||||
|
if (parent->waiting > 0)
|
||||||
|
return;
|
||||||
|
{
|
||||||
|
auto kv = parent->cli->st_cli.parse_etcd_kv(result["responses"][0]["response_range"]["kvs"][0]);
|
||||||
|
size = kv.value["size"].uint64_value();
|
||||||
|
new_parent_id = kv.value["parent_id"].uint64_value();
|
||||||
|
uint64_t parent_pool_id = kv.value["parent_pool_id"].uint64_value();
|
||||||
|
if (new_parent_id)
|
||||||
|
{
|
||||||
|
new_parent_id = INODE_WITH_POOL(parent_pool_id ? parent_pool_id : old_pool_id, new_parent_id);
|
||||||
|
}
|
||||||
|
cfg_mod_rev = kv.mod_revision;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void attempt_create()
|
||||||
|
{
|
||||||
|
inode_config_t new_cfg = {
|
||||||
|
.num = INODE_WITH_POOL(new_pool_id, new_id),
|
||||||
|
.name = image_name,
|
||||||
|
.size = size,
|
||||||
|
.parent_id = (new_snap != "" ? INODE_WITH_POOL(old_pool_id, old_id) : new_parent_id),
|
||||||
|
.readonly = false,
|
||||||
|
};
|
||||||
|
json11::Json::array checks = json11::Json::array {
|
||||||
|
json11::Json::object {
|
||||||
|
{ "target", "VERSION" },
|
||||||
|
{ "version", 0 },
|
||||||
|
{ "key", base64_encode(
|
||||||
|
parent->cli->st_cli.etcd_prefix+"/config/inode/"+
|
||||||
|
std::to_string(new_pool_id)+"/"+std::to_string(new_id)
|
||||||
|
) },
|
||||||
|
},
|
||||||
|
json11::Json::object {
|
||||||
|
{ "target", "VERSION" },
|
||||||
|
{ "version", 0 },
|
||||||
|
{ "key", base64_encode(
|
||||||
|
parent->cli->st_cli.etcd_prefix+"/index/image/"+image_name+
|
||||||
|
(new_snap != "" ? "@"+new_snap : "")
|
||||||
|
) },
|
||||||
|
},
|
||||||
|
json11::Json::object {
|
||||||
|
{ "target", "MOD" },
|
||||||
|
{ "mod_revision", max_id_mod_rev },
|
||||||
|
{ "key", base64_encode(parent->cli->st_cli.etcd_prefix+"/index/maxid/"+std::to_string(new_pool_id)) },
|
||||||
|
},
|
||||||
|
};
|
||||||
|
json11::Json::array success = json11::Json::array {
|
||||||
|
json11::Json::object {
|
||||||
|
{ "request_put", json11::Json::object {
|
||||||
|
{ "key", base64_encode(
|
||||||
|
parent->cli->st_cli.etcd_prefix+"/config/inode/"+
|
||||||
|
std::to_string(new_pool_id)+"/"+std::to_string(new_id)
|
||||||
|
) },
|
||||||
|
{ "value", base64_encode(
|
||||||
|
json11::Json(parent->cli->st_cli.serialize_inode_cfg(&new_cfg)).dump()
|
||||||
|
) },
|
||||||
|
} },
|
||||||
|
},
|
||||||
|
json11::Json::object {
|
||||||
|
{ "request_put", json11::Json::object {
|
||||||
|
{ "key", base64_encode(parent->cli->st_cli.etcd_prefix+"/index/image/"+image_name) },
|
||||||
|
{ "value", base64_encode(json11::Json(json11::Json::object{
|
||||||
|
{ "id", new_id },
|
||||||
|
{ "pool_id", (uint64_t)new_pool_id },
|
||||||
|
}).dump()) },
|
||||||
|
} },
|
||||||
|
},
|
||||||
|
json11::Json::object {
|
||||||
|
{ "request_put", json11::Json::object {
|
||||||
|
{ "key", base64_encode(
|
||||||
|
parent->cli->st_cli.etcd_prefix+"/index/maxid/"+
|
||||||
|
std::to_string(new_pool_id)
|
||||||
|
) },
|
||||||
|
{ "value", base64_encode(std::to_string(new_id)) }
|
||||||
|
} },
|
||||||
|
},
|
||||||
|
};
|
||||||
|
json11::Json::array failure = json11::Json::array {
|
||||||
|
json11::Json::object {
|
||||||
|
{ "request_range", json11::Json::object {
|
||||||
|
{ "key", base64_encode(
|
||||||
|
parent->cli->st_cli.etcd_prefix+"/index/image/"+
|
||||||
|
image_name+(new_snap != "" ? "@"+new_snap : "")
|
||||||
|
) },
|
||||||
|
} },
|
||||||
|
},
|
||||||
|
};
|
||||||
|
if (new_snap != "")
|
||||||
|
{
|
||||||
|
inode_config_t snap_cfg = {
|
||||||
|
.num = INODE_WITH_POOL(old_pool_id, old_id),
|
||||||
|
.name = image_name+"@"+new_snap,
|
||||||
|
.size = size,
|
||||||
|
.parent_id = new_parent_id,
|
||||||
|
.readonly = true,
|
||||||
|
};
|
||||||
|
checks.push_back(json11::Json::object {
|
||||||
|
{ "target", "MOD" },
|
||||||
|
{ "mod_revision", cfg_mod_rev },
|
||||||
|
{ "key", base64_encode(
|
||||||
|
parent->cli->st_cli.etcd_prefix+"/config/inode/"+
|
||||||
|
std::to_string(old_pool_id)+"/"+std::to_string(old_id)
|
||||||
|
) },
|
||||||
|
});
|
||||||
|
checks.push_back(json11::Json::object {
|
||||||
|
{ "target", "MOD" },
|
||||||
|
{ "mod_revision", idx_mod_rev },
|
||||||
|
{ "key", base64_encode(parent->cli->st_cli.etcd_prefix+"/index/image/"+image_name) }
|
||||||
|
});
|
||||||
|
success.push_back(json11::Json::object {
|
||||||
|
{ "request_put", json11::Json::object {
|
||||||
|
{ "key", base64_encode(
|
||||||
|
parent->cli->st_cli.etcd_prefix+"/config/inode/"+
|
||||||
|
std::to_string(old_pool_id)+"/"+std::to_string(old_id)
|
||||||
|
) },
|
||||||
|
{ "value", base64_encode(
|
||||||
|
json11::Json(parent->cli->st_cli.serialize_inode_cfg(&snap_cfg)).dump()
|
||||||
|
) },
|
||||||
|
} },
|
||||||
|
});
|
||||||
|
success.push_back(json11::Json::object {
|
||||||
|
{ "request_put", json11::Json::object {
|
||||||
|
{ "key", base64_encode(parent->cli->st_cli.etcd_prefix+"/index/image/"+image_name+"@"+new_snap) },
|
||||||
|
{ "value", base64_encode(json11::Json(json11::Json::object{
|
||||||
|
{ "id", old_id },
|
||||||
|
{ "pool_id", (uint64_t)old_pool_id },
|
||||||
|
}).dump()) },
|
||||||
|
} },
|
||||||
|
});
|
||||||
|
};
|
||||||
|
etcd_txn(json11::Json::object {
|
||||||
|
{ "compare", checks },
|
||||||
|
{ "success", success },
|
||||||
|
{ "failure", failure },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void etcd_txn(json11::Json txn)
|
||||||
|
{
|
||||||
|
parent->waiting++;
|
||||||
|
parent->cli->st_cli.etcd_txn(txn, ETCD_SLOW_TIMEOUT, [this](std::string err, json11::Json res)
|
||||||
|
{
|
||||||
|
parent->waiting--;
|
||||||
|
if (err != "")
|
||||||
|
{
|
||||||
|
fprintf(stderr, "Error reading from etcd: %s\n", err.c_str());
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
this->result = res;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -37,6 +436,65 @@ std::function<bool(void)> cli_tool_t::start_create(json11::Json cfg)
|
||||||
json11::Json::array cmd = cfg["command"].array_items();
|
json11::Json::array cmd = cfg["command"].array_items();
|
||||||
auto image_creator = new image_creator_t();
|
auto image_creator = new image_creator_t();
|
||||||
image_creator->parent = this;
|
image_creator->parent = this;
|
||||||
|
image_creator->image_name = cmd.size() > 1 ? cmd[1].string_value() : "";
|
||||||
|
image_creator->new_pool_id = cfg["pool"].uint64_value();
|
||||||
|
image_creator->new_pool_name = cfg["pool"].string_value();
|
||||||
|
if (cfg["snapshot"].string_value() != "")
|
||||||
|
{
|
||||||
|
image_creator->new_snap = cfg["snapshot"].string_value();
|
||||||
|
}
|
||||||
|
else if (cmd[0] == "snap-create")
|
||||||
|
{
|
||||||
|
int p = image_creator->image_name.find('@');
|
||||||
|
if (p == std::string::npos || p == image_creator->image_name.length()-1)
|
||||||
|
{
|
||||||
|
fprintf(stderr, "Please specify new snapshot name after @\n");
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
image_creator->new_snap = image_creator->image_name.substr(p + 1);
|
||||||
|
image_creator->image_name = image_creator->image_name.substr(0, p);
|
||||||
|
}
|
||||||
|
image_creator->new_parent = cfg["parent"].string_value();
|
||||||
|
if (cfg["size"].string_value() != "")
|
||||||
|
{
|
||||||
|
std::string size_str = cfg["size"].string_value();
|
||||||
|
uint64_t mul = 1;
|
||||||
|
char type_char = tolower(size_str[size_str.length()-1]);
|
||||||
|
if (type_char == 'k' || type_char == 'm' || type_char == 'g' || type_char == 't')
|
||||||
|
{
|
||||||
|
if (type_char == 'k')
|
||||||
|
mul = 1l<<10;
|
||||||
|
else if (type_char == 'm')
|
||||||
|
mul = 1l<<20;
|
||||||
|
else if (type_char == 'g')
|
||||||
|
mul = 1l<<30;
|
||||||
|
else /*if (type_char == 't')*/
|
||||||
|
mul = 1l<<40;
|
||||||
|
size_str = size_str.substr(0, size_str.length()-1);
|
||||||
|
}
|
||||||
|
uint64_t size = json11::Json(size_str).uint64_value() * mul;
|
||||||
|
if (size == 0)
|
||||||
|
{
|
||||||
|
fprintf(stderr, "Invalid syntax for size: %s\n", cfg["size"].string_value().c_str());
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
image_creator->size = size;
|
||||||
|
if (image_creator->new_snap != "")
|
||||||
|
{
|
||||||
|
fprintf(stderr, "--size can't be specified for snapshots\n");
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (image_creator->image_name == "")
|
||||||
|
{
|
||||||
|
fprintf(stderr, "Image name is missing\n");
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
if (image_creator->image_name.find('@') != std::string::npos)
|
||||||
|
{
|
||||||
|
fprintf(stderr, "Image name can't contain @ character\n");
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
return [image_creator]()
|
return [image_creator]()
|
||||||
{
|
{
|
||||||
image_creator->loop();
|
image_creator->loop();
|
||||||
|
|
|
@ -16,8 +16,11 @@ struct image_lister_t
|
||||||
{
|
{
|
||||||
cli_tool_t *parent;
|
cli_tool_t *parent;
|
||||||
|
|
||||||
int state = 0;
|
pool_id_t list_pool_id = 0;
|
||||||
|
std::string list_pool_name;
|
||||||
bool detailed = false;
|
bool detailed = false;
|
||||||
|
|
||||||
|
int state = 0;
|
||||||
std::map<inode_t, json11::Json::object> stats;
|
std::map<inode_t, json11::Json::object> stats;
|
||||||
json11::Json space_info;
|
json11::Json space_info;
|
||||||
|
|
||||||
|
@ -28,13 +31,36 @@ struct image_lister_t
|
||||||
|
|
||||||
void get_list()
|
void get_list()
|
||||||
{
|
{
|
||||||
|
if (list_pool_name != "")
|
||||||
|
{
|
||||||
|
for (auto & ic: parent->cli->st_cli.pool_config)
|
||||||
|
{
|
||||||
|
if (ic.second.name == list_pool_name)
|
||||||
|
{
|
||||||
|
list_pool_id = ic.first;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!list_pool_id)
|
||||||
|
{
|
||||||
|
fprintf(stderr, "Pool %s does not exist\n", list_pool_name.c_str());
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
for (auto & ic: parent->cli->st_cli.inode_config)
|
for (auto & ic: parent->cli->st_cli.inode_config)
|
||||||
{
|
{
|
||||||
|
if (list_pool_id && INODE_POOL(ic.second.num) != list_pool_id)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
auto & pool_cfg = parent->cli->st_cli.pool_config.at(INODE_POOL(ic.second.num));
|
||||||
auto item = json11::Json::object {
|
auto item = json11::Json::object {
|
||||||
{ "name", ic.second.name },
|
{ "name", ic.second.name },
|
||||||
{ "size", ic.second.size },
|
{ "size", ic.second.size },
|
||||||
|
{ "used_size", 0 },
|
||||||
{ "readonly", ic.second.readonly },
|
{ "readonly", ic.second.readonly },
|
||||||
{ "pool_id", (uint64_t)INODE_POOL(ic.second.num) },
|
{ "pool_id", (uint64_t)INODE_POOL(ic.second.num) },
|
||||||
|
{ "pool_name", pool_cfg.name },
|
||||||
{ "inode_num", INODE_NO_POOL(ic.second.num) },
|
{ "inode_num", INODE_NO_POOL(ic.second.num) },
|
||||||
};
|
};
|
||||||
if (ic.second.parent_id)
|
if (ic.second.parent_id)
|
||||||
|
@ -47,6 +73,7 @@ struct image_lister_t
|
||||||
}
|
}
|
||||||
if (!parent->json_output)
|
if (!parent->json_output)
|
||||||
{
|
{
|
||||||
|
item["used_size_fmt"] = format_size(0);
|
||||||
item["size_fmt"] = format_size(ic.second.size);
|
item["size_fmt"] = format_size(ic.second.size);
|
||||||
item["ro"] = ic.second.readonly ? "RO" : "-";
|
item["ro"] = ic.second.readonly ? "RO" : "-";
|
||||||
}
|
}
|
||||||
|
@ -70,20 +97,24 @@ struct image_lister_t
|
||||||
json11::Json::object {
|
json11::Json::object {
|
||||||
{ "request_range", json11::Json::object {
|
{ "request_range", json11::Json::object {
|
||||||
{ "key", base64_encode(
|
{ "key", base64_encode(
|
||||||
parent->cli->st_cli.etcd_prefix+"/pool/stats/"
|
parent->cli->st_cli.etcd_prefix+"/pool/stats"+
|
||||||
|
(list_pool_id ? "/"+std::to_string(list_pool_id) : "")+"/"
|
||||||
) },
|
) },
|
||||||
{ "range_end", base64_encode(
|
{ "range_end", base64_encode(
|
||||||
parent->cli->st_cli.etcd_prefix+"/pool/stats0"
|
parent->cli->st_cli.etcd_prefix+"/pool/stats"+
|
||||||
|
(list_pool_id ? "/"+std::to_string(list_pool_id) : "")+"0"
|
||||||
) },
|
) },
|
||||||
} },
|
} },
|
||||||
},
|
},
|
||||||
json11::Json::object {
|
json11::Json::object {
|
||||||
{ "request_range", json11::Json::object {
|
{ "request_range", json11::Json::object {
|
||||||
{ "key", base64_encode(
|
{ "key", base64_encode(
|
||||||
parent->cli->st_cli.etcd_prefix+"/inode/stats/"
|
parent->cli->st_cli.etcd_prefix+"/inode/stats"+
|
||||||
|
(list_pool_id ? "/"+std::to_string(list_pool_id) : "")+"/"
|
||||||
) },
|
) },
|
||||||
{ "range_end", base64_encode(
|
{ "range_end", base64_encode(
|
||||||
parent->cli->st_cli.etcd_prefix+"/inode/stats0"
|
parent->cli->st_cli.etcd_prefix+"/inode/stats"+
|
||||||
|
(list_pool_id ? "/"+std::to_string(list_pool_id) : "")+"0"
|
||||||
) },
|
) },
|
||||||
} },
|
} },
|
||||||
},
|
},
|
||||||
|
@ -133,10 +164,15 @@ resume_1:
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
inode_t inode_num = INODE_WITH_POOL(pool_id, inode_num);
|
inode_t inode_num = INODE_WITH_POOL(pool_id, inode_num);
|
||||||
|
uint64_t used_size = kv.value["raw_used"].uint64_value();
|
||||||
// save stats
|
// save stats
|
||||||
auto & pool_cfg = parent->cli->st_cli.pool_config.at(pool_id);
|
auto pool_it = parent->cli->st_cli.pool_config.find(pool_id);
|
||||||
uint64_t used_size = kv.value["raw_used"].uint64_value() / pool_pg_real_size[pool_id]
|
if (pool_it == parent->cli->st_cli.pool_config.end())
|
||||||
* (pool_cfg.scheme == POOL_SCHEME_REPLICATED ? 1 : pool_cfg.pg_size-pool_cfg.parity_chunks);
|
{
|
||||||
|
auto & pool_cfg = pool_it->second;
|
||||||
|
used_size = used_size / pool_pg_real_size[pool_id]
|
||||||
|
* (pool_cfg.scheme == POOL_SCHEME_REPLICATED ? 1 : pool_cfg.pg_size-pool_cfg.parity_chunks);
|
||||||
|
}
|
||||||
auto stat_it = stats.find(inode_num);
|
auto stat_it = stats.find(inode_num);
|
||||||
if (stat_it == stats.end())
|
if (stat_it == stats.end())
|
||||||
{
|
{
|
||||||
|
@ -145,6 +181,8 @@ resume_1:
|
||||||
{ "size", 0 },
|
{ "size", 0 },
|
||||||
{ "readonly", false },
|
{ "readonly", false },
|
||||||
{ "pool_id", (uint64_t)INODE_POOL(inode_num) },
|
{ "pool_id", (uint64_t)INODE_POOL(inode_num) },
|
||||||
|
{ "pool_name", pool_it == parent->cli->st_cli.pool_config.end()
|
||||||
|
? (pool_it->second.name == "" ? "<Unnamed>" : pool_it->second.name) : "?" },
|
||||||
{ "inode_num", INODE_NO_POOL(inode_num) },
|
{ "inode_num", INODE_NO_POOL(inode_num) },
|
||||||
};
|
};
|
||||||
if (!parent->json_output)
|
if (!parent->json_output)
|
||||||
|
@ -174,17 +212,24 @@ resume_1:
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// Table output: name, size_fmt, [used_size_fmt], ro, parent_name
|
// Table output: name, size_fmt, [used_size_fmt], ro, parent_name
|
||||||
json11::Json::array cols = json11::Json::array{
|
json11::Json::array cols;
|
||||||
json11::Json::object{
|
cols.push_back(json11::Json::object{
|
||||||
{ "key", "name" },
|
{ "key", "name" },
|
||||||
{ "title", "NAME" },
|
{ "title", "NAME" },
|
||||||
},
|
});
|
||||||
json11::Json::object{
|
if (!list_pool_id && parent->cli->st_cli.pool_config.size() > 1)
|
||||||
{ "key", "size_fmt" },
|
{
|
||||||
{ "title", "SIZE" },
|
cols.push_back(json11::Json::object{
|
||||||
|
{ "key", "used_size_fmt" },
|
||||||
|
{ "title", "USED" },
|
||||||
{ "right", true },
|
{ "right", true },
|
||||||
},
|
});
|
||||||
};
|
}
|
||||||
|
cols.push_back(json11::Json::object{
|
||||||
|
{ "key", "size_fmt" },
|
||||||
|
{ "title", "SIZE" },
|
||||||
|
{ "right", true },
|
||||||
|
});
|
||||||
if (detailed)
|
if (detailed)
|
||||||
{
|
{
|
||||||
cols.push_back(json11::Json::object{
|
cols.push_back(json11::Json::object{
|
||||||
|
@ -306,6 +351,8 @@ std::function<bool(void)> cli_tool_t::start_ls(json11::Json cfg)
|
||||||
json11::Json::array cmd = cfg["command"].array_items();
|
json11::Json::array cmd = cfg["command"].array_items();
|
||||||
auto lister = new image_lister_t();
|
auto lister = new image_lister_t();
|
||||||
lister->parent = this;
|
lister->parent = this;
|
||||||
|
lister->list_pool_id = cfg["pool"].uint64_value();
|
||||||
|
lister->list_pool_name = lister->list_pool_id ? "" : cfg["pool"].string_value();
|
||||||
lister->detailed = cfg["long"].bool_value();
|
lister->detailed = cfg["long"].bool_value();
|
||||||
return [lister]()
|
return [lister]()
|
||||||
{
|
{
|
||||||
|
|
Loading…
Reference in New Issue