]> git.apps.os.sepia.ceph.com Git - ceph.git/commitdiff
rbd: add 'config global' command to get/store overrides in mon config db 24428/head
authorMykola Golub <mgolub@suse.com>
Thu, 4 Oct 2018 15:09:16 +0000 (18:09 +0300)
committerMykola Golub <mgolub@suse.com>
Wed, 10 Oct 2018 14:31:32 +0000 (17:31 +0300)
Signed-off-by: Mykola Golub <mgolub@suse.com>
doc/man/8/rbd.rst
qa/workunits/rbd/cli_generic.sh
src/test/cli/rbd/help.t
src/tools/rbd/Utils.cc
src/tools/rbd/Utils.h
src/tools/rbd/action/Config.cc

index 1895ff99cd79c6666fc502ffcbe2a24bba7af967..e1a1e72864069ed20d38d063a3a8d54d2868d63d 100644 (file)
@@ -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*
index 3f22893648087e1e7225380fc1b11c501b5d644c..0dd5936d7bd6344768723d7fdbbb61122f658b81 100755 (executable)
@@ -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$'
index c4a0b37f3c6bba32679e1ff88b2d1b428775661c..1b9e4d0fee13fc5ceedcc3f66175a649e8197357 100644 (file)
@@ -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
     (-) 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 <config-entity> <key> 
+  
+  Get a global-level configuration override.
+  
+  Positional arguments
+    <config-entity>      config entity (global, client, client.<id>)
+    <key>                config key
+  
+  rbd help config global list
+  usage: rbd config global list [--format <format>] [--pretty-format] 
+                                <config-entity> 
+  
+  List global-level configuration overrides.
+  
+  Positional arguments
+    <config-entity>      config entity (global, client, client.<id>)
+  
+  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 <config-entity> <key> 
+  
+  Remove a global-level configuration override.
+  
+  Positional arguments
+    <config-entity>      config entity (global, client, client.<id>)
+    <key>                config key
+  
+  rbd help config global set
+  usage: rbd config global set <config-entity> <key> <value> 
+  
+  Set a global-level configuration override.
+  
+  Positional arguments
+    <config-entity>      config entity (global, client, client.<id>)
+    <key>                config key
+    <value>              config value
+  
   rbd help config image get
   usage: rbd config image get [--pool <pool>] [--namespace <namespace>] 
                               [--image <image>] 
index 1a4acbcb03fa998b5a6c274192c7e7b8931e50a4..0170be5bb87027da32ae0f3faa9df82cc7768485 100644 (file)
@@ -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;
index 4cf554fc42d1e47a6bcaed3f3f4e655428088402..8f78d243614652334f820eee24405fb62e1f1b11 100644 (file)
@@ -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,
index ae1593a03117f83d4ebfb14aca75c5f980544bf5..0f1932d3bfc91b05891be6df37ca8958496b7eb1 100644 (file)
@@ -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 <iostream>
+
+#include <boost/algorithm/string/predicate.hpp>
 #include <boost/program_options.hpp>
 
 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.<id>)");
+}
+
 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.<id>)" << 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<std::string, std::pair<std::string, std::string>> *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<std::string> &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<std::string, std::pair<std::string, std::string>> 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<std::string> &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<std::string> &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<std::string> &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<std::string, std::pair<std::string, std::string>> 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);