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
========
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.
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;
{
bufferlist snap_namebl, snap_idbl;
cls_rbd_snap snap_meta;
+ uint64_t snap_limit;
try {
bufferlist::iterator iter = in->begin();
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<string, bufferlist> vals;
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<string, bufferlist>::iterator it = vals.begin();
it != vals.end(); ++it) {
cls_rbd_snap old_meta;
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 *******************************/
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)
return 0;
}
+
namespace mirror {
static const std::string UUID("mirror_uuid");
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",
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);
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,
*/
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);
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);
operation/SnapshotRenameRequest.cc
operation/SnapshotRollbackRequest.cc
operation/SnapshotUnprotectRequest.cc
+ operation/SnapshotLimitRequest.cc
operation/TrimRequest.cc)
add_library(rbd_api STATIC librbd.cc)
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
librbd/operation/SnapshotRenameRequest.h \
librbd/operation/SnapshotRollbackRequest.h \
librbd/operation/SnapshotUnprotectRequest.h \
+ librbd/operation/SnapshotLimitRequest.h \
librbd/operation/TrimRequest.h
endif # WITH_RBD
#include "librbd/operation/SnapshotRenameRequest.h"
#include "librbd/operation/SnapshotRollbackRequest.h"
#include "librbd/operation/SnapshotUnprotectRequest.h"
+#include "librbd/operation/SnapshotLimitRequest.h"
#include <set>
#include <boost/bind.hpp>
request->send();
}
+template <typename I>
+int Operations<I>::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 <typename I>
+void Operations<I>::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<I> *request =
+ new operation::SnapshotLimitRequest<I>(m_image_ctx, on_finish, limit);
+ request->send();
+}
+
template <typename I>
int Operations<I>::prepare_image_update() {
assert(m_image_ctx.owner_lock.is_locked() &&
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:
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)
ProgressContext& prog_ctx);
int snap_list(ImageCtx *ictx, std::vector<snap_info_t>& 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,
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);
on_safe->complete(0);
}
+template <typename I>
+void Replay<I>::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<I>(
+ m_image_ctx, new ExecuteOp<I, journal::SnapLimitEvent>(m_image_ctx,
+ event,
+ on_op_complete));
+
+ on_ready->complete(0);
+}
+
template <typename I>
void Replay<I>::handle_event(const journal::UnknownEvent &event,
Context *on_ready, Context *on_safe) {
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);
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);
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 {
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;
ResizeEvent,
FlattenEvent,
DemoteEvent,
+ SnapLimitEvent,
UnknownEvent> Event;
struct EventEntry {
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;
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;
--- /dev/null
+// -*- 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 <typename I>
+SnapshotLimitRequest<I>::SnapshotLimitRequest(I &image_ctx,
+ Context *on_finish,
+ uint64_t limit)
+ : Request<I>(image_ctx, on_finish), m_snap_limit(limit) {
+}
+
+template <typename I>
+void SnapshotLimitRequest<I>::send_op() {
+ send_limit_snaps();
+}
+
+template <typename I>
+bool SnapshotLimitRequest<I>::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 <typename I>
+void SnapshotLimitRequest<I>::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<librbd::ImageCtx>;
--- /dev/null
+// -*- 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 <iosfwd>
+#include <string>
+
+class Context;
+
+namespace librbd {
+
+class ImageCtx;
+
+namespace operation {
+
+template <typename ImageCtxT = ImageCtx>
+class SnapshotLimitRequest : public Request<ImageCtxT> {
+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<librbd::ImageCtx>;
+
+#endif // CEPH_LIBRBD_OPERATION_SNAPSHOT_LIMIT_REQUEST_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"
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,
class Timeout(Error):
pass
+class DiskQuotaExceeded(Error):
+ pass
+
cdef errno_to_exception = {
errno.EPERM : PermissionError,
errno.EDOM : ArgumentOutOfRange,
errno.ESHUTDOWN : ConnectionShutdown,
errno.ETIMEDOUT : Timeout,
+ errno.EDQUOT : DiskQuotaExceeded,
}
cdef make_ex(ret, msg):
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
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.
--image arg image name
--snap arg snapshot name
+ rbd help snap limit clear
+ usage: rbd snap limit clear [--pool <pool>] [--image <image>]
+ <image-spec>
+
+ Remove snapshot limit.
+
+ Positional arguments
+ <image-spec> image specification
+ (example: [<pool-name>/]<image-name>)
+
+ Optional arguments
+ -p [ --pool ] arg pool name
+ --image arg image name
+
+ rbd help snap limit set
+ usage: rbd snap limit set [--pool <pool>] [--image <image>] [--limit <limit>]
+ <image-spec>
+
+ Limit the number of snapshots.
+
+ Positional arguments
+ <image-spec> image specification
+ (example: [<pool-name>/]<image-name>)
+
+ 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 <pool>] [--image <image>] [--format <format>]
[--pretty-format]
-// -*- 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"
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;
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
-// -*- 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
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);
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)
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)
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
(PATH.c_str(), po::value<std::string>(), 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<uint64_t>(), 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");
static const std::string VERBOSE("verbose");
static const std::string NO_ERROR("no-error");
+static const std::string LIMIT("limit");
+
static const std::set<std::string> SWITCH_ARGUMENTS = {
WHOLE_OBJECT, NO_PROGRESS, PRETTY_FORMAT, VERBOSE, NO_ERROR};
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 = "");
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);
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;
}
}
+ 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';
}
}
+ 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) {
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);
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<uint64_t>();
+ } 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);
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);
)
)
+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,