From 0e643fb926f0484bb4f860740c7f85b692de6737 Mon Sep 17 00:00:00 2001 From: Jason Dillaman Date: Thu, 9 Nov 2017 12:10:30 -0500 Subject: [PATCH] librbd: refactor object write request state machines Fixes: http://tracker.ceph.com/issues/20789 Signed-off-by: Jason Dillaman --- src/librbd/Journal.cc | 2 +- src/librbd/io/ObjectRequest.cc | 481 ++++------ src/librbd/io/ObjectRequest.h | 188 ++-- src/test/librbd/io/test_mock_ImageRequest.cc | 2 +- src/test/librbd/io/test_mock_ObjectRequest.cc | 888 +++++++++++++++++- src/test/librbd/mock/MockImageCtx.h | 5 +- .../librbd/operation/test_mock_TrimRequest.cc | 2 +- 7 files changed, 1133 insertions(+), 435 deletions(-) diff --git a/src/librbd/Journal.cc b/src/librbd/Journal.cc index 8203390808d..64031efe473 100644 --- a/src/librbd/Journal.cc +++ b/src/librbd/Journal.cc @@ -1496,7 +1496,7 @@ void Journal::handle_io_event_safe(int r, uint64_t tid) { it != aio_object_requests.end(); ++it) { if (r < 0) { // don't send aio requests if the journal fails -- bubble error up - (*it)->complete(r); + (*it)->fail(r); } else { // send any waiting aio requests now that journal entry is safe (*it)->send(); diff --git a/src/librbd/io/ObjectRequest.cc b/src/librbd/io/ObjectRequest.cc index 2d17793ba80..15babcaeff6 100644 --- a/src/librbd/io/ObjectRequest.cc +++ b/src/librbd/io/ObjectRequest.cc @@ -108,12 +108,11 @@ template ObjectRequest::ObjectRequest(I *ictx, const std::string &oid, uint64_t objectno, uint64_t off, uint64_t len, librados::snap_t snap_id, - bool hide_enoent, const char *trace_name, - const ZTracer::Trace &trace, + const char *trace_name, + const ZTracer::Trace &trace, Context *completion) : m_ictx(ictx), m_oid(oid), m_object_no(objectno), m_object_off(off), m_object_len(len), m_snap_id(snap_id), m_completion(completion), - m_hide_enoent(hide_enoent), m_trace(util::create_trace(*ictx, "", trace)) { if (m_trace.valid()) { m_trace.copy_name(trace_name + std::string(" ") + oid); @@ -130,19 +129,6 @@ void ObjectRequest::add_write_hint(I& image_ctx, } } -template -void ObjectRequest::complete(int r) -{ - if (this->should_complete(r)) { - ldout(m_ictx->cct, 20) << dendl; - if (m_hide_enoent && r == -ENOENT) { - r = 0; - } - m_completion->complete(r); - delete this; - } -} - template bool ObjectRequest::compute_parent_extents(Extents *parent_extents) { assert(m_ictx->snap_lock.is_locked()); @@ -199,7 +185,7 @@ ObjectReadRequest::ObjectReadRequest(I *ictx, const std::string &oid, int op_flags, bool cache_initiated, const ZTracer::Trace &parent_trace, Context *completion) - : ObjectRequest(ictx, oid, objectno, offset, len, snap_id, false, "read", + : ObjectRequest(ictx, oid, objectno, offset, len, snap_id, "read", parent_trace, completion), m_op_flags(op_flags), m_cache_initiated(cache_initiated) { } @@ -405,29 +391,34 @@ void ObjectReadRequest::copyup() { this->finish(0); } -template -bool ObjectReadRequest::should_complete(int r) { - // TODO remove once fully converted - assert(false); -} - /** write **/ template AbstractObjectWriteRequest::AbstractObjectWriteRequest( I *ictx, const std::string &oid, uint64_t object_no, uint64_t object_off, - uint64_t len, const ::SnapContext &snapc, bool hide_enoent, - const char *trace_name, const ZTracer::Trace &parent_trace, - Context *completion) + uint64_t len, const ::SnapContext &snapc, const char *trace_name, + const ZTracer::Trace &parent_trace, Context *completion) : ObjectRequest(ictx, oid, object_no, object_off, len, CEPH_NOSNAP, - hide_enoent, trace_name, parent_trace, completion), - m_state(LIBRBD_AIO_WRITE_FLAT), m_snap_seq(snapc.seq.val) + trace_name, parent_trace, completion), + m_snap_seq(snapc.seq.val) { m_snaps.insert(m_snaps.end(), snapc.snaps.begin(), snapc.snaps.end()); - RWLock::RLocker snap_locker(ictx->snap_lock); - RWLock::RLocker parent_locker(ictx->parent_lock); - this->compute_parent_extents(&this->m_parent_extents); + { + RWLock::RLocker snap_locker(ictx->snap_lock); + RWLock::RLocker parent_locker(ictx->parent_lock); + this->compute_parent_extents(&m_parent_extents); + } + + if (this->m_object_off == 0 && + this->m_object_len == ictx->get_object_size()) { + m_full_object = true; + } + + if (!this->has_parent() || + (m_full_object && m_snaps.empty() && !is_post_copyup_write_required())) { + this->m_copyup_enabled = false; + } } template @@ -440,100 +431,6 @@ void AbstractObjectWriteRequest::add_write_hint( } } -template -void AbstractObjectWriteRequest::handle_copyup(int r) { - I *image_ctx = this->m_ictx; - ldout(image_ctx->cct, 20) << "r=" << r << dendl; - - // TODO - assert(m_state == LIBRBD_AIO_WRITE_COPYUP); - this->complete(r); -} - -template -void AbstractObjectWriteRequest::guard_write() -{ - I *image_ctx = this->m_ictx; - if (this->has_parent()) { - m_state = LIBRBD_AIO_WRITE_GUARD; - m_write.assert_exists(); - ldout(image_ctx->cct, 20) << "guarding write" << dendl; - } -} - -template -bool AbstractObjectWriteRequest::should_complete(int r) -{ - I *image_ctx = this->m_ictx; - ldout(image_ctx->cct, 20) << this->get_op_type() << this->m_oid << " " - << this->m_object_off << "~" << this->m_object_len - << " r = " << r << dendl; - - bool finished = true; - switch (m_state) { - case LIBRBD_AIO_WRITE_PRE: - ldout(image_ctx->cct, 20) << "WRITE_PRE" << dendl; - if (r < 0) { - return true; - } - - send_write_op(); - finished = false; - break; - - case LIBRBD_AIO_WRITE_POST: - ldout(image_ctx->cct, 20) << "WRITE_POST" << dendl; - finished = true; - break; - - case LIBRBD_AIO_WRITE_GUARD: - ldout(image_ctx->cct, 20) << "WRITE_CHECK_GUARD" << dendl; - - if (r == -ENOENT) { - handle_write_guard(); - finished = false; - break; - } else if (r < 0) { - // pass the error code to the finish context - m_state = LIBRBD_AIO_WRITE_ERROR; - this->complete(r); - finished = false; - break; - } - - finished = send_post_object_map_update(); - break; - - case LIBRBD_AIO_WRITE_COPYUP: - ldout(image_ctx->cct, 20) << "WRITE_COPYUP" << dendl; - if (r < 0) { - m_state = LIBRBD_AIO_WRITE_ERROR; - this->complete(r); - finished = false; - } else { - finished = send_post_object_map_update(); - } - break; - - case LIBRBD_AIO_WRITE_FLAT: - ldout(image_ctx->cct, 20) << "WRITE_FLAT" << dendl; - - finished = send_post_object_map_update(); - break; - - case LIBRBD_AIO_WRITE_ERROR: - assert(r < 0); - lderr(image_ctx->cct) << "WRITE_ERROR: " << cpp_strerror(r) << dendl; - break; - - default: - lderr(image_ctx->cct) << "invalid request state: " << m_state << dendl; - ceph_abort(); - } - - return finished; -} - template void AbstractObjectWriteRequest::send() { I *image_ctx = this->m_ictx; @@ -552,83 +449,119 @@ void AbstractObjectWriteRequest::send() { } } - send_write(); + if (!m_object_may_exist && is_no_op_for_nonexistent_object()) { + ldout(image_ctx->cct, 20) << "skipping no-op on nonexistent object" + << dendl; + this->async_finish(0); + return; + } + + pre_write_object_map_update(); } template -void AbstractObjectWriteRequest::send_pre_object_map_update() { +void AbstractObjectWriteRequest::pre_write_object_map_update() { I *image_ctx = this->m_ictx; - ldout(image_ctx->cct, 20) << dendl; - { - RWLock::RLocker snap_lock(image_ctx->snap_lock); - if (image_ctx->object_map != nullptr) { - uint8_t new_state = this->get_pre_write_object_map_state(); - RWLock::WLocker object_map_locker(image_ctx->object_map_lock); - ldout(image_ctx->cct, 20) << this->m_oid << " " << this->m_object_off - << "~" << this->m_object_len << dendl; - m_state = LIBRBD_AIO_WRITE_PRE; - - if (image_ctx->object_map->template aio_update>( - CEPH_NOSNAP, this->m_object_no, new_state, {}, this->m_trace, - this)) { - return; - } - } + image_ctx->snap_lock.get_read(); + if (image_ctx->object_map == nullptr || !is_object_map_update_enabled()) { + image_ctx->snap_lock.put_read(); + write_object(); + return; + } + + if (!m_object_may_exist && m_copyup_enabled) { + // optimization: copyup required + image_ctx->snap_lock.put_read(); + copyup(); + return; + } + + uint8_t new_state = this->get_pre_write_object_map_state(); + ldout(image_ctx->cct, 20) << this->m_oid << " " << this->m_object_off + << "~" << this->m_object_len << dendl; + + image_ctx->object_map_lock.get_write(); + if (image_ctx->object_map->template aio_update< + AbstractObjectWriteRequest, + &AbstractObjectWriteRequest::handle_pre_write_object_map_update>( + CEPH_NOSNAP, this->m_object_no, new_state, {}, this->m_trace, this)) { + image_ctx->object_map_lock.put_write(); + image_ctx->snap_lock.put_read(); + return; } - send_write_op(); + image_ctx->object_map_lock.put_write(); + image_ctx->snap_lock.put_read(); + write_object(); } template -bool AbstractObjectWriteRequest::send_post_object_map_update() { +void AbstractObjectWriteRequest::handle_pre_write_object_map_update(int r) { I *image_ctx = this->m_ictx; - ldout(image_ctx->cct, 20) << dendl; - - RWLock::RLocker snap_locker(image_ctx->snap_lock); - if (image_ctx->object_map == nullptr || !post_object_map_update()) { - return true; - } + ldout(image_ctx->cct, 20) << "r=" << r << dendl; - // should have been flushed prior to releasing lock - assert(image_ctx->exclusive_lock->is_lock_owner()); + assert(r == 0); + write_object(); +} - RWLock::WLocker object_map_locker(image_ctx->object_map_lock); - ldout(image_ctx->cct, 20) << this->m_oid << " " << this->m_object_off - << "~" << this->m_object_len << dendl; - m_state = LIBRBD_AIO_WRITE_POST; +template +void AbstractObjectWriteRequest::write_object() { + I *image_ctx = this->m_ictx; + ldout(image_ctx->cct, 20) << dendl; - if (image_ctx->object_map->template aio_update>( - CEPH_NOSNAP, this->m_object_no, OBJECT_NONEXISTENT, OBJECT_PENDING, - this->m_trace, this)) { - return false; + librados::ObjectWriteOperation write; + if (m_copyup_enabled) { + ldout(image_ctx->cct, 20) << "guarding write" << dendl; + write.assert_exists(); } - return true; + add_write_hint(&write); + add_write_ops(&write); + assert(write.size() != 0); + + librados::AioCompletion *rados_completion = util::create_rados_callback< + AbstractObjectWriteRequest, + &AbstractObjectWriteRequest::handle_write_object>(this); + int r = image_ctx->data_ctx.aio_operate( + this->m_oid, rados_completion, &write, m_snap_seq, m_snaps, + (this->m_trace.valid() ? this->m_trace.get_info() : nullptr)); + assert(r == 0); + rados_completion->release(); } template -void AbstractObjectWriteRequest::send_write() { +void AbstractObjectWriteRequest::handle_write_object(int r) { I *image_ctx = this->m_ictx; - ldout(image_ctx->cct, 20) << this->m_oid << " " << this->m_object_off << "~" - << this->m_object_len << " object exist " - << m_object_may_exist << dendl; + ldout(image_ctx->cct, 20) << "r=" << r << dendl; - if (!m_object_may_exist && this->has_parent()) { - m_state = LIBRBD_AIO_WRITE_GUARD; - handle_write_guard(); - } else { - send_pre_object_map_update(); + r = filter_write_result(r); + if (r == -ENOENT) { + if (m_copyup_enabled) { + copyup(); + return; + } + } else if (r == -EILSEQ) { + ldout(image_ctx->cct, 10) << "failed to write object" << dendl; + this->finish(r); + return; + } else if (r < 0) { + lderr(image_ctx->cct) << "failed to write object: " << cpp_strerror(r) + << dendl; + this->finish(r); + return; } + + post_write_object_map_update(); } template -void AbstractObjectWriteRequest::send_copyup() -{ +void AbstractObjectWriteRequest::copyup() { I *image_ctx = this->m_ictx; - ldout(image_ctx->cct, 20) << this->m_oid << " " << this->m_object_off - << "~" << this->m_object_len << dendl; - m_state = LIBRBD_AIO_WRITE_COPYUP; + ldout(image_ctx->cct, 20) << dendl; + + assert(!m_copyup_in_progress); + m_copyup_in_progress = true; image_ctx->copyup_list_lock.Lock(); auto it = image_ctx->copyup_list.find(this->m_object_no); @@ -651,98 +584,77 @@ void AbstractObjectWriteRequest::send_copyup() } template -void AbstractObjectWriteRequest::send_write_op() -{ +void AbstractObjectWriteRequest::handle_copyup(int r) { I *image_ctx = this->m_ictx; - m_state = LIBRBD_AIO_WRITE_FLAT; - if (m_guard) { - guard_write(); + ldout(image_ctx->cct, 20) << "r=" << r << dendl; + + assert(m_copyup_in_progress); + m_copyup_in_progress = false; + + if (r < 0) { + lderr(image_ctx->cct) << "failed to copyup object: " << cpp_strerror(r) + << dendl; + this->finish(r); + return; } - add_write_hint(&m_write); - add_write_ops(&m_write); - assert(m_write.size() != 0); + if (is_post_copyup_write_required()) { + write_object(); + return; + } - librados::AioCompletion *rados_completion = - util::create_rados_callback(this); - int r = image_ctx->data_ctx.aio_operate( - this->m_oid, rados_completion, &m_write, m_snap_seq, m_snaps, - (this->m_trace.valid() ? this->m_trace.get_info() : nullptr)); - assert(r == 0); - rados_completion->release(); + post_write_object_map_update(); } template -void AbstractObjectWriteRequest::handle_write_guard() -{ +void AbstractObjectWriteRequest::post_write_object_map_update() { I *image_ctx = this->m_ictx; - bool has_parent; - { - RWLock::RLocker snap_locker(image_ctx->snap_lock); - RWLock::RLocker parent_locker(image_ctx->parent_lock); - has_parent = this->compute_parent_extents(&this->m_parent_extents); - } - // If parent still exists, overlap might also have changed. - if (has_parent) { - send_copyup(); - } else { - // parent may have disappeared -- send original write again - ldout(image_ctx->cct, 20) << "should_complete(" << this - << "): parent overlap now 0" << dendl; - send_write(); + + image_ctx->snap_lock.get_read(); + if (image_ctx->object_map == nullptr || !is_object_map_update_enabled() || + !is_non_existent_post_write_object_map_state()) { + image_ctx->snap_lock.put_read(); + this->finish(0); + return; } -} -template -void ObjectWriteRequest::add_write_ops(librados::ObjectWriteOperation *wr) { - I *image_ctx = this->m_ictx; + ldout(image_ctx->cct, 20) << dendl; - if (this->m_object_off == 0 && - this->m_object_len == image_ctx->get_object_size()) { - wr->write_full(m_write_data); - } else { - wr->write(this->m_object_off, m_write_data); + // should have been flushed prior to releasing lock + assert(image_ctx->exclusive_lock->is_lock_owner()); + image_ctx->object_map_lock.get_write(); + if (image_ctx->object_map->template aio_update< + AbstractObjectWriteRequest, + &AbstractObjectWriteRequest::handle_post_write_object_map_update>( + CEPH_NOSNAP, this->m_object_no, OBJECT_NONEXISTENT, OBJECT_PENDING, + this->m_trace, this)) { + image_ctx->object_map_lock.put_write(); + image_ctx->snap_lock.put_read(); + return; } - wr->set_op_flags2(m_op_flags); + + image_ctx->object_map_lock.put_write(); + image_ctx->snap_lock.put_read(); + this->finish(0); } template -void ObjectWriteRequest::send_write() { +void AbstractObjectWriteRequest::handle_post_write_object_map_update(int r) { I *image_ctx = this->m_ictx; - bool write_full = (this->m_object_off == 0 && - this->m_object_len == image_ctx->get_object_size()); - ldout(image_ctx->cct, 20) << this->m_oid << " " - << this->m_object_off << "~" << this->m_object_len - << " object exist " << this->m_object_may_exist - << " write_full " << write_full << dendl; - if (write_full && !this->has_parent()) { - this->m_guard = false; - } + ldout(image_ctx->cct, 20) << "r=" << r << dendl; - AbstractObjectWriteRequest::send_write(); + assert(r == 0); + this->finish(0); } template -void ObjectDiscardRequest::send_write() { - I *image_ctx = this->m_ictx; - ldout(image_ctx->cct, 20) << this->m_oid << " " << get_op_type() << " " - << this->m_object_off << "~" - << this->m_object_len << ", " - << "object exist " << this->m_object_may_exist - << dendl; - - if (!this->m_object_may_exist && !this->has_parent()) { - // optimization: nothing to discard - this->m_state = AbstractObjectWriteRequest::LIBRBD_AIO_WRITE_FLAT; - Context *ctx = util::create_context_callback>(this); - image_ctx->op_work_queue->queue(ctx, 0); - } else if (m_discard_action == DISCARD_ACTION_REMOVE || - m_discard_action == DISCARD_ACTION_REMOVE_TRUNCATE) { - // optimization: skip the copyup path for removals - this->send_pre_object_map_update(); +void ObjectWriteRequest::add_write_ops(librados::ObjectWriteOperation *wr) { + if (this->m_full_object) { + wr->write_full(m_write_data); } else { - AbstractObjectWriteRequest::send_write(); + wr->write(this->m_object_off, m_write_data); } + wr->set_op_flags2(m_op_flags); } template @@ -752,31 +664,12 @@ void ObjectWriteSameRequest::add_write_ops( wr->set_op_flags2(m_op_flags); } -template -void ObjectWriteSameRequest::send_write() { - I *image_ctx = this->m_ictx; - bool write_full = (this->m_object_off == 0 && - this->m_object_len == image_ctx->get_object_size()); - ldout(image_ctx->cct, 20) << this->m_oid << " " << this->m_object_off << "~" - << this->m_object_len << " write_full " - << write_full << dendl; - if (write_full && !this->has_parent()) { - this->m_guard = false; - } - - AbstractObjectWriteRequest::send_write(); -} - template void ObjectCompareAndWriteRequest::add_write_ops( librados::ObjectWriteOperation *wr) { - I *image_ctx = this->m_ictx; - - // add cmpext ops wr->cmpext(this->m_object_off, m_cmp_bl, nullptr); - if (this->m_object_off == 0 && - this->m_object_len == image_ctx->get_object_size()) { + if (this->m_full_object) { wr->write_full(m_write_bl); } else { wr->write(this->m_object_off, m_write_bl); @@ -785,52 +678,24 @@ void ObjectCompareAndWriteRequest::add_write_ops( } template -void ObjectCompareAndWriteRequest::send_write() { - I *image_ctx = this->m_ictx; - bool write_full = (this->m_object_off == 0 && - this->m_object_len == image_ctx->get_object_size()); - ldout(image_ctx->cct, 20) << this->m_oid << " " - << this->m_object_off << "~" << this->m_object_len - << " object exist " << this->m_object_may_exist - << " write_full " << write_full << dendl; - if (write_full && !this->has_parent()) { - this->m_guard = false; - } +int ObjectCompareAndWriteRequest::filter_write_result(int r) const { + if (r <= -MAX_ERRNO) { + I *image_ctx = this->m_ictx; + Extents image_extents; - AbstractObjectWriteRequest::send_write(); -} - -template -void ObjectCompareAndWriteRequest::complete(int r) -{ - I *image_ctx = this->m_ictx; - if (this->should_complete(r)) { - ldout(image_ctx->cct, 20) << "complete " << this << dendl; - - if (this->m_hide_enoent && r == -ENOENT) { - r = 0; - } - - vector > file_extents; - if (r <= -MAX_ERRNO) { - // object extent compare mismatch - uint64_t offset = -MAX_ERRNO - r; - Striper::extent_to_file(image_ctx->cct, &image_ctx->layout, - this->m_object_no, offset, this->m_object_len, - file_extents); - - assert(file_extents.size() == 1); + // object extent compare mismatch + uint64_t offset = -MAX_ERRNO - r; + Striper::extent_to_file(image_ctx->cct, &image_ctx->layout, + this->m_object_no, offset, this->m_object_len, + image_extents); + assert(image_extents.size() == 1); - uint64_t mismatch_offset = file_extents[0].first; - if (this->m_mismatch_offset) - *this->m_mismatch_offset = mismatch_offset; - r = -EILSEQ; + if (m_mismatch_offset) { + *m_mismatch_offset = image_extents[0].first; } - - //compare and write object extent error - this->m_completion->complete(r); - delete this; + r = -EILSEQ; } + return r; } } // namespace io diff --git a/src/librbd/io/ObjectRequest.h b/src/librbd/io/ObjectRequest.h index f8d6945fc36..a015fb199ec 100644 --- a/src/librbd/io/ObjectRequest.h +++ b/src/librbd/io/ObjectRequest.h @@ -23,16 +23,12 @@ namespace io { struct AioCompletion; template class CopyupRequest; -template class ObjectRemoveRequest; -template class ObjectTruncateRequest; -template class ObjectWriteRequest; -template class ObjectZeroRequest; struct ObjectRequestHandle { virtual ~ObjectRequestHandle() { } - virtual void complete(int r) = 0; + virtual void fail(int r) = 0; virtual void send() = 0; }; @@ -82,9 +78,8 @@ public: ObjectRequest(ImageCtxT *ictx, const std::string &oid, uint64_t objectno, uint64_t off, uint64_t len, - librados::snap_t snap_id, bool hide_enoent, - const char *trace_name, const ZTracer::Trace &parent_trace, - Context *completion); + librados::snap_t snap_id, const char *trace_name, + const ZTracer::Trace &parent_trace, Context *completion); ~ObjectRequest() override { m_trace.event("finish"); } @@ -92,9 +87,10 @@ public: static void add_write_hint(ImageCtxT& image_ctx, librados::ObjectWriteOperation *wr); - virtual void complete(int r); + void fail(int r) { + finish(r); + } - virtual bool should_complete(int r) = 0; void send() override = 0; bool has_parent() const { @@ -111,8 +107,6 @@ protected: uint64_t m_object_no, m_object_off, m_object_len; librados::snap_t m_snap_id; Context *m_completion; - Extents m_parent_extents; - bool m_hide_enoent; ZTracer::Trace m_trace; void async_finish(int r); @@ -144,7 +138,6 @@ public: bool cache_initiated, const ZTracer::Trace &parent_trace, Context *completion); - bool should_complete(int r) override; void send() override; inline uint64_t get_offset() const { @@ -215,7 +208,7 @@ public: AbstractObjectWriteRequest(ImageCtxT *ictx, const std::string &oid, uint64_t object_no, uint64_t object_off, uint64_t len, const ::SnapContext &snapc, - bool hide_enoent, const char *trace_name, + const char *trace_name, const ZTracer::Trace &parent_trace, Context *completion); @@ -233,78 +226,82 @@ public: void handle_copyup(int r); - bool should_complete(int r) override; void send() override; +protected: + bool m_full_object = false; + + virtual bool is_no_op_for_nonexistent_object() const { + return false; + } + virtual bool is_object_map_update_enabled() const { + return true; + } + virtual bool is_post_copyup_write_required() const { + return false; + } + virtual bool is_non_existent_post_write_object_map_state() const { + return false; + } + + virtual void add_write_hint(librados::ObjectWriteOperation *wr); + virtual void add_write_ops(librados::ObjectWriteOperation *wr) = 0; + + virtual int filter_write_result(int r) const { + return r; + } + +private: /** - * Writes go through the following state machine to deal with - * layering and the object map: + * @verbatim * - * - * | - * |\ - * | \ -or- - * | ---------------------------------> LIBRBD_AIO_WRITE_PRE - * | . | - * | . | - * | . v - * | . . . . > LIBRBD_AIO_WRITE_FLAT. . . - * | | . - * | | . - * | | . - * v need copyup (copyup performs pre) | . - * LIBRBD_AIO_WRITE_GUARD -----------> LIBRBD_AIO_WRITE_COPYUP | . - * . | | . | . - * . | | . | . - * . | /-----/ . | . - * . | | . | . - * . \-------------------\ | /-------------------/ . - * . | | | . . - * . v v v . . - * . LIBRBD_AIO_WRITE_POST . . - * . | . . - * . | . . . . . . . . . - * . | . . - * . v v . - * . . . . . . . . . . . . . . > < . . . . . . . . . . . . . . + * + * | + * v (no-op write request) + * DETECT_NO_OP . . . . . . . . . . . . . . . . . . . + * | . + * v (skip if not required/disabled) . + * PRE_UPDATE_OBJECT_MAP . + * | . . + * | . (child dne) . + * | . . . . . . . . . . + * | . . + * | (post-copyup write) . . + * | . . . . . . . . . . . . . . + * | . . . . + * v v . v . + * WRITE . . . . . . . . > COPYUP (if required) . + * | | . + * |/----------------------/ . + * | . + * v (skip if not required/disabled) . + * POST_UPDATE_OBJECT_MAP . + * | . + * v . + * < . . . . . . . . . . . . . . . . . . . . * - * The _PRE/_POST states are skipped if the object map is disabled. - * The write starts in _WRITE_GUARD or _FLAT depending on whether or not - * there is a parent overlap. + * @endverbatim */ -protected: - enum write_state_d { - LIBRBD_AIO_WRITE_GUARD, - LIBRBD_AIO_WRITE_COPYUP, - LIBRBD_AIO_WRITE_FLAT, - LIBRBD_AIO_WRITE_PRE, - LIBRBD_AIO_WRITE_POST, - LIBRBD_AIO_WRITE_ERROR - }; - write_state_d m_state; - librados::ObjectWriteOperation m_write; uint64_t m_snap_seq; std::vector m_snaps; + + Extents m_parent_extents; bool m_object_may_exist = false; - bool m_guard = true; + bool m_copyup_enabled = true; + bool m_copyup_in_progress = false; - virtual void add_write_hint(librados::ObjectWriteOperation *wr); - virtual void add_write_ops(librados::ObjectWriteOperation *wr) = 0; + void pre_write_object_map_update(); + void handle_pre_write_object_map_update(int r); - virtual void guard_write(); - virtual bool post_object_map_update() { - return false; - } - virtual void send_write(); - virtual void send_write_op(); - virtual void handle_write_guard(); + void write_object(); + void handle_write_object(int r); - void send_pre_object_map_update(); + void copyup(); + + void post_write_object_map_update(); + void handle_post_write_object_map_update(int r); -private: - bool send_post_object_map_update(); - void send_copyup(); }; template @@ -316,8 +313,8 @@ public: int op_flags, const ZTracer::Trace &parent_trace, Context *completion) : AbstractObjectWriteRequest(ictx, oid, object_no, object_off, - data.length(), snapc, false, - "write", parent_trace, completion), + data.length(), snapc, "write", + parent_trace, completion), m_write_data(data), m_op_flags(op_flags) { } @@ -332,8 +329,6 @@ public: protected: void add_write_ops(librados::ObjectWriteOperation *wr) override; - void send_write() override; - private: ceph::bufferlist m_write_data; int m_op_flags; @@ -348,10 +343,10 @@ public: bool disable_clone_remove, bool update_object_map, const ZTracer::Trace &parent_trace, Context *completion) : AbstractObjectWriteRequest(ictx, oid, object_no, object_off, - object_len, snapc, true, "discard", + object_len, snapc, "discard", parent_trace, completion), m_update_object_map(update_object_map) { - if (object_off == 0 && object_len == ictx->layout.object_size) { + if (this->m_full_object) { if (disable_clone_remove && this->has_parent()) { // need to hide the parent object instead of child object m_discard_action = DISCARD_ACTION_REMOVE_TRUNCATE; @@ -359,7 +354,6 @@ public: } else { m_discard_action = DISCARD_ACTION_REMOVE; } - this->m_guard = (!snapc.snaps.empty()); } else if (object_off + object_len == ictx->layout.object_size) { m_discard_action = DISCARD_ACTION_TRUNCATE; } else { @@ -390,6 +384,16 @@ public: } protected: + bool is_no_op_for_nonexistent_object() const override { + return (!this->has_parent()); + } + bool is_object_map_update_enabled() const override { + return m_update_object_map; + } + bool is_non_existent_post_write_object_map_state() const override { + return (m_discard_action == DISCARD_ACTION_REMOVE); + } + void add_write_hint(librados::ObjectWriteOperation *wr) override { // no hint for discard } @@ -412,13 +416,6 @@ protected: } } - bool post_object_map_update() override { - // trim operation updates the object map in batches - return (m_update_object_map && m_discard_action == DISCARD_ACTION_REMOVE); - } - - void send_write() override; - private: enum DiscardAction { DISCARD_ACTION_REMOVE, @@ -442,9 +439,8 @@ public: const ZTracer::Trace &parent_trace, Context *completion) : AbstractObjectWriteRequest(ictx, oid, object_no, object_off, - object_len, snapc, false, - "writesame", parent_trace, - completion), + object_len, snapc, "writesame", + parent_trace, completion), m_write_data(data), m_op_flags(op_flags) { } @@ -455,8 +451,6 @@ public: protected: void add_write_ops(librados::ObjectWriteOperation *wr) override; - void send_write() override; - private: ceph::bufferlist m_write_data; int m_op_flags; @@ -474,7 +468,7 @@ public: const ZTracer::Trace &parent_trace, Context *completion) : AbstractObjectWriteRequest(ictx, oid, object_no, object_off, - cmp_bl.length(), snapc, false, + cmp_bl.length(), snapc, "compare_and_write", parent_trace, completion), m_cmp_bl(cmp_bl), m_write_bl(write_bl), @@ -485,12 +479,18 @@ public: return "compare_and_write"; } - void complete(int r) override; + void add_copyup_ops(librados::ObjectWriteOperation *wr) override { + // no-op on copyup + } protected: + virtual bool is_post_copyup_write_required() const { + return true; + } + void add_write_ops(librados::ObjectWriteOperation *wr) override; - void send_write() override; + int filter_write_result(int r) const override; private: ceph::bufferlist m_cmp_bl; diff --git a/src/test/librbd/io/test_mock_ImageRequest.cc b/src/test/librbd/io/test_mock_ImageRequest.cc index bd1ff679530..e85ae259cd0 100644 --- a/src/test/librbd/io/test_mock_ImageRequest.cc +++ b/src/test/librbd/io/test_mock_ImageRequest.cc @@ -102,8 +102,8 @@ struct ObjectRequest : public ObjectRequestHandle { s_instance = nullptr; } - MOCK_METHOD1(complete, void(int)); MOCK_METHOD0(send, void()); + MOCK_METHOD1(fail, void(int)); }; template <> diff --git a/src/test/librbd/io/test_mock_ObjectRequest.cc b/src/test/librbd/io/test_mock_ObjectRequest.cc index 7e6eb15d1a8..6f0425029ec 100644 --- a/src/test/librbd/io/test_mock_ObjectRequest.cc +++ b/src/test/librbd/io/test_mock_ObjectRequest.cc @@ -35,23 +35,27 @@ namespace io { template <> struct CopyupRequest { + MOCK_METHOD0(send, void()); + MOCK_METHOD1(append_request, void(AbstractObjectWriteRequest*)); +}; + +template <> +struct CopyupRequest : public CopyupRequest { static CopyupRequest* s_instance; - static CopyupRequest* create(librbd::MockImageCtx *ictx, + static CopyupRequest* create(librbd::MockTestImageCtx *ictx, const std::string &oid, uint64_t objectno, Extents &&image_extents, const ZTracer::Trace &parent_trace) { return s_instance; } - MOCK_METHOD0(send, void()); - CopyupRequest() { s_instance = this; } }; template <> -struct ImageRequest { +struct ImageRequest { static ImageRequest *s_instance; static void aio_read(librbd::MockImageCtx *ictx, AioCompletion *c, Extents &&image_extents, ReadResult &&read_result, @@ -66,8 +70,8 @@ struct ImageRequest { } }; -CopyupRequest* CopyupRequest::s_instance = nullptr; -ImageRequest* ImageRequest::s_instance = nullptr; +CopyupRequest* CopyupRequest::s_instance = nullptr; +ImageRequest* ImageRequest::s_instance = nullptr; } // namespace io } // namespace librbd @@ -86,10 +90,15 @@ using ::testing::WithArg; using ::testing::WithArgs; struct TestMockIoObjectRequest : public TestMockFixture { - typedef ObjectRequest MockObjectRequest; - typedef ObjectReadRequest MockObjectReadRequest; - typedef CopyupRequest MockCopyupRequest; - typedef ImageRequest MockImageRequest; + typedef ObjectRequest MockObjectRequest; + typedef ObjectReadRequest MockObjectReadRequest; + typedef ObjectWriteRequest MockObjectWriteRequest; + typedef ObjectDiscardRequest MockObjectDiscardRequest; + typedef ObjectWriteSameRequest MockObjectWriteSameRequest; + typedef ObjectCompareAndWriteRequest MockObjectCompareAndWriteRequest; + typedef AbstractObjectWriteRequest MockAbstractObjectWriteRequest; + typedef CopyupRequest MockCopyupRequest; + typedef ImageRequest MockImageRequest; void expect_object_may_exist(MockTestImageCtx &mock_image_ctx, uint64_t object_no, bool exists) { @@ -99,6 +108,11 @@ struct TestMockIoObjectRequest : public TestMockFixture { } } + void expect_get_object_size(MockTestImageCtx &mock_image_ctx) { + EXPECT_CALL(mock_image_ctx, get_object_size()).WillRepeatedly(Return( + mock_image_ctx.layout.object_size)); + } + void expect_get_parent_overlap(MockTestImageCtx &mock_image_ctx, librados::snap_t snap_id, uint64_t overlap, int r) { @@ -125,6 +139,11 @@ struct TestMockIoObjectRequest : public TestMockFixture { .WillOnce(Return(flags)); } + void expect_is_lock_owner(MockExclusiveLock& mock_exclusive_lock) { + EXPECT_CALL(mock_exclusive_lock, is_lock_owner()).WillRepeatedly( + Return(true)); + } + void expect_read(MockTestImageCtx &mock_image_ctx, const std::string& oid, uint64_t off, uint64_t len, const std::string& data, int r) { @@ -192,6 +211,114 @@ struct TestMockIoObjectRequest : public TestMockFixture { .WillOnce(Invoke([&mock_copyup_request, r]() { })); } + + void expect_copyup(MockCopyupRequest& mock_copyup_request, + MockAbstractObjectWriteRequest** write_request, int r) { + EXPECT_CALL(mock_copyup_request, append_request(_)) + .WillOnce(Invoke([write_request](MockAbstractObjectWriteRequest *req) { + *write_request = req; + })); + EXPECT_CALL(mock_copyup_request, send()) + .WillOnce(Invoke([write_request, r]() { + (*write_request)->handle_copyup(r); + })); + } + + void expect_object_map_update(MockTestImageCtx &mock_image_ctx, + uint64_t start_object, uint64_t end_object, + uint8_t state, + const boost::optional ¤t_state, + bool updated, int ret_val) { + if (mock_image_ctx.object_map != nullptr) { + EXPECT_CALL(*mock_image_ctx.object_map, + aio_update(CEPH_NOSNAP, start_object, end_object, state, + current_state, _, _)) + .WillOnce(WithArg<6>(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_assert_exists(MockTestImageCtx &mock_image_ctx, int r) { + EXPECT_CALL(get_mock_io_ctx(mock_image_ctx.data_ctx), assert_exists(_)) + .WillOnce(Return(r)); + } + + void expect_write(MockTestImageCtx &mock_image_ctx, + uint64_t offset, uint64_t length, int r) { + auto &expect = EXPECT_CALL(get_mock_io_ctx(mock_image_ctx.data_ctx), + write(_, _, length, offset, _)); + if (r < 0) { + expect.WillOnce(Return(r)); + } else { + expect.WillOnce(DoDefault()); + } + } + + void expect_write_full(MockTestImageCtx &mock_image_ctx, int r) { + auto &expect = EXPECT_CALL(get_mock_io_ctx(mock_image_ctx.data_ctx), + write_full(_, _, _)); + if (r < 0) { + expect.WillOnce(Return(r)); + } else { + expect.WillOnce(DoDefault()); + } + } + + void expect_writesame(MockTestImageCtx &mock_image_ctx, + uint64_t offset, uint64_t length, int r) { + auto &expect = EXPECT_CALL(get_mock_io_ctx(mock_image_ctx.data_ctx), + writesame(_, _, length, offset, _)); + if (r < 0) { + expect.WillOnce(Return(r)); + } else { + expect.WillOnce(DoDefault()); + } + } + + void expect_remove(MockTestImageCtx &mock_image_ctx, int r) { + auto &expect = EXPECT_CALL(get_mock_io_ctx(mock_image_ctx.data_ctx), + remove(_, _)); + if (r < 0) { + expect.WillOnce(Return(r)); + } else { + expect.WillOnce(DoDefault()); + } + } + + void expect_truncate(MockTestImageCtx &mock_image_ctx, int offset, int r) { + auto &expect = EXPECT_CALL(get_mock_io_ctx(mock_image_ctx.data_ctx), + truncate(_, offset, _)); + if (r < 0) { + expect.WillOnce(Return(r)); + } else { + expect.WillOnce(DoDefault()); + } + } + + void expect_zero(MockTestImageCtx &mock_image_ctx, int offset, int length, + int r) { + auto &expect = EXPECT_CALL(get_mock_io_ctx(mock_image_ctx.data_ctx), + zero(_, offset, length, _)); + if (r < 0) { + expect.WillOnce(Return(r)); + } else { + expect.WillOnce(DoDefault()); + } + } + + void expect_cmpext(MockTestImageCtx &mock_image_ctx, int offset, int r) { + auto &expect = EXPECT_CALL(get_mock_io_ctx(mock_image_ctx.data_ctx), + cmpext(_, offset, _)); + if (r < 0) { + expect.WillOnce(Return(r)); + } else { + expect.WillOnce(DoDefault()); + } + } }; TEST_F(TestMockIoObjectRequest, Read) { @@ -210,11 +337,13 @@ TEST_F(TestMockIoObjectRequest, Read) { InSequence seq; expect_object_may_exist(mock_image_ctx, 0, true); expect_get_read_flags(mock_image_ctx, CEPH_NOSNAP, 0); - expect_read(mock_image_ctx, "object0", 0, 4096, std::string(4096, '1'), 0); + expect_read(mock_image_ctx, ictx->get_object_name(0), 0, 4096, + std::string(4096, '1'), 0); C_SaferCond ctx; auto req = MockObjectReadRequest::create( - &mock_image_ctx, "object0", 0, 0, 4096, CEPH_NOSNAP, 0, false, {}, &ctx); + &mock_image_ctx, ictx->get_object_name(0), 0, 0, 4096, CEPH_NOSNAP, 0, + false, {}, &ctx); req->send(); ASSERT_EQ(0, ctx.wait()); } @@ -235,14 +364,14 @@ TEST_F(TestMockIoObjectRequest, SparseReadThreshold) { InSequence seq; expect_object_may_exist(mock_image_ctx, 0, true); expect_get_read_flags(mock_image_ctx, CEPH_NOSNAP, 0); - expect_sparse_read(mock_image_ctx, "object0", 0, + expect_sparse_read(mock_image_ctx, ictx->get_object_name(0), 0, ictx->sparse_read_threshold_bytes, std::string(ictx->sparse_read_threshold_bytes, '1'), 0); C_SaferCond ctx; auto req = MockObjectReadRequest::create( - &mock_image_ctx, "object0", 0, 0, ictx->sparse_read_threshold_bytes, - CEPH_NOSNAP, 0, false, {}, &ctx); + &mock_image_ctx, ictx->get_object_name(0), 0, 0, + ictx->sparse_read_threshold_bytes, CEPH_NOSNAP, 0, false, {}, &ctx); req->send(); ASSERT_EQ(0, ctx.wait()); } @@ -263,11 +392,12 @@ TEST_F(TestMockIoObjectRequest, ReadError) { InSequence seq; expect_object_may_exist(mock_image_ctx, 0, true); expect_get_read_flags(mock_image_ctx, CEPH_NOSNAP, 0); - expect_read(mock_image_ctx, "object0", 0, 4096, "", -EPERM); + expect_read(mock_image_ctx, ictx->get_object_name(0), 0, 4096, "", -EPERM); C_SaferCond ctx; auto req = MockObjectReadRequest::create( - &mock_image_ctx, "object0", 0, 0, 4096, CEPH_NOSNAP, 0, false, {}, &ctx); + &mock_image_ctx, ictx->get_object_name(0), 0, 0, 4096, CEPH_NOSNAP, 0, + false, {}, &ctx); req->send(); ASSERT_EQ(-EPERM, ctx.wait()); } @@ -286,12 +416,13 @@ TEST_F(TestMockIoObjectRequest, CacheRead) { expect_op_work_queue(mock_image_ctx); InSequence seq; - expect_cache_read(mock_image_ctx, "object0", 0, 0, 4096, + expect_cache_read(mock_image_ctx, ictx->get_object_name(0), 0, 0, 4096, std::string(4096, '1'), 0); C_SaferCond ctx; auto req = MockObjectReadRequest::create( - &mock_image_ctx, "object0", 0, 0, 4096, CEPH_NOSNAP, 0, false, {}, &ctx); + &mock_image_ctx, ictx->get_object_name(0), 0, 0, 4096, CEPH_NOSNAP, 0, + false, {}, &ctx); req->send(); ASSERT_EQ(0, ctx.wait()); } @@ -310,12 +441,13 @@ TEST_F(TestMockIoObjectRequest, CacheReadError) { expect_op_work_queue(mock_image_ctx); InSequence seq; - expect_cache_read(mock_image_ctx, "object0", 0, 0, 4096, + expect_cache_read(mock_image_ctx, ictx->get_object_name(0), 0, 0, 4096, "", -EPERM); C_SaferCond ctx; auto req = MockObjectReadRequest::create( - &mock_image_ctx, "object0", 0, 0, 4096, CEPH_NOSNAP, 0, false, {}, &ctx); + &mock_image_ctx, ictx->get_object_name(0), 0, 0, 4096, CEPH_NOSNAP, 0, + false, {}, &ctx); req->send(); ASSERT_EQ(-EPERM, ctx.wait()); } @@ -352,7 +484,7 @@ TEST_F(TestMockIoObjectRequest, ParentRead) { InSequence seq; expect_object_may_exist(mock_image_ctx, 0, true); expect_get_read_flags(mock_image_ctx, CEPH_NOSNAP, 0); - expect_read(mock_image_ctx, "object0", 0, 4096, "", -ENOENT); + expect_read(mock_image_ctx, ictx->get_object_name(0), 0, 4096, "", -ENOENT); MockImageRequest mock_image_request; expect_get_parent_overlap(mock_image_ctx, CEPH_NOSNAP, 4096, 0); @@ -361,7 +493,8 @@ TEST_F(TestMockIoObjectRequest, ParentRead) { C_SaferCond ctx; auto req = MockObjectReadRequest::create( - &mock_image_ctx, "object0", 0, 0, 4096, CEPH_NOSNAP, 0, false, {}, &ctx); + &mock_image_ctx, ictx->get_object_name(0), 0, 0, 4096, CEPH_NOSNAP, 0, + false, {}, &ctx); req->send(); ASSERT_EQ(0, ctx.wait()); } @@ -398,7 +531,7 @@ TEST_F(TestMockIoObjectRequest, ParentReadError) { InSequence seq; expect_object_may_exist(mock_image_ctx, 0, true); expect_get_read_flags(mock_image_ctx, CEPH_NOSNAP, 0); - expect_read(mock_image_ctx, "object0", 0, 4096, "", -ENOENT); + expect_read(mock_image_ctx, ictx->get_object_name(0), 0, 4096, "", -ENOENT); MockImageRequest mock_image_request; expect_get_parent_overlap(mock_image_ctx, CEPH_NOSNAP, 4096, 0); @@ -407,7 +540,8 @@ TEST_F(TestMockIoObjectRequest, ParentReadError) { C_SaferCond ctx; auto req = MockObjectReadRequest::create( - &mock_image_ctx, "object0", 0, 0, 4096, CEPH_NOSNAP, 0, false, {}, &ctx); + &mock_image_ctx, ictx->get_object_name(0), 0, 0, 4096, CEPH_NOSNAP, 0, + false, {}, &ctx); req->send(); ASSERT_EQ(-EPERM, ctx.wait()); } @@ -443,11 +577,12 @@ TEST_F(TestMockIoObjectRequest, CacheInitiated) { InSequence seq; expect_object_may_exist(mock_image_ctx, 0, true); expect_get_read_flags(mock_image_ctx, CEPH_NOSNAP, 0); - expect_read(mock_image_ctx, "object0", 0, 4096, "", -ENOENT); + expect_read(mock_image_ctx, ictx->get_object_name(0), 0, 4096, "", -ENOENT); C_SaferCond ctx; auto req = MockObjectReadRequest::create( - &mock_image_ctx, "object0", 0, 0, 4096, CEPH_NOSNAP, 0, true, {}, &ctx); + &mock_image_ctx, ictx->get_object_name(0), 0, 0, 4096, CEPH_NOSNAP, 0, true, + {}, &ctx); req->send(); ASSERT_EQ(-ENOENT, ctx.wait()); } @@ -484,7 +619,7 @@ TEST_F(TestMockIoObjectRequest, CopyOnRead) { InSequence seq; expect_object_may_exist(mock_image_ctx, 0, true); expect_get_read_flags(mock_image_ctx, CEPH_NOSNAP, 0); - expect_read(mock_image_ctx, "object0", 0, 4096, "", -ENOENT); + expect_read(mock_image_ctx, ictx->get_object_name(0), 0, 4096, "", -ENOENT); MockImageRequest mock_image_request; expect_get_parent_overlap(mock_image_ctx, CEPH_NOSNAP, 4096, 0); @@ -498,10 +633,705 @@ TEST_F(TestMockIoObjectRequest, CopyOnRead) { C_SaferCond ctx; auto req = MockObjectReadRequest::create( - &mock_image_ctx, "object0", 0, 0, 4096, CEPH_NOSNAP, 0, false, {}, &ctx); + &mock_image_ctx, ictx->get_object_name(0), 0, 0, 4096, CEPH_NOSNAP, 0, + false, {}, &ctx); + req->send(); + ASSERT_EQ(0, ctx.wait()); +} + +TEST_F(TestMockIoObjectRequest, Write) { + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockTestImageCtx mock_image_ctx(*ictx); + expect_get_object_size(mock_image_ctx); + + MockExclusiveLock mock_exclusive_lock; + if (ictx->test_features(RBD_FEATURE_EXCLUSIVE_LOCK)) { + mock_image_ctx.exclusive_lock = &mock_exclusive_lock; + expect_is_lock_owner(mock_exclusive_lock); + } + + MockObjectMap mock_object_map; + if (ictx->test_features(RBD_FEATURE_OBJECT_MAP)) { + mock_image_ctx.object_map = &mock_object_map; + } + + bufferlist bl; + bl.append(std::string(4096, '1')); + + InSequence seq; + expect_get_parent_overlap(mock_image_ctx, CEPH_NOSNAP, 0, 0); + expect_object_may_exist(mock_image_ctx, 0, true); + expect_object_map_update(mock_image_ctx, 0, 1, OBJECT_EXISTS, {}, false, 0); + expect_write(mock_image_ctx, 0, 4096, 0); + + C_SaferCond ctx; + auto req = MockObjectWriteRequest::create_write( + &mock_image_ctx, ictx->get_object_name(0), 0, 0, bl, mock_image_ctx.snapc, + 0, {}, &ctx); + req->send(); + ASSERT_EQ(0, ctx.wait()); +} + +TEST_F(TestMockIoObjectRequest, WriteFull) { + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockTestImageCtx mock_image_ctx(*ictx); + expect_get_object_size(mock_image_ctx); + + MockExclusiveLock mock_exclusive_lock; + if (ictx->test_features(RBD_FEATURE_EXCLUSIVE_LOCK)) { + mock_image_ctx.exclusive_lock = &mock_exclusive_lock; + expect_is_lock_owner(mock_exclusive_lock); + } + + MockObjectMap mock_object_map; + if (ictx->test_features(RBD_FEATURE_OBJECT_MAP)) { + mock_image_ctx.object_map = &mock_object_map; + } + + bufferlist bl; + bl.append(std::string(ictx->get_object_size(), '1')); + + InSequence seq; + expect_get_parent_overlap(mock_image_ctx, CEPH_NOSNAP, 0, 0); + expect_object_may_exist(mock_image_ctx, 0, true); + expect_object_map_update(mock_image_ctx, 0, 1, OBJECT_EXISTS, {}, false, 0); + expect_write_full(mock_image_ctx, 0); + + C_SaferCond ctx; + auto req = MockObjectWriteRequest::create_write( + &mock_image_ctx, ictx->get_object_name(0), 0, 0, bl, mock_image_ctx.snapc, + 0, {}, &ctx); + req->send(); + ASSERT_EQ(0, ctx.wait()); +} + +TEST_F(TestMockIoObjectRequest, WriteObjectMap) { + REQUIRE_FEATURE(RBD_FEATURE_OBJECT_MAP); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockTestImageCtx mock_image_ctx(*ictx); + expect_op_work_queue(mock_image_ctx); + expect_get_object_size(mock_image_ctx); + + MockExclusiveLock mock_exclusive_lock; + mock_image_ctx.exclusive_lock = &mock_exclusive_lock; + expect_is_lock_owner(mock_exclusive_lock); + + MockObjectMap mock_object_map; + mock_image_ctx.object_map = &mock_object_map; + + bufferlist bl; + bl.append(std::string(4096, '1')); + + InSequence seq; + expect_get_parent_overlap(mock_image_ctx, CEPH_NOSNAP, 0, 0); + expect_object_may_exist(mock_image_ctx, 0, true); + expect_object_map_update(mock_image_ctx, 0, 1, OBJECT_EXISTS, {}, true, 0); + expect_write(mock_image_ctx, 0, 4096, 0); + + C_SaferCond ctx; + auto req = MockObjectWriteRequest::create_write( + &mock_image_ctx, ictx->get_object_name(0), 0, 0, bl, mock_image_ctx.snapc, + 0, {}, &ctx); + req->send(); + ASSERT_EQ(0, ctx.wait()); +} + +TEST_F(TestMockIoObjectRequest, WriteError) { + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockTestImageCtx mock_image_ctx(*ictx); + expect_get_object_size(mock_image_ctx); + + bufferlist bl; + bl.append(std::string(4096, '1')); + + InSequence seq; + expect_get_parent_overlap(mock_image_ctx, CEPH_NOSNAP, 0, 0); + expect_write(mock_image_ctx, 0, 4096, -EPERM); + + C_SaferCond ctx; + auto req = MockObjectWriteRequest::create_write( + &mock_image_ctx, ictx->get_object_name(0), 0, 0, bl, mock_image_ctx.snapc, + 0, {}, &ctx); + req->send(); + ASSERT_EQ(-EPERM, ctx.wait()); +} + +TEST_F(TestMockIoObjectRequest, Copyup) { + REQUIRE_FEATURE(RBD_FEATURE_LAYERING); + + librbd::Image image; + librbd::RBD rbd; + ASSERT_EQ(0, rbd.open(m_ioctx, image, m_image_name.c_str(), NULL)); + ASSERT_EQ(0, image.snap_create("one")); + ASSERT_EQ(0, image.snap_protect("one")); + image.close(); + + std::string clone_name = get_temp_image_name(); + int order = 0; + ASSERT_EQ(0, rbd.clone(m_ioctx, m_image_name.c_str(), "one", m_ioctx, + clone_name.c_str(), RBD_FEATURE_LAYERING, &order)); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(clone_name, &ictx)); + + MockTestImageCtx mock_image_ctx(*ictx); + expect_get_object_size(mock_image_ctx); + + MockExclusiveLock mock_exclusive_lock; + if (ictx->test_features(RBD_FEATURE_EXCLUSIVE_LOCK)) { + mock_image_ctx.exclusive_lock = &mock_exclusive_lock; + expect_is_lock_owner(mock_exclusive_lock); + } + + MockObjectMap mock_object_map; + if (ictx->test_features(RBD_FEATURE_OBJECT_MAP)) { + mock_image_ctx.object_map = &mock_object_map; + } + + bufferlist bl; + bl.append(std::string(4096, '1')); + + InSequence seq; + expect_get_parent_overlap(mock_image_ctx, CEPH_NOSNAP, 4096, 0); + expect_prune_parent_extents(mock_image_ctx, {{0, 4096}}, 4096, 4096); + expect_object_may_exist(mock_image_ctx, 0, true); + expect_object_map_update(mock_image_ctx, 0, 1, OBJECT_EXISTS, {}, false, 0); + expect_assert_exists(mock_image_ctx, -ENOENT); + + MockAbstractObjectWriteRequest *write_request = nullptr; + MockCopyupRequest mock_copyup_request; + expect_copyup(mock_copyup_request, &write_request, 0); + + C_SaferCond ctx; + auto req = MockObjectWriteRequest::create_write( + &mock_image_ctx, ictx->get_object_name(0), 0, 0, bl, mock_image_ctx.snapc, + 0, {}, &ctx); + req->send(); + ASSERT_EQ(0, ctx.wait()); +} + +TEST_F(TestMockIoObjectRequest, CopyupOptimization) { + REQUIRE_FEATURE(RBD_FEATURE_LAYERING | RBD_FEATURE_OBJECT_MAP); + + librbd::Image image; + librbd::RBD rbd; + ASSERT_EQ(0, rbd.open(m_ioctx, image, m_image_name.c_str(), NULL)); + ASSERT_EQ(0, image.snap_create("one")); + ASSERT_EQ(0, image.snap_protect("one")); + image.close(); + + std::string clone_name = get_temp_image_name(); + int order = 0; + ASSERT_EQ(0, rbd.clone(m_ioctx, m_image_name.c_str(), "one", m_ioctx, + clone_name.c_str(), RBD_FEATURE_LAYERING, &order)); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(clone_name, &ictx)); + + MockTestImageCtx mock_image_ctx(*ictx); + expect_get_object_size(mock_image_ctx); + + MockExclusiveLock mock_exclusive_lock; + mock_image_ctx.exclusive_lock = &mock_exclusive_lock; + expect_is_lock_owner(mock_exclusive_lock); + + MockObjectMap mock_object_map; + mock_image_ctx.object_map = &mock_object_map; + + bufferlist bl; + bl.append(std::string(4096, '1')); + + InSequence seq; + expect_get_parent_overlap(mock_image_ctx, CEPH_NOSNAP, 4096, 0); + expect_prune_parent_extents(mock_image_ctx, {{0, 4096}}, 4096, 4096); + expect_object_may_exist(mock_image_ctx, 0, false); + + MockAbstractObjectWriteRequest *write_request = nullptr; + MockCopyupRequest mock_copyup_request; + expect_copyup(mock_copyup_request, &write_request, 0); + + C_SaferCond ctx; + auto req = MockObjectWriteRequest::create_write( + &mock_image_ctx, ictx->get_object_name(0), 0, 0, bl, mock_image_ctx.snapc, + 0, {}, &ctx); + req->send(); + ASSERT_EQ(0, ctx.wait()); +} + +TEST_F(TestMockIoObjectRequest, CopyupError) { + REQUIRE_FEATURE(RBD_FEATURE_LAYERING); + + librbd::Image image; + librbd::RBD rbd; + ASSERT_EQ(0, rbd.open(m_ioctx, image, m_image_name.c_str(), NULL)); + ASSERT_EQ(0, image.snap_create("one")); + ASSERT_EQ(0, image.snap_protect("one")); + image.close(); + + std::string clone_name = get_temp_image_name(); + int order = 0; + ASSERT_EQ(0, rbd.clone(m_ioctx, m_image_name.c_str(), "one", m_ioctx, + clone_name.c_str(), RBD_FEATURE_LAYERING, &order)); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(clone_name, &ictx)); + + MockTestImageCtx mock_image_ctx(*ictx); + expect_get_object_size(mock_image_ctx); + + bufferlist bl; + bl.append(std::string(4096, '1')); + + InSequence seq; + expect_get_parent_overlap(mock_image_ctx, CEPH_NOSNAP, 4096, 0); + expect_prune_parent_extents(mock_image_ctx, {{0, 4096}}, 4096, 4096); + expect_assert_exists(mock_image_ctx, -ENOENT); + + MockAbstractObjectWriteRequest *write_request = nullptr; + MockCopyupRequest mock_copyup_request; + expect_copyup(mock_copyup_request, &write_request, -EPERM); + + C_SaferCond ctx; + auto req = MockObjectWriteRequest::create_write( + &mock_image_ctx, ictx->get_object_name(0), 0, 0, bl, mock_image_ctx.snapc, + 0, {}, &ctx); + req->send(); + ASSERT_EQ(-EPERM, ctx.wait()); +} + +TEST_F(TestMockIoObjectRequest, DiscardRemove) { + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockTestImageCtx mock_image_ctx(*ictx); + expect_get_object_size(mock_image_ctx); + + MockExclusiveLock mock_exclusive_lock; + if (ictx->test_features(RBD_FEATURE_EXCLUSIVE_LOCK)) { + mock_image_ctx.exclusive_lock = &mock_exclusive_lock; + expect_is_lock_owner(mock_exclusive_lock); + } + + MockObjectMap mock_object_map; + if (ictx->test_features(RBD_FEATURE_OBJECT_MAP)) { + mock_image_ctx.object_map = &mock_object_map; + } + + InSequence seq; + expect_get_parent_overlap(mock_image_ctx, CEPH_NOSNAP, 0, 0); + expect_object_may_exist(mock_image_ctx, 0, true); + expect_object_map_update(mock_image_ctx, 0, 1, OBJECT_PENDING, {}, false, 0); + expect_remove(mock_image_ctx, 0); + expect_object_map_update(mock_image_ctx, 0, 1, OBJECT_NONEXISTENT, + OBJECT_PENDING, false, 0); + + C_SaferCond ctx; + auto req = MockObjectDiscardRequest::create_discard( + &mock_image_ctx, ictx->get_object_name(0), 0, 0, + mock_image_ctx.get_object_size(), mock_image_ctx.snapc, false, true, {}, + &ctx); req->send(); ASSERT_EQ(0, ctx.wait()); } +TEST_F(TestMockIoObjectRequest, DiscardRemoveTruncate) { + REQUIRE_FEATURE(RBD_FEATURE_LAYERING); + + librbd::Image image; + librbd::RBD rbd; + ASSERT_EQ(0, rbd.open(m_ioctx, image, m_image_name.c_str(), NULL)); + ASSERT_EQ(0, image.snap_create("one")); + ASSERT_EQ(0, image.snap_protect("one")); + image.close(); + + std::string clone_name = get_temp_image_name(); + int order = 0; + ASSERT_EQ(0, rbd.clone(m_ioctx, m_image_name.c_str(), "one", m_ioctx, + clone_name.c_str(), RBD_FEATURE_LAYERING, &order)); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(clone_name, &ictx)); + + MockTestImageCtx mock_image_ctx(*ictx); + expect_get_object_size(mock_image_ctx); + + MockExclusiveLock mock_exclusive_lock; + if (ictx->test_features(RBD_FEATURE_EXCLUSIVE_LOCK)) { + mock_image_ctx.exclusive_lock = &mock_exclusive_lock; + expect_is_lock_owner(mock_exclusive_lock); + } + + MockObjectMap mock_object_map; + if (ictx->test_features(RBD_FEATURE_OBJECT_MAP)) { + mock_image_ctx.object_map = &mock_object_map; + } + + InSequence seq; + expect_get_parent_overlap(mock_image_ctx, CEPH_NOSNAP, 4096, 0); + expect_prune_parent_extents(mock_image_ctx, {{0, 4096}}, 4096, 4096); + expect_object_may_exist(mock_image_ctx, 0, true); + expect_truncate(mock_image_ctx, 0, 0); + + C_SaferCond ctx; + auto req = MockObjectDiscardRequest::create_discard( + &mock_image_ctx, ictx->get_object_name(0), 0, 0, + mock_image_ctx.get_object_size(), mock_image_ctx.snapc, true, true, {}, + &ctx); + req->send(); + ASSERT_EQ(0, ctx.wait()); +} + +TEST_F(TestMockIoObjectRequest, DiscardTruncate) { + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockTestImageCtx mock_image_ctx(*ictx); + expect_get_object_size(mock_image_ctx); + + MockExclusiveLock mock_exclusive_lock; + if (ictx->test_features(RBD_FEATURE_EXCLUSIVE_LOCK)) { + mock_image_ctx.exclusive_lock = &mock_exclusive_lock; + expect_is_lock_owner(mock_exclusive_lock); + } + + MockObjectMap mock_object_map; + if (ictx->test_features(RBD_FEATURE_OBJECT_MAP)) { + mock_image_ctx.object_map = &mock_object_map; + } + + InSequence seq; + expect_get_parent_overlap(mock_image_ctx, CEPH_NOSNAP, 0, 0); + expect_object_may_exist(mock_image_ctx, 0, true); + expect_object_map_update(mock_image_ctx, 0, 1, OBJECT_EXISTS, {}, false, 0); + expect_truncate(mock_image_ctx, 1, 0); + + C_SaferCond ctx; + auto req = MockObjectDiscardRequest::create_discard( + &mock_image_ctx, ictx->get_object_name(0), 0, 1, + mock_image_ctx.get_object_size() - 1, mock_image_ctx.snapc, false, true, {}, + &ctx); + req->send(); + ASSERT_EQ(0, ctx.wait()); +} + +TEST_F(TestMockIoObjectRequest, DiscardZero) { + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockTestImageCtx mock_image_ctx(*ictx); + expect_get_object_size(mock_image_ctx); + + MockExclusiveLock mock_exclusive_lock; + if (ictx->test_features(RBD_FEATURE_EXCLUSIVE_LOCK)) { + mock_image_ctx.exclusive_lock = &mock_exclusive_lock; + expect_is_lock_owner(mock_exclusive_lock); + } + + MockObjectMap mock_object_map; + if (ictx->test_features(RBD_FEATURE_OBJECT_MAP)) { + mock_image_ctx.object_map = &mock_object_map; + } + + InSequence seq; + expect_get_parent_overlap(mock_image_ctx, CEPH_NOSNAP, 0, 0); + expect_object_may_exist(mock_image_ctx, 0, true); + expect_object_map_update(mock_image_ctx, 0, 1, OBJECT_EXISTS, {}, false, 0); + expect_zero(mock_image_ctx, 1, 1, 0); + + C_SaferCond ctx; + auto req = MockObjectDiscardRequest::create_discard( + &mock_image_ctx, ictx->get_object_name(0), 0, 1, 1, mock_image_ctx.snapc, + false, true, {}, &ctx); + req->send(); + ASSERT_EQ(0, ctx.wait()); +} + +TEST_F(TestMockIoObjectRequest, DiscardDisableObjectMapUpdate) { + REQUIRE_FEATURE(RBD_FEATURE_OBJECT_MAP); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockTestImageCtx mock_image_ctx(*ictx); + expect_get_object_size(mock_image_ctx); + + MockExclusiveLock mock_exclusive_lock; + mock_image_ctx.exclusive_lock = &mock_exclusive_lock; + expect_is_lock_owner(mock_exclusive_lock); + + MockObjectMap mock_object_map; + mock_image_ctx.object_map = &mock_object_map; + + InSequence seq; + expect_get_parent_overlap(mock_image_ctx, CEPH_NOSNAP, 0, 0); + expect_object_may_exist(mock_image_ctx, 0, true); + expect_remove(mock_image_ctx, 0); + + C_SaferCond ctx; + auto req = MockObjectDiscardRequest::create_discard( + &mock_image_ctx, ictx->get_object_name(0), 0, 0, + mock_image_ctx.get_object_size(), mock_image_ctx.snapc, true, false, {}, + &ctx); + req->send(); + ASSERT_EQ(0, ctx.wait()); +} + +TEST_F(TestMockIoObjectRequest, DiscardNoOp) { + REQUIRE_FEATURE(RBD_FEATURE_OBJECT_MAP); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockTestImageCtx mock_image_ctx(*ictx); + expect_op_work_queue(mock_image_ctx); + expect_get_object_size(mock_image_ctx); + + MockExclusiveLock mock_exclusive_lock; + mock_image_ctx.exclusive_lock = &mock_exclusive_lock; + expect_is_lock_owner(mock_exclusive_lock); + + MockObjectMap mock_object_map; + mock_image_ctx.object_map = &mock_object_map; + + InSequence seq; + expect_get_parent_overlap(mock_image_ctx, CEPH_NOSNAP, 0, 0); + expect_object_may_exist(mock_image_ctx, 0, false); + + C_SaferCond ctx; + auto req = MockObjectDiscardRequest::create_discard( + &mock_image_ctx, ictx->get_object_name(0), 0, 0, + mock_image_ctx.get_object_size(), mock_image_ctx.snapc, true, false, {}, + &ctx); + req->send(); + ASSERT_EQ(0, ctx.wait()); +} + +TEST_F(TestMockIoObjectRequest, WriteSame) { + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockTestImageCtx mock_image_ctx(*ictx); + expect_get_object_size(mock_image_ctx); + + MockExclusiveLock mock_exclusive_lock; + if (ictx->test_features(RBD_FEATURE_EXCLUSIVE_LOCK)) { + mock_image_ctx.exclusive_lock = &mock_exclusive_lock; + expect_is_lock_owner(mock_exclusive_lock); + } + + MockObjectMap mock_object_map; + if (ictx->test_features(RBD_FEATURE_OBJECT_MAP)) { + mock_image_ctx.object_map = &mock_object_map; + } + + bufferlist bl; + bl.append(std::string(4096, '1')); + + InSequence seq; + expect_get_parent_overlap(mock_image_ctx, CEPH_NOSNAP, 0, 0); + expect_object_may_exist(mock_image_ctx, 0, true); + expect_object_map_update(mock_image_ctx, 0, 1, OBJECT_EXISTS, {}, false, 0); + expect_writesame(mock_image_ctx, 0, 4096, 0); + + C_SaferCond ctx; + auto req = MockObjectWriteSameRequest::create_writesame( + &mock_image_ctx, ictx->get_object_name(0), 0, 0, 4096, bl, + mock_image_ctx.snapc, 0, {}, &ctx); + req->send(); + ASSERT_EQ(0, ctx.wait()); +} + +TEST_F(TestMockIoObjectRequest, CompareAndWrite) { + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockTestImageCtx mock_image_ctx(*ictx); + expect_get_object_size(mock_image_ctx); + + MockExclusiveLock mock_exclusive_lock; + if (ictx->test_features(RBD_FEATURE_EXCLUSIVE_LOCK)) { + mock_image_ctx.exclusive_lock = &mock_exclusive_lock; + expect_is_lock_owner(mock_exclusive_lock); + } + + MockObjectMap mock_object_map; + if (ictx->test_features(RBD_FEATURE_OBJECT_MAP)) { + mock_image_ctx.object_map = &mock_object_map; + } + + bufferlist cmp_bl; + cmp_bl.append_zero(4096); + + bufferlist bl; + bl.append(std::string(4096, '1')); + + InSequence seq; + expect_get_parent_overlap(mock_image_ctx, CEPH_NOSNAP, 0, 0); + expect_object_may_exist(mock_image_ctx, 0, true); + expect_object_map_update(mock_image_ctx, 0, 1, OBJECT_EXISTS, {}, false, 0); + expect_cmpext(mock_image_ctx, 0, 0); + expect_write(mock_image_ctx, 0, 4096, 0); + + C_SaferCond ctx; + uint64_t mismatch_offset; + auto req = MockObjectWriteSameRequest::create_compare_and_write( + &mock_image_ctx, ictx->get_object_name(0), 0, 0, cmp_bl, bl, + mock_image_ctx.snapc, &mismatch_offset, 0, {}, &ctx); + req->send(); + ASSERT_EQ(0, ctx.wait()); +} + +TEST_F(TestMockIoObjectRequest, CompareAndWriteFull) { + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockTestImageCtx mock_image_ctx(*ictx); + expect_get_object_size(mock_image_ctx); + + MockExclusiveLock mock_exclusive_lock; + if (ictx->test_features(RBD_FEATURE_EXCLUSIVE_LOCK)) { + mock_image_ctx.exclusive_lock = &mock_exclusive_lock; + expect_is_lock_owner(mock_exclusive_lock); + } + + MockObjectMap mock_object_map; + if (ictx->test_features(RBD_FEATURE_OBJECT_MAP)) { + mock_image_ctx.object_map = &mock_object_map; + } + + bufferlist cmp_bl; + cmp_bl.append_zero(ictx->get_object_size()); + + bufferlist bl; + bl.append(std::string(ictx->get_object_size(), '1')); + + InSequence seq; + expect_get_parent_overlap(mock_image_ctx, CEPH_NOSNAP, 0, 0); + expect_object_may_exist(mock_image_ctx, 0, true); + expect_object_map_update(mock_image_ctx, 0, 1, OBJECT_EXISTS, {}, false, 0); + expect_cmpext(mock_image_ctx, 0, 0); + expect_write_full(mock_image_ctx, 0); + + C_SaferCond ctx; + uint64_t mismatch_offset; + auto req = MockObjectWriteSameRequest::create_compare_and_write( + &mock_image_ctx, ictx->get_object_name(0), 0, 0, cmp_bl, bl, + mock_image_ctx.snapc, &mismatch_offset, 0, {}, &ctx); + req->send(); + ASSERT_EQ(0, ctx.wait()); +} + +TEST_F(TestMockIoObjectRequest, CompareAndWriteCopyup) { + REQUIRE_FEATURE(RBD_FEATURE_LAYERING); + + librbd::Image image; + librbd::RBD rbd; + ASSERT_EQ(0, rbd.open(m_ioctx, image, m_image_name.c_str(), NULL)); + ASSERT_EQ(0, image.snap_create("one")); + ASSERT_EQ(0, image.snap_protect("one")); + image.close(); + + std::string clone_name = get_temp_image_name(); + int order = 0; + ASSERT_EQ(0, rbd.clone(m_ioctx, m_image_name.c_str(), "one", m_ioctx, + clone_name.c_str(), RBD_FEATURE_LAYERING, &order)); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(clone_name, &ictx)); + + MockTestImageCtx mock_image_ctx(*ictx); + expect_get_object_size(mock_image_ctx); + + MockExclusiveLock mock_exclusive_lock; + if (ictx->test_features(RBD_FEATURE_EXCLUSIVE_LOCK)) { + mock_image_ctx.exclusive_lock = &mock_exclusive_lock; + expect_is_lock_owner(mock_exclusive_lock); + } + + MockObjectMap mock_object_map; + if (ictx->test_features(RBD_FEATURE_OBJECT_MAP)) { + mock_image_ctx.object_map = &mock_object_map; + } + + bufferlist cmp_bl; + cmp_bl.append_zero(4096); + + bufferlist bl; + bl.append(std::string(4096, '1')); + + InSequence seq; + expect_get_parent_overlap(mock_image_ctx, CEPH_NOSNAP, 4096, 0); + expect_prune_parent_extents(mock_image_ctx, {{0, 4096}}, 4096, 4096); + expect_object_may_exist(mock_image_ctx, 0, true); + expect_object_map_update(mock_image_ctx, 0, 1, OBJECT_EXISTS, {}, false, 0); + expect_assert_exists(mock_image_ctx, -ENOENT); + + MockAbstractObjectWriteRequest *write_request = nullptr; + MockCopyupRequest mock_copyup_request; + expect_copyup(mock_copyup_request, &write_request, 0); + + expect_assert_exists(mock_image_ctx, 0); + expect_cmpext(mock_image_ctx, 0, 0); + expect_write(mock_image_ctx, 0, 4096, 0); + + C_SaferCond ctx; + uint64_t mismatch_offset; + auto req = MockObjectWriteSameRequest::create_compare_and_write( + &mock_image_ctx, ictx->get_object_name(0), 0, 0, cmp_bl, bl, + mock_image_ctx.snapc, &mismatch_offset, 0, {}, &ctx); + req->send(); + ASSERT_EQ(0, ctx.wait()); +} + +TEST_F(TestMockIoObjectRequest, CompareAndWriteMismatch) { + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockTestImageCtx mock_image_ctx(*ictx); + expect_get_object_size(mock_image_ctx); + + MockExclusiveLock mock_exclusive_lock; + if (ictx->test_features(RBD_FEATURE_EXCLUSIVE_LOCK)) { + mock_image_ctx.exclusive_lock = &mock_exclusive_lock; + expect_is_lock_owner(mock_exclusive_lock); + } + + MockObjectMap mock_object_map; + if (ictx->test_features(RBD_FEATURE_OBJECT_MAP)) { + mock_image_ctx.object_map = &mock_object_map; + } + + bufferlist cmp_bl; + cmp_bl.append_zero(4096); + + bufferlist bl; + bl.append(std::string(4096, '1')); + + InSequence seq; + expect_get_parent_overlap(mock_image_ctx, CEPH_NOSNAP, 0, 0); + expect_object_may_exist(mock_image_ctx, 0, true); + expect_object_map_update(mock_image_ctx, 0, 1, OBJECT_EXISTS, {}, false, 0); + expect_cmpext(mock_image_ctx, 0, -MAX_ERRNO - 1); + + C_SaferCond ctx; + uint64_t mismatch_offset; + auto req = MockObjectWriteSameRequest::create_compare_and_write( + &mock_image_ctx, ictx->get_object_name(0), 0, 0, cmp_bl, bl, + mock_image_ctx.snapc, &mismatch_offset, 0, {}, &ctx); + req->send(); + ASSERT_EQ(-EILSEQ, ctx.wait()); + ASSERT_EQ(1ULL, mismatch_offset); +} + } // namespace io } // namespace librbd + diff --git a/src/test/librbd/mock/MockImageCtx.h b/src/test/librbd/mock/MockImageCtx.h index ee020c13509..17697a12d19 100644 --- a/src/test/librbd/mock/MockImageCtx.h +++ b/src/test/librbd/mock/MockImageCtx.h @@ -108,7 +108,8 @@ struct MockImageCtx { image_ctx.mirroring_resync_after_disconnect), mirroring_replay_delay(image_ctx.mirroring_replay_delay), non_blocking_aio(image_ctx.non_blocking_aio), - blkin_trace_all(image_ctx.blkin_trace_all) + blkin_trace_all(image_ctx.blkin_trace_all), + enable_alloc_hint(image_ctx.enable_alloc_hint) { md_ctx.dup(image_ctx.md_ctx); data_ctx.dup(image_ctx.data_ctx); @@ -147,6 +148,7 @@ struct MockImageCtx { MOCK_METHOD0(init_layout, void()); MOCK_CONST_METHOD1(get_object_name, std::string(uint64_t)); + MOCK_CONST_METHOD0(get_object_size, uint64_t()); MOCK_CONST_METHOD0(get_current_size, uint64_t()); MOCK_CONST_METHOD1(get_image_size, uint64_t(librados::snap_t)); MOCK_CONST_METHOD1(get_object_count, uint64_t(librados::snap_t)); @@ -315,6 +317,7 @@ struct MockImageCtx { int mirroring_replay_delay; bool non_blocking_aio; bool blkin_trace_all; + bool enable_alloc_hint; }; } // namespace librbd diff --git a/src/test/librbd/operation/test_mock_TrimRequest.cc b/src/test/librbd/operation/test_mock_TrimRequest.cc index 8821a7cb9ed..9509b203fda 100644 --- a/src/test/librbd/operation/test_mock_TrimRequest.cc +++ b/src/test/librbd/operation/test_mock_TrimRequest.cc @@ -94,7 +94,7 @@ struct ObjectRequest : public ObjectRequestHandle { MOCK_METHOD3(construct, void(uint64_t, uint64_t, bool)); MOCK_METHOD0(send, void()); - MOCK_METHOD1(complete, void(int)); + MOCK_METHOD1(fail, void(int)); }; ObjectRequest* ObjectRequest::s_instance = nullptr; -- 2.39.5