From b3e4d43c15169bfc6cc4918e8ce3d9f99dadcad8 Mon Sep 17 00:00:00 2001 From: Mykola Golub Date: Thu, 4 Oct 2018 18:09:16 +0300 Subject: [PATCH] rbd: add 'config global' command to get/store overrides in mon config db Signed-off-by: Mykola Golub --- doc/man/8/rbd.rst | 14 +- qa/workunits/rbd/cli_generic.sh | 21 ++ src/test/cli/rbd/help.t | 46 +++++ src/tools/rbd/Utils.cc | 15 +- src/tools/rbd/Utils.h | 2 + src/tools/rbd/action/Config.cc | 339 +++++++++++++++++++++++++++++++- 6 files changed, 431 insertions(+), 6 deletions(-) diff --git a/doc/man/8/rbd.rst b/doc/man/8/rbd.rst index 1895ff99cd79c..e1a1e72864069 100644 --- a/doc/man/8/rbd.rst +++ b/doc/man/8/rbd.rst @@ -197,10 +197,22 @@ Commands The parent snapshot must be protected (see `rbd snap protect`). This requires image format 2. +:command:`config global get` *config-entity* *key* + Get a global-level configuration override. + +:command:`config global list` [--format plain | json | xml] [--pretty-format] *config-entity* + List global-level configuration overrides. + +:command:`config global set` *config-entity* *key* *value* + Set a global-level configuration override. + +:command:`config global remove` *config-entity* *key* + Remove a global-level configuration override. + :command:`config image get` *image-spec* *key* Get an image-level configuration override. -:command:`config image list` [--format plain | json | xml] [--pretty-format] *image-spec* *key* +:command:`config image list` [--format plain | json | xml] [--pretty-format] *image-spec* List image-level configuration overrides. :command:`config image set` *image-spec* *key* *value* diff --git a/qa/workunits/rbd/cli_generic.sh b/qa/workunits/rbd/cli_generic.sh index 3f22893648087..0dd5936d7bd63 100755 --- a/qa/workunits/rbd/cli_generic.sh +++ b/qa/workunits/rbd/cli_generic.sh @@ -772,6 +772,27 @@ test_config() { echo "testing config..." remove_images + expect_fail rbd config global set osd rbd_cache true + expect_fail rbd config global set global debug_ms 10 + expect_fail rbd config global set global rbd_UNKNOWN false + expect_fail rbd config global set global rbd_cache INVALID + rbd config global set global rbd_cache false + rbd config global set client rbd_cache true + rbd config global set client.123 rbd_cache false + rbd config global get global rbd_cache | grep '^false$' + rbd config global get client rbd_cache | grep '^true$' + rbd config global get client.123 rbd_cache | grep '^false$' + expect_fail rbd config global get client.UNKNOWN rbd_cache + rbd config global list global | grep '^rbd_cache * false * global *$' + rbd config global list client | grep '^rbd_cache * true * client *$' + rbd config global list client.123 | grep '^rbd_cache * false * client.123 *$' + rbd config global list client.UNKNOWN | grep '^rbd_cache * true * client *$' + rbd config global rm client rbd_cache + expect_fail rbd config global get client rbd_cache + rbd config global list client | grep '^rbd_cache * false * global *$' + rbd config global rm client.123 rbd_cache + rbd config global rm global rbd_cache + rbd config pool set rbd rbd_cache true rbd config pool list rbd | grep '^rbd_cache * true * pool *$' rbd config pool get rbd rbd_cache | grep '^true$' diff --git a/src/test/cli/rbd/help.t b/src/test/cli/rbd/help.t index c4a0b37f3c6bb..1b9e4d0fee13f 100644 --- a/src/test/cli/rbd/help.t +++ b/src/test/cli/rbd/help.t @@ -8,6 +8,11 @@ bench Simple benchmark. children Display children of snapshot. clone Clone a snapshot into a CoW child image. + config global get Get a global-level configuration override. + config global list (... ls) List global-level configuration overrides. + config global remove (... rm) Remove a global-level configuration + override. + config global set Set a global-level configuration override. config image get Get an image-level configuration override. config image list (... ls) List image-level configuration overrides. config image remove (... rm) Remove an image-level configuration @@ -234,6 +239,47 @@ (-) supports disabling-only on existing images (+) enabled by default for new images if features not specified + rbd help config global get + usage: rbd config global get + + Get a global-level configuration override. + + Positional arguments + config entity (global, client, client.) + config key + + rbd help config global list + usage: rbd config global list [--format ] [--pretty-format] + + + List global-level configuration overrides. + + Positional arguments + config entity (global, client, client.) + + Optional arguments + --format arg output format (plain, json, or xml) [default: plain] + --pretty-format pretty formatting (json and xml) + + rbd help config global remove + usage: rbd config global remove + + Remove a global-level configuration override. + + Positional arguments + config entity (global, client, client.) + config key + + rbd help config global set + usage: rbd config global set + + Set a global-level configuration override. + + Positional arguments + config entity (global, client, client.) + config key + config value + rbd help config image get usage: rbd config image get [--pool ] [--namespace ] [--image ] diff --git a/src/tools/rbd/Utils.cc b/src/tools/rbd/Utils.cc index 1a4acbcb03fa9..0170be5bb8702 100644 --- a/src/tools/rbd/Utils.cc +++ b/src/tools/rbd/Utils.cc @@ -626,8 +626,7 @@ void init_context() { common_init_finish(g_ceph_context); } -int init(const std::string &pool_name, const std::string& namespace_name, - librados::Rados *rados, librados::IoCtx *io_ctx) { +int init_rados(librados::Rados *rados) { init_context(); int r = rados->init_with_context(g_ceph_context); @@ -642,6 +641,18 @@ int init(const std::string &pool_name, const std::string& namespace_name, return r; } + return 0; +} + +int init(const std::string &pool_name, const std::string& namespace_name, + librados::Rados *rados, librados::IoCtx *io_ctx) { + init_context(); + + int r = init_rados(rados); + if (r < 0) { + return r; + } + r = init_io_ctx(*rados, pool_name, namespace_name, io_ctx); if (r < 0) { return r; diff --git a/src/tools/rbd/Utils.h b/src/tools/rbd/Utils.h index 4cf554fc42d1e..8f78d24361465 100644 --- a/src/tools/rbd/Utils.h +++ b/src/tools/rbd/Utils.h @@ -151,6 +151,8 @@ int get_formatter(const boost::program_options::variables_map &vm, void init_context(); +int init_rados(librados::Rados *rados); + int init(const std::string &pool_name, const std::string& namespace_name, librados::Rados *rados, librados::IoCtx *io_ctx); int init_io_ctx(librados::Rados &rados, const std::string &pool_name, diff --git a/src/tools/rbd/action/Config.cc b/src/tools/rbd/action/Config.cc index ae1593a03117f..0f1932d3bfc91 100644 --- a/src/tools/rbd/action/Config.cc +++ b/src/tools/rbd/action/Config.cc @@ -3,14 +3,21 @@ #include "common/Formatter.h" #include "common/TextTable.h" -#include "common/config_proxy.h" +#include "common/ceph_context.h" +#include "common/ceph_json.h" +#include "common/escape.h" #include "common/errno.h" +#include "common/options.h" +#include "global/global_context.h" +#include "include/stringify.h" #include "tools/rbd/ArgumentTypes.h" #include "tools/rbd/Shell.h" #include "tools/rbd/Utils.h" #include + +#include #include namespace rbd { @@ -25,6 +32,12 @@ namespace { const std::string METADATA_CONF_PREFIX = "conf_"; const uint32_t MAX_KEYS = 64; +void add_config_entity_option( + boost::program_options::options_description *positional) { + positional->add_options() + ("config-entity", "config entity (global, client, client.)"); +} + void add_pool_option(boost::program_options::options_description *positional) { positional->add_options() ("pool-name", "pool name"); @@ -35,6 +48,19 @@ void add_key_option(po::options_description *positional) { ("key", "config key"); } +int get_config_entity(const po::variables_map &vm, std::string *config_entity) { + *config_entity = utils::get_positional_argument(vm, 0); + + if (*config_entity != "global" && *config_entity != "client" && + !boost::starts_with(*config_entity, ("client."))) { + std::cerr << "rbd: invalid config entity: " << *config_entity + << " (must be global, client or client.)" << std::endl; + return -EINVAL; + } + + return 0; +} + int get_pool(const po::variables_map &vm, std::string *pool_name) { *pool_name = utils::get_positional_argument(vm, 0); if (pool_name->empty()) { @@ -51,6 +77,19 @@ int get_key(const po::variables_map &vm, std::string *key) { std::cerr << "rbd: config key was not specified" << std::endl; return -EINVAL; } + + if (!boost::starts_with(*key, "rbd_")) { + std::cerr << "rbd: not rbd option: " << *key << std::endl; + return -EINVAL; + } + + std::string value; + int r = g_ceph_context->_conf.get_val(key->c_str(), &value); + if (r < 0) { + std::cerr << "rbd: invalid config key: " << *key << std::endl; + return -EINVAL; + } + return 0; } @@ -73,8 +112,285 @@ std::ostream& operator<<(std::ostream& os, return os; } +int config_global_list( + librados::Rados &rados, const std::string &config_entity, + std::map> *options) { + bool client_id_config_entity = + boost::starts_with(config_entity, ("client.")); + std::string cmd = + "{" + "\"prefix\": \"config dump\", " + "\"format\": \"json\" " + "}"; + bufferlist in_bl; + bufferlist out_bl; + std::string ss; + int r = rados.mon_command(cmd, in_bl, &out_bl, &ss); + if (r < 0) { + std::cerr << "rbd: error reading config: " << ss << std::endl; + return r; + } + + json_spirit::mValue json_root; + if (!json_spirit::read(out_bl.to_str(), json_root)) { + std::cerr << "rbd: error parsing config dump" << std::endl; + return -EINVAL; + } + + try { + auto &json_array = json_root.get_array(); + for (auto& e : json_array) { + auto &json_obj = e.get_obj(); + std::string section; + std::string name; + std::string value; + + for (auto &pairs : json_obj) { + if (pairs.first == "section") { + section = pairs.second.get_str(); + } else if (pairs.first == "name") { + name = pairs.second.get_str(); + } else if (pairs.first == "value") { + value = pairs.second.get_str(); + } + } + + if (!boost::starts_with(name, "rbd_")) { + continue; + } + if (section != "global" && section != "client" && + (!client_id_config_entity || section != config_entity)) { + continue; + } + if (config_entity == "global" && section != "global") { + continue; + } + auto it = options->find(name); + if (it == options->end()) { + (*options)[name] = {value, section}; + continue; + } + if (section == "client") { + if (it->second.second == "global") { + it->second = {value, section}; + } + } else if (client_id_config_entity) { + it->second = {value, section}; + } + } + } catch (std::runtime_error &e) { + std::cerr << "rbd: error parsing config dump: " << e.what() << std::endl; + return -EINVAL; + } + + return 0; +} + } // anonymous namespace +void get_global_get_arguments(po::options_description *positional, + po::options_description *options) { + add_config_entity_option(positional); + add_key_option(positional); +} + +int execute_global_get(const po::variables_map &vm, + const std::vector &ceph_global_init_args) { + std::string config_entity; + int r = get_config_entity(vm, &config_entity); + if (r < 0) { + return r; + } + + std::string key; + r = get_key(vm, &key); + if (r < 0) { + return r; + } + + librados::Rados rados; + r = utils::init_rados(&rados); + if (r < 0) { + return r; + } + + std::map> options; + r = config_global_list(rados, config_entity, &options); + if (r < 0) { + return r; + } + + auto it = options.find(key); + + if (it == options.end() || it->second.second != config_entity) { + std::cerr << "rbd: " << key << " is not set" << std::endl; + return -ENOENT; + } + + std::cout << it->second.first << std::endl; + return 0; +} + +void get_global_set_arguments(po::options_description *positional, + po::options_description *options) { + add_config_entity_option(positional); + add_key_option(positional); + positional->add_options() + ("value", "config value"); +} + +int execute_global_set(const po::variables_map &vm, + const std::vector &ceph_global_init_args) { + std::string config_entity; + int r = get_config_entity(vm, &config_entity); + if (r < 0) { + return r; + } + + std::string key; + r = get_key(vm, &key); + if (r < 0) { + return r; + } + + librados::Rados rados; + r = utils::init_rados(&rados); + if (r < 0) { + return r; + } + + std::string value = utils::get_positional_argument(vm, 2); + std::string cmd = + "{" + "\"prefix\": \"config set\", " + "\"who\": \"" + stringify(json_stream_escaper(config_entity)) + "\", " + "\"name\": \"" + key + "\", " + "\"value\": \"" + stringify(json_stream_escaper(value)) + "\"" + "}"; + bufferlist in_bl; + std::string ss; + r = rados.mon_command(cmd, in_bl, nullptr, &ss); + if (r < 0) { + std::cerr << "rbd: error setting " << key << ": " << ss << std::endl; + return r; + } + + return 0; +} + +void get_global_remove_arguments(po::options_description *positional, + po::options_description *options) { + add_config_entity_option(positional); + add_key_option(positional); +} + +int execute_global_remove( + const po::variables_map &vm, + const std::vector &ceph_global_init_args) { + std::string config_entity; + int r = get_config_entity(vm, &config_entity); + if (r < 0) { + return r; + } + + std::string key; + r = get_key(vm, &key); + if (r < 0) { + return r; + } + + librados::Rados rados; + r = utils::init_rados(&rados); + if (r < 0) { + return r; + } + + std::string cmd = + "{" + "\"prefix\": \"config rm\", " + "\"who\": \"" + stringify(json_stream_escaper(config_entity)) + "\", " + "\"name\": \"" + key + "\"" + "}"; + bufferlist in_bl; + std::string ss; + r = rados.mon_command(cmd, in_bl, nullptr, &ss); + if (r < 0) { + std::cerr << "rbd: error removing " << key << ": " << ss << std::endl; + return r; + } + + return 0; +} + +void get_global_list_arguments(po::options_description *positional, + po::options_description *options) { + add_config_entity_option(positional); + at::add_format_options(options); +} + +int execute_global_list(const po::variables_map &vm, + const std::vector &ceph_global_init_args) { + std::string config_entity; + int r = get_config_entity(vm, &config_entity); + if (r < 0) { + return r; + } + + at::Format::Formatter f; + r = utils::get_formatter(vm, &f); + if (r < 0) { + return r; + } + + librados::Rados rados; + r = utils::init_rados(&rados); + if (r < 0) { + return r; + } + + std::map> options; + r = config_global_list(rados, config_entity, &options); + if (r < 0) { + return r; + } + + if (options.empty() && !f) { + return 0; + } + + TextTable tbl; + + if (f) { + f->open_array_section("config"); + } else { + tbl.define_column("Name", TextTable::LEFT, TextTable::LEFT); + tbl.define_column("Value", TextTable::LEFT, TextTable::LEFT); + tbl.define_column("Section", TextTable::LEFT, TextTable::LEFT); + } + + for (const auto &it : options) { + if (f) { + f->open_object_section("option"); + f->dump_string("name", it.first); + f->dump_string("value", it.second.first); + f->dump_string("section", it.second.second); + f->close_section(); + } else { + tbl << it.first << it.second.first << it.second.second + << TextTable::endrow; + } + } + + if (f) { + f->close_section(); + f->flush(std::cout); + } else { + std::cout << tbl; + } + + return 0; +} + void get_pool_get_arguments(po::options_description *positional, po::options_description *options) { add_pool_option(positional); @@ -263,7 +579,7 @@ int execute_pool_list(const po::variables_map &vm, f->close_section(); f->flush(std::cout); } else { - std::cout << std::endl << tbl; + std::cout << tbl; } return 0; @@ -503,12 +819,29 @@ int execute_image_list(const po::variables_map &vm, f->close_section(); f->flush(std::cout); } else { - std::cout << std::endl << tbl; + std::cout << tbl; } return 0; } +Shell::Action action_global_get( + {"config", "global", "get"}, {}, + "Get a global-level configuration override.", "", + &get_global_get_arguments, &execute_global_get); +Shell::Action action_global_set( + {"config", "global", "set"}, {}, + "Set a global-level configuration override.", "", + &get_global_set_arguments, &execute_global_set); +Shell::Action action_global_remove( + {"config", "global", "remove"}, {"config", "global", "rm"}, + "Remove a global-level configuration override.", "", + &get_global_remove_arguments, &execute_global_remove); +Shell::Action action_global_list( + {"config", "global", "list"}, {"config", "global", "ls"}, + "List global-level configuration overrides.", "", + &get_global_list_arguments, &execute_global_list); + Shell::Action action_pool_get( {"config", "pool", "get"}, {}, "Get a pool-level configuration override.", "", &get_pool_get_arguments, &execute_pool_get); -- 2.39.5