From: Douglas Fuller Date: Mon, 9 May 2016 18:21:21 +0000 (-0700) Subject: rbd: add methods to set and get snapshot limits X-Git-Tag: v11.0.0~146^2 X-Git-Url: http://git-server-git.apps.pok.os.sepia.ceph.com/?a=commitdiff_plain;h=653bc453e3c8f1062cdbc4d0d8f77f623f48915b;p=ceph.git rbd: add methods to set and get snapshot limits Add a subcommand, rbd snap limit set --limit , to limit the number of snapshots that may be made of a given image. To clear, use rbd snap limit clear . Add an omap key, 'snap_limit' to the RBD header for implementation. Add object classes to set and query the limit. Older OSDs will ignore the limit. Fixes: http://tracker.ceph.com/issues/15706 Signed-off-by: Douglas Fuller --- diff --git a/doc/man/8/rbd.rst b/doc/man/8/rbd.rst index 6a327e99888e..42ef2c850373 100644 --- a/doc/man/8/rbd.rst +++ b/doc/man/8/rbd.rst @@ -159,6 +159,10 @@ Parameters by examining the in-memory object map instead of querying RADOS for each object within the image. +.. option:: --limit + + Specifies the limit for the number of snapshots permitted. + Commands ======== @@ -313,6 +317,13 @@ Commands This requires image format 2. +:command:`snap limit set` [--limit] *limit* *image-spec* + Set a limit for the number of snapshots allowed on an image. + +:command:`snap limit clear` *image-spec* + Remove any previously set limit on the number of snapshots allowed on + an image. + :command:`map` [-o | --options *map-options* ] [--read-only] *image-spec* | *snap-spec* Maps the specified image to a block device via the rbd kernel module. diff --git a/src/cls/rbd/cls_rbd.cc b/src/cls/rbd/cls_rbd.cc index 78fe30808342..979f81668ace 100644 --- a/src/cls/rbd/cls_rbd.cc +++ b/src/cls/rbd/cls_rbd.cc @@ -108,6 +108,8 @@ cls_method_handle_t h_metadata_set; cls_method_handle_t h_metadata_remove; cls_method_handle_t h_metadata_list; cls_method_handle_t h_metadata_get; +cls_method_handle_t h_snapshot_get_limit; +cls_method_handle_t h_snapshot_set_limit; cls_method_handle_t h_old_snapshots_list; cls_method_handle_t h_old_snapshot_add; cls_method_handle_t h_old_snapshot_remove; @@ -1499,6 +1501,7 @@ int snapshot_add(cls_method_context_t hctx, bufferlist *in, bufferlist *out) { bufferlist snap_namebl, snap_idbl; cls_rbd_snap snap_meta; + uint64_t snap_limit; try { bufferlist::iterator iter = in->begin(); @@ -1542,7 +1545,16 @@ int snapshot_add(cls_method_context_t hctx, bufferlist *in, bufferlist *out) return r; } + r = read_key(hctx, "snap_limit", &snap_limit); + if (r == -ENOENT) { + snap_limit = UINT64_MAX; + } else if (r < 0) { + CLS_ERR("Could not read snapshot limit off disk: %s", cpp_strerror(r).c_str()); + return r; + } + int max_read = RBD_MAX_KEYS_READ; + uint64_t total_read = 0; string last_read = RBD_SNAP_KEY_PREFIX; do { map vals; @@ -1551,6 +1563,12 @@ int snapshot_add(cls_method_context_t hctx, bufferlist *in, bufferlist *out) if (r < 0) return r; + total_read += vals.size(); + if (total_read >= snap_limit) { + CLS_ERR("Attempt to create snapshot over limit of %lu", snap_limit); + return -EDQUOT; + } + for (map::iterator it = vals.begin(); it != vals.end(); ++it) { cls_rbd_snap old_meta; @@ -2675,6 +2693,50 @@ int metadata_get(cls_method_context_t hctx, bufferlist *in, bufferlist *out) return 0; } +int snapshot_get_limit(cls_method_context_t hctx, bufferlist *in, + bufferlist *out) +{ + int rc; + uint64_t snap_limit; + + rc = read_key(hctx, "snap_limit", &snap_limit); + if (rc == -ENOENT) { + rc = 0; + ::encode(UINT64_MAX, *out); + } else { + ::encode(snap_limit, *out); + } + + CLS_LOG(20, "read snapshot limit %lu", snap_limit); + return rc; +} + +int snapshot_set_limit(cls_method_context_t hctx, bufferlist *in, + bufferlist *out) +{ + int rc; + uint64_t new_limit; + bufferlist bl; + + try { + bufferlist::iterator iter = in->begin(); + ::decode(new_limit, iter); + } catch (const buffer::error &err) { + return -EINVAL; + } + + if (new_limit == UINT64_MAX) { + CLS_LOG(20, "remove snapshot limit\n"); + rc = cls_cxx_map_remove_key(hctx, "snap_limit"); + } else { + CLS_LOG(20, "set snapshot limit to %lu\n", new_limit); + ::encode(new_limit, bl); + rc = cls_cxx_map_set_val(hctx, "snap_limit", &bl); + } + + return rc; +} + /****************************** Old format *******************************/ @@ -2746,6 +2808,17 @@ int old_snapshot_add(cls_method_context_t hctx, bufferlist *in, bufferlist *out) if (header->snap_seq > snap_id) return -ESTALE; + uint64_t snap_limit; + rc = read_key(hctx, "snap_limit", &snap_limit); + if (rc == -ENOENT) { + snap_limit = UINT64_MAX; + } else if (rc < 0) { + return rc; + } + + if (header->snap_count >= snap_limit) + return -EDQUOT; + const char *cur_snap_name; for (cur_snap_name = snap_names; cur_snap_name < end; cur_snap_name += strlen(cur_snap_name) + 1) { if (strncmp(cur_snap_name, snap_name, end - cur_snap_name) == 0) @@ -2967,6 +3040,7 @@ int old_snapshot_rename(cls_method_context_t hctx, bufferlist *in, bufferlist *o return 0; } + namespace mirror { static const std::string UUID("mirror_uuid"); @@ -4215,6 +4289,12 @@ void __cls_init() cls_register_cxx_method(h_class, "metadata_get", CLS_METHOD_RD, metadata_get, &h_metadata_get); + cls_register_cxx_method(h_class, "snapshot_get_limit", + CLS_METHOD_RD, + snapshot_get_limit, &h_snapshot_get_limit); + cls_register_cxx_method(h_class, "snapshot_set_limit", + CLS_METHOD_WR, + snapshot_set_limit, &h_snapshot_set_limit); /* methods for the rbd_children object */ cls_register_cxx_method(h_class, "add_child", diff --git a/src/cls/rbd/cls_rbd_client.cc b/src/cls/rbd/cls_rbd_client.cc index ec57f7f8daae..0a51f924c107 100644 --- a/src/cls/rbd/cls_rbd_client.cc +++ b/src/cls/rbd/cls_rbd_client.cc @@ -653,6 +653,33 @@ namespace librbd { op->exec("rbd", "set_protection_status", in); } + int snapshot_get_limit(librados::IoCtx *ioctx, const std::string &oid, + uint64_t *limit) + { + bufferlist in, out; + int r = ioctx->exec(oid, "rbd", "snapshot_get_limit", in, out); + + if (r < 0) { + return r; + } + + try { + bufferlist::iterator iter = out.begin(); + ::decode(*limit, iter); + } catch (const buffer::error &err) { + return -EBADMSG; + } + + return 0; + } + + void snapshot_set_limit(librados::ObjectWriteOperation *op, uint64_t limit) + { + bufferlist in; + ::encode(limit, in); + op->exec("rbd", "snapshot_set_limit", in); + } + void get_stripe_unit_count_start(librados::ObjectReadOperation *op) { bufferlist empty_bl; op->exec("rbd", "get_stripe_unit_count", empty_bl); diff --git a/src/cls/rbd/cls_rbd_client.h b/src/cls/rbd/cls_rbd_client.h index b3dd22eb3753..0696bd1e9f2a 100644 --- a/src/cls/rbd/cls_rbd_client.h +++ b/src/cls/rbd/cls_rbd_client.h @@ -122,6 +122,10 @@ namespace librbd { snapid_t snap_id, uint8_t protection_status); void set_protection_status(librados::ObjectWriteOperation *op, snapid_t snap_id, uint8_t protection_status); + int snapshot_get_limit(librados::IoCtx *ioctx, const std::string &oid, + uint64_t *limit); + void snapshot_set_limit(librados::ObjectWriteOperation *op, + uint64_t limit); void get_stripe_unit_count_start(librados::ObjectReadOperation *op); int get_stripe_unit_count_finish(bufferlist::iterator *it, diff --git a/src/include/rbd/librbd.h b/src/include/rbd/librbd.h index fb61b8f70bd3..c56167150445 100644 --- a/src/include/rbd/librbd.h +++ b/src/include/rbd/librbd.h @@ -354,6 +354,23 @@ CEPH_RBD_API int rbd_snap_unprotect(rbd_image_t image, const char *snap_name); */ CEPH_RBD_API int rbd_snap_is_protected(rbd_image_t image, const char *snap_name, int *is_protected); +/** + * Get the current snapshot limit for an image. If no limit is set, + * UINT64_MAX is returned. + * + * @param limit pointer where the limit will be stored on success + * @returns 0 on success, negative error code on failure + */ +CEPH_RBD_API int rbd_snap_get_limit(rbd_image_t image, uint64_t *limit); + +/** + * Set a limit for the number of snapshots that may be taken of an image. + * + * @param limit the maximum number of snapshots allowed in the future. + * @returns 0 on success, negative error code on failure + */ +CEPH_RBD_API int rbd_snap_set_limit(rbd_image_t image, uint64_t limit); + CEPH_RBD_API int rbd_snap_set(rbd_image_t image, const char *snapname); CEPH_RBD_API int rbd_flatten(rbd_image_t image); diff --git a/src/include/rbd/librbd.hpp b/src/include/rbd/librbd.hpp index e4c434ab490f..78c5a50b62c3 100644 --- a/src/include/rbd/librbd.hpp +++ b/src/include/rbd/librbd.hpp @@ -247,6 +247,8 @@ public: int snap_is_protected(const char *snap_name, bool *is_protected); int snap_set(const char *snap_name); int snap_rename(const char *srcname, const char *dstname); + int snap_get_limit(uint64_t *limit); + int snap_set_limit(uint64_t limit); /* I/O */ ssize_t read(uint64_t ofs, size_t len, ceph::bufferlist& bl); diff --git a/src/librbd/CMakeLists.txt b/src/librbd/CMakeLists.txt index e75407ce031b..5f8eba51dd6e 100644 --- a/src/librbd/CMakeLists.txt +++ b/src/librbd/CMakeLists.txt @@ -59,6 +59,7 @@ set(librbd_internal_srcs operation/SnapshotRenameRequest.cc operation/SnapshotRollbackRequest.cc operation/SnapshotUnprotectRequest.cc + operation/SnapshotLimitRequest.cc operation/TrimRequest.cc) add_library(rbd_api STATIC librbd.cc) diff --git a/src/librbd/Makefile.am b/src/librbd/Makefile.am index 08c9738806ee..4cf7f5559736 100644 --- a/src/librbd/Makefile.am +++ b/src/librbd/Makefile.am @@ -64,6 +64,7 @@ librbd_internal_la_SOURCES = \ librbd/operation/SnapshotRenameRequest.cc \ librbd/operation/SnapshotRollbackRequest.cc \ librbd/operation/SnapshotUnprotectRequest.cc \ + librbd/operation/SnapshotLimitRequest.cc \ librbd/operation/TrimRequest.cc noinst_LTLIBRARIES += librbd_internal.la @@ -154,6 +155,7 @@ noinst_HEADERS += \ librbd/operation/SnapshotRenameRequest.h \ librbd/operation/SnapshotRollbackRequest.h \ librbd/operation/SnapshotUnprotectRequest.h \ + librbd/operation/SnapshotLimitRequest.h \ librbd/operation/TrimRequest.h endif # WITH_RBD diff --git a/src/librbd/Operations.cc b/src/librbd/Operations.cc index c523b912680d..8e314e759453 100644 --- a/src/librbd/Operations.cc +++ b/src/librbd/Operations.cc @@ -21,6 +21,7 @@ #include "librbd/operation/SnapshotRenameRequest.h" #include "librbd/operation/SnapshotRollbackRequest.h" #include "librbd/operation/SnapshotUnprotectRequest.h" +#include "librbd/operation/SnapshotLimitRequest.h" #include #include @@ -1023,6 +1024,56 @@ void Operations::execute_snap_unprotect(const char *snap_name, request->send(); } +template +int Operations::snap_set_limit(uint64_t limit) { + CephContext *cct = m_image_ctx.cct; + ldout(cct, 5) << this << " " << __func__ << ": limit=" << limit << dendl; + + if (m_image_ctx.read_only) { + return -EROFS; + } + + int r = m_image_ctx.state->refresh_if_required(); + if (r < 0) { + return r; + } + + { + RWLock::RLocker owner_lock(m_image_ctx.owner_lock); + C_SaferCond limit_ctx; + + if (m_image_ctx.exclusive_lock != nullptr && + !m_image_ctx.exclusive_lock->is_lock_owner()) { + C_SaferCond lock_ctx; + + m_image_ctx.exclusive_lock->request_lock(&lock_ctx); + r = lock_ctx.wait(); + if (r < 0) { + return r; + } + } + + execute_snap_set_limit(limit, &limit_ctx); + r = limit_ctx.wait(); + } + + return r; +} + +template +void Operations::execute_snap_set_limit(const uint64_t limit, + Context *on_finish) { + assert(m_image_ctx.owner_lock.is_locked()); + + CephContext *cct = m_image_ctx.cct; + ldout(cct, 5) << this << " " << __func__ << ": limit=" << limit + << dendl; + + operation::SnapshotLimitRequest *request = + new operation::SnapshotLimitRequest(m_image_ctx, on_finish, limit); + request->send(); +} + template int Operations::prepare_image_update() { assert(m_image_ctx.owner_lock.is_locked() && diff --git a/src/librbd/Operations.h b/src/librbd/Operations.h index 95af4dcf1e96..fb0f8379a1ea 100644 --- a/src/librbd/Operations.h +++ b/src/librbd/Operations.h @@ -58,6 +58,9 @@ public: int snap_unprotect(const char *snap_name); void execute_snap_unprotect(const char *snap_name, Context *on_finish); + int snap_set_limit(uint64_t limit); + void execute_snap_set_limit(uint64_t limit, Context *on_finish); + int prepare_image_update(); private: diff --git a/src/librbd/internal.cc b/src/librbd/internal.cc index 92fab7f4cf12..817de81f4169 100644 --- a/src/librbd/internal.cc +++ b/src/librbd/internal.cc @@ -2148,6 +2148,17 @@ remove_mirroring_image: return 0; } + int snap_get_limit(ImageCtx *ictx, uint64_t *limit) + { + return cls_client::snapshot_get_limit(&ictx->md_ctx, ictx->header_oid, + limit); + } + + int snap_set_limit(ImageCtx *ictx, uint64_t limit) + { + return ictx->operations->snap_set_limit(limit); + } + struct CopyProgressCtx { explicit CopyProgressCtx(ProgressContext &p) : destictx(NULL), src_size(0), prog_ctx(p) diff --git a/src/librbd/internal.h b/src/librbd/internal.h index dcb03509d509..74f4b98dca9d 100644 --- a/src/librbd/internal.h +++ b/src/librbd/internal.h @@ -128,6 +128,8 @@ namespace librbd { ProgressContext& prog_ctx); int snap_list(ImageCtx *ictx, std::vector& snaps); int snap_exists(ImageCtx *ictx, const char *snap_name, bool *exists); + int snap_get_limit(ImageCtx *ictx, uint64_t *limit); + int snap_set_limit(ImageCtx *ictx, uint64_t limit); int snap_is_protected(ImageCtx *ictx, const char *snap_name, bool *is_protected); int copy(ImageCtx *ictx, IoCtx& dest_md_ctx, const char *destname, diff --git a/src/librbd/journal/Replay.cc b/src/librbd/journal/Replay.cc index c6c4f55b602f..4bc714ce9fef 100644 --- a/src/librbd/journal/Replay.cc +++ b/src/librbd/journal/Replay.cc @@ -86,6 +86,10 @@ struct ExecuteOp : public Context { on_op_complete); } + void execute(const journal::SnapLimitEvent &_) { + image_ctx.operations->execute_snap_set_limit(event.limit, on_op_complete); + } + virtual void finish(int r) override { RWLock::RLocker owner_locker(image_ctx.owner_lock); execute(event); @@ -599,6 +603,29 @@ void Replay::handle_event(const journal::DemoteEvent &event, on_safe->complete(0); } +template +void Replay::handle_event(const journal::SnapLimitEvent &event, + Context *on_ready, Context *on_safe) { + CephContext *cct = m_image_ctx.cct; + ldout(cct, 20) << this << " " << __func__ << ": Snap limit event" + << dendl; + + Mutex::Locker locker(m_lock); + OpEvent *op_event; + Context *on_op_complete = create_op_context_callback(event.op_tid, on_ready, + on_safe, &op_event); + if (on_op_complete == nullptr) { + return; + } + + op_event->on_op_finish_event = new C_RefreshIfRequired( + m_image_ctx, new ExecuteOp(m_image_ctx, + event, + on_op_complete)); + + on_ready->complete(0); +} + template void Replay::handle_event(const journal::UnknownEvent &event, Context *on_ready, Context *on_safe) { diff --git a/src/librbd/journal/Replay.h b/src/librbd/journal/Replay.h index aeca5ba26db6..ceb67466b011 100644 --- a/src/librbd/journal/Replay.h +++ b/src/librbd/journal/Replay.h @@ -154,6 +154,8 @@ private: Context *on_safe); void handle_event(const DemoteEvent &event, Context *on_ready, Context *on_safe); + void handle_event(const SnapLimitEvent &event, Context *on_ready, + Context *on_safe); void handle_event(const UnknownEvent &event, Context *on_ready, Context *on_safe); diff --git a/src/librbd/journal/Types.cc b/src/librbd/journal/Types.cc index 8f9f942733ac..91a9b678e8f9 100644 --- a/src/librbd/journal/Types.cc +++ b/src/librbd/journal/Types.cc @@ -154,6 +154,21 @@ void SnapEventBase::dump(Formatter *f) const { f->dump_string("snap_name", snap_name); } +void SnapLimitEvent::encode(bufferlist &bl) const { + OpEventBase::encode(bl); + ::encode(limit, bl); +} + +void SnapLimitEvent::decode(__u8 version, bufferlist::iterator& it) { + OpEventBase::decode(version, it); + ::decode(limit, it); +} + +void SnapLimitEvent::dump(Formatter *f) const { + OpEventBase::dump(f); + f->dump_unsigned("limit", limit); +} + void SnapRenameEvent::encode(bufferlist& bl) const { SnapEventBase::encode(bl); ::encode(snap_id, bl); diff --git a/src/librbd/journal/Types.h b/src/librbd/journal/Types.h index 4008a0f15bfa..11a0621917c2 100644 --- a/src/librbd/journal/Types.h +++ b/src/librbd/journal/Types.h @@ -35,7 +35,8 @@ enum EventType { EVENT_TYPE_RENAME = 10, EVENT_TYPE_RESIZE = 11, EVENT_TYPE_FLATTEN = 12, - EVENT_TYPE_DEMOTE = 13 + EVENT_TYPE_DEMOTE = 13, + EVENT_TYPE_SNAP_LIMIT = 14 }; struct AioDiscardEvent { @@ -202,6 +203,21 @@ struct SnapUnprotectEvent : public SnapEventBase { using SnapEventBase::dump; }; +struct SnapLimitEvent : public OpEventBase { + static const EventType TYPE = EVENT_TYPE_SNAP_LIMIT; + uint64_t limit; + + SnapLimitEvent() { + } + SnapLimitEvent(uint64_t op_tid, const uint64_t _limit) + : OpEventBase(op_tid), limit(_limit) { + } + + void encode(bufferlist& bl) const; + void decode(__u8 version, bufferlist::iterator& it); + void dump(Formatter *f) const; +}; + struct SnapRollbackEvent : public SnapEventBase { static const EventType TYPE = EVENT_TYPE_SNAP_ROLLBACK; @@ -291,6 +307,7 @@ typedef boost::variant Event; struct EventEntry { diff --git a/src/librbd/librbd.cc b/src/librbd/librbd.cc index 2cb5132ed24b..ab7c04f47810 100644 --- a/src/librbd/librbd.cc +++ b/src/librbd/librbd.cc @@ -983,6 +983,25 @@ namespace librbd { return r; } + int Image::snap_get_limit(uint64_t *limit) + { + ImageCtx *ictx = (ImageCtx *)ctx; + tracepoint(librbd, snap_get_limit_enter, ictx, ictx->name.c_str()); + int r = librbd::snap_get_limit(ictx, limit); + tracepoint(librbd, snap_get_limit_exit, r, *limit); + return r; + } + + int Image::snap_set_limit(uint64_t limit) + { + ImageCtx *ictx = (ImageCtx *)ctx; + + tracepoint(librbd, snap_set_limit_enter, ictx, ictx->name.c_str(), limit); + int r = ictx->operations->snap_set_limit(limit); + tracepoint(librbd, snap_set_limit_exit, r); + return r; + } + int Image::snap_set(const char *snap_name) { ImageCtx *ictx = (ImageCtx *)ctx; @@ -2224,6 +2243,24 @@ extern "C" int rbd_snap_is_protected(rbd_image_t image, const char *snap_name, return 0; } +extern "C" int rbd_snap_get_limit(rbd_image_t image, uint64_t *limit) +{ + librbd::ImageCtx *ictx = (librbd::ImageCtx *)image; + tracepoint(librbd, snap_get_limit_enter, ictx, ictx->name.c_str()); + int r = librbd::snap_get_limit(ictx, limit); + tracepoint(librbd, snap_get_limit_exit, r, *limit); + return r; +} + +extern "C" int rbd_snap_set_limit(rbd_image_t image, uint64_t limit) +{ + librbd::ImageCtx *ictx = (librbd::ImageCtx *)image; + tracepoint(librbd, snap_set_limit_enter, ictx, ictx->name.c_str(), limit); + int r = librbd::snap_set_limit(ictx, limit); + tracepoint(librbd, snap_set_limit_exit, r); + return r; +} + extern "C" int rbd_snap_set(rbd_image_t image, const char *snap_name) { librbd::ImageCtx *ictx = (librbd::ImageCtx *)image; diff --git a/src/librbd/operation/SnapshotLimitRequest.cc b/src/librbd/operation/SnapshotLimitRequest.cc new file mode 100644 index 000000000000..670e0f0c165f --- /dev/null +++ b/src/librbd/operation/SnapshotLimitRequest.cc @@ -0,0 +1,67 @@ +// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- +// vim: ts=8 sw=2 smarttab + +#include "librbd/operation/SnapshotLimitRequest.h" +#include "common/dout.h" +#include "common/errno.h" +#include "librbd/ImageCtx.h" + +#define dout_subsys ceph_subsys_rbd +#undef dout_prefix +#define dout_prefix *_dout << "librbd::SnapshotLimitRequest: " + +namespace librbd { +namespace operation { + +template +SnapshotLimitRequest::SnapshotLimitRequest(I &image_ctx, + Context *on_finish, + uint64_t limit) + : Request(image_ctx, on_finish), m_snap_limit(limit) { +} + +template +void SnapshotLimitRequest::send_op() { + send_limit_snaps(); +} + +template +bool SnapshotLimitRequest::should_complete(int r) { + I &image_ctx = this->m_image_ctx; + CephContext *cct = image_ctx.cct; + ldout(cct, 5) << this << " " << __func__ << "r=" << r << dendl; + + if (r < 0) { + lderr(cct) << "encountered error: " << cpp_strerror(r) << dendl; + } + return true; +} + +template +void SnapshotLimitRequest::send_limit_snaps() { + I &image_ctx = this->m_image_ctx; + assert(image_ctx.owner_lock.is_locked()); + + CephContext *cct = image_ctx.cct; + ldout(cct, 5) << this << " " << __func__ << dendl; + + { + RWLock::RLocker md_locker(image_ctx.md_lock); + RWLock::RLocker snap_locker(image_ctx.snap_lock); + + librados::ObjectWriteOperation op; + cls_client::snapshot_set_limit(&op, m_snap_limit); + + librados::AioCompletion *rados_completion = + this->create_callback_completion(); + int r = image_ctx.md_ctx.aio_operate(image_ctx.header_oid, rados_completion, + &op); + assert(r == 0); + rados_completion->release(); + } +} + +} // namespace operation +} // namespace librbd + +template class librbd::operation::SnapshotLimitRequest; diff --git a/src/librbd/operation/SnapshotLimitRequest.h b/src/librbd/operation/SnapshotLimitRequest.h new file mode 100644 index 000000000000..bcd205b3d16b --- /dev/null +++ b/src/librbd/operation/SnapshotLimitRequest.h @@ -0,0 +1,44 @@ +// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- +// vim: ts=8 sw=2 smarttab + +#ifndef CEPH_LIBRBD_OPERATION_SNAPSHOT_LIMIT_REQUEST_H +#define CEPH_LIBRBD_OPERATION_SNAPSHOT_LIMIT_REQUEST_H + +#include "librbd/operation/Request.h" +#include +#include + +class Context; + +namespace librbd { + +class ImageCtx; + +namespace operation { + +template +class SnapshotLimitRequest : public Request { +public: + SnapshotLimitRequest(ImageCtxT &image_ctx, Context *on_finish, + uint64_t limit); + +protected: + virtual void send_op(); + virtual bool should_complete(int r); + + virtual journal::Event create_event(uint64_t op_tid) const { + return journal::SnapLimitEvent(op_tid, m_snap_limit); + } + +private: + uint64_t m_snap_limit; + + void send_limit_snaps(); +}; + +} // namespace operation +} // namespace librbd + +extern template class librbd::operation::SnapshotLimitRequest; + +#endif // CEPH_LIBRBD_OPERATION_SNAPSHOT_LIMIT_REQUEST_H diff --git a/src/pybind/rbd/rbd.pyx b/src/pybind/rbd/rbd.pyx index 23a9895cfcb8..ba8910ebfa6e 100644 --- a/src/pybind/rbd/rbd.pyx +++ b/src/pybind/rbd/rbd.pyx @@ -35,6 +35,9 @@ cdef extern from "Python.h": ctypedef int (*librbd_progress_fn_t)(uint64_t offset, uint64_t total, void* ptr) +cdef extern from "limits.h": + cdef uint64_t INT64_MAX + cdef extern from "rbd/librbd.h" nogil: enum: _RBD_FEATURE_LAYERING "RBD_FEATURE_LAYERING" @@ -148,6 +151,8 @@ cdef extern from "rbd/librbd.h" nogil: int rbd_snap_unprotect(rbd_image_t image, const char *snap_name) int rbd_snap_is_protected(rbd_image_t image, const char *snap_name, int *is_protected) + int rbd_snap_get_limit(rbd_image_t image, uint64_t *limit) + int rbd_snap_set_limit(rbd_image_t image, uint64_t limit) int rbd_snap_set(rbd_image_t image, const char *snapname) int rbd_flatten(rbd_image_t image) int rbd_rebuild_object_map(rbd_image_t image, librbd_progress_fn_t cb, @@ -266,6 +271,9 @@ class ConnectionShutdown(Error): class Timeout(Error): pass +class DiskQuotaExceeded(Error): + pass + cdef errno_to_exception = { errno.EPERM : PermissionError, @@ -281,6 +289,7 @@ cdef errno_to_exception = { errno.EDOM : ArgumentOutOfRange, errno.ESHUTDOWN : ConnectionShutdown, errno.ETIMEDOUT : Timeout, + errno.EDQUOT : DiskQuotaExceeded, } cdef make_ex(ret, msg): @@ -1031,6 +1040,45 @@ cdef class Image(object): raise make_ex(ret, 'error checking if snapshot %s@%s is protected' % (self.name, name)) return is_protected == 1 + def get_snap_limit(self): + """ + Get the snapshot limit for an image. + """ + + cdef: + uint64_t limit + with nogil: + ret = rbd_snap_get_limit(self.image, &limit) + if ret != 0: + raise make_ex(ret, 'error getting snapshot limit for %s' % self.name) + return limit + + def set_snap_limit(self, limit): + """ + Set the snapshot limit for an image. + + :param limit: the new limit to set + """ + + cdef: + uint64_t _limit = limit + with nogil: + ret = rbd_snap_set_limit(self.image, _limit) + if ret != 0: + raise make_ex(ret, 'error setting snapshot limit for %s' % self.name) + return ret + + def remove_snap_limit(self): + """ + Remove the snapshot limit for an image, essentially setting + the limit to the maximum size allowed by the implementation. + """ + with nogil: + ret = rbd_snap_set_limit(self.image, UINT64_MAX) + if ret != 0: + raise make_ex(ret, 'error removing snapshot limit for %s' % self.name) + return ret + def set_snap(self, name): """ Set the snapshot to read from. Writes will raise ReadOnlyImage diff --git a/src/test/cli/rbd/help.t b/src/test/cli/rbd/help.t index 4848370feb4a..de34cee4c346 100644 --- a/src/test/cli/rbd/help.t +++ b/src/test/cli/rbd/help.t @@ -66,6 +66,8 @@ resize Resize (expand or shrink) image. showmapped Show the rbd images mapped by the kernel. snap create (snap add) Create a snapshot. + snap limit clear Remove snapshot limit. + snap limit set Limit the number of snapshots. snap list (snap ls) Dump list of image snapshots. snap protect Prevent a snapshot from being deleted. snap purge Deletes all snapshots. @@ -1072,6 +1074,35 @@ --image arg image name --snap arg snapshot name + rbd help snap limit clear + usage: rbd snap limit clear [--pool ] [--image ] + + + Remove snapshot limit. + + Positional arguments + image specification + (example: [/]) + + Optional arguments + -p [ --pool ] arg pool name + --image arg image name + + rbd help snap limit set + usage: rbd snap limit set [--pool ] [--image ] [--limit ] + + + Limit the number of snapshots. + + Positional arguments + image specification + (example: [/]) + + Optional arguments + -p [ --pool ] arg pool name + --image arg image name + --limit arg maximum allowed snapshot count + rbd help snap list usage: rbd snap list [--pool ] [--image ] [--format ] [--pretty-format] diff --git a/src/test/cls_rbd/test_cls_rbd.cc b/src/test/cls_rbd/test_cls_rbd.cc index f438b0fac647..4552bf38426a 100644 --- a/src/test/cls_rbd/test_cls_rbd.cc +++ b/src/test/cls_rbd/test_cls_rbd.cc @@ -1,4 +1,4 @@ -// -*- mode:C; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- +// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- // vim: ts=8 sw=2 smarttab #include "common/ceph_context.h" @@ -519,6 +519,36 @@ TEST_F(TestClsRbd, protection_status) ioctx.close(); } +TEST_F(TestClsRbd, snapshot_limits) +{ + librados::IoCtx ioctx; + ASSERT_EQ(0, _rados.ioctx_create(_pool_name.c_str(), ioctx)); + + librados::ObjectWriteOperation op; + string oid = get_temp_image_name(); + uint64_t limit; + + ASSERT_EQ(-ENOENT, snapshot_get_limit(&ioctx, oid, &limit)); + + ASSERT_EQ(0, create_image(&ioctx, oid, 0, 22, RBD_FEATURE_LAYERING, oid)); + + snapshot_set_limit(&op, 2); + + ASSERT_EQ(0, ioctx.operate(oid, &op)); + + ASSERT_EQ(0, snapshot_get_limit(&ioctx, oid, &limit)); + ASSERT_EQ(2, limit); + + ASSERT_EQ(0, snapshot_add(&ioctx, oid, 10, "snap1")); + ASSERT_EQ(0, snapshot_add(&ioctx, oid, 20, "snap2")); + ASSERT_EQ(-EDQUOT, snapshot_add(&ioctx, oid, 30, "snap3")); + + ASSERT_EQ(0, snapshot_remove(&ioctx, oid, 10)); + ASSERT_EQ(0, snapshot_remove(&ioctx, oid, 20)); + + ioctx.close(); +} + TEST_F(TestClsRbd, parents) { librados::IoCtx ioctx; diff --git a/src/test/librbd/mock/MockOperations.h b/src/test/librbd/mock/MockOperations.h index 5bbd9b364968..5d4e2221e537 100644 --- a/src/test/librbd/mock/MockOperations.h +++ b/src/test/librbd/mock/MockOperations.h @@ -39,6 +39,8 @@ struct MockOperations { Context *on_finish)); MOCK_METHOD2(execute_snap_unprotect, void(const char *snap_name, Context *on_finish)); + MOCK_METHOD2(execute_snap_set_limit, void(uint64_t limit, + Context *on_finish)); }; } // namespace librbd diff --git a/src/test/librbd/test_librbd.cc b/src/test/librbd/test_librbd.cc index c3754610b12d..44149b45869a 100644 --- a/src/test/librbd/test_librbd.cc +++ b/src/test/librbd/test_librbd.cc @@ -1,4 +1,4 @@ -// -*- mode:C; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- +// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- // vim: ts=8 sw=2 smarttab /* * Ceph - scalable distributed file system @@ -2985,6 +2985,69 @@ TEST_F(TestLibRBD, Flatten) ASSERT_PASSED(validate_object_map, clone_image); } +TEST_F(TestLibRBD, SnapshotLimit) +{ + rados_ioctx_t ioctx; + rados_ioctx_create(_cluster, m_pool_name.c_str(), &ioctx); + + rbd_image_t image; + int order = 0; + std::string name = get_temp_image_name(); + uint64_t size = 2 << 20; + uint64_t limit; + + 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_snap_get_limit(image, &limit)); + ASSERT_EQ(UINT64_MAX, limit); + ASSERT_EQ(0, rbd_snap_set_limit(image, 2)); + ASSERT_EQ(0, rbd_snap_get_limit(image, &limit)); + ASSERT_EQ(2, limit); + + ASSERT_EQ(0, rbd_snap_create(image, "snap1")); + ASSERT_EQ(0, rbd_snap_create(image, "snap2")); + ASSERT_EQ(-EDQUOT, rbd_snap_create(image, "snap3")); + ASSERT_EQ(0, rbd_snap_set_limit(image, UINT64_MAX)); + ASSERT_EQ(0, rbd_snap_create(image, "snap3")); + ASSERT_EQ(0, rbd_close(image)); + + rados_ioctx_destroy(ioctx); +} + + +TEST_F(TestLibRBD, SnapshotLimitPP) +{ + librados::IoCtx ioctx; + ASSERT_EQ(0, _rados.ioctx_create(m_pool_name.c_str(), ioctx)); + + { + librbd::RBD rbd; + librbd::Image image; + std::string name = get_temp_image_name(); + uint64_t size = 2 << 20; + int order = 0; + uint64_t limit; + + ASSERT_EQ(0, create_image_pp(rbd, ioctx, name.c_str(), size, &order)); + ASSERT_EQ(0, rbd.open(ioctx, image, name.c_str(), NULL)); + + ASSERT_EQ(0, image.snap_get_limit(&limit)); + ASSERT_EQ(UINT64_MAX, limit); + ASSERT_EQ(0, image.snap_set_limit(2)); + ASSERT_EQ(0, image.snap_get_limit(&limit)); + ASSERT_EQ(2, limit); + + ASSERT_EQ(0, image.snap_create("snap1")); + ASSERT_EQ(0, image.snap_create("snap2")); + ASSERT_EQ(-EDQUOT, image.snap_create("snap3")); + ASSERT_EQ(0, image.snap_set_limit(UINT64_MAX)); + ASSERT_EQ(0, image.snap_create("snap3")); + } + + ioctx.close(); +} + TEST_F(TestLibRBD, RebuildObjectMapViaLockOwner) { REQUIRE_FEATURE(RBD_FEATURE_EXCLUSIVE_LOCK | RBD_FEATURE_OBJECT_MAP); diff --git a/src/test/pybind/test_rbd.py b/src/test/pybind/test_rbd.py index 04a3a3820da9..bc34b08406c3 100644 --- a/src/test/pybind/test_rbd.py +++ b/src/test/pybind/test_rbd.py @@ -12,7 +12,7 @@ from rados import (Rados, LIBRADOS_OP_FLAG_FADVISE_RANDOM) from rbd import (RBD, Image, ImageNotFound, InvalidArgument, ImageExists, ImageBusy, ImageHasSnapshots, ReadOnlyImage, - FunctionNotSupported, ArgumentOutOfRange, + FunctionNotSupported, ArgumentOutOfRange, DiskQuotaExceeded, RBD_FEATURE_LAYERING, RBD_FEATURE_STRIPINGV2, RBD_FEATURE_EXCLUSIVE_LOCK) @@ -502,6 +502,19 @@ class TestImage(object): assert_raises(ImageNotFound, self.image.unprotect_snap, 'snap1') assert_raises(ImageNotFound, self.image.is_protected_snap, 'snap1') + def test_limit_snaps(self): + self.image.set_snap_limit(2) + eq(2, self.image.get_snap_limit()) + self.image.create_snap('snap1') + self.image.create_snap('snap2') + assert_raises(DiskQuotaExceeded, self.image.create_snap, 'snap3') + self.image.remove_snap_limit() + self.image.create_snap('snap3') + + self.image.remove_snap('snap1') + self.image.remove_snap('snap2') + self.image.remove_snap('snap3') + @require_features([RBD_FEATURE_EXCLUSIVE_LOCK]) def test_remove_with_exclusive_lock(self): assert_raises(ImageBusy, remove_image) diff --git a/src/test/run-rbd-unit-tests.sh b/src/test/run-rbd-unit-tests.sh index b14343630387..be1c6d814f6a 100755 --- a/src/test/run-rbd-unit-tests.sh +++ b/src/test/run-rbd-unit-tests.sh @@ -5,7 +5,9 @@ source $(dirname $0)/detect-build-env-vars.sh PATH="$CEPH_BIN:$PATH" +unset RBD_FEATURES unittest_librbd + for i in 0 1 61 109 do RBD_FEATURES=$i unittest_librbd diff --git a/src/tools/rbd/ArgumentTypes.cc b/src/tools/rbd/ArgumentTypes.cc index 94355bbb63f3..ec0aa3e0507b 100644 --- a/src/tools/rbd/ArgumentTypes.cc +++ b/src/tools/rbd/ArgumentTypes.cc @@ -255,6 +255,13 @@ void add_path_options(boost::program_options::options_description *pos, (PATH.c_str(), po::value(), description.c_str()); } +void add_limit_option(po::options_description *opt) { + std::string description = "maximum allowed snapshot count"; + + opt->add_options() + (LIMIT.c_str(), po::value(), description.c_str()); +} + void add_no_progress_option(boost::program_options::options_description *opt) { opt->add_options() (NO_PROGRESS.c_str(), po::bool_switch(), "disable progress output"); diff --git a/src/tools/rbd/ArgumentTypes.h b/src/tools/rbd/ArgumentTypes.h index 50c74aa8ff3b..d8935ae9fcbe 100644 --- a/src/tools/rbd/ArgumentTypes.h +++ b/src/tools/rbd/ArgumentTypes.h @@ -77,6 +77,8 @@ static const std::string PRETTY_FORMAT("pretty-format"); static const std::string VERBOSE("verbose"); static const std::string NO_ERROR("no-error"); +static const std::string LIMIT("limit"); + static const std::set SWITCH_ARGUMENTS = { WHOLE_OBJECT, NO_PROGRESS, PRETTY_FORMAT, VERBOSE, NO_ERROR}; @@ -111,6 +113,7 @@ struct JournalObjectSize {}; std::string get_name_prefix(ArgumentModifier modifier); std::string get_description_prefix(ArgumentModifier modifier); + void add_pool_option(boost::program_options::options_description *opt, ArgumentModifier modifier, const std::string &desc_suffix = ""); @@ -159,6 +162,8 @@ void add_path_options(boost::program_options::options_description *pos, boost::program_options::options_description *opt, const std::string &description); +void add_limit_option(boost::program_options::options_description *opt); + void add_no_progress_option(boost::program_options::options_description *opt); void add_format_options(boost::program_options::options_description *opt); diff --git a/src/tools/rbd/action/Info.cc b/src/tools/rbd/action/Info.cc index 8e31e45028da..65635e9b2a25 100644 --- a/src/tools/rbd/action/Info.cc +++ b/src/tools/rbd/action/Info.cc @@ -69,7 +69,7 @@ static int do_show_info(const char *imgname, librbd::Image& image, librbd::image_info_t info; std::string parent_pool, parent_name, parent_snapname; uint8_t old_format; - uint64_t overlap, features, flags; + uint64_t overlap, features, flags, snap_limit; bool snap_protected = false; librbd::mirror_image_info_t mirror_image; int r; @@ -108,6 +108,10 @@ static int do_show_info(const char *imgname, librbd::Image& image, } } + r = image.snap_get_limit(&snap_limit); + if (r < 0) + return r; + char prefix[RBD_MAX_BLOCK_NAME_SIZE + 1]; strncpy(prefix, info.block_name_prefix, RBD_MAX_BLOCK_NAME_SIZE); prefix[RBD_MAX_BLOCK_NAME_SIZE] = '\0'; @@ -150,6 +154,14 @@ static int do_show_info(const char *imgname, librbd::Image& image, } } + if (snap_limit < UINT64_MAX) { + if (f) { + f->dump_unsigned("snapshot_limit", snap_limit); + } else { + std::cout << "\tsnapshot_limit: " << snap_limit << std::endl; + } + } + // parent info, if present if ((image.parent_info(&parent_pool, &parent_name, &parent_snapname) == 0) && parent_name.length() > 0) { diff --git a/src/tools/rbd/action/Snap.cc b/src/tools/rbd/action/Snap.cc index 345e747fb9ca..1557c8d574e9 100644 --- a/src/tools/rbd/action/Snap.cc +++ b/src/tools/rbd/action/Snap.cc @@ -148,6 +148,16 @@ int do_unprotect_snap(librbd::Image& image, const char *snapname) return 0; } +int do_set_limit(librbd::Image& image, uint64_t limit) +{ + return image.snap_set_limit(limit); +} + +int do_clear_limit(librbd::Image& image) +{ + return image.snap_set_limit(UINT64_MAX); +} + void get_list_arguments(po::options_description *positional, po::options_description *options) { at::add_image_spec_options(positional, options, at::ARGUMENT_MODIFIER_NONE); @@ -409,6 +419,80 @@ int execute_unprotect(const po::variables_map &vm) { return 0; } +void get_set_limit_arguments(po::options_description *pos, + po::options_description *opt) { + at::add_image_spec_options(pos, opt, at::ARGUMENT_MODIFIER_NONE); + at::add_limit_option(opt); +} + +int execute_set_limit(const po::variables_map &vm) { + size_t arg_index = 0; + std::string pool_name; + std::string image_name; + std::string snap_name; + uint64_t limit; + + int r = utils::get_pool_image_snapshot_names( + vm, at::ARGUMENT_MODIFIER_NONE, &arg_index, &pool_name, &image_name, + &snap_name, utils::SNAPSHOT_PRESENCE_NONE, utils::SPEC_VALIDATION_NONE); + + if (vm.count(at::LIMIT)) { + limit = vm[at::LIMIT].as(); + } else { + return -ERANGE; + } + + librados::Rados rados; + librados::IoCtx io_ctx; + librbd::Image image; + r = utils::init_and_open_image(pool_name, image_name, "", false, &rados, + &io_ctx, &image); + if (r < 0) { + return r; + } + + r = do_set_limit(image, limit); + if (r < 0) { + std::cerr << "rbd: setting snapshot limit failed: " << cpp_strerror(r) + << std::endl; + return r; + } + return 0; +} + +void get_clear_limit_arguments(po::options_description *pos, + po::options_description *opt) { + at::add_image_spec_options(pos, opt, at::ARGUMENT_MODIFIER_NONE); +} + +int execute_clear_limit(const po::variables_map &vm) { + size_t arg_index = 0; + std::string pool_name; + std::string image_name; + std::string snap_name; + + int r = utils::get_pool_image_snapshot_names( + vm, at::ARGUMENT_MODIFIER_NONE, &arg_index, &pool_name, &image_name, + &snap_name, utils::SNAPSHOT_PRESENCE_NONE, utils::SPEC_VALIDATION_NONE); + + librados::Rados rados; + librados::IoCtx io_ctx; + librbd::Image image; + r = utils::init_and_open_image(pool_name, image_name, "", false, &rados, + &io_ctx, &image); + if (r < 0) { + return r; + } + + r = do_clear_limit(image); + if (r < 0) { + std::cerr << "rbd: clearing snapshot limit failed: " << cpp_strerror(r) + << std::endl; + return r; + } + return 0; +} + void get_rename_arguments(po::options_description *positional, po::options_description *options) { at::add_snap_spec_options(positional, options, at::ARGUMENT_MODIFIER_SOURCE); @@ -488,6 +572,12 @@ Shell::Action action_protect( Shell::Action action_unprotect( {"snap", "unprotect"}, {}, "Allow a snapshot to be deleted.", "", &get_unprotect_arguments, &execute_unprotect); +Shell::Action action_set_limit( + {"snap", "limit", "set"}, {}, "Limit the number of snapshots.", "", + &get_set_limit_arguments, &execute_set_limit); +Shell::Action action_clear_limit( + {"snap", "limit", "clear"}, {}, "Remove snapshot limit.", "", + &get_clear_limit_arguments, &execute_clear_limit); Shell::Action action_rename( {"snap", "rename"}, {}, "Rename a snapshot.", "", &get_rename_arguments, &execute_rename); diff --git a/src/tracing/librbd.tp b/src/tracing/librbd.tp index f91e4e3ee93b..c162deaf5dd6 100644 --- a/src/tracing/librbd.tp +++ b/src/tracing/librbd.tp @@ -1290,6 +1290,46 @@ TRACEPOINT_EVENT(librbd, snap_exists_exit, ) ) +TRACEPOINT_EVENT(librbd, snap_get_limit_enter, + TP_ARGS( + void*, imagectx, + const char*, name), + TP_FIELDS( + ctf_integer_hex(void*, imagectx, imagectx) + ctf_string(name, name) + ) +) + +TRACEPOINT_EVENT(librbd, snap_get_limit_exit, + TP_ARGS( + int, retval, + uint64_t, limit), + TP_FIELDS( + ctf_integer(int, retval, retval) + ctf_integer(uint64_t, limit, limit) + ) +) + +TRACEPOINT_EVENT(librbd, snap_set_limit_enter, + TP_ARGS( + void*,imagectx, + const char*, name, + const int, limit), + TP_FIELDS( + ctf_integer_hex(void*, imagectx, imagectx) + ctf_string(name, name) + ctf_integer(int, limit, limit) + ) +) + +TRACEPOINT_EVENT(librbd, snap_set_limit_exit, + TP_ARGS( + int, retval), + TP_FIELDS( + ctf_integer(int, retval, retval) + ) +) + TRACEPOINT_EVENT(librbd, snap_set_enter, TP_ARGS( void*, imagectx,