ObjectRequest<I>::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);
}
}
-template <typename I>
-void ObjectRequest<I>::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 <typename I>
bool ObjectRequest<I>::compute_parent_extents(Extents *parent_extents) {
assert(m_ictx->snap_lock.is_locked());
int op_flags, bool cache_initiated,
const ZTracer::Trace &parent_trace,
Context *completion)
- : ObjectRequest<I>(ictx, oid, objectno, offset, len, snap_id, false, "read",
+ : ObjectRequest<I>(ictx, oid, objectno, offset, len, snap_id, "read",
parent_trace, completion),
m_op_flags(op_flags), m_cache_initiated(cache_initiated) {
}
this->finish(0);
}
-template <typename I>
-bool ObjectReadRequest<I>::should_complete(int r) {
- // TODO remove once fully converted
- assert(false);
-}
-
/** write **/
template <typename I>
AbstractObjectWriteRequest<I>::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<I>(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 <typename I>
}
}
-template <typename I>
-void AbstractObjectWriteRequest<I>::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 <typename I>
-void AbstractObjectWriteRequest<I>::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 <typename I>
-bool AbstractObjectWriteRequest<I>::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 <typename I>
void AbstractObjectWriteRequest<I>::send() {
I *image_ctx = this->m_ictx;
}
}
- 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 <typename I>
-void AbstractObjectWriteRequest<I>::send_pre_object_map_update() {
+void AbstractObjectWriteRequest<I>::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<ObjectRequest<I>>(
- 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<I>,
+ &AbstractObjectWriteRequest<I>::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 <typename I>
-bool AbstractObjectWriteRequest<I>::send_post_object_map_update() {
+void AbstractObjectWriteRequest<I>::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 <typename I>
+void AbstractObjectWriteRequest<I>::write_object() {
+ I *image_ctx = this->m_ictx;
+ ldout(image_ctx->cct, 20) << dendl;
- if (image_ctx->object_map->template aio_update<ObjectRequest<I>>(
- 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<I>,
+ &AbstractObjectWriteRequest<I>::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 <typename I>
-void AbstractObjectWriteRequest<I>::send_write() {
+void AbstractObjectWriteRequest<I>::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 <typename I>
-void AbstractObjectWriteRequest<I>::send_copyup()
-{
+void AbstractObjectWriteRequest<I>::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);
}
template <typename I>
-void AbstractObjectWriteRequest<I>::send_write_op()
-{
+void AbstractObjectWriteRequest<I>::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 <typename I>
-void AbstractObjectWriteRequest<I>::handle_write_guard()
-{
+void AbstractObjectWriteRequest<I>::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 <typename I>
-void ObjectWriteRequest<I>::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<I>,
+ &AbstractObjectWriteRequest<I>::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 <typename I>
-void ObjectWriteRequest<I>::send_write() {
+void AbstractObjectWriteRequest<I>::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<I>::send_write();
+ assert(r == 0);
+ this->finish(0);
}
template <typename I>
-void ObjectDiscardRequest<I>::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<I>::LIBRBD_AIO_WRITE_FLAT;
- Context *ctx = util::create_context_callback<ObjectRequest<I>>(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<I>::add_write_ops(librados::ObjectWriteOperation *wr) {
+ if (this->m_full_object) {
+ wr->write_full(m_write_data);
} else {
- AbstractObjectWriteRequest<I>::send_write();
+ wr->write(this->m_object_off, m_write_data);
}
+ wr->set_op_flags2(m_op_flags);
}
template <typename I>
wr->set_op_flags2(m_op_flags);
}
-template <typename I>
-void ObjectWriteSameRequest<I>::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<I>::send_write();
-}
-
template <typename I>
void ObjectCompareAndWriteRequest<I>::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);
}
template <typename I>
-void ObjectCompareAndWriteRequest<I>::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<I>::filter_write_result(int r) const {
+ if (r <= -MAX_ERRNO) {
+ I *image_ctx = this->m_ictx;
+ Extents image_extents;
- AbstractObjectWriteRequest<I>::send_write();
-}
-
-template <typename I>
-void ObjectCompareAndWriteRequest<I>::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<pair<uint64_t,uint64_t> > 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
template <>
struct CopyupRequest<librbd::MockImageCtx> {
+ MOCK_METHOD0(send, void());
+ MOCK_METHOD1(append_request, void(AbstractObjectWriteRequest<librbd::MockTestImageCtx>*));
+};
+
+template <>
+struct CopyupRequest<librbd::MockTestImageCtx> : public CopyupRequest<librbd::MockImageCtx> {
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<librbd::MockImageCtx> {
+struct ImageRequest<librbd::MockTestImageCtx> {
static ImageRequest *s_instance;
static void aio_read(librbd::MockImageCtx *ictx, AioCompletion *c,
Extents &&image_extents, ReadResult &&read_result,
}
};
-CopyupRequest<librbd::MockImageCtx>* CopyupRequest<librbd::MockImageCtx>::s_instance = nullptr;
-ImageRequest<librbd::MockImageCtx>* ImageRequest<librbd::MockImageCtx>::s_instance = nullptr;
+CopyupRequest<librbd::MockTestImageCtx>* CopyupRequest<librbd::MockTestImageCtx>::s_instance = nullptr;
+ImageRequest<librbd::MockTestImageCtx>* ImageRequest<librbd::MockTestImageCtx>::s_instance = nullptr;
} // namespace io
} // namespace librbd
using ::testing::WithArgs;
struct TestMockIoObjectRequest : public TestMockFixture {
- typedef ObjectRequest<librbd::MockImageCtx> MockObjectRequest;
- typedef ObjectReadRequest<librbd::MockImageCtx> MockObjectReadRequest;
- typedef CopyupRequest<librbd::MockImageCtx> MockCopyupRequest;
- typedef ImageRequest<librbd::MockImageCtx> MockImageRequest;
+ typedef ObjectRequest<librbd::MockTestImageCtx> MockObjectRequest;
+ typedef ObjectReadRequest<librbd::MockTestImageCtx> MockObjectReadRequest;
+ typedef ObjectWriteRequest<librbd::MockTestImageCtx> MockObjectWriteRequest;
+ typedef ObjectDiscardRequest<librbd::MockTestImageCtx> MockObjectDiscardRequest;
+ typedef ObjectWriteSameRequest<librbd::MockTestImageCtx> MockObjectWriteSameRequest;
+ typedef ObjectCompareAndWriteRequest<librbd::MockTestImageCtx> MockObjectCompareAndWriteRequest;
+ typedef AbstractObjectWriteRequest<librbd::MockTestImageCtx> MockAbstractObjectWriteRequest;
+ typedef CopyupRequest<librbd::MockTestImageCtx> MockCopyupRequest;
+ typedef ImageRequest<librbd::MockTestImageCtx> MockImageRequest;
void expect_object_may_exist(MockTestImageCtx &mock_image_ctx,
uint64_t object_no, bool exists) {
}
}
+ 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) {
.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) {
.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<uint8_t> ¤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) {
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());
}
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());
}
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());
}
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());
}
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());
}
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);
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());
}
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);
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());
}
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());
}
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);
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
+