]> git.apps.os.sepia.ceph.com Git - ceph.git/commitdiff
librbd: API for listing pool and image level config overrides
authorMykola Golub <mgolub@suse.com>
Tue, 4 Sep 2018 10:18:48 +0000 (13:18 +0300)
committerMykola Golub <mgolub@suse.com>
Thu, 20 Sep 2018 07:35:18 +0000 (10:35 +0300)
Signed-off-by: Mykola Golub <mgolub@suse.com>
src/include/rbd/librbd.h
src/include/rbd/librbd.hpp
src/librbd/CMakeLists.txt
src/librbd/api/Config.cc [new file with mode: 0644]
src/librbd/api/Config.h [new file with mode: 0644]
src/librbd/librbd.cc
src/pybind/rbd/rbd.pyx
src/test/librbd/test_librbd.cc
src/test/pybind/test_rbd.py

index 35a81ff3998690a0c6e1cbb77f59c5473e317c92..4c381614533c176ba6d5fcb3a27d8ee2851a2faa 100644 (file)
@@ -250,6 +250,18 @@ typedef struct {
   char *state_description;
 } rbd_image_migration_status_t;
 
+typedef enum {
+  RBD_CONFIG_SOURCE_CONFIG = 0,
+  RBD_CONFIG_SOURCE_POOL = 1,
+  RBD_CONFIG_SOURCE_IMAGE = 2,
+} rbd_config_source_t;
+
+typedef struct {
+  char *name;
+  char *value;
+  rbd_config_source_t source;
+} rbd_config_option_t;
+
 CEPH_RBD_API void rbd_image_options_create(rbd_image_options_t* opts);
 CEPH_RBD_API void rbd_image_options_destroy(rbd_image_options_t opts);
 CEPH_RBD_API int rbd_image_options_set_string(rbd_image_options_t opts,
@@ -403,6 +415,12 @@ CEPH_RBD_API int rbd_pool_metadata_list(rados_ioctx_t io_ctx, const char *start,
                                         size_t *key_len, char *values,
                                         size_t *vals_len);
 
+CEPH_RBD_API int rbd_config_pool_list(rados_ioctx_t io_ctx,
+                                      rbd_config_option_t *options,
+                                      int *max_options);
+CEPH_RBD_API void rbd_config_pool_list_cleanup(rbd_config_option_t *options,
+                                               int max_options);
+
 CEPH_RBD_API int rbd_open(rados_ioctx_t io, const char *name,
                           rbd_image_t *image, const char *snap_name);
 CEPH_RBD_API int rbd_open_by_id(rados_ioctx_t io, const char *id,
@@ -1011,6 +1029,12 @@ CEPH_RBD_API int rbd_watchers_list(rbd_image_t image,
 CEPH_RBD_API void rbd_watchers_list_cleanup(rbd_image_watcher_t *watchers,
                                            size_t num_watchers);
 
+CEPH_RBD_API int rbd_config_image_list(rbd_image_t image,
+                                       rbd_config_option_t *options,
+                                       int *max_options);
+CEPH_RBD_API void rbd_config_image_list_cleanup(rbd_config_option_t *options,
+                                                int max_options);
+
 CEPH_RBD_API int rbd_group_image_add(rados_ioctx_t group_p,
                                      const char *group_name,
                                      rados_ioctx_t image_p,
index 980a331fa27120834108f73861c8bb7b1f78fc55..1447484a7e72aacf4020fb2befbbb8af8bce05a4 100644 (file)
@@ -141,6 +141,14 @@ namespace librbd {
     std::string state_description;
   } image_migration_status_t;
 
+  typedef rbd_config_source_t config_source_t;
+
+  typedef struct {
+    std::string name;
+    std::string value;
+    config_source_t source;
+  } config_option_t;
+
 class CEPH_RBD_API RBD
 {
 public:
@@ -286,6 +294,8 @@ public:
   int pool_metadata_list(IoCtx &io_ctx, const std::string &start, uint64_t max,
                          std::map<std::string, ceph::bufferlist> *pairs);
 
+  int config_list(IoCtx& io_ctx, std::vector<config_option_t> *options);
+
 private:
   /* We don't allow assignment or copying */
   RBD(const RBD& rhs);
@@ -570,6 +580,8 @@ public:
 
   int list_watchers(std::list<image_watcher_t> &watchers);
 
+  int config_list(std::vector<config_option_t> *options);
+
 private:
   friend class RBD;
 
index af423df862284693cffe7dffefd7a2f450e506af..b6f38f219cfacd40eb7fc752bc51bc9b477f3b6a 100644 (file)
@@ -24,6 +24,7 @@ set(librbd_internal_srcs
   TrashWatcher.cc
   Utils.cc
   Watcher.cc
+  api/Config.cc
   api/DiffIterate.cc
   api/Group.cc
   api/Image.cc
diff --git a/src/librbd/api/Config.cc b/src/librbd/api/Config.cc
new file mode 100644 (file)
index 0000000..f7d1cb4
--- /dev/null
@@ -0,0 +1,221 @@
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab
+
+#include "librbd/api/Config.h"
+#include "cls/rbd/cls_rbd_client.h"
+#include "common/dout.h"
+#include "common/errno.h"
+#include "librbd/ImageCtx.h"
+#include "librbd/Utils.h"
+#include "librbd/api/PoolMetadata.h"
+
+#define dout_subsys ceph_subsys_rbd
+#undef dout_prefix
+#define dout_prefix *_dout << "librbd::Config: " << __func__ << ": "
+
+namespace librbd {
+namespace api {
+
+namespace {
+
+const uint32_t MAX_KEYS = 64;
+
+typedef std::map<std::string, std::pair<std::string, config_source_t>> Parent;
+
+struct Options : Parent {
+  Options(bool image_apply_only_options) :
+    Parent{
+      {"rbd_atime_update_interval", {}},
+      {"rbd_balance_parent_reads", {}},
+      {"rbd_balance_snap_reads", {}},
+      {"rbd_blacklist_expire_seconds", {}},
+      {"rbd_blacklist_on_break_lock", {}},
+      {"rbd_cache", {}},
+      {"rbd_cache_block_writes_upfront", {}},
+      {"rbd_cache_max_dirty", {}},
+      {"rbd_cache_max_dirty_age", {}},
+      {"rbd_cache_max_dirty_object", {}},
+      {"rbd_cache_size", {}},
+      {"rbd_cache_target_dirty", {}},
+      {"rbd_cache_writethrough_until_flush", {}},
+      {"rbd_clone_copy_on_read", {}},
+      {"rbd_concurrent_management_ops", {}},
+      {"rbd_journal_commit_age", {}},
+      {"rbd_journal_max_concurrent_object_sets", {}},
+      {"rbd_journal_max_payload_bytes", {}},
+      {"rbd_journal_object_flush_age", {}},
+      {"rbd_journal_object_flush_bytes", {}},
+      {"rbd_journal_object_flush_interval", {}},
+      {"rbd_journal_order", {}},
+      {"rbd_journal_pool", {}},
+      {"rbd_journal_splay_width", {}},
+      {"rbd_localize_parent_reads", {}},
+      {"rbd_localize_snap_reads", {}},
+      {"rbd_mirroring_delete_delay", {}},
+      {"rbd_mirroring_replay_delay", {}},
+      {"rbd_mirroring_resync_after_disconnect", {}},
+      {"rbd_mtime_update_interval", {}},
+      {"rbd_non_blocking_aio", {}},
+      {"rbd_qos_bps_limit", {}},
+      {"rbd_qos_iops_limit", {}},
+      {"rbd_qos_read_bps_limit", {}},
+      {"rbd_qos_read_iops_limit", {}},
+      {"rbd_qos_write_bps_limit", {}},
+      {"rbd_qos_write_iops_limit", {}},
+      {"rbd_readahead_disable_after_bytes", {}},
+      {"rbd_readahead_max_bytes", {}},
+      {"rbd_readahead_trigger_requests", {}},
+      {"rbd_request_timed_out_seconds", {}},
+      {"rbd_skip_partial_discard", {}},
+      {"rbd_sparse_read_threshold_bytes", {}},
+    } {
+    if (!image_apply_only_options) {
+      Parent image_create_opts = {
+        {"rbd_default_data_pool", {}},
+        {"rbd_default_features", {}},
+        {"rbd_default_order", {}},
+        {"rbd_default_stripe_count", {}},
+        {"rbd_default_stripe_unit", {}},
+        {"rbd_journal_order", {}},
+        {"rbd_journal_pool", {}},
+        {"rbd_journal_splay_width", {}},
+      };
+      insert(image_create_opts.begin(), image_create_opts.end());
+    }
+  }
+
+  int init(librados::IoCtx& io_ctx) {
+    CephContext *cct = (CephContext *)io_ctx.cct();
+
+    for (auto &it : *this) {
+      int r = cct->_conf.get_val(it.first.c_str(), &it.second.first);
+      ceph_assert(r == 0);
+      it.second.second = RBD_CONFIG_SOURCE_CONFIG;
+    }
+
+    std::string last_key = ImageCtx::METADATA_CONF_PREFIX;
+    bool more_results = true;
+
+    while (more_results) {
+      std::map<std::string, bufferlist> pairs;
+
+      int r = librbd::api::PoolMetadata<>::list(io_ctx, last_key, MAX_KEYS,
+                                                &pairs);
+      if (r < 0) {
+        return r;
+      }
+
+      if (pairs.empty()) {
+        break;
+      }
+
+      more_results = (pairs.size() == MAX_KEYS);
+      last_key = pairs.rbegin()->first;
+
+      for (auto kv : pairs) {
+        std::string key;
+        if (!util::is_metadata_config_override(kv.first, &key)) {
+          more_results = false;
+          break;
+        }
+        auto it = find(key);
+        if (it != end()) {
+          it->second = {{kv.second.c_str(), kv.second.length()},
+                        RBD_CONFIG_SOURCE_POOL};
+        }
+      }
+    }
+    return 0;
+  }
+};
+
+} // anonymous namespace
+
+template <typename I>
+bool Config<I>::is_option_name(librados::IoCtx& io_ctx,
+                               const std::string &name) {
+  Options opts(false);
+
+  return (opts.find(name) != opts.end());
+}
+
+template <typename I>
+int Config<I>::list(librados::IoCtx& io_ctx,
+                    std::vector<config_option_t> *options) {
+  Options opts(false);
+
+  int r = opts.init(io_ctx);
+  if (r < 0) {
+    return r;
+  }
+
+  for (auto &it : opts) {
+    options->push_back({it.first, it.second.first, it.second.second});
+  }
+
+  return 0;
+}
+
+template <typename I>
+bool Config<I>::is_option_name(I *image_ctx, const std::string &name) {
+  Options opts(true);
+
+  return (opts.find(name) != opts.end());
+}
+
+template <typename I>
+int Config<I>::list(I *image_ctx, std::vector<config_option_t> *options) {
+  CephContext *cct = image_ctx->cct;
+  Options opts(true);
+
+  int r = opts.init(image_ctx->md_ctx);
+  if (r < 0) {
+    return r;
+  }
+
+  std::string last_key = ImageCtx::METADATA_CONF_PREFIX;
+  bool more_results = true;
+
+  while (more_results) {
+    std::map<std::string, bufferlist> pairs;
+
+    r = cls_client::metadata_list(&image_ctx->md_ctx, image_ctx->header_oid,
+                                  last_key, MAX_KEYS, &pairs);
+    if (r < 0) {
+      lderr(cct) << "failed reading image metadata: " << cpp_strerror(r)
+                 << dendl;
+      return r;
+    }
+
+    if (pairs.empty()) {
+      break;
+    }
+
+    more_results = (pairs.size() == MAX_KEYS);
+    last_key = pairs.rbegin()->first;
+
+    for (auto kv : pairs) {
+      std::string key;
+      if (!util::is_metadata_config_override(kv.first, &key)) {
+        more_results = false;
+        break;
+      }
+      auto it = opts.find(key);
+      if (it != opts.end()) {
+        it->second = {{kv.second.c_str(), kv.second.length()},
+                      RBD_CONFIG_SOURCE_IMAGE};
+      }
+    }
+  }
+
+  for (auto &it : opts) {
+    options->push_back({it.first, it.second.first, it.second.second});
+  }
+
+  return 0;
+}
+
+} // namespace api
+} // namespace librbd
+
+template class librbd::api::Config<librbd::ImageCtx>;
diff --git a/src/librbd/api/Config.h b/src/librbd/api/Config.h
new file mode 100644 (file)
index 0000000..9f27000
--- /dev/null
@@ -0,0 +1,37 @@
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab
+
+#ifndef CEPH_LIBRBD_API_CONFIG_H
+#define CEPH_LIBRBD_API_CONFIG_H
+
+#include "include/rbd/librbd.hpp"
+
+namespace librados {
+
+class IoCtx;
+
+}
+
+namespace librbd {
+
+class ImageCtx;
+
+namespace api {
+
+template <typename ImageCtxT = librbd::ImageCtx>
+class Config {
+public:
+  static bool is_option_name(librados::IoCtx& io_ctx, const std::string &name);
+  static int list(librados::IoCtx& io_ctx,
+                  std::vector<config_option_t> *options);
+
+  static bool is_option_name(ImageCtxT *image_ctx, const std::string &name);
+  static int list(ImageCtxT *image_ctx, std::vector<config_option_t> *options);
+};
+
+} // namespace api
+} // namespace librbd
+
+extern template class librbd::api::Config<librbd::ImageCtx>;
+
+#endif // CEPH_LIBRBD_API_CONFIG_H
index 0782a9ba2940c55f0824cdff4a0a884693d93a81..75413110ba0f9dde2d8498aa9a74a6c285483acb 100644 (file)
@@ -26,6 +26,7 @@
 #include "librbd/ImageState.h"
 #include "librbd/internal.h"
 #include "librbd/Operations.h"
+#include "librbd/api/Config.h"
 #include "librbd/api/DiffIterate.h"
 #include "librbd/api/Group.h"
 #include "librbd/api/Image.h"
@@ -202,6 +203,18 @@ void trash_image_info_cpp_to_c(const librbd::trash_image_info_t &cpp_info,
   c_info->deferment_end_time = cpp_info.deferment_end_time;
 }
 
+void config_option_cpp_to_c(const librbd::config_option_t &cpp_option,
+                            rbd_config_option_t *c_option) {
+  c_option->name = strdup(cpp_option.name.c_str());
+  c_option->value = strdup(cpp_option.value.c_str());
+  c_option->source = cpp_option.source;
+}
+
+void config_option_cleanup(rbd_config_option_t &option) {
+    free(option.name);
+    free(option.value);
+}
+
 struct C_MirrorImageGetInfo : public Context {
     rbd_mirror_image_info_t *mirror_image_info;
   Context *on_finish;
@@ -1024,6 +1037,10 @@ namespace librbd {
     return r;
   }
 
+  int RBD::config_list(IoCtx& io_ctx, std::vector<config_option_t> *options) {
+    return librbd::api::Config<>::list(io_ctx, options);
+  }
+
   RBD::AioCompletion::AioCompletion(void *cb_arg, callback_t complete_cb)
   {
     pc = reinterpret_cast<void*>(librbd::io::AioCompletion::create(
@@ -2402,6 +2419,11 @@ namespace librbd {
     return r;
   }
 
+  int Image::config_list(std::vector<config_option_t> *options) {
+    ImageCtx *ictx = (ImageCtx *)ctx;
+    return librbd::api::Config<>::list(ictx, options);
+  }
+
 } // namespace librbd
 
 extern "C" void rbd_version(int *major, int *minor, int *extra)
@@ -3368,6 +3390,37 @@ extern "C" int rbd_pool_metadata_list(rados_ioctx_t p, const char *start,
   return 0;
 }
 
+extern "C" int rbd_config_pool_list(rados_ioctx_t p,
+                                    rbd_config_option_t *options,
+                                    int *max_options) {
+  librados::IoCtx io_ctx;
+  librados::IoCtx::from_rados_ioctx_t(p, io_ctx);
+
+  std::vector<librbd::config_option_t> option_vector;
+  int r = librbd::api::Config<>::list(io_ctx, &option_vector);
+  if (r < 0) {
+    return r;
+  }
+
+  if (*max_options < static_cast<int>(option_vector.size())) {
+    *max_options = static_cast<int>(option_vector.size());
+    return -ERANGE;
+  }
+
+  for (int i = 0; i < static_cast<int>(option_vector.size()); ++i) {
+    config_option_cpp_to_c(option_vector[i], &options[i]);
+  }
+  *max_options = static_cast<int>(option_vector.size());
+  return 0;
+}
+
+extern "C" void rbd_config_pool_list_cleanup(rbd_config_option_t *options,
+                                             int max_options) {
+  for (int i = 0; i < max_options; ++i) {
+    config_option_cleanup(options[i]);
+  }
+}
+
 extern "C" int rbd_open(rados_ioctx_t p, const char *name, rbd_image_t *image,
                        const char *snap_name)
 {
@@ -5539,3 +5592,33 @@ extern "C" void rbd_watchers_list_cleanup(rbd_image_watcher_t *watchers,
     free(watchers[i].addr);
   }
 }
+
+extern "C" int rbd_config_image_list(rbd_image_t image,
+                                     rbd_config_option_t *options,
+                                     int *max_options) {
+  librbd::ImageCtx *ictx = (librbd::ImageCtx*)image;
+
+  std::vector<librbd::config_option_t> option_vector;
+  int r = librbd::api::Config<>::list(ictx, &option_vector);
+  if (r < 0) {
+    return r;
+  }
+
+  if (*max_options < static_cast<int>(option_vector.size())) {
+    *max_options = static_cast<int>(option_vector.size());
+    return -ERANGE;
+  }
+
+  for (int i = 0; i < static_cast<int>(option_vector.size()); ++i) {
+    config_option_cpp_to_c(option_vector[i], &options[i]);
+  }
+  *max_options = static_cast<int>(option_vector.size());
+  return 0;
+}
+
+extern "C" void rbd_config_image_list_cleanup(rbd_config_option_t *options,
+                                              int max_options) {
+  for (int i = 0; i < max_options; ++i) {
+    config_option_cleanup(options[i]);
+  }
+}
index d660f3b54284135032385cf1aff28f0595a32efd..b300edd0d1a38793b80466c02c5c16fec0cbf011 100644 (file)
@@ -216,6 +216,16 @@ cdef extern from "rbd/librbd.h" nogil:
         rbd_image_migration_state_t state
         char *state_description
 
+    ctypedef enum rbd_config_source_t:
+        _RBD_CONFIG_SOURCE_CONFIG "RBD_CONFIG_SOURCE_CONFIG"
+        _RBD_CONFIG_SOURCE_POOL "RBD_CONFIG_SOURCE_POOL"
+        _RBD_CONFIG_SOURCE_IMAGE "RBD_CONFIG_SOURCE_IMAGE"
+
+    ctypedef struct rbd_config_option_t:
+        char *name
+        char *value
+        rbd_config_source_t source
+
     ctypedef void (*rbd_callback_t)(rbd_completion_t cb, void *arg)
     ctypedef int (*librbd_progress_fn_t)(uint64_t offset, uint64_t total, void* ptr)
 
@@ -302,6 +312,11 @@ cdef extern from "rbd/librbd.h" nogil:
                                uint64_t max, char *keys, size_t *key_len,
                                char *values, size_t *vals_len)
 
+    int rbd_config_pool_list(rados_ioctx_t io_ctx, rbd_config_option_t *options,
+                             int *max_options)
+    void rbd_config_pool_list_cleanup(rbd_config_option_t *options,
+                                      int max_options)
+
     int rbd_open(rados_ioctx_t io, const char *name,
                  rbd_image_t *image, const char *snap_name)
     int rbd_open_by_id(rados_ioctx_t io, const char *image_id,
@@ -504,6 +519,11 @@ cdef extern from "rbd/librbd.h" nogil:
     void rbd_watchers_list_cleanup(rbd_image_watcher_t *watchers,
                                    size_t num_watchers)
 
+    int rbd_config_image_list(rbd_image_t image, rbd_config_option_t *options,
+                              int *max_options)
+    void rbd_config_image_list_cleanup(rbd_config_option_t *options,
+                                       int max_options)
+
 RBD_FEATURE_LAYERING = _RBD_FEATURE_LAYERING
 RBD_FEATURE_STRIPINGV2 = _RBD_FEATURE_STRIPINGV2
 RBD_FEATURE_EXCLUSIVE_LOCK = _RBD_FEATURE_EXCLUSIVE_LOCK
@@ -571,6 +591,10 @@ RBD_IMAGE_MIGRATION_STATE_PREPARED = _RBD_IMAGE_MIGRATION_STATE_PREPARED
 RBD_IMAGE_MIGRATION_STATE_EXECUTING = _RBD_IMAGE_MIGRATION_STATE_EXECUTING
 RBD_IMAGE_MIGRATION_STATE_EXECUTED = _RBD_IMAGE_MIGRATION_STATE_EXECUTED
 
+RBD_CONFIG_SOURCE_CONFIG = _RBD_CONFIG_SOURCE_CONFIG
+RBD_CONFIG_SOURCE_POOL = _RBD_CONFIG_SOURCE_POOL
+RBD_CONFIG_SOURCE_IMAGE = _RBD_CONFIG_SOURCE_IMAGE
+
 class Error(Exception):
     pass
 
@@ -1661,6 +1685,14 @@ class RBD(object):
         """
         return PoolMetadataIterator(ioctx)
 
+    def config_list(self, ioctx):
+        """
+        List pool-level config overrides.
+
+        :returns: :class:`ConfigPoolIterator`
+        """
+        return ConfigPoolIterator(ioctx)
+
     def group_create(self, ioctx, name):
         """
         Create a group.
@@ -1958,6 +1990,55 @@ cdef class PoolMetadataIterator(object):
             free(c_keys)
             free(c_vals)
 
+cdef class ConfigPoolIterator(object):
+    """
+    Iterator over pool-level overrides for a pool.
+
+    Yields a dictionary containing information about an override.
+
+    Keys are:
+
+    * ``name`` (str) - override name
+
+    * ``value`` (str) - override value
+
+    * ``source`` (str) - override source
+    """
+
+    cdef:
+        rbd_config_option_t *options
+        int num_options
+
+    def __init__(self, ioctx):
+        cdef:
+            rados_ioctx_t _ioctx = convert_ioctx(ioctx)
+        self.options = NULL
+        self.num_options = 32
+        while True:
+            self.options = <rbd_config_option_t *>realloc_chk(
+                self.options, self.num_options * sizeof(rbd_mirror_peer_t))
+            with nogil:
+                ret = rbd_config_pool_list(_ioctx, self.options, &self.num_options)
+            if ret < 0:
+                if ret == -errno.ERANGE:
+                    continue
+                self.num_options = 0
+                raise make_ex(ret, 'error listing config options')
+            break
+
+    def __iter__(self):
+        for i in range(self.num_options):
+            yield {
+                'name'   : decode_cstr(self.options[i].name),
+                'value'  : decode_cstr(self.options[i].value),
+                'source' : self.options[i].source,
+                }
+
+    def __dealloc__(self):
+        if self.options:
+            rbd_config_pool_list_cleanup(self.options, self.num_options)
+            free(self.options)
+
 cdef int diff_iterate_cb(uint64_t offset, size_t length, int write, void *cb) \
     except? -9000 with gil:
     # Make sure that if we wound up with an exception from a previous callback,
@@ -3757,6 +3838,14 @@ written." % (self.name, ret, length))
         """
         return WatcherIterator(self)
 
+    def config_list(self):
+        """
+        List image-level config overrides.
+
+        :returns: :class:`ConfigPoolIterator`
+        """
+        return ConfigImageIterator(self)
+
     def snap_get_namespace_type(self, snap_id):
         """
         Get the snapshot namespace type.
@@ -4175,6 +4264,54 @@ cdef class WatcherIterator(object):
             rbd_watchers_list_cleanup(self.watchers, self.num_watchers)
             free(self.watchers)
 
+cdef class ConfigImageIterator(object):
+    """
+    Iterator over image-level overrides for an image.
+
+    Yields a dictionary containing information about an override.
+
+    Keys are:
+
+    * ``name`` (str) - override name
+
+    * ``value`` (str) - override value
+
+    * ``source`` (str) - override source
+    """
+
+    cdef:
+        rbd_config_option_t *options
+        int num_options
+
+    def __init__(self, Image image):
+        self.options = NULL
+        self.num_options = 32
+        while True:
+            self.options = <rbd_config_option_t *>realloc_chk(
+                self.options, self.num_options * sizeof(rbd_mirror_peer_t))
+            with nogil:
+                ret = rbd_config_image_list(image.image, self.options,
+                                            &self.num_options)
+            if ret < 0:
+                if ret == -errno.ERANGE:
+                    continue
+                self.num_options = 0
+                raise make_ex(ret, 'error listing config options')
+            break
+
+    def __iter__(self):
+        for i in range(self.num_options):
+            yield {
+                'name'   : decode_cstr(self.options[i].name),
+                'value'  : decode_cstr(self.options[i].value),
+                'source' : self.options[i].source,
+                }
+
+    def __dealloc__(self):
+        if self.options:
+            rbd_config_image_list_cleanup(self.options, self.num_options)
+            free(self.options)
+
 cdef class GroupImageIterator(object):
     """
     Iterator over image info for a group.
index f0f9b0e5ec7ce8dd6ed9b402931e0b2327c6f7d6..432d626ef2a8f46979361867028b302c59858120 100644 (file)
@@ -7096,6 +7096,7 @@ TEST_F(TestLibRBD, PoolMetadata)
   ASSERT_STREQ(vals, "value2");
 
   // test config setting
+  ASSERT_EQ(-EINVAL, rbd_pool_metadata_set(ioctx, "conf_UNKNOWN", "false"));
   ASSERT_EQ(0, rbd_pool_metadata_set(ioctx, "conf_rbd_cache", "false"));
   ASSERT_EQ(-EINVAL, rbd_pool_metadata_set(ioctx, "conf_rbd_cache", "INVALID"));
   ASSERT_EQ(0, rbd_pool_metadata_remove(ioctx, "conf_rbd_cache"));
@@ -7189,6 +7190,7 @@ TEST_F(TestLibRBD, PoolMetadataPP)
   ASSERT_EQ(0, strncmp("value3", pairs["key3"].c_str(), 6));
 
   // test config setting
+  ASSERT_EQ(-EINVAL, rbd.pool_metadata_set(ioctx, "conf_UNKNOWN", "false"));
   ASSERT_EQ(0, rbd.pool_metadata_set(ioctx, "conf_rbd_cache", "false"));
   ASSERT_EQ(-EINVAL, rbd.pool_metadata_set(ioctx, "conf_rbd_cache", "INVALID"));
   ASSERT_EQ(0, rbd.pool_metadata_remove(ioctx, "conf_rbd_cache"));
@@ -7199,6 +7201,167 @@ TEST_F(TestLibRBD, PoolMetadataPP)
   ASSERT_EQ(0, rbd.pool_metadata_remove(ioctx, "key3"));
 }
 
+TEST_F(TestLibRBD, Config)
+{
+  REQUIRE_FORMAT_V2();
+
+  rados_ioctx_t ioctx;
+  rados_ioctx_create(_cluster, m_pool_name.c_str(), &ioctx);
+
+  ASSERT_EQ(0, rbd_pool_metadata_set(ioctx, "conf_rbd_cache", "false"));
+
+  rbd_config_option_t options[1024];
+  int max_options = 0;
+  ASSERT_EQ(-ERANGE, rbd_config_pool_list(ioctx, options, &max_options));
+  ASSERT_EQ(0, rbd_config_pool_list(ioctx, options, &max_options));
+  ASSERT_GT(max_options, 0);
+  ASSERT_LT(max_options, 1024);
+  for (int i = 0; i < max_options; i++) {
+    if (options[i].name == std::string("rbd_cache")) {
+      ASSERT_EQ(options[i].source, RBD_CONFIG_SOURCE_POOL);
+      ASSERT_STREQ("false", options[i].value);
+    } else {
+      ASSERT_EQ(options[i].source, RBD_CONFIG_SOURCE_CONFIG);
+    }
+  }
+  rbd_config_pool_list_cleanup(options, max_options);
+
+  rbd_image_t image;
+  int order = 0;
+  std::string name = get_temp_image_name();
+  uint64_t size = 2 << 20;
+
+  ASSERT_EQ(0, create_image(ioctx, name.c_str(), size, &order));
+  ASSERT_EQ(0, rbd_open(ioctx, name.c_str(), &image, NULL));
+
+  ASSERT_EQ(0, rbd_config_image_list(image, options, &max_options));
+  for (int i = 0; i < max_options; i++) {
+    if (options[i].name == std::string("rbd_cache")) {
+      ASSERT_EQ(options[i].source, RBD_CONFIG_SOURCE_POOL);
+      ASSERT_STREQ("false", options[i].value);
+    } else {
+      ASSERT_EQ(options[i].source, RBD_CONFIG_SOURCE_CONFIG);
+    }
+  }
+  rbd_config_image_list_cleanup(options, max_options);
+
+  ASSERT_EQ(0, rbd_metadata_set(image, "conf_rbd_cache", "true"));
+
+  ASSERT_EQ(0, rbd_config_image_list(image, options, &max_options));
+  for (int i = 0; i < max_options; i++) {
+    if (options[i].name == std::string("rbd_cache")) {
+      ASSERT_EQ(options[i].source, RBD_CONFIG_SOURCE_IMAGE);
+      ASSERT_STREQ("true", options[i].value);
+    } else {
+      ASSERT_EQ(options[i].source, RBD_CONFIG_SOURCE_CONFIG);
+    }
+  }
+  rbd_config_image_list_cleanup(options, max_options);
+
+  ASSERT_EQ(0, rbd_metadata_remove(image, "conf_rbd_cache"));
+
+  ASSERT_EQ(0, rbd_config_image_list(image, options, &max_options));
+  for (int i = 0; i < max_options; i++) {
+    if (options[i].name == std::string("rbd_cache")) {
+      ASSERT_EQ(options[i].source, RBD_CONFIG_SOURCE_POOL);
+      ASSERT_STREQ("false", options[i].value);
+    } else {
+      ASSERT_EQ(options[i].source, RBD_CONFIG_SOURCE_CONFIG);
+    }
+  }
+  rbd_config_image_list_cleanup(options, max_options);
+
+  ASSERT_EQ(0, rbd_close(image));
+
+  ASSERT_EQ(0, rbd_pool_metadata_remove(ioctx, "conf_rbd_cache"));
+
+  ASSERT_EQ(-ERANGE, rbd_config_pool_list(ioctx, options, &max_options));
+  ASSERT_EQ(0, rbd_config_pool_list(ioctx, options, &max_options));
+  for (int i = 0; i < max_options; i++) {
+    ASSERT_EQ(options[i].source, RBD_CONFIG_SOURCE_CONFIG);
+  }
+  rbd_config_pool_list_cleanup(options, max_options);
+
+  rados_ioctx_destroy(ioctx);
+}
+
+TEST_F(TestLibRBD, ConfigPP)
+{
+  REQUIRE_FORMAT_V2();
+
+  librbd::RBD rbd;
+  string value;
+
+  librados::IoCtx ioctx;
+  ASSERT_EQ(0, _rados.ioctx_create(m_pool_name.c_str(), ioctx));
+
+  ASSERT_EQ(0, rbd.pool_metadata_set(ioctx, "conf_rbd_cache", "false"));
+
+  std::vector<librbd::config_option_t> options;
+  ASSERT_EQ(0, rbd.config_list(ioctx, &options));
+  for (auto &option : options) {
+    if (option.name == std::string("rbd_cache")) {
+      ASSERT_EQ(option.source, RBD_CONFIG_SOURCE_POOL);
+      ASSERT_EQ("false", option.value);
+    } else {
+      ASSERT_EQ(option.source, RBD_CONFIG_SOURCE_CONFIG);
+    }
+  }
+
+  int order = 0;
+  std::string name = get_temp_image_name();
+  uint64_t size = 2 << 20;
+  ASSERT_EQ(0, create_image_pp(rbd, ioctx, name.c_str(), size, &order));
+
+  librbd::Image image;
+  ASSERT_EQ(0, rbd.open(ioctx, image, name.c_str(), nullptr));
+
+  options.clear();
+  ASSERT_EQ(0, image.config_list(&options));
+  for (auto &option : options) {
+    if (option.name == std::string("rbd_cache")) {
+      ASSERT_EQ(option.source, RBD_CONFIG_SOURCE_POOL);
+      ASSERT_EQ("false", option.value);
+    } else {
+      ASSERT_EQ(option.source, RBD_CONFIG_SOURCE_CONFIG);
+    }
+  }
+
+  ASSERT_EQ(0, image.metadata_set("conf_rbd_cache", "true"));
+
+  options.clear();
+  ASSERT_EQ(0, image.config_list(&options));
+  for (auto &option : options) {
+    if (option.name == std::string("rbd_cache")) {
+      ASSERT_EQ(option.source, RBD_CONFIG_SOURCE_IMAGE);
+      ASSERT_EQ("true", option.value);
+    } else {
+      ASSERT_EQ(option.source, RBD_CONFIG_SOURCE_CONFIG);
+    }
+  }
+
+  ASSERT_EQ(0, image.metadata_remove("conf_rbd_cache"));
+
+  options.clear();
+  ASSERT_EQ(0, image.config_list(&options));
+  for (auto &option : options) {
+    if (option.name == std::string("rbd_cache")) {
+      ASSERT_EQ(option.source, RBD_CONFIG_SOURCE_POOL);
+      ASSERT_EQ("false", option.value);
+    } else {
+      ASSERT_EQ(option.source, RBD_CONFIG_SOURCE_CONFIG);
+    }
+  }
+
+  ASSERT_EQ(0, rbd.pool_metadata_remove(ioctx, "conf_rbd_cache"));
+
+  options.clear();
+  ASSERT_EQ(0, rbd.config_list(ioctx, &options));
+  for (auto &option : options) {
+    ASSERT_EQ(option.source, RBD_CONFIG_SOURCE_CONFIG);
+  }
+}
+
 // poorman's ceph_assert()
 namespace ceph {
   void __ceph_assert_fail(const char *assertion, const char *file, int line,
index 749f6877ea4b93fdd9cddf803baa9ed14ff11788..9dc98537336cb6efd1a94c60bdbf6170a5297f4d 100644 (file)
@@ -23,7 +23,8 @@ from rbd import (RBD, Group, Image, ImageNotFound, InvalidArgument, ImageExists,
                  RBD_MIRROR_IMAGE_DISABLED, MIRROR_IMAGE_STATUS_STATE_UNKNOWN,
                  RBD_LOCK_MODE_EXCLUSIVE, RBD_OPERATION_FEATURE_GROUP,
                  RBD_SNAP_NAMESPACE_TYPE_TRASH,
-                 RBD_IMAGE_MIGRATION_STATE_PREPARED)
+                 RBD_IMAGE_MIGRATION_STATE_PREPARED, RBD_CONFIG_SOURCE_CONFIG,
+                 RBD_CONFIG_SOURCE_POOL, RBD_CONFIG_SOURCE_IMAGE)
 
 rados = None
 ioctx = None
@@ -357,6 +358,25 @@ def test_pool_metadata():
         metadata = list(rbd.pool_metadata_list(ioctx))
         eq(len(metadata), N - i - 1)
 
+def test_config_list():
+    rbd = RBD()
+
+    for option in rbd.config_list(ioctx):
+        eq(option['source'], RBD_CONFIG_SOURCE_CONFIG)
+
+    rbd.pool_metadata_set(ioctx, "conf_rbd_cache", "true")
+
+    for option in rbd.config_list(ioctx):
+        if option['name'] == "rbd_cache":
+            eq(option['source'], RBD_CONFIG_SOURCE_POOL)
+        else:
+            eq(option['source'], RBD_CONFIG_SOURCE_CONFIG)
+
+    rbd.pool_metadata_remove(ioctx, "conf_rbd_cache")
+
+    for option in rbd.config_list(ioctx):
+        eq(option['source'], RBD_CONFIG_SOURCE_CONFIG)
+
 def rand_data(size):
     return os.urandom(size)
 
@@ -1027,6 +1047,24 @@ class TestImage(object):
         # watcher.
         eq(len(watchers), 1)
 
+    def test_config_list(self):
+        with Image(ioctx, image_name) as image:
+            for option in image.config_list():
+                eq(option['source'], RBD_CONFIG_SOURCE_CONFIG)
+
+            image.metadata_set("conf_rbd_cache", "true")
+
+            for option in image.config_list():
+                if option['name'] == "rbd_cache":
+                    eq(option['source'], RBD_CONFIG_SOURCE_IMAGE)
+                else:
+                    eq(option['source'], RBD_CONFIG_SOURCE_CONFIG)
+
+            image.metadata_remove("conf_rbd_cache")
+
+            for option in image.config_list():
+                eq(option['source'], RBD_CONFIG_SOURCE_CONFIG)
+
 class TestImageId(object):
 
     def setUp(self):