From: Dan Mick Date: Mon, 9 Jul 2012 21:55:35 +0000 (-0700) Subject: rbd, librbd, rbd.py: cloning (copy-on-write child image of snapshot) X-Git-Tag: v0.50~96^2~5 X-Git-Url: http://git-server-git.apps.pok.os.sepia.ceph.com/?a=commitdiff_plain;h=a94fc8c81feac2273cb787b7cf50d5e8b6ae4719;p=ceph.git rbd, librbd, rbd.py: cloning (copy-on-write child image of snapshot) Signed-off-by: Dan Mick --- diff --git a/src/include/rbd/librbd.h b/src/include/rbd/librbd.h index 9ee1958b1d2b..ef098cfbf681 100644 --- a/src/include/rbd/librbd.h +++ b/src/include/rbd/librbd.h @@ -69,6 +69,9 @@ int rbd_list(rados_ioctx_t io, char *names, size_t *size); int rbd_create(rados_ioctx_t io, const char *name, uint64_t size, int *order); int rbd_create2(rados_ioctx_t io, const char *name, uint64_t size, uint64_t features, int *order); +int rbd_clone(rados_ioctx_t p_ioctx, const char *p_name, + const char *p_snapname, rados_ioctx_t c_ioctx, + const char *c_name, uint64_t features, int *c_order); int rbd_remove(rados_ioctx_t io, const char *name); int rbd_remove_with_progress(rados_ioctx_t io, const char *name, librbd_progress_fn_t cb, void *cbdata); diff --git a/src/include/rbd/librbd.hpp b/src/include/rbd/librbd.hpp index f0f2782994df..875f9b8d5b4f 100644 --- a/src/include/rbd/librbd.hpp +++ b/src/include/rbd/librbd.hpp @@ -70,6 +70,9 @@ public: int create(IoCtx& io_ctx, const char *name, uint64_t size, int *order); int create2(IoCtx& io_ctx, const char *name, uint64_t size, uint64_t features, int *order); + int clone(IoCtx& p_ioctx, const char *p_name, const char *p_snapname, + IoCtx& c_ioctx, const char *c_name, uint64_t features, + int *c_order); int remove(IoCtx& io_ctx, const char *name); int remove_with_progress(IoCtx& io_ctx, const char *name, ProgressContext& pctx); int rename(IoCtx& src_io_ctx, const char *srcname, const char *destname); diff --git a/src/librbd.cc b/src/librbd.cc index b00ff0b2f6af..2dcae2ee90fe 100644 --- a/src/librbd.cc +++ b/src/librbd.cc @@ -672,6 +672,38 @@ namespace librbd { return c; } +ProgressContext::~ProgressContext() +{ +} + +class NoOpProgressContext : public librbd::ProgressContext +{ +public: + NoOpProgressContext() + { + } + int update_progress(uint64_t offset, uint64_t src_size) + { + return 0; + } +}; + +class CProgressContext : public librbd::ProgressContext +{ +public: + CProgressContext(librbd_progress_fn_t fn, void *data) + : m_fn(fn), m_data(data) + { + } + int update_progress(uint64_t offset, uint64_t src_size) + { + return m_fn(offset, src_size, m_data); + } +private: + librbd_progress_fn_t m_fn; + void *m_data; +}; + void WatchCtx::invalidate() { Mutex::Locker l(lock); @@ -1127,6 +1159,105 @@ int create(IoCtx& io_ctx, const char *imgname, uint64_t size, return 0; } +/* + * Parent may be in different pool, hence different IoCtx + */ +int clone(IoCtx& p_ioctx, const char *p_name, const char *p_snapname, + IoCtx& c_ioctx, const char *c_name, + uint64_t features, int *c_order) +{ + CephContext *cct = (CephContext *)p_ioctx.cct(); + ldout(cct, 20) << "clone " << &p_ioctx << " name " << p_name << " snap " + << p_snapname << "to child " << &c_ioctx << " name " + << c_name << " features = " << features << " order = " + << *c_order << dendl; + + if (features & ~RBD_FEATURES_ALL) { + lderr(cct) << "librbd does not support requested features" << dendl; + return -ENOSYS; + } + + // make sure child doesn't already exist, in either format + int r = detect_format(c_ioctx, c_name, NULL, NULL); + if (r != -ENOENT) { + lderr(cct) << "rbd image " << c_name << " already exists" << dendl; + return -EEXIST; + } + + if (p_snapname == NULL) { + lderr(cct) << "image to be cloned must be a snapshot" << dendl; + return -EINVAL; + } + + // make sure parent snapshot exists + ImageCtx *p_imctx = new ImageCtx(p_name, p_snapname, p_ioctx); + r = open_image(p_imctx); + if (r < 0) { + lderr(cct) << "error opening parent image: " + << cpp_strerror(-r) << dendl; + return r; + } + + if (p_imctx->old_format) { + lderr(cct) << "parent image must be in new format" << dendl; + return -EINVAL; + } + + if ((p_imctx->features & RBD_FEATURE_LAYERING) != RBD_FEATURE_LAYERING) { + lderr(cct) << "parent image must support layering" << dendl; + return -EINVAL; + } + + int order = *c_order; + + if (!*c_order) { + order = p_imctx->order; + } + + uint64_t size = p_imctx->get_image_size(); + int remove_r; + librbd::NoOpProgressContext no_op; + ImageCtx *c_imctx = NULL; + r = create(c_ioctx, c_name, size, false, features, &order); + if (r < 0) { + lderr(cct) << "error creating child: " << cpp_strerror(r) << dendl; + goto err_close_parent; + } + + uint64_t p_poolid; + p_poolid = p_ioctx.get_id(); + + c_imctx = new ImageCtx(c_name, NULL, c_ioctx); + r = open_image(c_imctx); + if (r < 0) { + lderr(cct) << "Error opening new image: " << cpp_strerror(r) << dendl; + goto err_remove; + } + + r = cls_client::set_parent(&c_ioctx, c_imctx->header_oid, p_poolid, + p_imctx->id, p_imctx->snapid, size); + if (r < 0) { + lderr(cct) << "couldn't set parent: " << r << dendl; + goto err_close_child; + } + ldout(cct, 2) << "done." << dendl; + close_image(c_imctx); + close_image(p_imctx); + return 0; + + err_close_child: + close_image(c_imctx); + err_remove: + remove_r = remove(c_ioctx, c_name, no_op); + if (remove_r < 0) { + lderr(cct) << "Error removing failed clone: " + << cpp_strerror(remove_r) << dendl; + } + err_close_parent: + close_image(p_imctx); + return r; +} + int rename(IoCtx& io_ctx, const char *srcname, const char *dstname) { CephContext *cct = (CephContext *)io_ctx.cct(); @@ -1588,38 +1719,6 @@ int ictx_refresh(ImageCtx *ictx) return 0; } -ProgressContext::~ProgressContext() -{ -} - -class NoOpProgressContext : public librbd::ProgressContext -{ -public: - NoOpProgressContext() - { - } - int update_progress(uint64_t offset, uint64_t src_size) - { - return 0; - } -}; - -class CProgressContext : public librbd::ProgressContext -{ -public: - CProgressContext(librbd_progress_fn_t fn, void *data) - : m_fn(fn), m_data(data) - { - } - int update_progress(uint64_t offset, uint64_t src_size) - { - return m_fn(offset, src_size, m_data); - } -private: - librbd_progress_fn_t m_fn; - void *m_data; -}; - int snap_rollback(ImageCtx *ictx, const char *snap_name, ProgressContext& prog_ctx) { CephContext *cct = ictx->cct; @@ -2473,6 +2572,14 @@ int RBD::create2(IoCtx& io_ctx, const char *name, uint64_t size, return librbd::create(io_ctx, name, size, false, features, order); } +int RBD::clone(IoCtx& p_ioctx, const char *p_name, const char *p_snapname, + IoCtx& c_ioctx, const char *c_name, uint64_t features, + int *c_order) +{ + return librbd::clone(p_ioctx, p_name, p_snapname, c_ioctx, c_name, + features, c_order); +} + int RBD::remove(IoCtx& io_ctx, const char *name) { librbd::NoOpProgressContext prog_ctx; @@ -2755,6 +2862,17 @@ extern "C" int rbd_create2(rados_ioctx_t p, const char *name, return librbd::create(io_ctx, name, size, false, features, order); } +extern "C" int rbd_clone(rados_ioctx_t p_ioctx, const char *p_name, + const char *p_snapname, rados_ioctx_t c_ioctx, + const char *c_name, uint64_t features, int *c_order) +{ + librados::IoCtx p_ioc, c_ioc; + librados::IoCtx::from_rados_ioctx_t(p_ioctx, p_ioc); + librados::IoCtx::from_rados_ioctx_t(c_ioctx, c_ioc); + return librbd::clone(p_ioc, p_name, p_snapname, c_ioc, c_name, + features, c_order); +} + extern "C" int rbd_remove(rados_ioctx_t p, const char *name) { librados::IoCtx io_ctx; diff --git a/src/pybind/rbd.py b/src/pybind/rbd.py index 9fc537ea1366..31efde092aa3 100644 --- a/src/pybind/rbd.py +++ b/src/pybind/rbd.py @@ -23,6 +23,8 @@ import errno ANONYMOUS_AUID = 0xffffffffffffffff ADMIN_AUID = 0 +RBD_FEATURE_LAYERING = 1 + class Error(Exception): pass @@ -59,6 +61,12 @@ class ImageBusy(Error): class ImageHasSnapshots(Error): pass +class FunctionNotSupported(Error): + pass + +class ArgumentOutOfRange(Error): + pass + def make_ex(ret, msg): """ Translate a librbd return code into an exception. @@ -79,6 +87,8 @@ def make_ex(ret, msg): errno.EROFS : ReadOnlyImage, errno.EBUSY : ImageBusy, errno.ENOTEMPTY : ImageHasSnapshots, + errno.ENOSYS : FunctionNotSupported, + errno.EDOM : ArgumentOutOfRange } ret = abs(ret) if ret in errors: @@ -157,6 +167,46 @@ class RBD(object): if ret < 0: raise make_ex(ret, 'error creating image') + def clone(self, p_ioctx, p_name, p_snapname, c_ioctx, c_name, + features=0, order=None): + """ + Clone a parent rbd snapshot into a COW sparse child. + + :param p_ioctx: the parent context that represents the parent snap + :type ioctx: :class:`rados.Ioctx` + :param p_name: the parent image name + :type name: str + :param p_snapname: the parent image snapshot name + :type name: str + :param c_ioctx: the child context that represents the new clone + :type ioctx: :class:`rados.Ioctx` + :param c_name: the clone (child) name + :type name: str + :param features: bitmask of features to enable; if set, must include layering + :type features: int + :param order: the image is split into (2**order) byte objects + :type order: int + :raises: :class:`TypeError` + :raises: :class:`InvalidArgument` + :raises: :class:`ImageExists` + :raises: :class:`FunctionNotSupported` + :raises: :class:`ArgumentOutOfRange` + """ + if order is None: + order = 0 + if not isinstance(p_snapname, str) or not isinstance(p_name, str): + raise TypeError('parent name and snapname must be strings') + if not isinstance(c_name, str): + raise TypeError('child name must be a string') + + ret = self.librbd.rbd_clone(p_ioctx.io, c_char_p(p_name), + c_char_p(p_snapname), + c_ioctx.io, c_char_p(c_name), + c_uint64(features), + byref(c_int(order))) + if ret < 0: + raise make_ex(ret, 'error creating clone') + def list(self, ioctx): """ List image names. diff --git a/src/rbd.cc b/src/rbd.cc index f87a30245deb..59a09e3c9945 100644 --- a/src/rbd.cc +++ b/src/rbd.cc @@ -62,46 +62,54 @@ static string dir_info_oid = RBD_INFO; void usage() { - cout << "usage: rbd [-n ] [OPTIONS] ...\n" - << "where 'pool' is a rados pool name (default is 'rbd') and 'cmd' is one of:\n" - << " (ls | list) [pool-name] list rbd images\n" - << " info [--snap ] show information about image size,\n" - << " striping, etc.\n" - << " create [--order ] --size create an empty image\n" - << " resize --size resize (expand or contract) image\n" - << " rm delete an image\n" - << " export [--snap ] export image to file\n" - << " import import image from file (dest defaults\n" - << " as the filename part of file)\n" - << " (cp | copy) [--snap ] copy src image to dest\n" - << " (mv | rename) rename src image to dest\n" - << " snap ls dump list of image snapshots\n" - << " snap create --snap create a snapshot\n" - << " snap rollback --snap rollback image head to snapshot\n" - << " snap rm --snap deletes a snapshot\n" - << " snap purge deletes all snapshots\n" - << " watch watch events on image\n" - << " map map the image to a block device\n" - << " using the kernel\n" - << " unmap unmap a rbd device that was\n" - << " mapped by the kernel\n" - << " showmapped show the rbd images mapped\n" - << " by the kernel\n" - << "\n" - << "Other input options:\n" - << " -p, --pool source pool name\n" - << " --image image name\n" - << " --dest destination [pool and] image name\n" - << " --snap specify snapshot name\n" - << " --dest-pool destination pool name\n" - << " --path path name for import/export (if not specified)\n" - << " --size size parameter for create and resize commands\n" - << " --order the object size in bits, such that the objects\n" - << " are (1 << order) bytes. Default is 22 (4 MB).\n" - << "\n" - << "For the map command:\n" - << " --user rados user to authenticate as\n" - << " --secret file containing secret key for use with cephx\n"; + cout << +"usage: rbd [-n ] [OPTIONS] ...\n" +"where 'pool' is a rados pool name (default is 'rbd') and 'cmd' is one of:\n" +" (ls | list) [pool-name] list rbd images\n" +" info show information about image size,\n" +" striping, etc.\n" +" create [--order ] --size create an empty image\n" +" clone [--order ] \n" +" clone a snapshot into a COW\n" +" child image\n" +" resize --size resize (expand or contract) image\n" +" rm delete an image\n" +" export export image to file\n" +" import import image from file\n" +" (dest defaults)\n" +" as the filename part of file)\n" +" (cp | copy) copy src image to dest\n" +" (mv | rename) rename src image to dest\n" +" snap ls dump list of image snapshots\n" +" snap create create a snapshot\n" +" snap rollback rollback image to snapshot\n" +" snap rm deletes a snapshot\n" +" snap purge deletes all snapshots\n" +" watch watch events on image\n" +" map map image to a block device\n" +" using the kernel\n" +" unmap unmap a rbd device that was\n" +" mapped by the kernel\n" +" showmapped show the rbd images mapped\n" +" by the kernel\n" +"\n" +", are [pool/]name[@snap], or you may specify\n" +"individual pieces of names with -p/--pool, --image, and/or --snap.\n" +"\n" +"Other input options:\n" +" -p, --pool source pool name\n" +" --image image name\n" +" --dest destination [pool and] image name\n" +" --snap snapshot name\n" +" --dest-pool destination pool name\n" +" --path path name for import/export\n" +" --size size of image for create and resize\n" +" --order the object size in bits; object size will be\n" +" (1 << order) bytes. Default is 22 (4 MB).\n" +"\n" +"For the map command:\n" +" --user rados user to authenticate as\n" +" --secret file containing secret key for use with cephx\n"; } void usage_exit() @@ -170,6 +178,9 @@ static int do_create(librbd::RBD &rbd, librados::IoCtx& io_ctx, bool old_format, uint64_t features) { int r; + if (features == 0) + features = RBD_FEATURES_ALL; + if (old_format) r = rbd.create(io_ctx, imgname, size, order); else @@ -179,6 +190,20 @@ static int do_create(librbd::RBD &rbd, librados::IoCtx& io_ctx, return 0; } +static int do_clone(librbd::RBD &rbd, librados::IoCtx &p_ioctx, + const char *p_name, const char *p_snapname, + librados::IoCtx &c_ioctx, const char *c_name, + uint64_t features, int *c_order) +{ + if (features == 0) + features = RBD_FEATURES_ALL; + else if ((features & RBD_FEATURE_LAYERING) != RBD_FEATURE_LAYERING) + return -EINVAL; + + return rbd.clone(p_ioctx, p_name, p_snapname, c_ioctx, c_name, features, + c_order); +} + static int do_rename(librbd::RBD &rbd, librados::IoCtx& io_ctx, const char *imgname, const char *destname) { @@ -858,6 +883,7 @@ enum { OPT_LIST, OPT_INFO, OPT_CREATE, + OPT_CLONE, OPT_RESIZE, OPT_RM, OPT_EXPORT, @@ -885,6 +911,8 @@ static int get_cmd(const char *cmd, bool snapcmd) return OPT_INFO; if (strcmp(cmd, "create") == 0) return OPT_CREATE; + if (strcmp(cmd, "clone") == 0) + return OPT_CLONE; if (strcmp(cmd, "resize") == 0) return OPT_RESIZE; if (strcmp(cmd, "rm") == 0) @@ -957,7 +985,7 @@ int main(int argc, const char **argv) uint64_t size = 0; // in bytes int order = 0; bool old_format = true; - const char *imgname = NULL, *snapname = NULL, *destname = NULL, *dest_poolname = NULL, *path = NULL, *secretfile = NULL, *user = NULL, *devpath = NULL; + const char *imgname = NULL, *snapname = NULL, *destname = NULL, *dest_poolname = NULL, *dest_snapname = NULL, *path = NULL, *secretfile = NULL, *user = NULL, *devpath = NULL; std::string val; std::ostringstream err; @@ -998,6 +1026,8 @@ int main(int argc, const char **argv) secretfile = strdup(val.c_str()); } else if (ceph_argparse_witharg(args, i, &val, "--user", (char*)NULL)) { user = strdup(val.c_str()); + } else if (ceph_argparse_witharg(args, i, &val, "--parent", (char *)NULL)) { + imgname = strdup(val.c_str()); } else { ++i; } @@ -1056,6 +1086,13 @@ int main(int argc, const char **argv) case OPT_RENAME: set_conf_param(v, &imgname, &destname); break; + case OPT_CLONE: + if (imgname == NULL) { + set_conf_param(v, &imgname, NULL); + } else { + set_conf_param(v, &destname, NULL); + } + break; case OPT_SHOWMAPPED: usage_exit(); break; @@ -1093,17 +1130,17 @@ int main(int argc, const char **argv) if (snapname && opt_cmd != OPT_SNAP_CREATE && opt_cmd != OPT_SNAP_ROLLBACK && opt_cmd != OPT_SNAP_REMOVE && opt_cmd != OPT_INFO && opt_cmd != OPT_EXPORT && opt_cmd != OPT_COPY && - opt_cmd != OPT_MAP) { + opt_cmd != OPT_MAP && opt_cmd != OPT_CLONE) { cerr << "error: snapname specified for a command that doesn't use it" << std::endl; usage_exit(); } if ((opt_cmd == OPT_SNAP_CREATE || opt_cmd == OPT_SNAP_ROLLBACK || - opt_cmd == OPT_SNAP_REMOVE) && !snapname) { + opt_cmd == OPT_SNAP_REMOVE || opt_cmd == OPT_CLONE) && !snapname) { cerr << "error: snap name was not specified" << std::endl; usage_exit(); } - set_pool_image_name(dest_poolname, destname, (char **)&dest_poolname, (char **)&destname, NULL); + set_pool_image_name(dest_poolname, destname, (char **)&dest_poolname, (char **)&destname, (char **)&dest_snapname); if (!poolname) poolname = "rbd"; @@ -1113,11 +1150,21 @@ int main(int argc, const char **argv) if (opt_cmd == OPT_EXPORT && !path) path = imgname; - if (opt_cmd == OPT_COPY && !destname ) { + if ((opt_cmd == OPT_COPY || opt_cmd == OPT_CLONE) && !destname ) { cerr << "error: destination image name was not specified" << std::endl; usage_exit(); } + if ((opt_cmd == OPT_CLONE) && dest_snapname) { + cerr << "error: cannot clone to a snapshot" << std::endl; + usage_exit(); + } + + if ((opt_cmd == OPT_CLONE) && size) { + cerr << "error: clone must begin at size of parent" << std::endl; + usage_exit(); + } + if ((opt_cmd == OPT_RENAME) && (strcmp(poolname, dest_poolname) != 0)) { cerr << "error: mv/rename across pools not supported" << std::endl; cerr << "source pool: " << poolname << " dest pool: " << dest_poolname @@ -1168,7 +1215,7 @@ int main(int argc, const char **argv) } } - if (opt_cmd == OPT_COPY || opt_cmd == OPT_IMPORT) { + if (opt_cmd == OPT_COPY || opt_cmd == OPT_IMPORT || opt_cmd == OPT_CLONE) { r = rados.ioctx_create(dest_poolname, dest_io_ctx); if (r < 0) { cerr << "error opening pool " << dest_poolname << ": " << cpp_strerror(-r) << std::endl; @@ -1209,6 +1256,21 @@ int main(int argc, const char **argv) } break; + case OPT_CLONE: + if (order && (order < 12 || order > 25)) { + cerr << "order must be between 12 (4 KB) and 25 (32 MB)" << std::endl; + usage(); + exit(1); + } + + r = do_clone(rbd, io_ctx, imgname, snapname, dest_io_ctx, destname, + RBD_FEATURE_LAYERING, &order); + if (r < 0) { + cerr << "clone error: " << cpp_strerror(-r) << std::endl; + exit(1); + } + break; + case OPT_RENAME: r = do_rename(rbd, io_ctx, imgname, destname); if (r < 0) {