Skip to content

Commit

Permalink
hammer: rbd snap rollback: restore the link to parent
Browse files Browse the repository at this point in the history
So snapshot, flatten, rollback of a cloned image does not loose any data

Fixes: #14512
Signed-off-by: Alexey Sheplyakov <asheplyakov@mirantis.com>
  • Loading branch information
Alexey Sheplyakov committed Apr 12, 2016
1 parent e219e85 commit 85f611d
Show file tree
Hide file tree
Showing 3 changed files with 165 additions and 2 deletions.
63 changes: 63 additions & 0 deletions src/librbd/internal.cc
Expand Up @@ -413,6 +413,58 @@ 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<librados::snap_t, SnapInfo>::const_iterator it = ictx->snap_info.find(snap_id);
assert(it != ictx->snap_info.end());
const SnapInfo& snap_info(it->second);
if (snap_info.parent.spec.snap_id == CEPH_NOSNAP) {
if (ictx->parent_md.spec.snap_id == CEPH_NOSNAP) {
ldout(cct, 20) << __func__ << ": nop: neither head nor snapshot has a parent" << dendl;
} else {
ldout(cct, 20) << __func__ << ": snapshot has no parent, "
<< "removing 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;
}
} else {
if (ictx->parent_md == snap_info.parent) {
ldout(cct, 20) << __func__ << ": nop: head and snapshot have the same parent" << dendl;
} else {
if (ictx->parent_md.spec.snap_id != CEPH_NOSNAP) {
// should 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 the old parent link: "
<< cpp_strerror(r) << dendl;
return r;
}
}
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 r;
}

int rollback_image(ImageCtx *ictx, uint64_t snap_id,
ProgressContext& prog_ctx)
{
Expand Down Expand Up @@ -444,6 +496,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;
}

Expand Down
10 changes: 8 additions & 2 deletions src/librbd/parent_types.h
Expand Up @@ -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);
}
};
Expand All @@ -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);
}
};
}

Expand Down
94 changes: 94 additions & 0 deletions src/test/pybind/test_rbd.py
Expand Up @@ -1048,3 +1048,97 @@ def test_follower_write(self):
for offset in [0, IMG_SIZE / 2]:
read = image2.read(offset, 256)
eq(data, read)


class TestRollbackTricky(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_resized(self):
expected_head, expected_tail = 'FOOBAR', 'OOPS'
with Image(ioctx, self.clone_name) as clone:
clone.resize(IMG_SIZE * 2)
clone.write('HEHE', IMG_SIZE / 2)
clone.write('BARBAZ', IMG_SIZE)
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)

0 comments on commit 85f611d

Please sign in to comment.