From aab3a40f95dafab34a7eadc2159d142a5f0f88f1 Mon Sep 17 00:00:00 2001 From: Alexey Sheplyakov Date: Mon, 11 Apr 2016 14:16:48 +0300 Subject: [PATCH] hammer: rbd snap rollback: restore the link to parent So snapshot, flatten, rollback of a cloned image does not loose any data Fixes: #14512 Signed-off-by: Alexey Sheplyakov --- src/librbd/internal.cc | 53 +++++++++++++++++++++ src/librbd/parent_types.h | 10 +++- src/test/pybind/test_rbd.py | 93 +++++++++++++++++++++++++++++++++++++ 3 files changed, 154 insertions(+), 2 deletions(-) diff --git a/src/librbd/internal.cc b/src/librbd/internal.cc index 8c6e1b8bd3b3c..f4b110eea8ce0 100644 --- a/src/librbd/internal.cc +++ b/src/librbd/internal.cc @@ -413,6 +413,48 @@ int validate_pool(IoCtx &io_ctx, CephContext *cct) { rados_completion->release(); } + int rollback_parent(ImageCtx *ictx, uint64_t snap_id) + { + assert(ictx); + assert(ictx->parent_lock.is_locked()); + assert(ictx->snap_lock.is_locked()); + + CephContext *cct = ictx->cct; + int r = 0; + std::map::const_iterator it = ictx->snap_info.find(snap_id); + if (it == ictx->snap_info.end()) { + ldout(cct, 10) << __func__ << ": no such snapshot: " << snap_id << dendl; + return -ENOENT; + } + const SnapInfo& snap_info(it->second); + if (ictx->parent_md == snap_info.parent) { + ldout(cct, 20) << __func__ << ": nop: head and snapshot have the same parent" << dendl; + return 0; + } + if (ictx->parent_md.spec.pool_id != -1) { + // remove the old parent link first, otherwise cls_client::set_parent + // will fail with -EEXISTS + ldout(cct, 20) << __func__ << ": removing the old parent link" << dendl; + r = cls_client::remove_parent(&ictx->md_ctx, ictx->header_oid); + if (r < 0) { + ldout(cct, 10) << __func__ << ": failed to remove parent link: " + << cpp_strerror(r) << dendl; + return r; + } + } + if (snap_info.parent.spec.pool_id != -1) { + ldout(cct, 20) << __func__ << ": updating the parent link" << dendl; + r = cls_client::set_parent(&ictx->md_ctx, ictx->header_oid, + snap_info.parent.spec, snap_info.parent.overlap); + if (r < 0) { + ldout(cct, 10) << __func__ << ": failed to set parent link: " + << cpp_strerror(r) << dendl; + return r; + } + } + return 0; + } + int rollback_image(ImageCtx *ictx, uint64_t snap_id, ProgressContext& prog_ctx) { @@ -444,6 +486,17 @@ int validate_pool(IoCtx &io_ctx, CephContext *cct) { RWLock::WLocker l(ictx->snap_lock); ictx->object_map.rollback(snap_id); } + + { + RWLock::WLocker snap_locker(ictx->snap_lock); + RWLock::WLocker parent_locker(ictx->parent_lock); + r = rollback_parent(ictx, snap_id); + if (r < 0) { + ldout(cct, 10) << __func__ << ": failed to rollback the parent link: " + << cpp_strerror(r) << dendl; + return r; + } + } return 0; } diff --git a/src/librbd/parent_types.h b/src/librbd/parent_types.h index 4dcc452962f95..de7e6129a7307 100644 --- a/src/librbd/parent_types.h +++ b/src/librbd/parent_types.h @@ -14,12 +14,12 @@ namespace librbd { parent_spec() : pool_id(-1), snap_id(CEPH_NOSNAP) {} parent_spec(uint64_t pool_id, string image_id, snapid_t snap_id) : pool_id(pool_id), image_id(image_id), snap_id(snap_id) {} - bool operator==(const parent_spec &other) { + bool operator==(const parent_spec &other) const { return ((this->pool_id == other.pool_id) && (this->image_id == other.image_id) && (this->snap_id == other.snap_id)); } - bool operator!=(const parent_spec &other) { + bool operator!=(const parent_spec &other) const { return !(*this == other); } }; @@ -28,6 +28,12 @@ namespace librbd { parent_spec spec; uint64_t overlap; parent_info() : overlap(0) {} + bool operator==(const parent_info &other) const { + return (spec == other.spec) && (overlap == other.overlap); + } + bool operator!=(const parent_info &other) const { + return (spec != other.spec) || (overlap != other.overlap); + } }; } diff --git a/src/test/pybind/test_rbd.py b/src/test/pybind/test_rbd.py index 4be09085b5c22..181a2ac82e273 100644 --- a/src/test/pybind/test_rbd.py +++ b/src/test/pybind/test_rbd.py @@ -1048,3 +1048,96 @@ def test_follower_write(self): for offset in [0, IMG_SIZE / 2]: read = image2.read(offset, 256) eq(data, read) + + +class TestCloneRollback(object): + + @require_features([RBD_FEATURE_LAYERING]) + def setUp(self): + self.rbd = RBD() + create_image() + self.clone_name = image_name + '_cloned' + with Image(ioctx, image_name) as image1: + image1.write('FOOBAR', 0) + image1.create_snap('FOOBAR') + image1.protect_snap('FOOBAR') + RBD().clone(ioctx, image_name, 'FOOBAR', + ioctx, self.clone_name, + features=RBD_FEATURE_LAYERING) + with Image(ioctx, self.clone_name) as clone: + clone.write('OOPS', IMG_SIZE / 2) + clone.create_snap('OOPS') + + def tearDown(self): + global ioctx + with Image(ioctx, self.clone_name) as clone: + clone.remove_snap('OOPS') + try: + clone.remove_snap('FLATTENED') + except: + pass + RBD().remove(ioctx, self.clone_name) + with Image(ioctx, image_name) as image1: + image1.unprotect_snap('FOOBAR') + image1.remove_snap('FOOBAR') + remove_image() + + def test_rollback_flattened_to_parented(self): + # target snapshot has a parent, and head does not + expected_head, expected_tail = 'FOOBAR', 'OOPS' + with Image(ioctx, self.clone_name) as clone: + clone.write('HEHE', IMG_SIZE / 2) + clone.flatten() + clone.rollback_to_snap('OOPS') + head = clone.read(0, len(expected_head)) + tail = clone.read(IMG_SIZE / 2, len(expected_tail)) + eq(head, expected_head) + eq(tail, expected_tail) + _, image, snap = clone.parent_info() + eq(image, image_name) + eq(snap, 'FOOBAR') + + def test_rollback_parented_to_flattened(self): + expected_head, expected_tail = 'FOOBAR', 'HEHE' + with Image(ioctx, self.clone_name) as clone: + clone.write(expected_tail, IMG_SIZE / 2) + clone.flatten() + clone.create_snap('FLATTENED') + clone.rollback_to_snap('OOPS') + # head has a parent, and the target snapshot does not + _, image, snap = clone.parent_info() + eq(image, image_name) + eq(snap, 'FOOBAR') + clone.rollback_to_snap('FLATTENED') + head = clone.read(0, len(expected_head)) + tail = clone.read(IMG_SIZE / 2, len(expected_tail)) + assert_raises(ImageNotFound, clone.parent_info) + eq(head, expected_head) + eq(tail, expected_tail) + + def test_rollback_same_parent(self): + expected_head, expected_tail = 'FOOBAR', 'OOPS' + with Image(ioctx, self.clone_name) as clone: + clone.write('HEHE', IMG_SIZE / 2) + clone.rollback_to_snap('OOPS') + head = clone.read(0, len(expected_head)) + tail = clone.read(IMG_SIZE / 2, len(expected_tail)) + _, image, snap = clone.parent_info() + eq(head, expected_head) + eq(tail, expected_tail) + eq(image, image_name) + eq(snap, 'FOOBAR') + + def test_rollback_same_parent_shrinked(self): + expected_head, expected_tail = 'FOOBAR', 'OOPS' + with Image(ioctx, self.clone_name) as clone: + clone.resize(IMG_SIZE / 2) + clone.write('HEHE', 0) + clone.rollback_to_snap('OOPS') + _, image, snap = clone.parent_info() + head = clone.read(0, len(expected_head)) + tail = clone.read(IMG_SIZE / 2, len(expected_tail)) + eq(image, image_name) + eq(snap, 'FOOBAR') + eq(head, expected_head) + eq(tail, expected_tail)