From ea4901f539c7404c07e72f5c42fc8abcf840dafa Mon Sep 17 00:00:00 2001 From: Jason Dillaman Date: Tue, 2 Apr 2019 14:10:42 -0400 Subject: [PATCH] test/librbd: initial unit test for copyup state machine Signed-off-by: Jason Dillaman --- src/librbd/io/CopyupRequest.cc | 327 +++--- src/librbd/io/CopyupRequest.h | 2 +- src/test/librbd/CMakeLists.txt | 1 + .../deep_copy/test_mock_ObjectCopyRequest.cc | 2 +- src/test/librbd/io/test_mock_CopyupRequest.cc | 987 ++++++++++++++++++ src/test/librbd/mock/MockObjectMap.h | 5 + 6 files changed, 1158 insertions(+), 166 deletions(-) create mode 100644 src/test/librbd/io/test_mock_CopyupRequest.cc diff --git a/src/librbd/io/CopyupRequest.cc b/src/librbd/io/CopyupRequest.cc index 4eeee3d4d5d..37fa3d00e0c 100644 --- a/src/librbd/io/CopyupRequest.cc +++ b/src/librbd/io/CopyupRequest.cc @@ -32,27 +32,29 @@ namespace io { namespace { -class C_UpdateObjectMap : public C_AsyncObjectThrottle<> { +template +class C_UpdateObjectMap : public C_AsyncObjectThrottle { public: - C_UpdateObjectMap(AsyncObjectThrottle<> &throttle, ImageCtx *image_ctx, + C_UpdateObjectMap(AsyncObjectThrottle &throttle, I *image_ctx, uint64_t object_no, uint8_t head_object_map_state, const std::vector *snap_ids, const ZTracer::Trace &trace, size_t snap_id_idx) - : C_AsyncObjectThrottle(throttle, *image_ctx), m_object_no(object_no), + : C_AsyncObjectThrottle(throttle, *image_ctx), m_object_no(object_no), m_head_object_map_state(head_object_map_state), m_snap_ids(*snap_ids), m_trace(trace), m_snap_id_idx(snap_id_idx) { } int send() override { - ceph_assert(m_image_ctx.owner_lock.is_locked()); - if (m_image_ctx.exclusive_lock == nullptr) { + auto& image_ctx = this->m_image_ctx; + ceph_assert(image_ctx.owner_lock.is_locked()); + if (image_ctx.exclusive_lock == nullptr) { return 1; } - ceph_assert(m_image_ctx.exclusive_lock->is_lock_owner()); + ceph_assert(image_ctx.exclusive_lock->is_lock_owner()); - RWLock::RLocker snap_locker(m_image_ctx.snap_lock); - if (m_image_ctx.object_map == nullptr) { + RWLock::RLocker snap_locker(image_ctx.snap_lock); + if (image_ctx.object_map == nullptr) { return 1; } @@ -65,25 +67,26 @@ public: } int update_head() { - RWLock::WLocker object_map_locker(m_image_ctx.object_map_lock); - bool sent = m_image_ctx.object_map->aio_update( + auto& image_ctx = this->m_image_ctx; + RWLock::WLocker object_map_locker(image_ctx.object_map_lock); + bool sent = image_ctx.object_map->template aio_update( CEPH_NOSNAP, m_object_no, m_head_object_map_state, {}, m_trace, false, this); return (sent ? 0 : 1); } int update_snapshot(uint64_t snap_id) { + auto& image_ctx = this->m_image_ctx; uint8_t state = OBJECT_EXISTS; - if (m_image_ctx.test_features(RBD_FEATURE_FAST_DIFF, - m_image_ctx.snap_lock) && + if (image_ctx.test_features(RBD_FEATURE_FAST_DIFF, image_ctx.snap_lock) && m_snap_id_idx > 0) { // first snapshot should be exists+dirty since it contains // the copyup data -- later snapshots inherit the data. state = OBJECT_EXISTS_CLEAN; } - RWLock::RLocker object_map_locker(m_image_ctx.object_map_lock); - bool sent = m_image_ctx.object_map->aio_update( + RWLock::RLocker object_map_locker(image_ctx.object_map_lock); + bool sent = image_ctx.object_map->template aio_update( snap_id, m_object_no, state, {}, m_trace, true, this); ceph_assert(sent); return 0; @@ -103,12 +106,12 @@ template CopyupRequest::CopyupRequest(I *ictx, const std::string &oid, uint64_t objectno, Extents &&image_extents, const ZTracer::Trace &parent_trace) - : m_ictx(util::get_image_ctx(ictx)), m_oid(oid), m_object_no(objectno), + : m_image_ctx(ictx), m_oid(oid), m_object_no(objectno), m_image_extents(image_extents), - m_trace(util::create_trace(*m_ictx, "copy-up", parent_trace)), + m_trace(util::create_trace(*m_image_ctx, "copy-up", parent_trace)), m_lock("CopyupRequest", false, false) { - m_async_op.start_op(*m_ictx); + m_async_op.start_op(*util::get_image_ctx(m_image_ctx)); } template @@ -121,9 +124,10 @@ template void CopyupRequest::append_request(AbstractObjectWriteRequest *req) { Mutex::Locker locker(m_lock); - ldout(m_ictx->cct, 20) << "oid=" << m_oid << ", " - << "object_request=" << req << ", " - << "append=" << m_append_request_permitted << dendl; + auto cct = m_image_ctx->cct; + ldout(cct, 20) << "oid=" << m_oid << ", " + << "object_request=" << req << ", " + << "append=" << m_append_request_permitted << dendl; if (m_append_request_permitted) { m_pending_requests.push_back(req); } else { @@ -138,15 +142,16 @@ void CopyupRequest::send() { template void CopyupRequest::read_from_parent() { - m_ictx->snap_lock.get_read(); - m_ictx->parent_lock.get_read(); + auto cct = m_image_ctx->cct; + m_image_ctx->snap_lock.get_read(); + m_image_ctx->parent_lock.get_read(); - if (m_ictx->parent == nullptr) { - ldout(m_ictx->cct, 5) << "parent detached" << dendl; - m_ictx->parent_lock.put_read(); - m_ictx->snap_lock.put_read(); + if (m_image_ctx->parent == nullptr) { + ldout(cct, 5) << "parent detached" << dendl; + m_image_ctx->parent_lock.put_read(); + m_image_ctx->snap_lock.put_read(); - m_ictx->op_work_queue->queue( + m_image_ctx->op_work_queue->queue( util::create_context_callback< CopyupRequest, &CopyupRequest::handle_read_from_parent>(this), -ENOENT); @@ -160,23 +165,25 @@ void CopyupRequest::read_from_parent() { m_deep_copy = false; auto comp = AioCompletion::create_and_start< CopyupRequest, - &CopyupRequest::handle_read_from_parent>(this, m_ictx, AIO_TYPE_READ); - - ldout(m_ictx->cct, 20) << "oid=" << m_oid << ", " - << "completion=" << comp << ", " - << "extents=" << m_image_extents - << dendl; - ImageRequest<>::aio_read(m_ictx->parent, comp, std::move(m_image_extents), - ReadResult{&m_copyup_data}, 0, m_trace); - - m_ictx->parent_lock.put_read(); - m_ictx->snap_lock.put_read(); + &CopyupRequest::handle_read_from_parent>( + this, util::get_image_ctx(m_image_ctx), AIO_TYPE_READ); + + ldout(cct, 20) << "oid=" << m_oid << ", " + << "completion=" << comp << ", " + << "extents=" << m_image_extents + << dendl; + ImageRequest::aio_read(m_image_ctx->parent, comp, + std::move(m_image_extents), + ReadResult{&m_copyup_data}, 0, m_trace); + + m_image_ctx->parent_lock.put_read(); + m_image_ctx->snap_lock.put_read(); } template void CopyupRequest::handle_read_from_parent(int r) { - ldout(m_ictx->cct, 20) << "oid=" << m_oid << ", " - << "r=" << r << dendl; + auto cct = m_image_ctx->cct; + ldout(cct, 20) << "oid=" << m_oid << ", r=" << r << dendl; m_lock.Lock(); m_copyup_required = is_copyup_required(); @@ -185,8 +192,7 @@ void CopyupRequest::handle_read_from_parent(int r) { if (r < 0 && r != -ENOENT) { m_lock.Unlock(); - lderr(m_ictx->cct) << "error reading from parent: " << cpp_strerror(r) - << dendl; + lderr(cct) << "error reading from parent: " << cpp_strerror(r) << dendl; finish(r); return; } @@ -194,7 +200,7 @@ void CopyupRequest::handle_read_from_parent(int r) { if (!m_copyup_required) { m_lock.Unlock(); - ldout(m_ictx->cct, 20) << "no-op, skipping" << dendl; + ldout(cct, 20) << "no-op, skipping" << dendl; finish(0); return; } @@ -205,42 +211,42 @@ void CopyupRequest::handle_read_from_parent(int r) { template void CopyupRequest::deep_copy() { - ceph_assert(m_ictx->snap_lock.is_locked()); - ceph_assert(m_ictx->parent_lock.is_locked()); - ceph_assert(m_ictx->parent != nullptr); + auto cct = m_image_ctx->cct; + ceph_assert(m_image_ctx->snap_lock.is_locked()); + ceph_assert(m_image_ctx->parent_lock.is_locked()); + ceph_assert(m_image_ctx->parent != nullptr); m_lock.Lock(); - m_flatten = is_copyup_required() ? true : m_ictx->migration_info.flatten; + m_flatten = is_copyup_required() ? true : m_image_ctx->migration_info.flatten; m_lock.Unlock(); - ldout(m_ictx->cct, 20) << "oid=" << m_oid << ", " - << "flatten=" << m_flatten << dendl; + ldout(cct, 20) << "oid=" << m_oid << ", flatten=" << m_flatten << dendl; m_deep_copy = true; auto ctx = util::create_context_callback< CopyupRequest, &CopyupRequest::handle_deep_copy>(this); auto req = deep_copy::ObjectCopyRequest::create( - m_ictx->parent, m_ictx, m_ictx->migration_info.snap_map, m_object_no, - m_flatten, ctx); + m_image_ctx->parent, m_image_ctx, m_image_ctx->migration_info.snap_map, + m_object_no, m_flatten, ctx); req->send(); - m_ictx->parent_lock.put_read(); - m_ictx->snap_lock.put_read(); + m_image_ctx->parent_lock.put_read(); + m_image_ctx->snap_lock.put_read(); } template void CopyupRequest::handle_deep_copy(int r) { - ldout(m_ictx->cct, 20) << "oid=" << m_oid << ", " - << "r=" << r << dendl; + auto cct = m_image_ctx->cct; + ldout(cct, 20) << "oid=" << m_oid << ", r=" << r << dendl; - m_ictx->snap_lock.get_read(); + m_image_ctx->snap_lock.get_read(); m_lock.Lock(); m_copyup_required = is_copyup_required(); if (r == -ENOENT && !m_flatten && m_copyup_required) { m_lock.Unlock(); - m_ictx->snap_lock.put_read(); + m_image_ctx->snap_lock.put_read(); - ldout(m_ictx->cct, 10) << "restart deep-copy with flatten" << dendl; + ldout(cct, 10) << "restart deep-copy with flatten" << dendl; send(); return; } @@ -249,38 +255,38 @@ void CopyupRequest::handle_deep_copy(int r) { if (r < 0 && r != -ENOENT) { m_lock.Unlock(); - m_ictx->snap_lock.put_read(); + m_image_ctx->snap_lock.put_read(); - lderr(m_ictx->cct) << "error encountered during deep-copy: " - << cpp_strerror(r) << dendl; + lderr(cct) << "error encountered during deep-copy: " << cpp_strerror(r) + << dendl; finish(r); return; } if (!m_copyup_required && !is_update_object_map_required(r)) { m_lock.Unlock(); - m_ictx->snap_lock.put_read(); + m_image_ctx->snap_lock.put_read(); if (r == -ENOENT) { r = 0; } - ldout(m_ictx->cct, 20) << "skipping" << dendl; + ldout(cct, 20) << "skipping" << dendl; finish(r); return; } m_lock.Unlock(); - m_ictx->snap_lock.put_read(); + m_image_ctx->snap_lock.put_read(); update_object_maps(); } template void CopyupRequest::update_object_maps() { - RWLock::RLocker owner_locker(m_ictx->owner_lock); - RWLock::RLocker snap_locker(m_ictx->snap_lock); - if (m_ictx->object_map == nullptr) { + RWLock::RLocker owner_locker(m_image_ctx->owner_lock); + RWLock::RLocker snap_locker(m_image_ctx->snap_lock); + if (m_image_ctx->object_map == nullptr) { snap_locker.unlock(); owner_locker.unlock(); @@ -288,22 +294,23 @@ void CopyupRequest::update_object_maps() { return; } - auto cct = m_ictx->cct; + auto cct = m_image_ctx->cct; ldout(cct, 20) << "oid=" << m_oid << dendl; - if (!m_ictx->snaps.empty()) { + if (!m_image_ctx->snaps.empty()) { if (m_deep_copy) { compute_deep_copy_snap_ids(); } else { - m_snap_ids.insert(m_snap_ids.end(), m_ictx->snaps.rbegin(), - m_ictx->snaps.rend()); + m_snap_ids.insert(m_snap_ids.end(), m_image_ctx->snaps.rbegin(), + m_image_ctx->snaps.rend()); } } bool copy_on_read = m_pending_requests.empty(); uint8_t head_object_map_state = OBJECT_EXISTS; if (copy_on_read && !m_snap_ids.empty() && - m_ictx->test_features(RBD_FEATURE_FAST_DIFF, m_ictx->snap_lock)) { + m_image_ctx->test_features(RBD_FEATURE_FAST_DIFF, + m_image_ctx->snap_lock)) { // HEAD is non-dirty since data is tied to first snapshot head_object_map_state = OBJECT_EXISTS_CLEAN; } @@ -314,35 +321,35 @@ void CopyupRequest::update_object_maps() { head_object_map_state = (*r_it)->get_pre_write_object_map_state(); } - RWLock::WLocker object_map_locker(m_ictx->object_map_lock); - if ((*m_ictx->object_map)[m_object_no] != head_object_map_state) { + RWLock::WLocker object_map_locker(m_image_ctx->object_map_lock); + if ((*m_image_ctx->object_map)[m_object_no] != head_object_map_state) { // (maybe) need to update the HEAD object map state m_snap_ids.push_back(CEPH_NOSNAP); } object_map_locker.unlock(); snap_locker.unlock(); - ceph_assert(m_ictx->exclusive_lock->is_lock_owner()); - AsyncObjectThrottle<>::ContextFactory context_factory( - boost::lambda::bind(boost::lambda::new_ptr(), - boost::lambda::_1, m_ictx, m_object_no, head_object_map_state, &m_snap_ids, - m_trace, boost::lambda::_2)); + ceph_assert(m_image_ctx->exclusive_lock->is_lock_owner()); + typename AsyncObjectThrottle::ContextFactory context_factory( + boost::lambda::bind(boost::lambda::new_ptr>(), + boost::lambda::_1, m_image_ctx, m_object_no, head_object_map_state, + &m_snap_ids, m_trace, boost::lambda::_2)); auto ctx = util::create_context_callback< CopyupRequest, &CopyupRequest::handle_update_object_maps>(this); - auto throttle = new AsyncObjectThrottle<>( - nullptr, *m_ictx, context_factory, ctx, nullptr, 0, m_snap_ids.size()); + auto throttle = new AsyncObjectThrottle( + nullptr, *m_image_ctx, context_factory, ctx, nullptr, 0, m_snap_ids.size()); throttle->start_ops( - m_ictx->config.template get_val("rbd_concurrent_management_ops")); + m_image_ctx->config.template get_val("rbd_concurrent_management_ops")); } template void CopyupRequest::handle_update_object_maps(int r) { - ldout(m_ictx->cct, 20) << "oid=" << m_oid << ", " - << "r=" << r << dendl; + auto cct = m_image_ctx->cct; + ldout(cct, 20) << "oid=" << m_oid << ", r=" << r << dendl; if (r < 0) { - lderr(m_ictx->cct) << "failed to update object map: " << cpp_strerror(r) - << dendl; + lderr(m_image_ctx->cct) << "failed to update object map: " + << cpp_strerror(r) << dendl; finish(r); return; @@ -353,23 +360,21 @@ void CopyupRequest::handle_update_object_maps(int r) { template void CopyupRequest::copyup() { + auto cct = m_image_ctx->cct; + m_image_ctx->snap_lock.get_read(); + auto snapc = m_image_ctx->snapc; + m_image_ctx->snap_lock.put_read(); + m_lock.Lock(); if (!m_copyup_required) { m_lock.Unlock(); - ldout(m_ictx->cct, 20) << "skipping copyup" << dendl; + ldout(cct, 20) << "skipping copyup" << dendl; finish(0); return; } - m_lock.Unlock(); - - ldout(m_ictx->cct, 20) << "oid=" << m_oid << dendl; - m_ictx->snap_lock.get_read(); - ::SnapContext snapc = m_ictx->snapc; - m_ictx->snap_lock.put_read(); - - std::vector snaps; + ldout(cct, 20) << "oid=" << m_oid << dendl; bool copy_on_read = m_pending_requests.empty(); bool deep_copyup = !snapc.snaps.empty() && !m_copyup_data.is_zero(); @@ -377,59 +382,63 @@ void CopyupRequest::copyup() { m_copyup_data.clear(); } - Mutex::Locker locker(m_lock); int r; + librados::ObjectWriteOperation copyup_op; if (copy_on_read || deep_copyup) { - librados::ObjectWriteOperation copyup_op; copyup_op.exec("rbd", "copyup", m_copyup_data); - ObjectRequest::add_write_hint(*m_ictx, ©up_op); + ObjectRequest::add_write_hint(*m_image_ctx, ©up_op); + ++m_pending_copyups; + } + + librados::ObjectWriteOperation write_op; + if (!copy_on_read) { + if (!deep_copyup) { + write_op.exec("rbd", "copyup", m_copyup_data); + ObjectRequest::add_write_hint(*m_image_ctx, &write_op); + } + + // merge all pending write ops into this single RADOS op + for (auto req : m_pending_requests) { + ldout(cct, 20) << "add_copyup_ops " << req << dendl; + req->add_copyup_ops(&write_op); + } + + if (write_op.size() > 0) { + ++m_pending_copyups; + } + } + m_lock.Unlock(); + // issue librados ops at the end to simplify test cases + std::vector snaps; + if (copyup_op.size() > 0) { // send only the copyup request with a blank snapshot context so that // all snapshots are detected from the parent for this object. If // this is a CoW request, a second request will be created for the // actual modification. - m_pending_copyups++; - ldout(m_ictx->cct, 20) << "copyup with empty snapshot context" << dendl; + ldout(cct, 20) << "copyup with empty snapshot context" << dendl; auto comp = util::create_rados_callback< CopyupRequest, &CopyupRequest::handle_copyup>(this); - r = m_ictx->data_ctx.aio_operate( + r = m_image_ctx->data_ctx.aio_operate( m_oid, comp, ©up_op, 0, snaps, (m_trace.valid() ? m_trace.get_info() : nullptr)); ceph_assert(r == 0); comp->release(); } - if (!copy_on_read) { - librados::ObjectWriteOperation write_op; - if (!deep_copyup) { - write_op.exec("rbd", "copyup", m_copyup_data); - ObjectRequest::add_write_hint(*m_ictx, &write_op); - } - - // merge all pending write ops into this single RADOS op - for (auto req : m_pending_requests) { - ldout(m_ictx->cct, 20) << "add_copyup_ops " << req << dendl; - req->add_copyup_ops(&write_op); - } - + if (write_op.size() > 0) { // compare-and-write doesn't add any write ops (copyup+cmpext+write // can't be executed in the same RADOS op because, unless the object // was already present in the clone, cmpext wouldn't see it) - if (!write_op.size()) { - return; - } - - m_pending_copyups++; - ldout(m_ictx->cct, 20) << (!deep_copyup && write_op.size() > 2 ? - "copyup + ops" : !deep_copyup ? - "copyup" : "ops") - << " with current snapshot context" << dendl; + ldout(cct, 20) << (!deep_copyup && write_op.size() > 2 ? + "copyup + ops" : !deep_copyup ? "copyup" : "ops") + << " with current snapshot context" << dendl; snaps.insert(snaps.end(), snapc.snaps.begin(), snapc.snaps.end()); auto comp = util::create_rados_callback< CopyupRequest, &CopyupRequest::handle_copyup>(this); - r = m_ictx->data_ctx.aio_operate( + r = m_image_ctx->data_ctx.aio_operate( m_oid, comp, &write_op, snapc.seq, snaps, (m_trace.valid() ? m_trace.get_info() : nullptr)); ceph_assert(r == 0); @@ -439,6 +448,7 @@ void CopyupRequest::copyup() { template void CopyupRequest::handle_copyup(int r) { + auto cct = m_image_ctx->cct; unsigned pending_copyups; { Mutex::Locker locker(m_lock); @@ -446,30 +456,23 @@ void CopyupRequest::handle_copyup(int r) { pending_copyups = --m_pending_copyups; } - ldout(m_ictx->cct, 20) << "oid=" << m_oid << ", " - << "r=" << r << ", " - << "pending=" << pending_copyups << dendl; + ldout(cct, 20) << "oid=" << m_oid << ", " << "r=" << r << ", " + << "pending=" << pending_copyups << dendl; - if (r == -ENOENT) { - if (pending_copyups == 0) { - // hide the -ENOENT error if this is the last op - r = 0; - } - } else if (r < 0) { - lderr(m_ictx->cct) << "failed to copyup object: " - << cpp_strerror(r) << dendl; + if (r < 0 && r != -ENOENT) { + lderr(cct) << "failed to copyup object: " << cpp_strerror(r) << dendl; complete_requests(false, r); } if (pending_copyups == 0) { - finish(r); + finish(0); } } template void CopyupRequest::finish(int r) { - ldout(m_ictx->cct, 20) << "oid=" << m_oid << ", " - << "r=" << r << dendl; + auto cct = m_image_ctx->cct; + ldout(cct, 20) << "oid=" << m_oid << ", r=" << r << dendl; complete_requests(true, r); delete this; @@ -477,12 +480,13 @@ void CopyupRequest::finish(int r) { template void CopyupRequest::complete_requests(bool override_restart_retval, int r) { + auto cct = m_image_ctx->cct; remove_from_list(); while (!m_pending_requests.empty()) { auto it = m_pending_requests.begin(); auto req = *it; - ldout(m_ictx->cct, 20) << "completing request " << req << dendl; + ldout(cct, 20) << "completing request " << req << dendl; req->handle_copyup(r); m_pending_requests.erase(it); } @@ -494,7 +498,7 @@ void CopyupRequest::complete_requests(bool override_restart_retval, int r) { while (!m_restart_requests.empty()) { auto it = m_restart_requests.begin(); auto req = *it; - ldout(m_ictx->cct, 20) << "restarting request " << req << dendl; + ldout(cct, 20) << "restarting request " << req << dendl; req->handle_copyup(r); m_restart_requests.erase(it); } @@ -508,11 +512,11 @@ void CopyupRequest::disable_append_requests() { template void CopyupRequest::remove_from_list() { - Mutex::Locker copyup_list_locker(m_ictx->copyup_list_lock); + Mutex::Locker copyup_list_locker(m_image_ctx->copyup_list_lock); - auto it = m_ictx->copyup_list.find(m_object_no); - if (it != m_ictx->copyup_list.end()) { - m_ictx->copyup_list.erase(it); + auto it = m_image_ctx->copyup_list.find(m_object_no); + if (it != m_image_ctx->copyup_list.end()) { + m_image_ctx->copyup_list.erase(it); } } @@ -540,55 +544,50 @@ bool CopyupRequest::is_copyup_required() { template bool CopyupRequest::is_deep_copy() const { - ceph_assert(m_ictx->snap_lock.is_locked()); - return !m_ictx->migration_info.empty(); + ceph_assert(m_image_ctx->snap_lock.is_locked()); + return !m_image_ctx->migration_info.empty(); } template bool CopyupRequest::is_update_object_map_required(int r) { - ceph_assert(m_ictx->snap_lock.is_locked()); + ceph_assert(m_image_ctx->snap_lock.is_locked()); if (r < 0) { return false; } - if (m_ictx->object_map == nullptr) { - return false; - } - - if (!is_deep_copy()) { + if (m_image_ctx->object_map == nullptr) { return false; } - auto it = m_ictx->migration_info.snap_map.find(CEPH_NOSNAP); - ceph_assert(it != m_ictx->migration_info.snap_map.end()); + auto it = m_image_ctx->migration_info.snap_map.find(CEPH_NOSNAP); + ceph_assert(it != m_image_ctx->migration_info.snap_map.end()); return it->second[0] != CEPH_NOSNAP; } template void CopyupRequest::compute_deep_copy_snap_ids() { - ceph_assert(m_ictx->snap_lock.is_locked()); + ceph_assert(m_image_ctx->snap_lock.is_locked()); // don't copy ids for the snaps updated by object deep copy or // that don't overlap std::set deep_copied; - for (auto &it : m_ictx->migration_info.snap_map) { + for (auto &it : m_image_ctx->migration_info.snap_map) { if (it.first != CEPH_NOSNAP) { deep_copied.insert(it.second.front()); } } - RWLock::RLocker parent_locker(m_ictx->parent_lock); - std::copy_if(m_ictx->snaps.rbegin(), m_ictx->snaps.rend(), + RWLock::RLocker parent_locker(m_image_ctx->parent_lock); + std::copy_if(m_image_ctx->snaps.rbegin(), m_image_ctx->snaps.rend(), std::back_inserter(m_snap_ids), - [this, cct=m_ictx->cct, &deep_copied](uint64_t snap_id) { + [this, cct=m_image_ctx->cct, &deep_copied](uint64_t snap_id) { if (deep_copied.count(snap_id)) { return false; } uint64_t parent_overlap = 0; - int r = m_ictx->get_parent_overlap(snap_id, - &parent_overlap); + int r = m_image_ctx->get_parent_overlap(snap_id, &parent_overlap); if (r < 0) { ldout(cct, 5) << "failed getting parent overlap for snap_id: " << snap_id << ": " << cpp_strerror(r) << dendl; @@ -597,11 +596,11 @@ void CopyupRequest::compute_deep_copy_snap_ids() { return false; } std::vector> extents; - Striper::extent_to_file(cct, &m_ictx->layout, + Striper::extent_to_file(cct, &m_image_ctx->layout, m_object_no, 0, - m_ictx->layout.object_size, + m_image_ctx->layout.object_size, extents); - auto overlap = m_ictx->prune_parent_extents( + auto overlap = m_image_ctx->prune_parent_extents( extents, parent_overlap); return overlap > 0; }); diff --git a/src/librbd/io/CopyupRequest.h b/src/librbd/io/CopyupRequest.h index 5043ada66c5..c30d5d409cb 100644 --- a/src/librbd/io/CopyupRequest.h +++ b/src/librbd/io/CopyupRequest.h @@ -78,7 +78,7 @@ private: typedef std::vector *> WriteRequests; - ImageCtx *m_ictx; + ImageCtxT *m_image_ctx; std::string m_oid; uint64_t m_object_no; Extents m_image_extents; diff --git a/src/test/librbd/CMakeLists.txt b/src/test/librbd/CMakeLists.txt index 2894bf94351..326511bccf8 100644 --- a/src/test/librbd/CMakeLists.txt +++ b/src/test/librbd/CMakeLists.txt @@ -69,6 +69,7 @@ set(unittest_librbd_srcs image/test_mock_RefreshRequest.cc image/test_mock_RemoveRequest.cc image/test_mock_ValidatePoolRequest.cc + io/test_mock_CopyupRequest.cc io/test_mock_ImageRequest.cc io/test_mock_ImageRequestWQ.cc io/test_mock_ObjectRequest.cc diff --git a/src/test/librbd/deep_copy/test_mock_ObjectCopyRequest.cc b/src/test/librbd/deep_copy/test_mock_ObjectCopyRequest.cc index 6119f7b385b..7816a507e90 100644 --- a/src/test/librbd/deep_copy/test_mock_ObjectCopyRequest.cc +++ b/src/test/librbd/deep_copy/test_mock_ObjectCopyRequest.cc @@ -64,7 +64,7 @@ ImageRequest *ImageRequest::s_instance = nul #include "librbd/deep_copy/ObjectCopyRequest.cc" template class librbd::deep_copy::ObjectCopyRequest; -bool operator==(const SnapContext& rhs, const SnapContext& lhs) { +static bool operator==(const SnapContext& rhs, const SnapContext& lhs) { return (rhs.seq == lhs.seq && rhs.snaps == lhs.snaps); } diff --git a/src/test/librbd/io/test_mock_CopyupRequest.cc b/src/test/librbd/io/test_mock_CopyupRequest.cc new file mode 100644 index 00000000000..823e5b0fe0b --- /dev/null +++ b/src/test/librbd/io/test_mock_CopyupRequest.cc @@ -0,0 +1,987 @@ +// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- +// vim: ts=8 sw=2 smarttab + +#include "test/librbd/test_mock_fixture.h" +#include "test/librbd/test_support.h" +#include "test/librbd/mock/MockExclusiveLock.h" +#include "test/librbd/mock/MockImageCtx.h" +#include "test/librbd/mock/MockJournal.h" +#include "test/librbd/mock/MockObjectMap.h" +#include "test/librados_test_stub/MockTestMemIoCtxImpl.h" +#include "test/librados_test_stub/MockTestMemRadosClient.h" +#include "include/rbd/librbd.hpp" +#include "librbd/deep_copy/ObjectCopyRequest.h" +#include "librbd/io/CopyupRequest.h" +#include "librbd/io/ImageRequest.h" +#include "librbd/io/ObjectRequest.h" +#include "librbd/io/ReadResult.h" + +namespace librbd { +namespace { + +struct MockTestImageCtx : public MockImageCtx { + MockTestImageCtx(ImageCtx &image_ctx, + MockTestImageCtx* mock_parent_image_ctx = nullptr) + : MockImageCtx(image_ctx) { + parent = mock_parent_image_ctx; + } + + std::map*> copyup_list; +}; + +} // anonymous namespace + +namespace util { + +inline ImageCtx *get_image_ctx(MockImageCtx *image_ctx) { + return image_ctx->image_ctx; +} + +} // namespace util + +namespace deep_copy { + +template <> +struct ObjectCopyRequest { + static ObjectCopyRequest* s_instance; + static ObjectCopyRequest* create(librbd::MockImageCtx* parent_image_ctx, + librbd::MockTestImageCtx* image_ctx, + const SnapMap &snap_map, + uint64_t object_number, bool flatten, + Context *on_finish) { + ceph_assert(s_instance != nullptr); + s_instance->object_number = object_number; + s_instance->flatten = flatten; + s_instance->on_finish = on_finish; + return s_instance; + } + + uint64_t object_number; + bool flatten; + Context *on_finish; + + ObjectCopyRequest() { + s_instance = this; + } + + MOCK_METHOD0(send, void()); +}; + +ObjectCopyRequest* ObjectCopyRequest::s_instance = nullptr; + +} // namespace deep_copy + +namespace io { + +template <> +struct ObjectRequest { + static void add_write_hint(librbd::MockTestImageCtx&, + librados::ObjectWriteOperation*) { + } +}; + +template <> +struct AbstractObjectWriteRequest { + C_SaferCond ctx; + void handle_copyup(int r) { + ctx.complete(r); + } + + MOCK_CONST_METHOD0(get_pre_write_object_map_state, uint8_t()); + MOCK_CONST_METHOD0(is_empty_write_op, bool()); + + MOCK_METHOD1(add_copyup_ops, void(librados::ObjectWriteOperation*)); +}; + +template <> +struct ImageRequest { + static ImageRequest *s_instance; + static void aio_read(librbd::MockImageCtx *ictx, AioCompletion *c, + Extents &&image_extents, ReadResult &&read_result, + int op_flags, const ZTracer::Trace &parent_trace) { + s_instance->aio_read(c, image_extents, &read_result); + } + + MOCK_METHOD3(aio_read, void(AioCompletion *, const Extents&, ReadResult*)); + + ImageRequest() { + s_instance = this; + } +}; + +ImageRequest* ImageRequest::s_instance = nullptr; + +} // namespace io +} // namespace librbd + +static bool operator==(const SnapContext& rhs, const SnapContext& lhs) { + return (rhs.seq == lhs.seq && rhs.snaps == lhs.snaps); +} + +#include "librbd/AsyncObjectThrottle.cc" +#include "librbd/io/CopyupRequest.cc" + +namespace librbd { +namespace io { + +using ::testing::_; +using ::testing::InSequence; +using ::testing::Invoke; +using ::testing::Return; +using ::testing::StrEq; +using ::testing::WithArg; +using ::testing::WithArgs; +using ::testing::WithoutArgs; + +struct TestMockIoCopyupRequest : public TestMockFixture { + typedef CopyupRequest MockCopyupRequest; + typedef ImageRequest MockImageRequest; + typedef ObjectRequest MockObjectRequest; + typedef AbstractObjectWriteRequest MockAbstractObjectWriteRequest; + typedef deep_copy::ObjectCopyRequest MockObjectCopyRequest; + + void SetUp() override { + TestMockFixture::SetUp(); + if (!is_feature_enabled(RBD_FEATURE_LAYERING)) { + return; + } + + m_parent_image_name = m_image_name; + m_image_name = get_temp_image_name(); + + librbd::Image image; + librbd::RBD rbd; + ASSERT_EQ(0, rbd.open(m_ioctx, image, m_parent_image_name.c_str(), + nullptr)); + ASSERT_EQ(0, image.snap_create("one")); + ASSERT_EQ(0, image.snap_protect("one")); + + uint64_t features; + ASSERT_EQ(0, image.features(&features)); + image.close(); + + int order = 0; + ASSERT_EQ(0, rbd.clone(m_ioctx, m_parent_image_name.c_str(), "one", m_ioctx, + m_image_name.c_str(), features, &order)); + } + + void expect_get_parent_overlap(MockTestImageCtx& mock_image_ctx, + librados::snap_t snap_id, uint64_t overlap, + int r) { + if (mock_image_ctx.object_map != nullptr) { + EXPECT_CALL(mock_image_ctx, get_parent_overlap(snap_id, _)) + .WillOnce(WithArg<1>(Invoke([overlap, r](uint64_t *o) { + *o = overlap; + return r; + }))); + } + } + + void expect_prune_parent_extents(MockTestImageCtx& mock_image_ctx, + uint64_t overlap, uint64_t object_overlap) { + if (mock_image_ctx.object_map != nullptr) { + EXPECT_CALL(mock_image_ctx, prune_parent_extents(_, overlap)) + .WillOnce(WithoutArgs(Invoke([object_overlap]() { + return object_overlap; + }))); + } + } + + void expect_read_parent(MockTestImageCtx& mock_image_ctx, + MockImageRequest& mock_image_request, + const Extents& image_extents, + const std::string& data, int r) { + EXPECT_CALL(mock_image_request, aio_read(_, image_extents, _)) + .WillOnce(WithArgs<0, 2>(Invoke( + [&mock_image_ctx, image_extents, data, r]( + AioCompletion* aio_comp, ReadResult* read_result) { + aio_comp->read_result = std::move(*read_result); + aio_comp->set_request_count(1); + auto ctx = new ReadResult::C_ImageReadRequest(aio_comp, + image_extents); + ctx->bl.append(data); + mock_image_ctx.image_ctx->op_work_queue->queue(ctx, r); + }))); + } + + void expect_copyup(MockTestImageCtx& mock_image_ctx, uint64_t snap_id, + const std::string& oid, const std::string& data, int r) { + bufferlist in_bl; + in_bl.append(data); + + SnapContext snapc; + if (snap_id == CEPH_NOSNAP) { + snapc = mock_image_ctx.snapc; + } + + EXPECT_CALL(get_mock_io_ctx(mock_image_ctx.data_ctx), + exec(oid, _, StrEq("rbd"), StrEq("copyup"), + ContentsEqual(in_bl), _, snapc)) + .WillOnce(Return(r)); + } + + void expect_write(MockTestImageCtx& mock_image_ctx, uint64_t snap_id, + const std::string& oid, int r) { + SnapContext snapc; + if (snap_id == CEPH_NOSNAP) { + snapc = mock_image_ctx.snapc; + } + + EXPECT_CALL(get_mock_io_ctx(mock_image_ctx.data_ctx), + write(oid, _, 0, 0, snapc)) + .WillOnce(Return(r)); + } + + void expect_test_features(MockTestImageCtx& mock_image_ctx) { + EXPECT_CALL(mock_image_ctx, test_features(_, _)) + .WillRepeatedly(WithArg<0>(Invoke([&mock_image_ctx](uint64_t features) { + return (mock_image_ctx.features & features) != 0; + }))); + } + + void expect_is_lock_owner(MockTestImageCtx& mock_image_ctx) { + if (mock_image_ctx.exclusive_lock != nullptr) { + EXPECT_CALL(*mock_image_ctx.exclusive_lock, + is_lock_owner()).WillRepeatedly(Return(true)); + } + } + + void expect_is_empty_write_op(MockAbstractObjectWriteRequest& mock_write_request, + bool is_empty) { + EXPECT_CALL(mock_write_request, is_empty_write_op()) + .WillOnce(Return(is_empty)); + } + + void expect_add_copyup_ops(MockAbstractObjectWriteRequest& mock_write_request) { + EXPECT_CALL(mock_write_request, add_copyup_ops(_)) + .WillOnce(Invoke([](librados::ObjectWriteOperation* op) { + op->write(0, bufferlist{}); + })); + } + + void expect_get_pre_write_object_map_state(MockTestImageCtx& mock_image_ctx, + MockAbstractObjectWriteRequest& mock_write_request, + uint8_t state) { + if (mock_image_ctx.object_map != nullptr) { + EXPECT_CALL(mock_write_request, get_pre_write_object_map_state()) + .WillOnce(Return(state)); + } + } + + void expect_object_map_at(MockTestImageCtx& mock_image_ctx, + uint64_t object_no, uint8_t state) { + if (mock_image_ctx.object_map != nullptr) { + EXPECT_CALL(*mock_image_ctx.object_map, at(object_no)) + .WillOnce(Return(state)); + } + } + + void expect_object_map_update(MockTestImageCtx& mock_image_ctx, + uint64_t snap_id, uint64_t object_no, + uint8_t state, bool updated, int ret_val) { + if (mock_image_ctx.object_map != nullptr) { + if (!mock_image_ctx.image_ctx->test_features(RBD_FEATURE_FAST_DIFF) && + state == OBJECT_EXISTS_CLEAN) { + state = OBJECT_EXISTS; + } + + EXPECT_CALL(*mock_image_ctx.object_map, + aio_update(snap_id, object_no, object_no + 1, state, + boost::optional(), _, + (snap_id != CEPH_NOSNAP), _)) + .WillOnce(WithArg<7>(Invoke([&mock_image_ctx, updated, ret_val](Context *ctx) { + if (updated) { + mock_image_ctx.op_work_queue->queue(ctx, ret_val); + } + return updated; + }))); + } + } + + void expect_object_copy(MockTestImageCtx& mock_image_ctx, + MockObjectCopyRequest& mock_object_copy_request, + bool flatten, int r) { + EXPECT_CALL(mock_object_copy_request, send()) + .WillOnce(Invoke( + [&mock_image_ctx, &mock_object_copy_request, flatten, r]() { + ASSERT_EQ(flatten, mock_object_copy_request.flatten); + mock_image_ctx.op_work_queue->queue( + mock_object_copy_request.on_finish, r); + })); + } + + std::string m_parent_image_name; +}; + +TEST_F(TestMockIoCopyupRequest, Standard) { + REQUIRE_FEATURE(RBD_FEATURE_LAYERING); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockTestImageCtx mock_parent_image_ctx(*ictx->parent); + MockTestImageCtx mock_image_ctx(*ictx, &mock_parent_image_ctx); + + MockExclusiveLock mock_exclusive_lock; + MockJournal mock_journal; + MockObjectMap mock_object_map; + initialize_features(ictx, mock_image_ctx, mock_exclusive_lock, mock_journal, + mock_object_map); + + expect_op_work_queue(mock_image_ctx); + expect_is_lock_owner(mock_image_ctx); + + InSequence seq; + + MockImageRequest mock_image_request; + std::string data(4096, '1'); + expect_read_parent(mock_parent_image_ctx, mock_image_request, {{0, 4096}}, + data, 0); + + MockAbstractObjectWriteRequest mock_write_request; + expect_get_pre_write_object_map_state(mock_image_ctx, mock_write_request, + OBJECT_EXISTS); + expect_object_map_at(mock_image_ctx, 0, OBJECT_NONEXISTENT); + expect_object_map_update(mock_image_ctx, CEPH_NOSNAP, 0, OBJECT_EXISTS, true, + 0); + + expect_add_copyup_ops(mock_write_request); + expect_copyup(mock_image_ctx, CEPH_NOSNAP, "oid", data, 0); + expect_write(mock_image_ctx, CEPH_NOSNAP, "oid", 0); + + auto req = new MockCopyupRequest(&mock_image_ctx, "oid", 0, + {{0, 4096}}, {}); + mock_image_ctx.copyup_list[0] = req; + req->append_request(&mock_write_request); + req->send(); + + ASSERT_EQ(0, mock_write_request.ctx.wait()); +} + +TEST_F(TestMockIoCopyupRequest, StandardWithSnaps) { + REQUIRE_FEATURE(RBD_FEATURE_LAYERING); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + ictx->snap_lock.get_write(); + ictx->add_snap(cls::rbd::UserSnapshotNamespace(), "2", 2, ictx->size, + ictx->parent_md, RBD_PROTECTION_STATUS_UNPROTECTED, + 0, {}); + ictx->add_snap(cls::rbd::UserSnapshotNamespace(), "1", 1, ictx->size, + ictx->parent_md, RBD_PROTECTION_STATUS_UNPROTECTED, + 0, {}); + ictx->snapc = {2, {2, 1}}; + ictx->snap_lock.put_write(); + + MockTestImageCtx mock_parent_image_ctx(*ictx->parent); + MockTestImageCtx mock_image_ctx(*ictx, &mock_parent_image_ctx); + + MockExclusiveLock mock_exclusive_lock; + MockJournal mock_journal; + MockObjectMap mock_object_map; + initialize_features(ictx, mock_image_ctx, mock_exclusive_lock, mock_journal, + mock_object_map); + + expect_test_features(mock_image_ctx); + expect_op_work_queue(mock_image_ctx); + expect_is_lock_owner(mock_image_ctx); + + InSequence seq; + + MockImageRequest mock_image_request; + std::string data(4096, '1'); + expect_read_parent(mock_parent_image_ctx, mock_image_request, {{0, 4096}}, + data, 0); + + MockAbstractObjectWriteRequest mock_write_request; + expect_get_pre_write_object_map_state(mock_image_ctx, mock_write_request, + OBJECT_EXISTS); + expect_object_map_at(mock_image_ctx, 0, OBJECT_NONEXISTENT); + expect_object_map_update(mock_image_ctx, 1, 0, OBJECT_EXISTS, true, 0); + expect_object_map_update(mock_image_ctx, 2, 0, OBJECT_EXISTS_CLEAN, true, 0); + expect_object_map_update(mock_image_ctx, CEPH_NOSNAP, 0, OBJECT_EXISTS, true, + 0); + + expect_add_copyup_ops(mock_write_request); + expect_copyup(mock_image_ctx, 0, "oid", data, 0); + expect_write(mock_image_ctx, CEPH_NOSNAP, "oid", 0); + + auto req = new MockCopyupRequest(&mock_image_ctx, "oid", 0, + {{0, 4096}}, {}); + mock_image_ctx.copyup_list[0] = req; + req->append_request(&mock_write_request); + req->send(); + + ASSERT_EQ(0, mock_write_request.ctx.wait()); +} + +TEST_F(TestMockIoCopyupRequest, CopyOnRead) { + REQUIRE_FEATURE(RBD_FEATURE_LAYERING); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockTestImageCtx mock_parent_image_ctx(*ictx->parent); + MockTestImageCtx mock_image_ctx(*ictx, &mock_parent_image_ctx); + + MockExclusiveLock mock_exclusive_lock; + MockJournal mock_journal; + MockObjectMap mock_object_map; + initialize_features(ictx, mock_image_ctx, mock_exclusive_lock, mock_journal, + mock_object_map); + + expect_op_work_queue(mock_image_ctx); + expect_is_lock_owner(mock_image_ctx); + + InSequence seq; + + MockImageRequest mock_image_request; + std::string data(4096, '1'); + expect_read_parent(mock_parent_image_ctx, mock_image_request, {{0, 4096}}, + data, 0); + + expect_object_map_at(mock_image_ctx, 0, OBJECT_NONEXISTENT); + expect_object_map_update(mock_image_ctx, CEPH_NOSNAP, 0, OBJECT_EXISTS, true, + 0); + + expect_copyup(mock_image_ctx, CEPH_NOSNAP, "oid", data, 0); + + auto req = new MockCopyupRequest(&mock_image_ctx, "oid", 0, + {{0, 4096}}, {}); + mock_image_ctx.copyup_list[0] = req; + req->send(); + ictx->flush_async_operations(); +} + +TEST_F(TestMockIoCopyupRequest, CopyOnReadWithSnaps) { + REQUIRE_FEATURE(RBD_FEATURE_LAYERING); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + ictx->snap_lock.get_write(); + ictx->add_snap(cls::rbd::UserSnapshotNamespace(), "1", 1, ictx->size, + ictx->parent_md, RBD_PROTECTION_STATUS_UNPROTECTED, + 0, {}); + ictx->snapc = {1, {1}}; + ictx->snap_lock.put_write(); + + MockTestImageCtx mock_parent_image_ctx(*ictx->parent); + MockTestImageCtx mock_image_ctx(*ictx, &mock_parent_image_ctx); + + MockExclusiveLock mock_exclusive_lock; + MockJournal mock_journal; + MockObjectMap mock_object_map; + initialize_features(ictx, mock_image_ctx, mock_exclusive_lock, mock_journal, + mock_object_map); + + expect_test_features(mock_image_ctx); + expect_op_work_queue(mock_image_ctx); + expect_is_lock_owner(mock_image_ctx); + + InSequence seq; + + MockImageRequest mock_image_request; + std::string data(4096, '1'); + expect_read_parent(mock_parent_image_ctx, mock_image_request, {{0, 4096}}, + data, 0); + + expect_object_map_at(mock_image_ctx, 0, OBJECT_NONEXISTENT); + expect_object_map_update(mock_image_ctx, 1, 0, OBJECT_EXISTS, true, 0); + expect_object_map_update(mock_image_ctx, CEPH_NOSNAP, 0, OBJECT_EXISTS_CLEAN, + true, 0); + + expect_copyup(mock_image_ctx, 0, "oid", data, 0); + + auto req = new MockCopyupRequest(&mock_image_ctx, "oid", 0, + {{0, 4096}}, {}); + mock_image_ctx.copyup_list[0] = req; + req->send(); + ictx->flush_async_operations(); +} + +TEST_F(TestMockIoCopyupRequest, DeepCopy) { + REQUIRE_FEATURE(RBD_FEATURE_LAYERING); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockTestImageCtx mock_parent_image_ctx(*ictx->parent); + MockTestImageCtx mock_image_ctx(*ictx, &mock_parent_image_ctx); + + MockExclusiveLock mock_exclusive_lock; + MockJournal mock_journal; + MockObjectMap mock_object_map; + initialize_features(ictx, mock_image_ctx, mock_exclusive_lock, mock_journal, + mock_object_map); + + expect_op_work_queue(mock_image_ctx); + expect_is_lock_owner(mock_image_ctx); + + InSequence seq; + + MockAbstractObjectWriteRequest mock_write_request; + MockObjectCopyRequest mock_object_copy_request; + mock_image_ctx.migration_info = {1, "", "", "image id", {}, ictx->size, true}; + expect_is_empty_write_op(mock_write_request, false); + expect_object_copy(mock_image_ctx, mock_object_copy_request, true, 0); + + expect_is_empty_write_op(mock_write_request, false); + expect_get_pre_write_object_map_state(mock_image_ctx, mock_write_request, + OBJECT_EXISTS); + expect_object_map_at(mock_image_ctx, 0, OBJECT_NONEXISTENT); + expect_object_map_update(mock_image_ctx, CEPH_NOSNAP, 0, OBJECT_EXISTS, true, + 0); + + expect_add_copyup_ops(mock_write_request); + expect_copyup(mock_image_ctx, CEPH_NOSNAP, "oid", "", 0); + expect_write(mock_image_ctx, CEPH_NOSNAP, "oid", 0); + + auto req = new MockCopyupRequest(&mock_image_ctx, "oid", 0, + {{0, 4096}}, {}); + mock_image_ctx.copyup_list[0] = req; + req->append_request(&mock_write_request); + req->send(); + + ASSERT_EQ(0, mock_write_request.ctx.wait()); +} + +TEST_F(TestMockIoCopyupRequest, DeepCopyOnRead) { + REQUIRE_FEATURE(RBD_FEATURE_LAYERING); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockTestImageCtx mock_parent_image_ctx(*ictx->parent); + MockTestImageCtx mock_image_ctx(*ictx, &mock_parent_image_ctx); + + MockExclusiveLock mock_exclusive_lock; + MockJournal mock_journal; + MockObjectMap mock_object_map; + initialize_features(ictx, mock_image_ctx, mock_exclusive_lock, mock_journal, + mock_object_map); + + expect_op_work_queue(mock_image_ctx); + expect_is_lock_owner(mock_image_ctx); + + InSequence seq; + + MockObjectCopyRequest mock_object_copy_request; + mock_image_ctx.migration_info = {1, "", "", "image id", {}, ictx->size, + false}; + expect_object_copy(mock_image_ctx, mock_object_copy_request, true, 0); + + expect_object_map_at(mock_image_ctx, 0, OBJECT_NONEXISTENT); + expect_object_map_update(mock_image_ctx, CEPH_NOSNAP, 0, OBJECT_EXISTS, true, + 0); + + expect_copyup(mock_image_ctx, CEPH_NOSNAP, "oid", "", 0); + + auto req = new MockCopyupRequest(&mock_image_ctx, "oid", 0, + {{0, 4096}}, {}); + mock_image_ctx.copyup_list[0] = req; + req->send(); + ictx->flush_async_operations(); +} + +TEST_F(TestMockIoCopyupRequest, DeepCopyWithSnaps) { + REQUIRE_FEATURE(RBD_FEATURE_LAYERING); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + ictx->snap_lock.get_write(); + ictx->add_snap(cls::rbd::UserSnapshotNamespace(), "3", 3, ictx->size, + ictx->parent_md, RBD_PROTECTION_STATUS_UNPROTECTED, + 0, {}); + ictx->add_snap(cls::rbd::UserSnapshotNamespace(), "2", 2, ictx->size, + ictx->parent_md, RBD_PROTECTION_STATUS_UNPROTECTED, + 0, {}); + ictx->add_snap(cls::rbd::UserSnapshotNamespace(), "1", 1, ictx->size, + ictx->parent_md, RBD_PROTECTION_STATUS_UNPROTECTED, + 0, {}); + ictx->snapc = {3, {3, 2, 1}}; + ictx->snap_lock.put_write(); + + MockTestImageCtx mock_parent_image_ctx(*ictx->parent); + MockTestImageCtx mock_image_ctx(*ictx, &mock_parent_image_ctx); + + MockExclusiveLock mock_exclusive_lock; + MockJournal mock_journal; + MockObjectMap mock_object_map; + initialize_features(ictx, mock_image_ctx, mock_exclusive_lock, mock_journal, + mock_object_map); + + expect_test_features(mock_image_ctx); + expect_op_work_queue(mock_image_ctx); + expect_is_lock_owner(mock_image_ctx); + + InSequence seq; + + MockAbstractObjectWriteRequest mock_write_request; + MockObjectCopyRequest mock_object_copy_request; + mock_image_ctx.migration_info = {1, "", "", "image id", + {{CEPH_NOSNAP, {2, 1}}, {10, {1}}}, + ictx->size, true}; + expect_is_empty_write_op(mock_write_request, false); + expect_object_copy(mock_image_ctx, mock_object_copy_request, true, 0); + + expect_is_empty_write_op(mock_write_request, false); + expect_get_parent_overlap(mock_image_ctx, 2, 0, 0); + expect_get_parent_overlap(mock_image_ctx, 3, 1, 0); + expect_prune_parent_extents(mock_image_ctx, 1, 1); + expect_get_pre_write_object_map_state(mock_image_ctx, mock_write_request, + OBJECT_EXISTS); + expect_object_map_at(mock_image_ctx, 0, OBJECT_NONEXISTENT); + expect_object_map_update(mock_image_ctx, 3, 0, OBJECT_EXISTS, true, 0); + expect_object_map_update(mock_image_ctx, CEPH_NOSNAP, 0, OBJECT_EXISTS, true, + 0); + + expect_add_copyup_ops(mock_write_request); + expect_copyup(mock_image_ctx, CEPH_NOSNAP, "oid", "", 0); + expect_write(mock_image_ctx, CEPH_NOSNAP, "oid", 0); + + auto req = new MockCopyupRequest(&mock_image_ctx, "oid", 0, + {{0, 4096}}, {}); + mock_image_ctx.copyup_list[0] = req; + req->append_request(&mock_write_request); + req->send(); + + ASSERT_EQ(0, mock_write_request.ctx.wait()); +} + +TEST_F(TestMockIoCopyupRequest, ZeroedCopyup) { + REQUIRE_FEATURE(RBD_FEATURE_LAYERING); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockTestImageCtx mock_image_ctx(*ictx); + + MockExclusiveLock mock_exclusive_lock; + MockJournal mock_journal; + MockObjectMap mock_object_map; + initialize_features(ictx, mock_image_ctx, mock_exclusive_lock, mock_journal, + mock_object_map); + + expect_op_work_queue(mock_image_ctx); + expect_is_lock_owner(mock_image_ctx); + + InSequence seq; + + MockAbstractObjectWriteRequest mock_write_request; + expect_is_empty_write_op(mock_write_request, false); + expect_get_pre_write_object_map_state(mock_image_ctx, mock_write_request, + OBJECT_EXISTS); + expect_object_map_at(mock_image_ctx, 0, OBJECT_NONEXISTENT); + expect_object_map_update(mock_image_ctx, CEPH_NOSNAP, 0, OBJECT_EXISTS, true, + 0); + + expect_add_copyup_ops(mock_write_request); + expect_copyup(mock_image_ctx, CEPH_NOSNAP, "oid", "", 0); + expect_write(mock_image_ctx, CEPH_NOSNAP, "oid", 0); + + auto req = new MockCopyupRequest(&mock_image_ctx, "oid", 0, + {{0, 4096}}, {}); + mock_image_ctx.copyup_list[0] = req; + req->append_request(&mock_write_request); + req->send(); + + ASSERT_EQ(0, mock_write_request.ctx.wait()); +} + +TEST_F(TestMockIoCopyupRequest, ZeroedCopyOnRead) { + REQUIRE_FEATURE(RBD_FEATURE_LAYERING); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockTestImageCtx mock_parent_image_ctx(*ictx->parent); + MockTestImageCtx mock_image_ctx(*ictx, &mock_parent_image_ctx); + + MockExclusiveLock mock_exclusive_lock; + MockJournal mock_journal; + MockObjectMap mock_object_map; + initialize_features(ictx, mock_image_ctx, mock_exclusive_lock, mock_journal, + mock_object_map); + + expect_op_work_queue(mock_image_ctx); + expect_is_lock_owner(mock_image_ctx); + + InSequence seq; + + MockImageRequest mock_image_request; + std::string data(4096, '\0'); + expect_read_parent(mock_parent_image_ctx, mock_image_request, {{0, 4096}}, + data, 0); + + expect_object_map_at(mock_image_ctx, 0, OBJECT_NONEXISTENT); + expect_object_map_update(mock_image_ctx, CEPH_NOSNAP, 0, OBJECT_EXISTS, true, + 0); + + expect_copyup(mock_image_ctx, CEPH_NOSNAP, "oid", "", 0); + + auto req = new MockCopyupRequest(&mock_image_ctx, "oid", 0, + {{0, 4096}}, {}); + mock_image_ctx.copyup_list[0] = req; + req->send(); + ictx->flush_async_operations(); +} + +TEST_F(TestMockIoCopyupRequest, NoOpCopyup) { + REQUIRE_FEATURE(RBD_FEATURE_LAYERING); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockTestImageCtx mock_parent_image_ctx(*ictx->parent); + MockTestImageCtx mock_image_ctx(*ictx, &mock_parent_image_ctx); + + MockExclusiveLock mock_exclusive_lock; + MockJournal mock_journal; + MockObjectMap mock_object_map; + initialize_features(ictx, mock_image_ctx, mock_exclusive_lock, mock_journal, + mock_object_map); + + expect_op_work_queue(mock_image_ctx); + expect_is_lock_owner(mock_image_ctx); + + InSequence seq; + + MockImageRequest mock_image_request; + expect_read_parent(mock_parent_image_ctx, mock_image_request, {{0, 4096}}, + "", -ENOENT); + + MockAbstractObjectWriteRequest mock_write_request; + expect_is_empty_write_op(mock_write_request, true); + + auto req = new MockCopyupRequest(&mock_image_ctx, "oid", 0, + {{0, 4096}}, {}); + mock_image_ctx.copyup_list[0] = req; + req->append_request(&mock_write_request); + req->send(); + + ASSERT_EQ(0, mock_write_request.ctx.wait()); +} + +TEST_F(TestMockIoCopyupRequest, RestartWrite) { + REQUIRE_FEATURE(RBD_FEATURE_LAYERING); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockTestImageCtx mock_parent_image_ctx(*ictx->parent); + MockTestImageCtx mock_image_ctx(*ictx, &mock_parent_image_ctx); + + MockExclusiveLock mock_exclusive_lock; + MockJournal mock_journal; + MockObjectMap mock_object_map; + initialize_features(ictx, mock_image_ctx, mock_exclusive_lock, mock_journal, + mock_object_map); + + expect_op_work_queue(mock_image_ctx); + expect_is_lock_owner(mock_image_ctx); + + InSequence seq; + + MockImageRequest mock_image_request; + std::string data(4096, '1'); + expect_read_parent(mock_parent_image_ctx, mock_image_request, {{0, 4096}}, + data, 0); + + MockAbstractObjectWriteRequest mock_write_request1; + expect_get_pre_write_object_map_state(mock_image_ctx, mock_write_request1, + OBJECT_EXISTS); + expect_object_map_at(mock_image_ctx, 0, OBJECT_NONEXISTENT); + expect_object_map_update(mock_image_ctx, CEPH_NOSNAP, 0, OBJECT_EXISTS, true, + 0); + + auto req = new MockCopyupRequest(&mock_image_ctx, "oid", 0, + {{0, 4096}}, {}); + expect_add_copyup_ops(mock_write_request1); + expect_copyup(mock_image_ctx, CEPH_NOSNAP, "oid", data, 0); + + MockAbstractObjectWriteRequest mock_write_request2; + EXPECT_CALL(get_mock_io_ctx(mock_image_ctx.data_ctx), + write("oid", _, 0, 0, _)) + .WillOnce(WithoutArgs(Invoke([req, &mock_write_request2]() { + req->append_request(&mock_write_request2); + return 0; + }))); + + mock_image_ctx.copyup_list[0] = req; + req->append_request(&mock_write_request1); + req->send(); + + ASSERT_EQ(0, mock_write_request1.ctx.wait()); + ASSERT_EQ(-ERESTART, mock_write_request2.ctx.wait()); +} + +TEST_F(TestMockIoCopyupRequest, ReadFromParentError) { + REQUIRE_FEATURE(RBD_FEATURE_LAYERING); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockTestImageCtx mock_parent_image_ctx(*ictx->parent); + MockTestImageCtx mock_image_ctx(*ictx, &mock_parent_image_ctx); + + MockExclusiveLock mock_exclusive_lock; + MockJournal mock_journal; + MockObjectMap mock_object_map; + initialize_features(ictx, mock_image_ctx, mock_exclusive_lock, mock_journal, + mock_object_map); + + expect_op_work_queue(mock_image_ctx); + expect_is_lock_owner(mock_image_ctx); + + InSequence seq; + + MockImageRequest mock_image_request; + expect_read_parent(mock_parent_image_ctx, mock_image_request, {{0, 4096}}, + "", -EPERM); + + MockAbstractObjectWriteRequest mock_write_request; + expect_is_empty_write_op(mock_write_request, false); + + auto req = new MockCopyupRequest(&mock_image_ctx, "oid", 0, + {{0, 4096}}, {}); + mock_image_ctx.copyup_list[0] = req; + req->append_request(&mock_write_request); + req->send(); + + ASSERT_EQ(-EPERM, mock_write_request.ctx.wait()); +} + +TEST_F(TestMockIoCopyupRequest, DeepCopyError) { + REQUIRE_FEATURE(RBD_FEATURE_LAYERING); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockTestImageCtx mock_parent_image_ctx(*ictx->parent); + MockTestImageCtx mock_image_ctx(*ictx, &mock_parent_image_ctx); + + MockExclusiveLock mock_exclusive_lock; + MockJournal mock_journal; + MockObjectMap mock_object_map; + initialize_features(ictx, mock_image_ctx, mock_exclusive_lock, mock_journal, + mock_object_map); + + expect_op_work_queue(mock_image_ctx); + expect_is_lock_owner(mock_image_ctx); + + InSequence seq; + + MockAbstractObjectWriteRequest mock_write_request; + MockObjectCopyRequest mock_object_copy_request; + mock_image_ctx.migration_info = {1, "", "", "image id", {}, ictx->size, true}; + expect_is_empty_write_op(mock_write_request, false); + expect_object_copy(mock_image_ctx, mock_object_copy_request, true, -EPERM); + + expect_is_empty_write_op(mock_write_request, false); + + auto req = new MockCopyupRequest(&mock_image_ctx, "oid", 0, + {{0, 4096}}, {}); + mock_image_ctx.copyup_list[0] = req; + req->append_request(&mock_write_request); + req->send(); + + ASSERT_EQ(-EPERM, mock_write_request.ctx.wait()); +} + +TEST_F(TestMockIoCopyupRequest, UpdateObjectMapError) { + REQUIRE_FEATURE(RBD_FEATURE_LAYERING | RBD_FEATURE_OBJECT_MAP); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockTestImageCtx mock_parent_image_ctx(*ictx->parent); + MockTestImageCtx mock_image_ctx(*ictx, &mock_parent_image_ctx); + + MockExclusiveLock mock_exclusive_lock; + MockJournal mock_journal; + MockObjectMap mock_object_map; + initialize_features(ictx, mock_image_ctx, mock_exclusive_lock, mock_journal, + mock_object_map); + + expect_op_work_queue(mock_image_ctx); + expect_is_lock_owner(mock_image_ctx); + + InSequence seq; + + MockImageRequest mock_image_request; + std::string data(4096, '1'); + expect_read_parent(mock_parent_image_ctx, mock_image_request, {{0, 4096}}, + data, 0); + + MockAbstractObjectWriteRequest mock_write_request; + expect_get_pre_write_object_map_state(mock_image_ctx, mock_write_request, + OBJECT_EXISTS); + expect_object_map_at(mock_image_ctx, 0, OBJECT_NONEXISTENT); + expect_object_map_update(mock_image_ctx, CEPH_NOSNAP, 0, OBJECT_EXISTS, true, + -EINVAL); + + auto req = new MockCopyupRequest(&mock_image_ctx, "oid", 0, + {{0, 4096}}, {}); + mock_image_ctx.copyup_list[0] = req; + req->append_request(&mock_write_request); + req->send(); + + ASSERT_EQ(-EINVAL, mock_write_request.ctx.wait()); +} + +TEST_F(TestMockIoCopyupRequest, CopyupError) { + REQUIRE_FEATURE(RBD_FEATURE_LAYERING); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + ictx->snap_lock.get_write(); + ictx->add_snap(cls::rbd::UserSnapshotNamespace(), "1", 1, ictx->size, + ictx->parent_md, RBD_PROTECTION_STATUS_UNPROTECTED, + 0, {}); + ictx->snapc = {1, {1}}; + ictx->snap_lock.put_write(); + + MockTestImageCtx mock_parent_image_ctx(*ictx->parent); + MockTestImageCtx mock_image_ctx(*ictx, &mock_parent_image_ctx); + + MockExclusiveLock mock_exclusive_lock; + MockJournal mock_journal; + MockObjectMap mock_object_map; + initialize_features(ictx, mock_image_ctx, mock_exclusive_lock, mock_journal, + mock_object_map); + + expect_test_features(mock_image_ctx); + expect_op_work_queue(mock_image_ctx); + expect_is_lock_owner(mock_image_ctx); + + InSequence seq; + + MockImageRequest mock_image_request; + std::string data(4096, '1'); + expect_read_parent(mock_parent_image_ctx, mock_image_request, {{0, 4096}}, + data, 0); + + MockAbstractObjectWriteRequest mock_write_request; + expect_get_pre_write_object_map_state(mock_image_ctx, mock_write_request, + OBJECT_EXISTS); + expect_object_map_at(mock_image_ctx, 0, OBJECT_NONEXISTENT); + expect_object_map_update(mock_image_ctx, 1, 0, OBJECT_EXISTS, true, 0); + expect_object_map_update(mock_image_ctx, CEPH_NOSNAP, 0, OBJECT_EXISTS, true, + 0); + + expect_add_copyup_ops(mock_write_request); + expect_copyup(mock_image_ctx, 0, "oid", data, -EPERM); + expect_write(mock_image_ctx, CEPH_NOSNAP, "oid", 0); + + auto req = new MockCopyupRequest(&mock_image_ctx, "oid", 0, + {{0, 4096}}, {}); + mock_image_ctx.copyup_list[0] = req; + req->append_request(&mock_write_request); + req->send(); + + ASSERT_EQ(-EPERM, mock_write_request.ctx.wait()); + ictx->flush_async_operations(); +} + +} // namespace io +} // namespace librbd diff --git a/src/test/librbd/mock/MockObjectMap.h b/src/test/librbd/mock/MockObjectMap.h index e756178b3a9..2692a30ff0b 100644 --- a/src/test/librbd/mock/MockObjectMap.h +++ b/src/test/librbd/mock/MockObjectMap.h @@ -11,6 +11,11 @@ namespace librbd { struct MockObjectMap { + MOCK_METHOD1(at, uint8_t(uint64_t)); + uint8_t operator[](uint64_t object_no) { + return at(object_no); + } + MOCK_CONST_METHOD1(enabled, bool(const RWLock &object_map_lock)); MOCK_CONST_METHOD0(size, uint64_t()); -- 2.39.5