From: Jason Dillaman Date: Tue, 30 Oct 2018 01:55:54 +0000 (-0400) Subject: librbd: new pool init/stat API methods X-Git-Tag: v14.1.0~944^2~1 X-Git-Url: http://git-server-git.apps.pok.os.sepia.ceph.com/?a=commitdiff_plain;h=f07fb350af09e58ce3be7e6220091918e5497de5;p=ceph.git librbd: new pool init/stat API methods The init method is a stub for handling new pool initialization. It currently only handles setting the application tag. The stats method will quickly calculate the number of images and provisioned space for those images within the pool. Querying the pool stats on a pool with 10,000 images only required approximately 2 seconds as compared to over 2 minutes for a "rbd ls -l" scan. Signed-off-by: Jason Dillaman --- diff --git a/src/include/rbd/librbd.h b/src/include/rbd/librbd.h index 0bb5f7e86a51..8a4196cf6a1f 100644 --- a/src/include/rbd/librbd.h +++ b/src/include/rbd/librbd.h @@ -59,6 +59,7 @@ extern "C" { typedef void *rbd_image_t; typedef void *rbd_image_options_t; +typedef void *rbd_pool_stats_t; typedef void *rbd_completion_t; typedef void (*rbd_callback_t)(rbd_completion_t cb, void *arg); @@ -264,6 +265,17 @@ typedef struct { rbd_config_source_t source; } rbd_config_option_t; +typedef enum { + RBD_POOL_STAT_OPTION_IMAGES, + RBD_POOL_STAT_OPTION_IMAGE_PROVISIONED_BYTES, + RBD_POOL_STAT_OPTION_IMAGE_MAX_PROVISIONED_BYTES, + RBD_POOL_STAT_OPTION_IMAGE_SNAPSHOTS, + RBD_POOL_STAT_OPTION_TRASH_IMAGES, + RBD_POOL_STAT_OPTION_TRASH_PROVISIONED_BYTES, + RBD_POOL_STAT_OPTION_TRASH_MAX_PROVISIONED_BYTES, + RBD_POOL_STAT_OPTION_TRASH_SNAPSHOTS +} rbd_pool_stat_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, @@ -1103,9 +1115,19 @@ CEPH_RBD_API int rbd_namespace_remove(rados_ioctx_t io, const char *namespace_name); CEPH_RBD_API int rbd_namespace_list(rados_ioctx_t io, char *namespace_names, size_t *size); -CEPH_RBD_API int rbd_namespace_exists(rados_ioctx_t io, const char *namespace_name, +CEPH_RBD_API int rbd_namespace_exists(rados_ioctx_t io, + const char *namespace_name, bool *exists); +CEPH_RBD_API int rbd_pool_init(rados_ioctx_t io, bool force); + +CEPH_RBD_API void rbd_pool_stats_create(rbd_pool_stats_t *stats); +CEPH_RBD_API void rbd_pool_stats_destroy(rbd_pool_stats_t stats); +CEPH_RBD_API int rbd_pool_stats_option_add_uint64(rbd_pool_stats_t stats, + int stat_option, + uint64_t* stat_val); +CEPH_RBD_API int rbd_pool_stats_get(rados_ioctx_t io, rbd_pool_stats_t stats); + #ifdef __cplusplus } #endif diff --git a/src/include/rbd/librbd.hpp b/src/include/rbd/librbd.hpp index f6a3d8be7ab3..3b8d2cdffe87 100644 --- a/src/include/rbd/librbd.hpp +++ b/src/include/rbd/librbd.hpp @@ -29,6 +29,7 @@ namespace librbd { class Image; class ImageOptions; + class PoolStats; typedef void *image_ctx_t; typedef void *completion_t; typedef void (*callback_t)(completion_t cb, void *arg); @@ -290,6 +291,9 @@ public: int namespace_list(IoCtx& io_ctx, std::vector* namespace_names); int namespace_exists(IoCtx& io_ctx, const char *namespace_name, bool *exists); + int pool_init(IoCtx& io_ctx, bool force); + int pool_stats_get(IoCtx& io_ctx, PoolStats *pool_stats); + int pool_metadata_get(IoCtx &io_ctx, const std::string &key, std::string *value); int pool_metadata_set(IoCtx &io_ctx, const std::string &key, @@ -329,6 +333,22 @@ private: rbd_image_options_t opts; }; +class CEPH_RBD_API PoolStats { +public: + PoolStats(); + ~PoolStats(); + + PoolStats(const PoolStats&) = delete; + PoolStats& operator=(const PoolStats&) = delete; + + int add(rbd_pool_stat_option_t option, uint64_t* opt_val); + +private: + friend class RBD; + + rbd_pool_stats_t pool_stats; +}; + class CEPH_RBD_API UpdateWatchCtx { public: virtual ~UpdateWatchCtx() {} diff --git a/src/librbd/CMakeLists.txt b/src/librbd/CMakeLists.txt index 5501dc633c24..38d625f10d31 100644 --- a/src/librbd/CMakeLists.txt +++ b/src/librbd/CMakeLists.txt @@ -31,6 +31,7 @@ set(librbd_internal_srcs api/Migration.cc api/Mirror.cc api/Namespace.cc + api/Pool.cc api/PoolMetadata.cc api/Snapshot.cc api/Trash.cc diff --git a/src/librbd/api/Pool.cc b/src/librbd/api/Pool.cc new file mode 100644 index 000000000000..6e3623119957 --- /dev/null +++ b/src/librbd/api/Pool.cc @@ -0,0 +1,354 @@ +// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- +// vim: ts=8 sw=2 smarttab + +#include "librbd/api/Pool.h" +#include "include/rados/librados.hpp" +#include "common/dout.h" +#include "common/errno.h" +#include "common/Throttle.h" +#include "cls/rbd/cls_rbd_client.h" +#include "osd/osd_types.h" +#include "librbd/Utils.h" +#include "librbd/api/Config.h" +#include "librbd/api/Image.h" +#include "librbd/api/Trash.h" + +#define dout_subsys ceph_subsys_rbd + +namespace librbd { +namespace api { + +namespace { + +#undef dout_prefix +#define dout_prefix *_dout << "librbd::api::Pool::ImageStatRequest: " \ + << __func__ << " " << this << ": " \ + << "(id=" << m_image_id << "): " + +template +class ImageStatRequest { +public: + ImageStatRequest(librados::IoCtx& io_ctx, SimpleThrottle& throttle, + const std::string& image_id, bool scan_snaps, + std::atomic* bytes, + std::atomic* max_bytes, + std::atomic* snaps) + : m_cct(reinterpret_cast(io_ctx.cct())), + m_io_ctx(io_ctx), m_throttle(throttle), m_image_id(image_id), + m_scan_snaps(scan_snaps), m_bytes(bytes), m_max_bytes(max_bytes), + m_snaps(snaps) { + m_throttle.start_op(); + } + + void send() { + get_head(); + } + +protected: + void finish(int r) { + (*m_max_bytes) += m_max_size; + m_throttle.end_op(r); + + delete this; + } + +private: + CephContext* m_cct; + librados::IoCtx& m_io_ctx; + SimpleThrottle& m_throttle; + const std::string& m_image_id; + bool m_scan_snaps; + std::atomic* m_bytes; + std::atomic* m_max_bytes; + std::atomic* m_snaps; + bufferlist m_out_bl; + + uint64_t m_max_size = 0; + ::SnapContext m_snapc; + + void get_head() { + ldout(m_cct, 15) << dendl; + + librados::ObjectReadOperation op; + cls_client::get_size_start(&op, CEPH_NOSNAP); + if (m_scan_snaps) { + cls_client::get_snapcontext_start(&op); + } + + m_out_bl.clear(); + auto aio_comp = util::create_rados_callback< + ImageStatRequest, &ImageStatRequest::handle_get_head>(this); + int r = m_io_ctx.aio_operate(util::header_name(m_image_id), aio_comp, &op, + &m_out_bl); + ceph_assert(r == 0); + aio_comp->release(); + } + + void handle_get_head(int r) { + ldout(m_cct, 15) << "r=" << r << dendl; + + auto it = m_out_bl.cbegin(); + if (r == 0) { + uint8_t order; + r = cls_client::get_size_finish(&it, &m_max_size, &order); + if (r == 0) { + (*m_bytes) += m_max_size; + } + } + if (m_scan_snaps && r == 0) { + r = cls_client::get_snapcontext_finish(&it, &m_snapc); + if (r == 0) { + (*m_snaps) += m_snapc.snaps.size(); + } + } + + if (r == -ENOENT) { + finish(r); + return; + } else if (r < 0) { + lderr(m_cct) << "failed to stat image: " << cpp_strerror(r) << dendl; + finish(r); + return; + } + + if (!m_snapc.is_valid()) { + lderr(m_cct) << "snap context is invalid" << dendl; + finish(-EIO); + return; + } + + get_snaps(); + } + + void get_snaps() { + if (!m_scan_snaps || m_snapc.snaps.empty()) { + finish(0); + return; + } + + ldout(m_cct, 15) << dendl; + librados::ObjectReadOperation op; + for (auto snap_seq : m_snapc.snaps) { + cls_client::get_size_start(&op, snap_seq); + } + + m_out_bl.clear(); + auto aio_comp = util::create_rados_callback< + ImageStatRequest, &ImageStatRequest::handle_get_snaps>(this); + int r = m_io_ctx.aio_operate(util::header_name(m_image_id), aio_comp, &op, + &m_out_bl); + ceph_assert(r == 0); + aio_comp->release(); + } + + void handle_get_snaps(int r) { + ldout(m_cct, 15) << "r=" << r << dendl; + + auto it = m_out_bl.cbegin(); + for ([[maybe_unused]] auto snap_seq : m_snapc.snaps) { + uint64_t size; + if (r == 0) { + uint8_t order; + r = cls_client::get_size_finish(&it, &size, &order); + } + if (r == 0 && m_max_size < size) { + m_max_size = size; + } + } + + if (r == -ENOENT) { + ldout(m_cct, 15) << "out-of-sync metadata" << dendl; + get_head(); + } else if (r < 0) { + lderr(m_cct) << "failed to retrieve snap size: " << cpp_strerror(r) + << dendl; + finish(r); + } else { + finish(0); + } + } + +}; + +template +void get_pool_stat_option_value(typename Pool::StatOptions* stat_options, + rbd_pool_stat_option_t option, + uint64_t** value) { + auto it = stat_options->find(option); + if (it == stat_options->end()) { + *value = nullptr; + } else { + *value = it->second; + } +} + +template +int get_pool_stats(librados::IoCtx& io_ctx, const ConfigProxy& config, + const std::vector& image_ids, uint64_t* image_count, + uint64_t* provisioned_bytes, uint64_t* max_provisioned_bytes, + uint64_t* snapshot_count) { + + bool scan_snaps = ((max_provisioned_bytes != nullptr) || + (snapshot_count != nullptr)); + + SimpleThrottle throttle( + config.template get_val("rbd_concurrent_management_ops"), true); + std::atomic bytes{0}; + std::atomic max_bytes{0}; + std::atomic snaps{0}; + for (auto& image_id : image_ids) { + if (throttle.pending_error()) { + break; + } + + auto req = new ImageStatRequest(io_ctx, throttle, image_id, + scan_snaps, &bytes, &max_bytes, &snaps); + req->send(); + } + + int r = throttle.wait_for_ret(); + if (r < 0) { + return r; + } + + if (image_count != nullptr) { + *image_count = image_ids.size(); + } + if (provisioned_bytes != nullptr) { + *provisioned_bytes = bytes.load(); + } + if (max_provisioned_bytes != nullptr) { + *max_provisioned_bytes = max_bytes.load(); + } + if (snapshot_count != nullptr) { + *snapshot_count = snaps.load(); + } + + return 0; +} + +} // anonymous namespace + +#undef dout_prefix +#define dout_prefix *_dout << "librbd::api::Pool: " << __func__ << ": " + +template +int Pool::init(librados::IoCtx& io_ctx, bool force) { + auto cct = reinterpret_cast(io_ctx.cct()); + ldout(cct, 10) << dendl; + + int r = io_ctx.application_enable(pg_pool_t::APPLICATION_NAME_RBD, force); + if (r < 0) { + return r; + } + + // TODO configure self-managed snapshots (and other one-time pool checks) + + return 0; +} + +template +int Pool::add_stat_option(StatOptions* stat_options, + rbd_pool_stat_option_t option, + uint64_t* value) { + switch (option) { + case RBD_POOL_STAT_OPTION_IMAGES: + case RBD_POOL_STAT_OPTION_IMAGE_PROVISIONED_BYTES: + case RBD_POOL_STAT_OPTION_IMAGE_MAX_PROVISIONED_BYTES: + case RBD_POOL_STAT_OPTION_IMAGE_SNAPSHOTS: + case RBD_POOL_STAT_OPTION_TRASH_IMAGES: + case RBD_POOL_STAT_OPTION_TRASH_PROVISIONED_BYTES: + case RBD_POOL_STAT_OPTION_TRASH_MAX_PROVISIONED_BYTES: + case RBD_POOL_STAT_OPTION_TRASH_SNAPSHOTS: + stat_options->emplace(option, value); + return 0; + default: + break; + } + return -ENOENT; +} + +template +int Pool::get_stats(librados::IoCtx& io_ctx, StatOptions* stat_options) { + auto cct = reinterpret_cast(io_ctx.cct()); + ldout(cct, 10) << dendl; + + ConfigProxy config{cct->_conf}; + api::Config::apply_pool_overrides(io_ctx, &config); + + uint64_t* image_count; + uint64_t* provisioned_bytes; + uint64_t* max_provisioned_bytes; + uint64_t* snapshot_count; + + get_pool_stat_option_value( + stat_options, RBD_POOL_STAT_OPTION_IMAGES, &image_count); + get_pool_stat_option_value( + stat_options, RBD_POOL_STAT_OPTION_IMAGE_PROVISIONED_BYTES, + &provisioned_bytes); + get_pool_stat_option_value( + stat_options, RBD_POOL_STAT_OPTION_IMAGE_MAX_PROVISIONED_BYTES, + &max_provisioned_bytes); + get_pool_stat_option_value( + stat_options, RBD_POOL_STAT_OPTION_IMAGE_SNAPSHOTS, &snapshot_count); + if (image_count != nullptr || provisioned_bytes != nullptr || + max_provisioned_bytes != nullptr || snapshot_count != nullptr) { + typename Image::ImageNameToIds images; + int r = Image::list_images(io_ctx, &images); + if (r < 0) { + return r; + } + + std::vector image_ids; + image_ids.reserve(images.size()); + for (auto& it : images) { + image_ids.push_back(std::move(it.second)); + } + + r = get_pool_stats(io_ctx, config, image_ids, image_count, + provisioned_bytes, max_provisioned_bytes, + snapshot_count); + if (r < 0) { + return r; + } + } + + get_pool_stat_option_value( + stat_options, RBD_POOL_STAT_OPTION_TRASH_IMAGES, &image_count); + get_pool_stat_option_value( + stat_options, RBD_POOL_STAT_OPTION_TRASH_PROVISIONED_BYTES, + &provisioned_bytes); + get_pool_stat_option_value( + stat_options, RBD_POOL_STAT_OPTION_TRASH_MAX_PROVISIONED_BYTES, + &max_provisioned_bytes); + get_pool_stat_option_value( + stat_options, RBD_POOL_STAT_OPTION_TRASH_SNAPSHOTS, &snapshot_count); + if (image_count != nullptr || provisioned_bytes != nullptr || + max_provisioned_bytes != nullptr || snapshot_count != nullptr) { + std::vector trash_entries; + int r = Trash::list(io_ctx, trash_entries); + if (r < 0 && r != -EOPNOTSUPP) { + return r; + } + + std::vector image_ids; + image_ids.reserve(trash_entries.size()); + for (auto& it : trash_entries) { + image_ids.push_back(std::move(it.id)); + } + + r = get_pool_stats(io_ctx, config, image_ids, image_count, + provisioned_bytes, max_provisioned_bytes, + snapshot_count); + if (r < 0) { + return r; + } + } + + return 0; +} + +} // namespace api +} // namespace librbd + +template class librbd::api::Pool; diff --git a/src/librbd/api/Pool.h b/src/librbd/api/Pool.h new file mode 100644 index 000000000000..e3b99a8ad800 --- /dev/null +++ b/src/librbd/api/Pool.h @@ -0,0 +1,39 @@ +// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- +// vim: ts=8 sw=2 smarttab + +#ifndef CEPH_LIBRBD_API_POOL_H +#define CEPH_LIBRBD_API_POOL_H + +#include "include/int_types.h" +#include "include/rbd/librbd.h" +#include + +namespace librados { class IoCtx; } + +namespace librbd { + +struct ImageCtx; + +namespace api { + +template +class Pool { +public: + typedef std::map StatOptions; + + static int init(librados::IoCtx& io_ctx, bool force); + + static int add_stat_option(StatOptions* stat_options, + rbd_pool_stat_option_t option, + uint64_t* value); + + static int get_stats(librados::IoCtx& io_ctx, StatOptions* stat_options); + +}; + +} // namespace api +} // namespace librbd + +extern template class librbd::api::Pool; + +#endif // CEPH_LIBRBD_API_POOL_H diff --git a/src/librbd/librbd.cc b/src/librbd/librbd.cc index 794084d852b6..bd6c9e9e2b5e 100644 --- a/src/librbd/librbd.cc +++ b/src/librbd/librbd.cc @@ -33,6 +33,7 @@ #include "librbd/api/Migration.h" #include "librbd/api/Mirror.h" #include "librbd/api/Namespace.h" +#include "librbd/api/Pool.h" #include "librbd/api/PoolMetadata.h" #include "librbd/api/Snapshot.h" #include "librbd/api/Trash.h" @@ -284,8 +285,23 @@ namespace librbd { }; /* - RBD - */ + * Pool stats + */ + PoolStats::PoolStats() { + rbd_pool_stats_create(&pool_stats); + } + + PoolStats::~PoolStats() { + rbd_pool_stats_destroy(pool_stats); + } + + int PoolStats::add(rbd_pool_stat_option_t option, uint64_t* opt_val) { + return rbd_pool_stats_option_add_uint64(pool_stats, option, opt_val); + } + + /* + * RBD + */ RBD::RBD() { } @@ -650,6 +666,16 @@ namespace librbd { return librbd::api::Namespace<>::exists(io_ctx, namespace_name, exists); } + int RBD::pool_init(IoCtx& io_ctx, bool force) { + return librbd::api::Pool<>::init(io_ctx, force); + } + + int RBD::pool_stats_get(IoCtx& io_ctx, PoolStats* stats) { + auto pool_stat_options = + reinterpret_cast::StatOptions*>(stats->pool_stats); + return librbd::api::Pool<>::get_stats(io_ctx, pool_stat_options); + } + int RBD::list(IoCtx& io_ctx, vector& names) { TracepointProvider::initialize(get_cct(io_ctx)); @@ -3036,6 +3062,44 @@ extern "C" int rbd_namespace_exists(rados_ioctx_t io, return librbd::api::Namespace<>::exists(io_ctx, namespace_name, exists); } +extern "C" int rbd_pool_init(rados_ioctx_t io, bool force) { + librados::IoCtx io_ctx; + librados::IoCtx::from_rados_ioctx_t(io, io_ctx); + + return librbd::api::Pool<>::init(io_ctx, force); +} + +extern "C" void rbd_pool_stats_create(rbd_pool_stats_t *stats) { + *stats = reinterpret_cast( + new librbd::api::Pool<>::StatOptions{}); +} + +extern "C" void rbd_pool_stats_destroy(rbd_pool_stats_t stats) { + auto pool_stat_options = + reinterpret_cast::StatOptions*>(stats); + delete pool_stat_options; +} + +extern "C" int rbd_pool_stats_option_add_uint64(rbd_pool_stats_t stats, + int stat_option, + uint64_t* stat_val) { + auto pool_stat_options = + reinterpret_cast::StatOptions*>(stats); + return librbd::api::Pool<>::add_stat_option( + pool_stat_options, static_cast(stat_option), + stat_val); +} + +extern "C" int rbd_pool_stats_get( + rados_ioctx_t io, rbd_pool_stats_t pool_stats) { + librados::IoCtx io_ctx; + librados::IoCtx::from_rados_ioctx_t(io, io_ctx); + + auto pool_stat_options = + reinterpret_cast::StatOptions*>(pool_stats); + return librbd::api::Pool<>::get_stats(io_ctx, pool_stat_options); +} + extern "C" int rbd_copy(rbd_image_t image, rados_ioctx_t dest_p, const char *destname) { diff --git a/src/pybind/rbd/rbd.pyx b/src/pybind/rbd/rbd.pyx index f1c687e0d719..2b2b909d4406 100644 --- a/src/pybind/rbd/rbd.pyx +++ b/src/pybind/rbd/rbd.pyx @@ -87,6 +87,7 @@ cdef extern from "rbd/librbd.h" nogil: ctypedef void* rados_ioctx_t ctypedef void* rbd_image_t ctypedef void* rbd_image_options_t + ctypedef void* rbd_pool_stats_t ctypedef void *rbd_completion_t ctypedef struct rbd_image_info_t: @@ -228,6 +229,16 @@ cdef extern from "rbd/librbd.h" nogil: char *value rbd_config_source_t source + ctypedef enum rbd_pool_stat_option_t: + _RBD_POOL_STAT_OPTION_IMAGES "RBD_POOL_STAT_OPTION_IMAGES" + _RBD_POOL_STAT_OPTION_IMAGE_PROVISIONED_BYTES "RBD_POOL_STAT_OPTION_IMAGE_PROVISIONED_BYTES" + _RBD_POOL_STAT_OPTION_IMAGE_MAX_PROVISIONED_BYTES "RBD_POOL_STAT_OPTION_IMAGE_MAX_PROVISIONED_BYTES" + _RBD_POOL_STAT_OPTION_IMAGE_SNAPSHOTS "RBD_POOL_STAT_OPTION_IMAGE_SNAPSHOTS" + _RBD_POOL_STAT_OPTION_TRASH_IMAGES "RBD_POOL_STAT_OPTION_TRASH_IMAGES" + _RBD_POOL_STAT_OPTION_TRASH_PROVISIONED_BYTES "RBD_POOL_STAT_OPTION_TRASH_PROVISIONED_BYTES" + _RBD_POOL_STAT_OPTION_TRASH_MAX_PROVISIONED_BYTES "RBD_POOL_STAT_OPTION_TRASH_MAX_PROVISIONED_BYTES" + _RBD_POOL_STAT_OPTION_TRASH_SNAPSHOTS "RBD_POOL_STAT_OPTION_TRASH_SNAPSHOTS" + 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) @@ -536,6 +547,14 @@ cdef extern from "rbd/librbd.h" nogil: void rbd_config_image_list_cleanup(rbd_config_option_t *options, int max_options) + int rbd_pool_init(rados_ioctx_t, bint force) + + void rbd_pool_stats_create(rbd_pool_stats_t *stats) + void rbd_pool_stats_destroy(rbd_pool_stats_t stats) + int rbd_pool_stats_option_add_uint64(rbd_pool_stats_t stats, + int stat_option, uint64_t* stat_val) + int rbd_pool_stats_get(rados_ioctx_t io, rbd_pool_stats_t stats) + RBD_FEATURE_LAYERING = _RBD_FEATURE_LAYERING RBD_FEATURE_STRIPINGV2 = _RBD_FEATURE_STRIPINGV2 RBD_FEATURE_EXCLUSIVE_LOCK = _RBD_FEATURE_EXCLUSIVE_LOCK @@ -607,6 +626,16 @@ RBD_CONFIG_SOURCE_CONFIG = _RBD_CONFIG_SOURCE_CONFIG RBD_CONFIG_SOURCE_POOL = _RBD_CONFIG_SOURCE_POOL RBD_CONFIG_SOURCE_IMAGE = _RBD_CONFIG_SOURCE_IMAGE +RBD_POOL_STAT_OPTION_IMAGES = _RBD_POOL_STAT_OPTION_IMAGES +RBD_POOL_STAT_OPTION_IMAGE_PROVISIONED_BYTES = _RBD_POOL_STAT_OPTION_IMAGE_PROVISIONED_BYTES +RBD_POOL_STAT_OPTION_IMAGE_MAX_PROVISIONED_BYTES = _RBD_POOL_STAT_OPTION_IMAGE_MAX_PROVISIONED_BYTES +RBD_POOL_STAT_OPTION_IMAGE_SNAPSHOTS = _RBD_POOL_STAT_OPTION_IMAGE_SNAPSHOTS +RBD_POOL_STAT_OPTION_TRASH_IMAGES = _RBD_POOL_STAT_OPTION_TRASH_IMAGES +RBD_POOL_STAT_OPTION_TRASH_PROVISIONED_BYTES = _RBD_POOL_STAT_OPTION_TRASH_PROVISIONED_BYTES +RBD_POOL_STAT_OPTION_TRASH_MAX_PROVISIONED_BYTES = _RBD_POOL_STAT_OPTION_TRASH_MAX_PROVISIONED_BYTES +RBD_POOL_STAT_OPTION_TRASH_SNAPSHOTS = _RBD_POOL_STAT_OPTION_TRASH_SNAPSHOTS + + class Error(Exception): pass @@ -1818,6 +1847,94 @@ class RBD(object): if ret != 0: raise make_ex(ret, 'error renaming group') + def pool_init(self, ioctx, force): + """ + Initialize an RBD pool + :param ioctx: determines which RADOS pool + :type ioctx: :class:`rados.Ioctx` + :param force: force init + :type force: bool + """ + cdef: + rados_ioctx_t _ioctx = convert_ioctx(ioctx) + bint _force = force + with nogil: + ret = rbd_pool_init(_ioctx, _force) + if ret != 0: + raise make_ex(ret, 'error initializing pool') + + def pool_stats_get(self, ioctx): + """ + Return RBD pool stats + + :param ioctx: determines which RADOS pool + :type ioctx: :class:`rados.Ioctx` + :returns: dict - contains the following keys: + + * ``image_count`` (int) - image count + + * ``image_provisioned_bytes`` (int) - image total HEAD provisioned bytes + + * ``image_max_provisioned_bytes`` (int) - image total max provisioned bytes + + * ``image_snap_count`` (int) - image snap count + + * ``trash_count`` (int) - trash image count + + * ``trash_provisioned_bytes`` (int) - trash total HEAD provisioned bytes + + * ``trash_max_provisioned_bytes`` (int) - trash total max provisioned bytes + + * ``trash_snap_count`` (int) - trash snap count + + """ + cdef: + rados_ioctx_t _ioctx = convert_ioctx(ioctx) + uint64_t _image_count = 0 + uint64_t _image_provisioned_bytes = 0 + uint64_t _image_max_provisioned_bytes = 0 + uint64_t _image_snap_count = 0 + uint64_t _trash_count = 0 + uint64_t _trash_provisioned_bytes = 0 + uint64_t _trash_max_provisioned_bytes = 0 + uint64_t _trash_snap_count = 0 + rbd_pool_stats_t _stats + + rbd_pool_stats_create(&_stats) + rbd_pool_stats_option_add_uint64(_stats, RBD_POOL_STAT_OPTION_IMAGES, + &_image_count) + rbd_pool_stats_option_add_uint64(_stats, RBD_POOL_STAT_OPTION_IMAGE_PROVISIONED_BYTES, + &_image_provisioned_bytes) + rbd_pool_stats_option_add_uint64(_stats, RBD_POOL_STAT_OPTION_IMAGE_MAX_PROVISIONED_BYTES, + &_image_max_provisioned_bytes) + rbd_pool_stats_option_add_uint64(_stats, RBD_POOL_STAT_OPTION_IMAGE_SNAPSHOTS, + &_image_snap_count) + rbd_pool_stats_option_add_uint64(_stats, RBD_POOL_STAT_OPTION_TRASH_IMAGES, + &_trash_count) + rbd_pool_stats_option_add_uint64(_stats, RBD_POOL_STAT_OPTION_TRASH_PROVISIONED_BYTES, + &_trash_provisioned_bytes) + rbd_pool_stats_option_add_uint64(_stats, RBD_POOL_STAT_OPTION_TRASH_MAX_PROVISIONED_BYTES, + &_trash_max_provisioned_bytes) + rbd_pool_stats_option_add_uint64(_stats, RBD_POOL_STAT_OPTION_TRASH_SNAPSHOTS, + &_trash_snap_count) + try: + with nogil: + ret = rbd_pool_stats_get(_ioctx, _stats) + if ret != 0: + raise make_ex(ret, 'error retrieving pool stats') + else: + return {'image_count': _image_count, + 'image_provisioned_bytes': _image_provisioned_bytes, + 'image_max_provisioned_bytes': _image_max_provisioned_bytes, + 'image_snap_count': _image_snap_count, + 'trash_count': _trash_count, + 'trash_provisioned_bytes': _trash_provisioned_bytes, + 'trash_max_provisioned_bytes': _trash_max_provisioned_bytes, + 'trash_snap_count': _trash_snap_count} + finally: + rbd_pool_stats_destroy(_stats) + + cdef class MirrorPeerIterator(object): """ Iterator over mirror peer info for a pool. diff --git a/src/test/librbd/test_librbd.cc b/src/test/librbd/test_librbd.cc index f77c5fdf682b..fd3373d4de40 100644 --- a/src/test/librbd/test_librbd.cc +++ b/src/test/librbd/test_librbd.cc @@ -7363,6 +7363,82 @@ TEST_F(TestLibRBD, ConfigPP) } } +TEST_F(TestLibRBD, PoolStatsPP) +{ + REQUIRE_FORMAT_V2(); + + librados::IoCtx ioctx; + ASSERT_EQ(0, _rados.ioctx_create(create_pool(true).c_str(), ioctx)); + + librbd::RBD rbd; + std::string image_name; + uint64_t size = 2 << 20; + uint64_t expected_size = 0; + for (size_t idx = 0; idx < 4; ++idx) { + image_name = get_temp_image_name(); + + int order = 0; + ASSERT_EQ(0, create_image_pp(rbd, ioctx, image_name.c_str(), size, &order)); + expected_size += size; + } + + librbd::Image image; + ASSERT_EQ(0, rbd.open(ioctx, image, image_name.c_str(), NULL)); + ASSERT_EQ(0, image.snap_create("snap1")); + ASSERT_EQ(0, image.resize(0)); + ASSERT_EQ(0, image.close()); + uint64_t expect_head_size = (expected_size - size); + + uint64_t image_count; + uint64_t provisioned_bytes; + uint64_t max_provisioned_bytes; + uint64_t snap_count; + uint64_t trash_image_count; + uint64_t trash_provisioned_bytes; + uint64_t trash_max_provisioned_bytes; + uint64_t trash_snap_count; + + librbd::PoolStats pool_stats1; + pool_stats1.add(RBD_POOL_STAT_OPTION_IMAGES, &image_count); + pool_stats1.add(RBD_POOL_STAT_OPTION_IMAGE_PROVISIONED_BYTES, + &provisioned_bytes); + ASSERT_EQ(0, rbd.pool_stats_get(ioctx, &pool_stats1)); + + ASSERT_EQ(4U, image_count); + ASSERT_EQ(expect_head_size, provisioned_bytes); + + pool_stats1.add(RBD_POOL_STAT_OPTION_IMAGE_MAX_PROVISIONED_BYTES, + &max_provisioned_bytes); + ASSERT_EQ(0, rbd.pool_stats_get(ioctx, &pool_stats1)); + ASSERT_EQ(4U, image_count); + ASSERT_EQ(expect_head_size, provisioned_bytes); + ASSERT_EQ(expected_size, max_provisioned_bytes); + + librbd::PoolStats pool_stats2; + pool_stats2.add(RBD_POOL_STAT_OPTION_IMAGE_SNAPSHOTS, &snap_count); + pool_stats2.add(RBD_POOL_STAT_OPTION_TRASH_IMAGES, &trash_image_count); + pool_stats2.add(RBD_POOL_STAT_OPTION_TRASH_SNAPSHOTS, &trash_snap_count); + ASSERT_EQ(0, rbd.pool_stats_get(ioctx, &pool_stats2)); + ASSERT_EQ(1U, snap_count); + ASSERT_EQ(0U, trash_image_count); + ASSERT_EQ(0U, trash_snap_count); + + ASSERT_EQ(0, rbd.trash_move(ioctx, image_name.c_str(), 0)); + + librbd::PoolStats pool_stats3; + pool_stats3.add(RBD_POOL_STAT_OPTION_TRASH_IMAGES, &trash_image_count); + pool_stats3.add(RBD_POOL_STAT_OPTION_TRASH_PROVISIONED_BYTES, + &trash_provisioned_bytes); + pool_stats3.add(RBD_POOL_STAT_OPTION_TRASH_MAX_PROVISIONED_BYTES, + &trash_max_provisioned_bytes); + pool_stats3.add(RBD_POOL_STAT_OPTION_TRASH_SNAPSHOTS, &trash_snap_count); + ASSERT_EQ(0, rbd.pool_stats_get(ioctx, &pool_stats3)); + ASSERT_EQ(1U, trash_image_count); + ASSERT_EQ(0U, trash_provisioned_bytes); + ASSERT_EQ(size, trash_max_provisioned_bytes); + ASSERT_EQ(1U, trash_snap_count); +} + // poorman's ceph_assert() namespace ceph { void __ceph_assert_fail(const char *assertion, const char *file, int line, diff --git a/src/test/librbd/test_support.cc b/src/test/librbd/test_support.cc index b0eb932e9830..e74068e6600a 100644 --- a/src/test/librbd/test_support.cc +++ b/src/test/librbd/test_support.cc @@ -115,9 +115,9 @@ int create_image_data_pool(librados::Rados &rados, std::string &data_pool, bool if (r < 0) { return r; } - ioctx.application_enable("rbd", true); - return r; + librbd::RBD rbd; + return rbd.pool_init(ioctx, true); } bool is_librados_test_stub(librados::Rados &rados) { diff --git a/src/test/pybind/test_rbd.py b/src/test/pybind/test_rbd.py index 3b8ef8e29ee0..f9f5131ccd7d 100644 --- a/src/test/pybind/test_rbd.py +++ b/src/test/pybind/test_rbd.py @@ -51,7 +51,7 @@ def setup_module(): rados.create_pool(pool_name) global ioctx ioctx = rados.open_ioctx(pool_name) - ioctx.application_enable('rbd') + RBD().pool_init(ioctx, True) global features features = os.getenv("RBD_FEATURES") features = int(features) if features is not None else 61 @@ -92,6 +92,7 @@ def create_image(): features=int(features)) else: RBD().create(ioctx, image_name, IMG_SIZE, IMG_ORDER, old_format=True) + return image_name def remove_image(): if image_name is not None: @@ -377,6 +378,36 @@ def test_config_list(): for option in rbd.config_list(ioctx): eq(option['source'], RBD_CONFIG_SOURCE_CONFIG) +@require_new_format() +def test_pool_stats(): + rbd = RBD() + + try: + image1 = create_image() + image2 = create_image() + image3 = create_image() + image4 = create_image() + with Image(ioctx, image4) as image: + image.create_snap('snap') + image.resize(0) + + stats = rbd.pool_stats_get(ioctx) + eq(stats['image_count'], 4) + eq(stats['image_provisioned_bytes'], 3 * IMG_SIZE) + eq(stats['image_max_provisioned_bytes'], 4 * IMG_SIZE) + eq(stats['image_snap_count'], 1) + eq(stats['trash_count'], 0) + eq(stats['trash_provisioned_bytes'], 0) + eq(stats['trash_max_provisioned_bytes'], 0) + eq(stats['trash_snap_count'], 0) + finally: + rbd.remove(ioctx, image1) + rbd.remove(ioctx, image2) + rbd.remove(ioctx, image3) + with Image(ioctx, image4) as image: + image.remove_snap('snap') + rbd.remove(ioctx, image4) + def rand_data(size): return os.urandom(size) @@ -1806,6 +1837,7 @@ class TestTrash(object): RBD().trash_move(ioctx, image_name, 1000) assert_raises(PermissionError, RBD().trash_remove, ioctx, image_id) + RBD().trash_remove(ioctx, image_id, True) def test_remove(self): create_image()