namespace {
-class C_UpdateObjectMap : public C_AsyncObjectThrottle<> {
+template <typename I>
+class C_UpdateObjectMap : public C_AsyncObjectThrottle<I> {
public:
- C_UpdateObjectMap(AsyncObjectThrottle<> &throttle, ImageCtx *image_ctx,
+ C_UpdateObjectMap(AsyncObjectThrottle<I> &throttle, I *image_ctx,
uint64_t object_no, uint8_t head_object_map_state,
const std::vector<uint64_t> *snap_ids,
const ZTracer::Trace &trace, size_t snap_id_idx)
- : C_AsyncObjectThrottle(throttle, *image_ctx), m_object_no(object_no),
+ : C_AsyncObjectThrottle<I>(throttle, *image_ctx), m_object_no(object_no),
m_head_object_map_state(head_object_map_state), m_snap_ids(*snap_ids),
m_trace(trace), m_snap_id_idx(snap_id_idx)
{
}
int send() override {
- ceph_assert(m_image_ctx.owner_lock.is_locked());
- if (m_image_ctx.exclusive_lock == nullptr) {
+ auto& image_ctx = this->m_image_ctx;
+ ceph_assert(image_ctx.owner_lock.is_locked());
+ if (image_ctx.exclusive_lock == nullptr) {
return 1;
}
- ceph_assert(m_image_ctx.exclusive_lock->is_lock_owner());
+ ceph_assert(image_ctx.exclusive_lock->is_lock_owner());
- RWLock::RLocker snap_locker(m_image_ctx.snap_lock);
- if (m_image_ctx.object_map == nullptr) {
+ RWLock::RLocker snap_locker(image_ctx.snap_lock);
+ if (image_ctx.object_map == nullptr) {
return 1;
}
}
int update_head() {
- RWLock::WLocker object_map_locker(m_image_ctx.object_map_lock);
- bool sent = m_image_ctx.object_map->aio_update<Context>(
+ auto& image_ctx = this->m_image_ctx;
+ RWLock::WLocker object_map_locker(image_ctx.object_map_lock);
+ bool sent = image_ctx.object_map->template aio_update<Context>(
CEPH_NOSNAP, m_object_no, m_head_object_map_state, {}, m_trace, false,
this);
return (sent ? 0 : 1);
}
int update_snapshot(uint64_t snap_id) {
+ auto& image_ctx = this->m_image_ctx;
uint8_t state = OBJECT_EXISTS;
- if (m_image_ctx.test_features(RBD_FEATURE_FAST_DIFF,
- m_image_ctx.snap_lock) &&
+ if (image_ctx.test_features(RBD_FEATURE_FAST_DIFF, image_ctx.snap_lock) &&
m_snap_id_idx > 0) {
// first snapshot should be exists+dirty since it contains
// the copyup data -- later snapshots inherit the data.
state = OBJECT_EXISTS_CLEAN;
}
- RWLock::RLocker object_map_locker(m_image_ctx.object_map_lock);
- bool sent = m_image_ctx.object_map->aio_update<Context>(
+ RWLock::RLocker object_map_locker(image_ctx.object_map_lock);
+ bool sent = image_ctx.object_map->template aio_update<Context>(
snap_id, m_object_no, state, {}, m_trace, true, this);
ceph_assert(sent);
return 0;
CopyupRequest<I>::CopyupRequest(I *ictx, const std::string &oid,
uint64_t objectno, Extents &&image_extents,
const ZTracer::Trace &parent_trace)
- : m_ictx(util::get_image_ctx(ictx)), m_oid(oid), m_object_no(objectno),
+ : m_image_ctx(ictx), m_oid(oid), m_object_no(objectno),
m_image_extents(image_extents),
- m_trace(util::create_trace(*m_ictx, "copy-up", parent_trace)),
+ m_trace(util::create_trace(*m_image_ctx, "copy-up", parent_trace)),
m_lock("CopyupRequest", false, false)
{
- m_async_op.start_op(*m_ictx);
+ m_async_op.start_op(*util::get_image_ctx(m_image_ctx));
}
template <typename I>
void CopyupRequest<I>::append_request(AbstractObjectWriteRequest<I> *req) {
Mutex::Locker locker(m_lock);
- ldout(m_ictx->cct, 20) << "oid=" << m_oid << ", "
- << "object_request=" << req << ", "
- << "append=" << m_append_request_permitted << dendl;
+ auto cct = m_image_ctx->cct;
+ ldout(cct, 20) << "oid=" << m_oid << ", "
+ << "object_request=" << req << ", "
+ << "append=" << m_append_request_permitted << dendl;
if (m_append_request_permitted) {
m_pending_requests.push_back(req);
} else {
template <typename I>
void CopyupRequest<I>::read_from_parent() {
- m_ictx->snap_lock.get_read();
- m_ictx->parent_lock.get_read();
+ auto cct = m_image_ctx->cct;
+ m_image_ctx->snap_lock.get_read();
+ m_image_ctx->parent_lock.get_read();
- if (m_ictx->parent == nullptr) {
- ldout(m_ictx->cct, 5) << "parent detached" << dendl;
- m_ictx->parent_lock.put_read();
- m_ictx->snap_lock.put_read();
+ if (m_image_ctx->parent == nullptr) {
+ ldout(cct, 5) << "parent detached" << dendl;
+ m_image_ctx->parent_lock.put_read();
+ m_image_ctx->snap_lock.put_read();
- m_ictx->op_work_queue->queue(
+ m_image_ctx->op_work_queue->queue(
util::create_context_callback<
CopyupRequest<I>, &CopyupRequest<I>::handle_read_from_parent>(this),
-ENOENT);
m_deep_copy = false;
auto comp = AioCompletion::create_and_start<
CopyupRequest<I>,
- &CopyupRequest<I>::handle_read_from_parent>(this, m_ictx, AIO_TYPE_READ);
-
- ldout(m_ictx->cct, 20) << "oid=" << m_oid << ", "
- << "completion=" << comp << ", "
- << "extents=" << m_image_extents
- << dendl;
- ImageRequest<>::aio_read(m_ictx->parent, comp, std::move(m_image_extents),
- ReadResult{&m_copyup_data}, 0, m_trace);
-
- m_ictx->parent_lock.put_read();
- m_ictx->snap_lock.put_read();
+ &CopyupRequest<I>::handle_read_from_parent>(
+ this, util::get_image_ctx(m_image_ctx), AIO_TYPE_READ);
+
+ ldout(cct, 20) << "oid=" << m_oid << ", "
+ << "completion=" << comp << ", "
+ << "extents=" << m_image_extents
+ << dendl;
+ ImageRequest<I>::aio_read(m_image_ctx->parent, comp,
+ std::move(m_image_extents),
+ ReadResult{&m_copyup_data}, 0, m_trace);
+
+ m_image_ctx->parent_lock.put_read();
+ m_image_ctx->snap_lock.put_read();
}
template <typename I>
void CopyupRequest<I>::handle_read_from_parent(int r) {
- ldout(m_ictx->cct, 20) << "oid=" << m_oid << ", "
- << "r=" << r << dendl;
+ auto cct = m_image_ctx->cct;
+ ldout(cct, 20) << "oid=" << m_oid << ", r=" << r << dendl;
m_lock.Lock();
m_copyup_required = is_copyup_required();
if (r < 0 && r != -ENOENT) {
m_lock.Unlock();
- lderr(m_ictx->cct) << "error reading from parent: " << cpp_strerror(r)
- << dendl;
+ lderr(cct) << "error reading from parent: " << cpp_strerror(r) << dendl;
finish(r);
return;
}
if (!m_copyup_required) {
m_lock.Unlock();
- ldout(m_ictx->cct, 20) << "no-op, skipping" << dendl;
+ ldout(cct, 20) << "no-op, skipping" << dendl;
finish(0);
return;
}
template <typename I>
void CopyupRequest<I>::deep_copy() {
- ceph_assert(m_ictx->snap_lock.is_locked());
- ceph_assert(m_ictx->parent_lock.is_locked());
- ceph_assert(m_ictx->parent != nullptr);
+ auto cct = m_image_ctx->cct;
+ ceph_assert(m_image_ctx->snap_lock.is_locked());
+ ceph_assert(m_image_ctx->parent_lock.is_locked());
+ ceph_assert(m_image_ctx->parent != nullptr);
m_lock.Lock();
- m_flatten = is_copyup_required() ? true : m_ictx->migration_info.flatten;
+ m_flatten = is_copyup_required() ? true : m_image_ctx->migration_info.flatten;
m_lock.Unlock();
- ldout(m_ictx->cct, 20) << "oid=" << m_oid << ", "
- << "flatten=" << m_flatten << dendl;
+ ldout(cct, 20) << "oid=" << m_oid << ", flatten=" << m_flatten << dendl;
m_deep_copy = true;
auto ctx = util::create_context_callback<
CopyupRequest<I>, &CopyupRequest<I>::handle_deep_copy>(this);
auto req = deep_copy::ObjectCopyRequest<I>::create(
- m_ictx->parent, m_ictx, m_ictx->migration_info.snap_map, m_object_no,
- m_flatten, ctx);
+ m_image_ctx->parent, m_image_ctx, m_image_ctx->migration_info.snap_map,
+ m_object_no, m_flatten, ctx);
req->send();
- m_ictx->parent_lock.put_read();
- m_ictx->snap_lock.put_read();
+ m_image_ctx->parent_lock.put_read();
+ m_image_ctx->snap_lock.put_read();
}
template <typename I>
void CopyupRequest<I>::handle_deep_copy(int r) {
- ldout(m_ictx->cct, 20) << "oid=" << m_oid << ", "
- << "r=" << r << dendl;
+ auto cct = m_image_ctx->cct;
+ ldout(cct, 20) << "oid=" << m_oid << ", r=" << r << dendl;
- m_ictx->snap_lock.get_read();
+ m_image_ctx->snap_lock.get_read();
m_lock.Lock();
m_copyup_required = is_copyup_required();
if (r == -ENOENT && !m_flatten && m_copyup_required) {
m_lock.Unlock();
- m_ictx->snap_lock.put_read();
+ m_image_ctx->snap_lock.put_read();
- ldout(m_ictx->cct, 10) << "restart deep-copy with flatten" << dendl;
+ ldout(cct, 10) << "restart deep-copy with flatten" << dendl;
send();
return;
}
if (r < 0 && r != -ENOENT) {
m_lock.Unlock();
- m_ictx->snap_lock.put_read();
+ m_image_ctx->snap_lock.put_read();
- lderr(m_ictx->cct) << "error encountered during deep-copy: "
- << cpp_strerror(r) << dendl;
+ lderr(cct) << "error encountered during deep-copy: " << cpp_strerror(r)
+ << dendl;
finish(r);
return;
}
if (!m_copyup_required && !is_update_object_map_required(r)) {
m_lock.Unlock();
- m_ictx->snap_lock.put_read();
+ m_image_ctx->snap_lock.put_read();
if (r == -ENOENT) {
r = 0;
}
- ldout(m_ictx->cct, 20) << "skipping" << dendl;
+ ldout(cct, 20) << "skipping" << dendl;
finish(r);
return;
}
m_lock.Unlock();
- m_ictx->snap_lock.put_read();
+ m_image_ctx->snap_lock.put_read();
update_object_maps();
}
template <typename I>
void CopyupRequest<I>::update_object_maps() {
- RWLock::RLocker owner_locker(m_ictx->owner_lock);
- RWLock::RLocker snap_locker(m_ictx->snap_lock);
- if (m_ictx->object_map == nullptr) {
+ RWLock::RLocker owner_locker(m_image_ctx->owner_lock);
+ RWLock::RLocker snap_locker(m_image_ctx->snap_lock);
+ if (m_image_ctx->object_map == nullptr) {
snap_locker.unlock();
owner_locker.unlock();
return;
}
- auto cct = m_ictx->cct;
+ auto cct = m_image_ctx->cct;
ldout(cct, 20) << "oid=" << m_oid << dendl;
- if (!m_ictx->snaps.empty()) {
+ if (!m_image_ctx->snaps.empty()) {
if (m_deep_copy) {
compute_deep_copy_snap_ids();
} else {
- m_snap_ids.insert(m_snap_ids.end(), m_ictx->snaps.rbegin(),
- m_ictx->snaps.rend());
+ m_snap_ids.insert(m_snap_ids.end(), m_image_ctx->snaps.rbegin(),
+ m_image_ctx->snaps.rend());
}
}
bool copy_on_read = m_pending_requests.empty();
uint8_t head_object_map_state = OBJECT_EXISTS;
if (copy_on_read && !m_snap_ids.empty() &&
- m_ictx->test_features(RBD_FEATURE_FAST_DIFF, m_ictx->snap_lock)) {
+ m_image_ctx->test_features(RBD_FEATURE_FAST_DIFF,
+ m_image_ctx->snap_lock)) {
// HEAD is non-dirty since data is tied to first snapshot
head_object_map_state = OBJECT_EXISTS_CLEAN;
}
head_object_map_state = (*r_it)->get_pre_write_object_map_state();
}
- RWLock::WLocker object_map_locker(m_ictx->object_map_lock);
- if ((*m_ictx->object_map)[m_object_no] != head_object_map_state) {
+ RWLock::WLocker object_map_locker(m_image_ctx->object_map_lock);
+ if ((*m_image_ctx->object_map)[m_object_no] != head_object_map_state) {
// (maybe) need to update the HEAD object map state
m_snap_ids.push_back(CEPH_NOSNAP);
}
object_map_locker.unlock();
snap_locker.unlock();
- ceph_assert(m_ictx->exclusive_lock->is_lock_owner());
- AsyncObjectThrottle<>::ContextFactory context_factory(
- boost::lambda::bind(boost::lambda::new_ptr<C_UpdateObjectMap>(),
- boost::lambda::_1, m_ictx, m_object_no, head_object_map_state, &m_snap_ids,
- m_trace, boost::lambda::_2));
+ ceph_assert(m_image_ctx->exclusive_lock->is_lock_owner());
+ typename AsyncObjectThrottle<I>::ContextFactory context_factory(
+ boost::lambda::bind(boost::lambda::new_ptr<C_UpdateObjectMap<I>>(),
+ boost::lambda::_1, m_image_ctx, m_object_no, head_object_map_state,
+ &m_snap_ids, m_trace, boost::lambda::_2));
auto ctx = util::create_context_callback<
CopyupRequest<I>, &CopyupRequest<I>::handle_update_object_maps>(this);
- auto throttle = new AsyncObjectThrottle<>(
- nullptr, *m_ictx, context_factory, ctx, nullptr, 0, m_snap_ids.size());
+ auto throttle = new AsyncObjectThrottle<I>(
+ nullptr, *m_image_ctx, context_factory, ctx, nullptr, 0, m_snap_ids.size());
throttle->start_ops(
- m_ictx->config.template get_val<uint64_t>("rbd_concurrent_management_ops"));
+ m_image_ctx->config.template get_val<uint64_t>("rbd_concurrent_management_ops"));
}
template <typename I>
void CopyupRequest<I>::handle_update_object_maps(int r) {
- ldout(m_ictx->cct, 20) << "oid=" << m_oid << ", "
- << "r=" << r << dendl;
+ auto cct = m_image_ctx->cct;
+ ldout(cct, 20) << "oid=" << m_oid << ", r=" << r << dendl;
if (r < 0) {
- lderr(m_ictx->cct) << "failed to update object map: " << cpp_strerror(r)
- << dendl;
+ lderr(m_image_ctx->cct) << "failed to update object map: "
+ << cpp_strerror(r) << dendl;
finish(r);
return;
template <typename I>
void CopyupRequest<I>::copyup() {
+ auto cct = m_image_ctx->cct;
+ m_image_ctx->snap_lock.get_read();
+ auto snapc = m_image_ctx->snapc;
+ m_image_ctx->snap_lock.put_read();
+
m_lock.Lock();
if (!m_copyup_required) {
m_lock.Unlock();
- ldout(m_ictx->cct, 20) << "skipping copyup" << dendl;
+ ldout(cct, 20) << "skipping copyup" << dendl;
finish(0);
return;
}
- m_lock.Unlock();
-
- ldout(m_ictx->cct, 20) << "oid=" << m_oid << dendl;
- m_ictx->snap_lock.get_read();
- ::SnapContext snapc = m_ictx->snapc;
- m_ictx->snap_lock.put_read();
-
- std::vector<librados::snap_t> snaps;
+ ldout(cct, 20) << "oid=" << m_oid << dendl;
bool copy_on_read = m_pending_requests.empty();
bool deep_copyup = !snapc.snaps.empty() && !m_copyup_data.is_zero();
m_copyup_data.clear();
}
- Mutex::Locker locker(m_lock);
int r;
+ librados::ObjectWriteOperation copyup_op;
if (copy_on_read || deep_copyup) {
- librados::ObjectWriteOperation copyup_op;
copyup_op.exec("rbd", "copyup", m_copyup_data);
- ObjectRequest<I>::add_write_hint(*m_ictx, ©up_op);
+ ObjectRequest<I>::add_write_hint(*m_image_ctx, ©up_op);
+ ++m_pending_copyups;
+ }
+
+ librados::ObjectWriteOperation write_op;
+ if (!copy_on_read) {
+ if (!deep_copyup) {
+ write_op.exec("rbd", "copyup", m_copyup_data);
+ ObjectRequest<I>::add_write_hint(*m_image_ctx, &write_op);
+ }
+
+ // merge all pending write ops into this single RADOS op
+ for (auto req : m_pending_requests) {
+ ldout(cct, 20) << "add_copyup_ops " << req << dendl;
+ req->add_copyup_ops(&write_op);
+ }
+
+ if (write_op.size() > 0) {
+ ++m_pending_copyups;
+ }
+ }
+ m_lock.Unlock();
+ // issue librados ops at the end to simplify test cases
+ std::vector<librados::snap_t> snaps;
+ if (copyup_op.size() > 0) {
// send only the copyup request with a blank snapshot context so that
// all snapshots are detected from the parent for this object. If
// this is a CoW request, a second request will be created for the
// actual modification.
- m_pending_copyups++;
- ldout(m_ictx->cct, 20) << "copyup with empty snapshot context" << dendl;
+ ldout(cct, 20) << "copyup with empty snapshot context" << dendl;
auto comp = util::create_rados_callback<
CopyupRequest<I>, &CopyupRequest<I>::handle_copyup>(this);
- r = m_ictx->data_ctx.aio_operate(
+ r = m_image_ctx->data_ctx.aio_operate(
m_oid, comp, ©up_op, 0, snaps,
(m_trace.valid() ? m_trace.get_info() : nullptr));
ceph_assert(r == 0);
comp->release();
}
- if (!copy_on_read) {
- librados::ObjectWriteOperation write_op;
- if (!deep_copyup) {
- write_op.exec("rbd", "copyup", m_copyup_data);
- ObjectRequest<I>::add_write_hint(*m_ictx, &write_op);
- }
-
- // merge all pending write ops into this single RADOS op
- for (auto req : m_pending_requests) {
- ldout(m_ictx->cct, 20) << "add_copyup_ops " << req << dendl;
- req->add_copyup_ops(&write_op);
- }
-
+ if (write_op.size() > 0) {
// compare-and-write doesn't add any write ops (copyup+cmpext+write
// can't be executed in the same RADOS op because, unless the object
// was already present in the clone, cmpext wouldn't see it)
- if (!write_op.size()) {
- return;
- }
-
- m_pending_copyups++;
- ldout(m_ictx->cct, 20) << (!deep_copyup && write_op.size() > 2 ?
- "copyup + ops" : !deep_copyup ?
- "copyup" : "ops")
- << " with current snapshot context" << dendl;
+ ldout(cct, 20) << (!deep_copyup && write_op.size() > 2 ?
+ "copyup + ops" : !deep_copyup ? "copyup" : "ops")
+ << " with current snapshot context" << dendl;
snaps.insert(snaps.end(), snapc.snaps.begin(), snapc.snaps.end());
auto comp = util::create_rados_callback<
CopyupRequest<I>, &CopyupRequest<I>::handle_copyup>(this);
- r = m_ictx->data_ctx.aio_operate(
+ r = m_image_ctx->data_ctx.aio_operate(
m_oid, comp, &write_op, snapc.seq, snaps,
(m_trace.valid() ? m_trace.get_info() : nullptr));
ceph_assert(r == 0);
template <typename I>
void CopyupRequest<I>::handle_copyup(int r) {
+ auto cct = m_image_ctx->cct;
unsigned pending_copyups;
{
Mutex::Locker locker(m_lock);
pending_copyups = --m_pending_copyups;
}
- ldout(m_ictx->cct, 20) << "oid=" << m_oid << ", "
- << "r=" << r << ", "
- << "pending=" << pending_copyups << dendl;
+ ldout(cct, 20) << "oid=" << m_oid << ", " << "r=" << r << ", "
+ << "pending=" << pending_copyups << dendl;
- if (r == -ENOENT) {
- if (pending_copyups == 0) {
- // hide the -ENOENT error if this is the last op
- r = 0;
- }
- } else if (r < 0) {
- lderr(m_ictx->cct) << "failed to copyup object: "
- << cpp_strerror(r) << dendl;
+ if (r < 0 && r != -ENOENT) {
+ lderr(cct) << "failed to copyup object: " << cpp_strerror(r) << dendl;
complete_requests(false, r);
}
if (pending_copyups == 0) {
- finish(r);
+ finish(0);
}
}
template <typename I>
void CopyupRequest<I>::finish(int r) {
- ldout(m_ictx->cct, 20) << "oid=" << m_oid << ", "
- << "r=" << r << dendl;
+ auto cct = m_image_ctx->cct;
+ ldout(cct, 20) << "oid=" << m_oid << ", r=" << r << dendl;
complete_requests(true, r);
delete this;
template <typename I>
void CopyupRequest<I>::complete_requests(bool override_restart_retval, int r) {
+ auto cct = m_image_ctx->cct;
remove_from_list();
while (!m_pending_requests.empty()) {
auto it = m_pending_requests.begin();
auto req = *it;
- ldout(m_ictx->cct, 20) << "completing request " << req << dendl;
+ ldout(cct, 20) << "completing request " << req << dendl;
req->handle_copyup(r);
m_pending_requests.erase(it);
}
while (!m_restart_requests.empty()) {
auto it = m_restart_requests.begin();
auto req = *it;
- ldout(m_ictx->cct, 20) << "restarting request " << req << dendl;
+ ldout(cct, 20) << "restarting request " << req << dendl;
req->handle_copyup(r);
m_restart_requests.erase(it);
}
template <typename I>
void CopyupRequest<I>::remove_from_list() {
- Mutex::Locker copyup_list_locker(m_ictx->copyup_list_lock);
+ Mutex::Locker copyup_list_locker(m_image_ctx->copyup_list_lock);
- auto it = m_ictx->copyup_list.find(m_object_no);
- if (it != m_ictx->copyup_list.end()) {
- m_ictx->copyup_list.erase(it);
+ auto it = m_image_ctx->copyup_list.find(m_object_no);
+ if (it != m_image_ctx->copyup_list.end()) {
+ m_image_ctx->copyup_list.erase(it);
}
}
template <typename I>
bool CopyupRequest<I>::is_deep_copy() const {
- ceph_assert(m_ictx->snap_lock.is_locked());
- return !m_ictx->migration_info.empty();
+ ceph_assert(m_image_ctx->snap_lock.is_locked());
+ return !m_image_ctx->migration_info.empty();
}
template <typename I>
bool CopyupRequest<I>::is_update_object_map_required(int r) {
- ceph_assert(m_ictx->snap_lock.is_locked());
+ ceph_assert(m_image_ctx->snap_lock.is_locked());
if (r < 0) {
return false;
}
- if (m_ictx->object_map == nullptr) {
- return false;
- }
-
- if (!is_deep_copy()) {
+ if (m_image_ctx->object_map == nullptr) {
return false;
}
- auto it = m_ictx->migration_info.snap_map.find(CEPH_NOSNAP);
- ceph_assert(it != m_ictx->migration_info.snap_map.end());
+ auto it = m_image_ctx->migration_info.snap_map.find(CEPH_NOSNAP);
+ ceph_assert(it != m_image_ctx->migration_info.snap_map.end());
return it->second[0] != CEPH_NOSNAP;
}
template <typename I>
void CopyupRequest<I>::compute_deep_copy_snap_ids() {
- ceph_assert(m_ictx->snap_lock.is_locked());
+ ceph_assert(m_image_ctx->snap_lock.is_locked());
// don't copy ids for the snaps updated by object deep copy or
// that don't overlap
std::set<uint64_t> deep_copied;
- for (auto &it : m_ictx->migration_info.snap_map) {
+ for (auto &it : m_image_ctx->migration_info.snap_map) {
if (it.first != CEPH_NOSNAP) {
deep_copied.insert(it.second.front());
}
}
- RWLock::RLocker parent_locker(m_ictx->parent_lock);
- std::copy_if(m_ictx->snaps.rbegin(), m_ictx->snaps.rend(),
+ RWLock::RLocker parent_locker(m_image_ctx->parent_lock);
+ std::copy_if(m_image_ctx->snaps.rbegin(), m_image_ctx->snaps.rend(),
std::back_inserter(m_snap_ids),
- [this, cct=m_ictx->cct, &deep_copied](uint64_t snap_id) {
+ [this, cct=m_image_ctx->cct, &deep_copied](uint64_t snap_id) {
if (deep_copied.count(snap_id)) {
return false;
}
uint64_t parent_overlap = 0;
- int r = m_ictx->get_parent_overlap(snap_id,
- &parent_overlap);
+ int r = m_image_ctx->get_parent_overlap(snap_id, &parent_overlap);
if (r < 0) {
ldout(cct, 5) << "failed getting parent overlap for snap_id: "
<< snap_id << ": " << cpp_strerror(r) << dendl;
return false;
}
std::vector<std::pair<uint64_t, uint64_t>> extents;
- Striper::extent_to_file(cct, &m_ictx->layout,
+ Striper::extent_to_file(cct, &m_image_ctx->layout,
m_object_no, 0,
- m_ictx->layout.object_size,
+ m_image_ctx->layout.object_size,
extents);
- auto overlap = m_ictx->prune_parent_extents(
+ auto overlap = m_image_ctx->prune_parent_extents(
extents, parent_overlap);
return overlap > 0;
});
--- /dev/null
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab
+
+#include "test/librbd/test_mock_fixture.h"
+#include "test/librbd/test_support.h"
+#include "test/librbd/mock/MockExclusiveLock.h"
+#include "test/librbd/mock/MockImageCtx.h"
+#include "test/librbd/mock/MockJournal.h"
+#include "test/librbd/mock/MockObjectMap.h"
+#include "test/librados_test_stub/MockTestMemIoCtxImpl.h"
+#include "test/librados_test_stub/MockTestMemRadosClient.h"
+#include "include/rbd/librbd.hpp"
+#include "librbd/deep_copy/ObjectCopyRequest.h"
+#include "librbd/io/CopyupRequest.h"
+#include "librbd/io/ImageRequest.h"
+#include "librbd/io/ObjectRequest.h"
+#include "librbd/io/ReadResult.h"
+
+namespace librbd {
+namespace {
+
+struct MockTestImageCtx : public MockImageCtx {
+ MockTestImageCtx(ImageCtx &image_ctx,
+ MockTestImageCtx* mock_parent_image_ctx = nullptr)
+ : MockImageCtx(image_ctx) {
+ parent = mock_parent_image_ctx;
+ }
+
+ std::map<uint64_t, librbd::io::CopyupRequest<librbd::MockTestImageCtx>*> copyup_list;
+};
+
+} // anonymous namespace
+
+namespace util {
+
+inline ImageCtx *get_image_ctx(MockImageCtx *image_ctx) {
+ return image_ctx->image_ctx;
+}
+
+} // namespace util
+
+namespace deep_copy {
+
+template <>
+struct ObjectCopyRequest<librbd::MockTestImageCtx> {
+ static ObjectCopyRequest* s_instance;
+ static ObjectCopyRequest* create(librbd::MockImageCtx* parent_image_ctx,
+ librbd::MockTestImageCtx* image_ctx,
+ const SnapMap &snap_map,
+ uint64_t object_number, bool flatten,
+ Context *on_finish) {
+ ceph_assert(s_instance != nullptr);
+ s_instance->object_number = object_number;
+ s_instance->flatten = flatten;
+ s_instance->on_finish = on_finish;
+ return s_instance;
+ }
+
+ uint64_t object_number;
+ bool flatten;
+ Context *on_finish;
+
+ ObjectCopyRequest() {
+ s_instance = this;
+ }
+
+ MOCK_METHOD0(send, void());
+};
+
+ObjectCopyRequest<librbd::MockTestImageCtx>* ObjectCopyRequest<librbd::MockTestImageCtx>::s_instance = nullptr;
+
+} // namespace deep_copy
+
+namespace io {
+
+template <>
+struct ObjectRequest<librbd::MockTestImageCtx> {
+ static void add_write_hint(librbd::MockTestImageCtx&,
+ librados::ObjectWriteOperation*) {
+ }
+};
+
+template <>
+struct AbstractObjectWriteRequest<librbd::MockTestImageCtx> {
+ C_SaferCond ctx;
+ void handle_copyup(int r) {
+ ctx.complete(r);
+ }
+
+ MOCK_CONST_METHOD0(get_pre_write_object_map_state, uint8_t());
+ MOCK_CONST_METHOD0(is_empty_write_op, bool());
+
+ MOCK_METHOD1(add_copyup_ops, void(librados::ObjectWriteOperation*));
+};
+
+template <>
+struct ImageRequest<librbd::MockTestImageCtx> {
+ static ImageRequest *s_instance;
+ static void aio_read(librbd::MockImageCtx *ictx, AioCompletion *c,
+ Extents &&image_extents, ReadResult &&read_result,
+ int op_flags, const ZTracer::Trace &parent_trace) {
+ s_instance->aio_read(c, image_extents, &read_result);
+ }
+
+ MOCK_METHOD3(aio_read, void(AioCompletion *, const Extents&, ReadResult*));
+
+ ImageRequest() {
+ s_instance = this;
+ }
+};
+
+ImageRequest<librbd::MockTestImageCtx>* ImageRequest<librbd::MockTestImageCtx>::s_instance = nullptr;
+
+} // namespace io
+} // namespace librbd
+
+static bool operator==(const SnapContext& rhs, const SnapContext& lhs) {
+ return (rhs.seq == lhs.seq && rhs.snaps == lhs.snaps);
+}
+
+#include "librbd/AsyncObjectThrottle.cc"
+#include "librbd/io/CopyupRequest.cc"
+
+namespace librbd {
+namespace io {
+
+using ::testing::_;
+using ::testing::InSequence;
+using ::testing::Invoke;
+using ::testing::Return;
+using ::testing::StrEq;
+using ::testing::WithArg;
+using ::testing::WithArgs;
+using ::testing::WithoutArgs;
+
+struct TestMockIoCopyupRequest : public TestMockFixture {
+ typedef CopyupRequest<librbd::MockTestImageCtx> MockCopyupRequest;
+ typedef ImageRequest<librbd::MockTestImageCtx> MockImageRequest;
+ typedef ObjectRequest<librbd::MockTestImageCtx> MockObjectRequest;
+ typedef AbstractObjectWriteRequest<librbd::MockTestImageCtx> MockAbstractObjectWriteRequest;
+ typedef deep_copy::ObjectCopyRequest<librbd::MockTestImageCtx> MockObjectCopyRequest;
+
+ void SetUp() override {
+ TestMockFixture::SetUp();
+ if (!is_feature_enabled(RBD_FEATURE_LAYERING)) {
+ return;
+ }
+
+ m_parent_image_name = m_image_name;
+ m_image_name = get_temp_image_name();
+
+ librbd::Image image;
+ librbd::RBD rbd;
+ ASSERT_EQ(0, rbd.open(m_ioctx, image, m_parent_image_name.c_str(),
+ nullptr));
+ ASSERT_EQ(0, image.snap_create("one"));
+ ASSERT_EQ(0, image.snap_protect("one"));
+
+ uint64_t features;
+ ASSERT_EQ(0, image.features(&features));
+ image.close();
+
+ int order = 0;
+ ASSERT_EQ(0, rbd.clone(m_ioctx, m_parent_image_name.c_str(), "one", m_ioctx,
+ m_image_name.c_str(), features, &order));
+ }
+
+ void expect_get_parent_overlap(MockTestImageCtx& mock_image_ctx,
+ librados::snap_t snap_id, uint64_t overlap,
+ int r) {
+ if (mock_image_ctx.object_map != nullptr) {
+ EXPECT_CALL(mock_image_ctx, get_parent_overlap(snap_id, _))
+ .WillOnce(WithArg<1>(Invoke([overlap, r](uint64_t *o) {
+ *o = overlap;
+ return r;
+ })));
+ }
+ }
+
+ void expect_prune_parent_extents(MockTestImageCtx& mock_image_ctx,
+ uint64_t overlap, uint64_t object_overlap) {
+ if (mock_image_ctx.object_map != nullptr) {
+ EXPECT_CALL(mock_image_ctx, prune_parent_extents(_, overlap))
+ .WillOnce(WithoutArgs(Invoke([object_overlap]() {
+ return object_overlap;
+ })));
+ }
+ }
+
+ void expect_read_parent(MockTestImageCtx& mock_image_ctx,
+ MockImageRequest& mock_image_request,
+ const Extents& image_extents,
+ const std::string& data, int r) {
+ EXPECT_CALL(mock_image_request, aio_read(_, image_extents, _))
+ .WillOnce(WithArgs<0, 2>(Invoke(
+ [&mock_image_ctx, image_extents, data, r](
+ AioCompletion* aio_comp, ReadResult* read_result) {
+ aio_comp->read_result = std::move(*read_result);
+ aio_comp->set_request_count(1);
+ auto ctx = new ReadResult::C_ImageReadRequest(aio_comp,
+ image_extents);
+ ctx->bl.append(data);
+ mock_image_ctx.image_ctx->op_work_queue->queue(ctx, r);
+ })));
+ }
+
+ void expect_copyup(MockTestImageCtx& mock_image_ctx, uint64_t snap_id,
+ const std::string& oid, const std::string& data, int r) {
+ bufferlist in_bl;
+ in_bl.append(data);
+
+ SnapContext snapc;
+ if (snap_id == CEPH_NOSNAP) {
+ snapc = mock_image_ctx.snapc;
+ }
+
+ EXPECT_CALL(get_mock_io_ctx(mock_image_ctx.data_ctx),
+ exec(oid, _, StrEq("rbd"), StrEq("copyup"),
+ ContentsEqual(in_bl), _, snapc))
+ .WillOnce(Return(r));
+ }
+
+ void expect_write(MockTestImageCtx& mock_image_ctx, uint64_t snap_id,
+ const std::string& oid, int r) {
+ SnapContext snapc;
+ if (snap_id == CEPH_NOSNAP) {
+ snapc = mock_image_ctx.snapc;
+ }
+
+ EXPECT_CALL(get_mock_io_ctx(mock_image_ctx.data_ctx),
+ write(oid, _, 0, 0, snapc))
+ .WillOnce(Return(r));
+ }
+
+ void expect_test_features(MockTestImageCtx& mock_image_ctx) {
+ EXPECT_CALL(mock_image_ctx, test_features(_, _))
+ .WillRepeatedly(WithArg<0>(Invoke([&mock_image_ctx](uint64_t features) {
+ return (mock_image_ctx.features & features) != 0;
+ })));
+ }
+
+ void expect_is_lock_owner(MockTestImageCtx& mock_image_ctx) {
+ if (mock_image_ctx.exclusive_lock != nullptr) {
+ EXPECT_CALL(*mock_image_ctx.exclusive_lock,
+ is_lock_owner()).WillRepeatedly(Return(true));
+ }
+ }
+
+ void expect_is_empty_write_op(MockAbstractObjectWriteRequest& mock_write_request,
+ bool is_empty) {
+ EXPECT_CALL(mock_write_request, is_empty_write_op())
+ .WillOnce(Return(is_empty));
+ }
+
+ void expect_add_copyup_ops(MockAbstractObjectWriteRequest& mock_write_request) {
+ EXPECT_CALL(mock_write_request, add_copyup_ops(_))
+ .WillOnce(Invoke([](librados::ObjectWriteOperation* op) {
+ op->write(0, bufferlist{});
+ }));
+ }
+
+ void expect_get_pre_write_object_map_state(MockTestImageCtx& mock_image_ctx,
+ MockAbstractObjectWriteRequest& mock_write_request,
+ uint8_t state) {
+ if (mock_image_ctx.object_map != nullptr) {
+ EXPECT_CALL(mock_write_request, get_pre_write_object_map_state())
+ .WillOnce(Return(state));
+ }
+ }
+
+ void expect_object_map_at(MockTestImageCtx& mock_image_ctx,
+ uint64_t object_no, uint8_t state) {
+ if (mock_image_ctx.object_map != nullptr) {
+ EXPECT_CALL(*mock_image_ctx.object_map, at(object_no))
+ .WillOnce(Return(state));
+ }
+ }
+
+ void expect_object_map_update(MockTestImageCtx& mock_image_ctx,
+ uint64_t snap_id, uint64_t object_no,
+ uint8_t state, bool updated, int ret_val) {
+ if (mock_image_ctx.object_map != nullptr) {
+ if (!mock_image_ctx.image_ctx->test_features(RBD_FEATURE_FAST_DIFF) &&
+ state == OBJECT_EXISTS_CLEAN) {
+ state = OBJECT_EXISTS;
+ }
+
+ EXPECT_CALL(*mock_image_ctx.object_map,
+ aio_update(snap_id, object_no, object_no + 1, state,
+ boost::optional<uint8_t>(), _,
+ (snap_id != CEPH_NOSNAP), _))
+ .WillOnce(WithArg<7>(Invoke([&mock_image_ctx, updated, ret_val](Context *ctx) {
+ if (updated) {
+ mock_image_ctx.op_work_queue->queue(ctx, ret_val);
+ }
+ return updated;
+ })));
+ }
+ }
+
+ void expect_object_copy(MockTestImageCtx& mock_image_ctx,
+ MockObjectCopyRequest& mock_object_copy_request,
+ bool flatten, int r) {
+ EXPECT_CALL(mock_object_copy_request, send())
+ .WillOnce(Invoke(
+ [&mock_image_ctx, &mock_object_copy_request, flatten, r]() {
+ ASSERT_EQ(flatten, mock_object_copy_request.flatten);
+ mock_image_ctx.op_work_queue->queue(
+ mock_object_copy_request.on_finish, r);
+ }));
+ }
+
+ std::string m_parent_image_name;
+};
+
+TEST_F(TestMockIoCopyupRequest, Standard) {
+ REQUIRE_FEATURE(RBD_FEATURE_LAYERING);
+
+ librbd::ImageCtx *ictx;
+ ASSERT_EQ(0, open_image(m_image_name, &ictx));
+
+ MockTestImageCtx mock_parent_image_ctx(*ictx->parent);
+ MockTestImageCtx mock_image_ctx(*ictx, &mock_parent_image_ctx);
+
+ MockExclusiveLock mock_exclusive_lock;
+ MockJournal mock_journal;
+ MockObjectMap mock_object_map;
+ initialize_features(ictx, mock_image_ctx, mock_exclusive_lock, mock_journal,
+ mock_object_map);
+
+ expect_op_work_queue(mock_image_ctx);
+ expect_is_lock_owner(mock_image_ctx);
+
+ InSequence seq;
+
+ MockImageRequest mock_image_request;
+ std::string data(4096, '1');
+ expect_read_parent(mock_parent_image_ctx, mock_image_request, {{0, 4096}},
+ data, 0);
+
+ MockAbstractObjectWriteRequest mock_write_request;
+ expect_get_pre_write_object_map_state(mock_image_ctx, mock_write_request,
+ OBJECT_EXISTS);
+ expect_object_map_at(mock_image_ctx, 0, OBJECT_NONEXISTENT);
+ expect_object_map_update(mock_image_ctx, CEPH_NOSNAP, 0, OBJECT_EXISTS, true,
+ 0);
+
+ expect_add_copyup_ops(mock_write_request);
+ expect_copyup(mock_image_ctx, CEPH_NOSNAP, "oid", data, 0);
+ expect_write(mock_image_ctx, CEPH_NOSNAP, "oid", 0);
+
+ auto req = new MockCopyupRequest(&mock_image_ctx, "oid", 0,
+ {{0, 4096}}, {});
+ mock_image_ctx.copyup_list[0] = req;
+ req->append_request(&mock_write_request);
+ req->send();
+
+ ASSERT_EQ(0, mock_write_request.ctx.wait());
+}
+
+TEST_F(TestMockIoCopyupRequest, StandardWithSnaps) {
+ REQUIRE_FEATURE(RBD_FEATURE_LAYERING);
+
+ librbd::ImageCtx *ictx;
+ ASSERT_EQ(0, open_image(m_image_name, &ictx));
+ ictx->snap_lock.get_write();
+ ictx->add_snap(cls::rbd::UserSnapshotNamespace(), "2", 2, ictx->size,
+ ictx->parent_md, RBD_PROTECTION_STATUS_UNPROTECTED,
+ 0, {});
+ ictx->add_snap(cls::rbd::UserSnapshotNamespace(), "1", 1, ictx->size,
+ ictx->parent_md, RBD_PROTECTION_STATUS_UNPROTECTED,
+ 0, {});
+ ictx->snapc = {2, {2, 1}};
+ ictx->snap_lock.put_write();
+
+ MockTestImageCtx mock_parent_image_ctx(*ictx->parent);
+ MockTestImageCtx mock_image_ctx(*ictx, &mock_parent_image_ctx);
+
+ MockExclusiveLock mock_exclusive_lock;
+ MockJournal mock_journal;
+ MockObjectMap mock_object_map;
+ initialize_features(ictx, mock_image_ctx, mock_exclusive_lock, mock_journal,
+ mock_object_map);
+
+ expect_test_features(mock_image_ctx);
+ expect_op_work_queue(mock_image_ctx);
+ expect_is_lock_owner(mock_image_ctx);
+
+ InSequence seq;
+
+ MockImageRequest mock_image_request;
+ std::string data(4096, '1');
+ expect_read_parent(mock_parent_image_ctx, mock_image_request, {{0, 4096}},
+ data, 0);
+
+ MockAbstractObjectWriteRequest mock_write_request;
+ expect_get_pre_write_object_map_state(mock_image_ctx, mock_write_request,
+ OBJECT_EXISTS);
+ expect_object_map_at(mock_image_ctx, 0, OBJECT_NONEXISTENT);
+ expect_object_map_update(mock_image_ctx, 1, 0, OBJECT_EXISTS, true, 0);
+ expect_object_map_update(mock_image_ctx, 2, 0, OBJECT_EXISTS_CLEAN, true, 0);
+ expect_object_map_update(mock_image_ctx, CEPH_NOSNAP, 0, OBJECT_EXISTS, true,
+ 0);
+
+ expect_add_copyup_ops(mock_write_request);
+ expect_copyup(mock_image_ctx, 0, "oid", data, 0);
+ expect_write(mock_image_ctx, CEPH_NOSNAP, "oid", 0);
+
+ auto req = new MockCopyupRequest(&mock_image_ctx, "oid", 0,
+ {{0, 4096}}, {});
+ mock_image_ctx.copyup_list[0] = req;
+ req->append_request(&mock_write_request);
+ req->send();
+
+ ASSERT_EQ(0, mock_write_request.ctx.wait());
+}
+
+TEST_F(TestMockIoCopyupRequest, CopyOnRead) {
+ REQUIRE_FEATURE(RBD_FEATURE_LAYERING);
+
+ librbd::ImageCtx *ictx;
+ ASSERT_EQ(0, open_image(m_image_name, &ictx));
+
+ MockTestImageCtx mock_parent_image_ctx(*ictx->parent);
+ MockTestImageCtx mock_image_ctx(*ictx, &mock_parent_image_ctx);
+
+ MockExclusiveLock mock_exclusive_lock;
+ MockJournal mock_journal;
+ MockObjectMap mock_object_map;
+ initialize_features(ictx, mock_image_ctx, mock_exclusive_lock, mock_journal,
+ mock_object_map);
+
+ expect_op_work_queue(mock_image_ctx);
+ expect_is_lock_owner(mock_image_ctx);
+
+ InSequence seq;
+
+ MockImageRequest mock_image_request;
+ std::string data(4096, '1');
+ expect_read_parent(mock_parent_image_ctx, mock_image_request, {{0, 4096}},
+ data, 0);
+
+ expect_object_map_at(mock_image_ctx, 0, OBJECT_NONEXISTENT);
+ expect_object_map_update(mock_image_ctx, CEPH_NOSNAP, 0, OBJECT_EXISTS, true,
+ 0);
+
+ expect_copyup(mock_image_ctx, CEPH_NOSNAP, "oid", data, 0);
+
+ auto req = new MockCopyupRequest(&mock_image_ctx, "oid", 0,
+ {{0, 4096}}, {});
+ mock_image_ctx.copyup_list[0] = req;
+ req->send();
+ ictx->flush_async_operations();
+}
+
+TEST_F(TestMockIoCopyupRequest, CopyOnReadWithSnaps) {
+ REQUIRE_FEATURE(RBD_FEATURE_LAYERING);
+
+ librbd::ImageCtx *ictx;
+ ASSERT_EQ(0, open_image(m_image_name, &ictx));
+ ictx->snap_lock.get_write();
+ ictx->add_snap(cls::rbd::UserSnapshotNamespace(), "1", 1, ictx->size,
+ ictx->parent_md, RBD_PROTECTION_STATUS_UNPROTECTED,
+ 0, {});
+ ictx->snapc = {1, {1}};
+ ictx->snap_lock.put_write();
+
+ MockTestImageCtx mock_parent_image_ctx(*ictx->parent);
+ MockTestImageCtx mock_image_ctx(*ictx, &mock_parent_image_ctx);
+
+ MockExclusiveLock mock_exclusive_lock;
+ MockJournal mock_journal;
+ MockObjectMap mock_object_map;
+ initialize_features(ictx, mock_image_ctx, mock_exclusive_lock, mock_journal,
+ mock_object_map);
+
+ expect_test_features(mock_image_ctx);
+ expect_op_work_queue(mock_image_ctx);
+ expect_is_lock_owner(mock_image_ctx);
+
+ InSequence seq;
+
+ MockImageRequest mock_image_request;
+ std::string data(4096, '1');
+ expect_read_parent(mock_parent_image_ctx, mock_image_request, {{0, 4096}},
+ data, 0);
+
+ expect_object_map_at(mock_image_ctx, 0, OBJECT_NONEXISTENT);
+ expect_object_map_update(mock_image_ctx, 1, 0, OBJECT_EXISTS, true, 0);
+ expect_object_map_update(mock_image_ctx, CEPH_NOSNAP, 0, OBJECT_EXISTS_CLEAN,
+ true, 0);
+
+ expect_copyup(mock_image_ctx, 0, "oid", data, 0);
+
+ auto req = new MockCopyupRequest(&mock_image_ctx, "oid", 0,
+ {{0, 4096}}, {});
+ mock_image_ctx.copyup_list[0] = req;
+ req->send();
+ ictx->flush_async_operations();
+}
+
+TEST_F(TestMockIoCopyupRequest, DeepCopy) {
+ REQUIRE_FEATURE(RBD_FEATURE_LAYERING);
+
+ librbd::ImageCtx *ictx;
+ ASSERT_EQ(0, open_image(m_image_name, &ictx));
+
+ MockTestImageCtx mock_parent_image_ctx(*ictx->parent);
+ MockTestImageCtx mock_image_ctx(*ictx, &mock_parent_image_ctx);
+
+ MockExclusiveLock mock_exclusive_lock;
+ MockJournal mock_journal;
+ MockObjectMap mock_object_map;
+ initialize_features(ictx, mock_image_ctx, mock_exclusive_lock, mock_journal,
+ mock_object_map);
+
+ expect_op_work_queue(mock_image_ctx);
+ expect_is_lock_owner(mock_image_ctx);
+
+ InSequence seq;
+
+ MockAbstractObjectWriteRequest mock_write_request;
+ MockObjectCopyRequest mock_object_copy_request;
+ mock_image_ctx.migration_info = {1, "", "", "image id", {}, ictx->size, true};
+ expect_is_empty_write_op(mock_write_request, false);
+ expect_object_copy(mock_image_ctx, mock_object_copy_request, true, 0);
+
+ expect_is_empty_write_op(mock_write_request, false);
+ expect_get_pre_write_object_map_state(mock_image_ctx, mock_write_request,
+ OBJECT_EXISTS);
+ expect_object_map_at(mock_image_ctx, 0, OBJECT_NONEXISTENT);
+ expect_object_map_update(mock_image_ctx, CEPH_NOSNAP, 0, OBJECT_EXISTS, true,
+ 0);
+
+ expect_add_copyup_ops(mock_write_request);
+ expect_copyup(mock_image_ctx, CEPH_NOSNAP, "oid", "", 0);
+ expect_write(mock_image_ctx, CEPH_NOSNAP, "oid", 0);
+
+ auto req = new MockCopyupRequest(&mock_image_ctx, "oid", 0,
+ {{0, 4096}}, {});
+ mock_image_ctx.copyup_list[0] = req;
+ req->append_request(&mock_write_request);
+ req->send();
+
+ ASSERT_EQ(0, mock_write_request.ctx.wait());
+}
+
+TEST_F(TestMockIoCopyupRequest, DeepCopyOnRead) {
+ REQUIRE_FEATURE(RBD_FEATURE_LAYERING);
+
+ librbd::ImageCtx *ictx;
+ ASSERT_EQ(0, open_image(m_image_name, &ictx));
+
+ MockTestImageCtx mock_parent_image_ctx(*ictx->parent);
+ MockTestImageCtx mock_image_ctx(*ictx, &mock_parent_image_ctx);
+
+ MockExclusiveLock mock_exclusive_lock;
+ MockJournal mock_journal;
+ MockObjectMap mock_object_map;
+ initialize_features(ictx, mock_image_ctx, mock_exclusive_lock, mock_journal,
+ mock_object_map);
+
+ expect_op_work_queue(mock_image_ctx);
+ expect_is_lock_owner(mock_image_ctx);
+
+ InSequence seq;
+
+ MockObjectCopyRequest mock_object_copy_request;
+ mock_image_ctx.migration_info = {1, "", "", "image id", {}, ictx->size,
+ false};
+ expect_object_copy(mock_image_ctx, mock_object_copy_request, true, 0);
+
+ expect_object_map_at(mock_image_ctx, 0, OBJECT_NONEXISTENT);
+ expect_object_map_update(mock_image_ctx, CEPH_NOSNAP, 0, OBJECT_EXISTS, true,
+ 0);
+
+ expect_copyup(mock_image_ctx, CEPH_NOSNAP, "oid", "", 0);
+
+ auto req = new MockCopyupRequest(&mock_image_ctx, "oid", 0,
+ {{0, 4096}}, {});
+ mock_image_ctx.copyup_list[0] = req;
+ req->send();
+ ictx->flush_async_operations();
+}
+
+TEST_F(TestMockIoCopyupRequest, DeepCopyWithSnaps) {
+ REQUIRE_FEATURE(RBD_FEATURE_LAYERING);
+
+ librbd::ImageCtx *ictx;
+ ASSERT_EQ(0, open_image(m_image_name, &ictx));
+ ictx->snap_lock.get_write();
+ ictx->add_snap(cls::rbd::UserSnapshotNamespace(), "3", 3, ictx->size,
+ ictx->parent_md, RBD_PROTECTION_STATUS_UNPROTECTED,
+ 0, {});
+ ictx->add_snap(cls::rbd::UserSnapshotNamespace(), "2", 2, ictx->size,
+ ictx->parent_md, RBD_PROTECTION_STATUS_UNPROTECTED,
+ 0, {});
+ ictx->add_snap(cls::rbd::UserSnapshotNamespace(), "1", 1, ictx->size,
+ ictx->parent_md, RBD_PROTECTION_STATUS_UNPROTECTED,
+ 0, {});
+ ictx->snapc = {3, {3, 2, 1}};
+ ictx->snap_lock.put_write();
+
+ MockTestImageCtx mock_parent_image_ctx(*ictx->parent);
+ MockTestImageCtx mock_image_ctx(*ictx, &mock_parent_image_ctx);
+
+ MockExclusiveLock mock_exclusive_lock;
+ MockJournal mock_journal;
+ MockObjectMap mock_object_map;
+ initialize_features(ictx, mock_image_ctx, mock_exclusive_lock, mock_journal,
+ mock_object_map);
+
+ expect_test_features(mock_image_ctx);
+ expect_op_work_queue(mock_image_ctx);
+ expect_is_lock_owner(mock_image_ctx);
+
+ InSequence seq;
+
+ MockAbstractObjectWriteRequest mock_write_request;
+ MockObjectCopyRequest mock_object_copy_request;
+ mock_image_ctx.migration_info = {1, "", "", "image id",
+ {{CEPH_NOSNAP, {2, 1}}, {10, {1}}},
+ ictx->size, true};
+ expect_is_empty_write_op(mock_write_request, false);
+ expect_object_copy(mock_image_ctx, mock_object_copy_request, true, 0);
+
+ expect_is_empty_write_op(mock_write_request, false);
+ expect_get_parent_overlap(mock_image_ctx, 2, 0, 0);
+ expect_get_parent_overlap(mock_image_ctx, 3, 1, 0);
+ expect_prune_parent_extents(mock_image_ctx, 1, 1);
+ expect_get_pre_write_object_map_state(mock_image_ctx, mock_write_request,
+ OBJECT_EXISTS);
+ expect_object_map_at(mock_image_ctx, 0, OBJECT_NONEXISTENT);
+ expect_object_map_update(mock_image_ctx, 3, 0, OBJECT_EXISTS, true, 0);
+ expect_object_map_update(mock_image_ctx, CEPH_NOSNAP, 0, OBJECT_EXISTS, true,
+ 0);
+
+ expect_add_copyup_ops(mock_write_request);
+ expect_copyup(mock_image_ctx, CEPH_NOSNAP, "oid", "", 0);
+ expect_write(mock_image_ctx, CEPH_NOSNAP, "oid", 0);
+
+ auto req = new MockCopyupRequest(&mock_image_ctx, "oid", 0,
+ {{0, 4096}}, {});
+ mock_image_ctx.copyup_list[0] = req;
+ req->append_request(&mock_write_request);
+ req->send();
+
+ ASSERT_EQ(0, mock_write_request.ctx.wait());
+}
+
+TEST_F(TestMockIoCopyupRequest, ZeroedCopyup) {
+ REQUIRE_FEATURE(RBD_FEATURE_LAYERING);
+
+ librbd::ImageCtx *ictx;
+ ASSERT_EQ(0, open_image(m_image_name, &ictx));
+
+ MockTestImageCtx mock_image_ctx(*ictx);
+
+ MockExclusiveLock mock_exclusive_lock;
+ MockJournal mock_journal;
+ MockObjectMap mock_object_map;
+ initialize_features(ictx, mock_image_ctx, mock_exclusive_lock, mock_journal,
+ mock_object_map);
+
+ expect_op_work_queue(mock_image_ctx);
+ expect_is_lock_owner(mock_image_ctx);
+
+ InSequence seq;
+
+ MockAbstractObjectWriteRequest mock_write_request;
+ expect_is_empty_write_op(mock_write_request, false);
+ expect_get_pre_write_object_map_state(mock_image_ctx, mock_write_request,
+ OBJECT_EXISTS);
+ expect_object_map_at(mock_image_ctx, 0, OBJECT_NONEXISTENT);
+ expect_object_map_update(mock_image_ctx, CEPH_NOSNAP, 0, OBJECT_EXISTS, true,
+ 0);
+
+ expect_add_copyup_ops(mock_write_request);
+ expect_copyup(mock_image_ctx, CEPH_NOSNAP, "oid", "", 0);
+ expect_write(mock_image_ctx, CEPH_NOSNAP, "oid", 0);
+
+ auto req = new MockCopyupRequest(&mock_image_ctx, "oid", 0,
+ {{0, 4096}}, {});
+ mock_image_ctx.copyup_list[0] = req;
+ req->append_request(&mock_write_request);
+ req->send();
+
+ ASSERT_EQ(0, mock_write_request.ctx.wait());
+}
+
+TEST_F(TestMockIoCopyupRequest, ZeroedCopyOnRead) {
+ REQUIRE_FEATURE(RBD_FEATURE_LAYERING);
+
+ librbd::ImageCtx *ictx;
+ ASSERT_EQ(0, open_image(m_image_name, &ictx));
+
+ MockTestImageCtx mock_parent_image_ctx(*ictx->parent);
+ MockTestImageCtx mock_image_ctx(*ictx, &mock_parent_image_ctx);
+
+ MockExclusiveLock mock_exclusive_lock;
+ MockJournal mock_journal;
+ MockObjectMap mock_object_map;
+ initialize_features(ictx, mock_image_ctx, mock_exclusive_lock, mock_journal,
+ mock_object_map);
+
+ expect_op_work_queue(mock_image_ctx);
+ expect_is_lock_owner(mock_image_ctx);
+
+ InSequence seq;
+
+ MockImageRequest mock_image_request;
+ std::string data(4096, '\0');
+ expect_read_parent(mock_parent_image_ctx, mock_image_request, {{0, 4096}},
+ data, 0);
+
+ expect_object_map_at(mock_image_ctx, 0, OBJECT_NONEXISTENT);
+ expect_object_map_update(mock_image_ctx, CEPH_NOSNAP, 0, OBJECT_EXISTS, true,
+ 0);
+
+ expect_copyup(mock_image_ctx, CEPH_NOSNAP, "oid", "", 0);
+
+ auto req = new MockCopyupRequest(&mock_image_ctx, "oid", 0,
+ {{0, 4096}}, {});
+ mock_image_ctx.copyup_list[0] = req;
+ req->send();
+ ictx->flush_async_operations();
+}
+
+TEST_F(TestMockIoCopyupRequest, NoOpCopyup) {
+ REQUIRE_FEATURE(RBD_FEATURE_LAYERING);
+
+ librbd::ImageCtx *ictx;
+ ASSERT_EQ(0, open_image(m_image_name, &ictx));
+
+ MockTestImageCtx mock_parent_image_ctx(*ictx->parent);
+ MockTestImageCtx mock_image_ctx(*ictx, &mock_parent_image_ctx);
+
+ MockExclusiveLock mock_exclusive_lock;
+ MockJournal mock_journal;
+ MockObjectMap mock_object_map;
+ initialize_features(ictx, mock_image_ctx, mock_exclusive_lock, mock_journal,
+ mock_object_map);
+
+ expect_op_work_queue(mock_image_ctx);
+ expect_is_lock_owner(mock_image_ctx);
+
+ InSequence seq;
+
+ MockImageRequest mock_image_request;
+ expect_read_parent(mock_parent_image_ctx, mock_image_request, {{0, 4096}},
+ "", -ENOENT);
+
+ MockAbstractObjectWriteRequest mock_write_request;
+ expect_is_empty_write_op(mock_write_request, true);
+
+ auto req = new MockCopyupRequest(&mock_image_ctx, "oid", 0,
+ {{0, 4096}}, {});
+ mock_image_ctx.copyup_list[0] = req;
+ req->append_request(&mock_write_request);
+ req->send();
+
+ ASSERT_EQ(0, mock_write_request.ctx.wait());
+}
+
+TEST_F(TestMockIoCopyupRequest, RestartWrite) {
+ REQUIRE_FEATURE(RBD_FEATURE_LAYERING);
+
+ librbd::ImageCtx *ictx;
+ ASSERT_EQ(0, open_image(m_image_name, &ictx));
+
+ MockTestImageCtx mock_parent_image_ctx(*ictx->parent);
+ MockTestImageCtx mock_image_ctx(*ictx, &mock_parent_image_ctx);
+
+ MockExclusiveLock mock_exclusive_lock;
+ MockJournal mock_journal;
+ MockObjectMap mock_object_map;
+ initialize_features(ictx, mock_image_ctx, mock_exclusive_lock, mock_journal,
+ mock_object_map);
+
+ expect_op_work_queue(mock_image_ctx);
+ expect_is_lock_owner(mock_image_ctx);
+
+ InSequence seq;
+
+ MockImageRequest mock_image_request;
+ std::string data(4096, '1');
+ expect_read_parent(mock_parent_image_ctx, mock_image_request, {{0, 4096}},
+ data, 0);
+
+ MockAbstractObjectWriteRequest mock_write_request1;
+ expect_get_pre_write_object_map_state(mock_image_ctx, mock_write_request1,
+ OBJECT_EXISTS);
+ expect_object_map_at(mock_image_ctx, 0, OBJECT_NONEXISTENT);
+ expect_object_map_update(mock_image_ctx, CEPH_NOSNAP, 0, OBJECT_EXISTS, true,
+ 0);
+
+ auto req = new MockCopyupRequest(&mock_image_ctx, "oid", 0,
+ {{0, 4096}}, {});
+ expect_add_copyup_ops(mock_write_request1);
+ expect_copyup(mock_image_ctx, CEPH_NOSNAP, "oid", data, 0);
+
+ MockAbstractObjectWriteRequest mock_write_request2;
+ EXPECT_CALL(get_mock_io_ctx(mock_image_ctx.data_ctx),
+ write("oid", _, 0, 0, _))
+ .WillOnce(WithoutArgs(Invoke([req, &mock_write_request2]() {
+ req->append_request(&mock_write_request2);
+ return 0;
+ })));
+
+ mock_image_ctx.copyup_list[0] = req;
+ req->append_request(&mock_write_request1);
+ req->send();
+
+ ASSERT_EQ(0, mock_write_request1.ctx.wait());
+ ASSERT_EQ(-ERESTART, mock_write_request2.ctx.wait());
+}
+
+TEST_F(TestMockIoCopyupRequest, ReadFromParentError) {
+ REQUIRE_FEATURE(RBD_FEATURE_LAYERING);
+
+ librbd::ImageCtx *ictx;
+ ASSERT_EQ(0, open_image(m_image_name, &ictx));
+
+ MockTestImageCtx mock_parent_image_ctx(*ictx->parent);
+ MockTestImageCtx mock_image_ctx(*ictx, &mock_parent_image_ctx);
+
+ MockExclusiveLock mock_exclusive_lock;
+ MockJournal mock_journal;
+ MockObjectMap mock_object_map;
+ initialize_features(ictx, mock_image_ctx, mock_exclusive_lock, mock_journal,
+ mock_object_map);
+
+ expect_op_work_queue(mock_image_ctx);
+ expect_is_lock_owner(mock_image_ctx);
+
+ InSequence seq;
+
+ MockImageRequest mock_image_request;
+ expect_read_parent(mock_parent_image_ctx, mock_image_request, {{0, 4096}},
+ "", -EPERM);
+
+ MockAbstractObjectWriteRequest mock_write_request;
+ expect_is_empty_write_op(mock_write_request, false);
+
+ auto req = new MockCopyupRequest(&mock_image_ctx, "oid", 0,
+ {{0, 4096}}, {});
+ mock_image_ctx.copyup_list[0] = req;
+ req->append_request(&mock_write_request);
+ req->send();
+
+ ASSERT_EQ(-EPERM, mock_write_request.ctx.wait());
+}
+
+TEST_F(TestMockIoCopyupRequest, DeepCopyError) {
+ REQUIRE_FEATURE(RBD_FEATURE_LAYERING);
+
+ librbd::ImageCtx *ictx;
+ ASSERT_EQ(0, open_image(m_image_name, &ictx));
+
+ MockTestImageCtx mock_parent_image_ctx(*ictx->parent);
+ MockTestImageCtx mock_image_ctx(*ictx, &mock_parent_image_ctx);
+
+ MockExclusiveLock mock_exclusive_lock;
+ MockJournal mock_journal;
+ MockObjectMap mock_object_map;
+ initialize_features(ictx, mock_image_ctx, mock_exclusive_lock, mock_journal,
+ mock_object_map);
+
+ expect_op_work_queue(mock_image_ctx);
+ expect_is_lock_owner(mock_image_ctx);
+
+ InSequence seq;
+
+ MockAbstractObjectWriteRequest mock_write_request;
+ MockObjectCopyRequest mock_object_copy_request;
+ mock_image_ctx.migration_info = {1, "", "", "image id", {}, ictx->size, true};
+ expect_is_empty_write_op(mock_write_request, false);
+ expect_object_copy(mock_image_ctx, mock_object_copy_request, true, -EPERM);
+
+ expect_is_empty_write_op(mock_write_request, false);
+
+ auto req = new MockCopyupRequest(&mock_image_ctx, "oid", 0,
+ {{0, 4096}}, {});
+ mock_image_ctx.copyup_list[0] = req;
+ req->append_request(&mock_write_request);
+ req->send();
+
+ ASSERT_EQ(-EPERM, mock_write_request.ctx.wait());
+}
+
+TEST_F(TestMockIoCopyupRequest, UpdateObjectMapError) {
+ REQUIRE_FEATURE(RBD_FEATURE_LAYERING | RBD_FEATURE_OBJECT_MAP);
+
+ librbd::ImageCtx *ictx;
+ ASSERT_EQ(0, open_image(m_image_name, &ictx));
+
+ MockTestImageCtx mock_parent_image_ctx(*ictx->parent);
+ MockTestImageCtx mock_image_ctx(*ictx, &mock_parent_image_ctx);
+
+ MockExclusiveLock mock_exclusive_lock;
+ MockJournal mock_journal;
+ MockObjectMap mock_object_map;
+ initialize_features(ictx, mock_image_ctx, mock_exclusive_lock, mock_journal,
+ mock_object_map);
+
+ expect_op_work_queue(mock_image_ctx);
+ expect_is_lock_owner(mock_image_ctx);
+
+ InSequence seq;
+
+ MockImageRequest mock_image_request;
+ std::string data(4096, '1');
+ expect_read_parent(mock_parent_image_ctx, mock_image_request, {{0, 4096}},
+ data, 0);
+
+ MockAbstractObjectWriteRequest mock_write_request;
+ expect_get_pre_write_object_map_state(mock_image_ctx, mock_write_request,
+ OBJECT_EXISTS);
+ expect_object_map_at(mock_image_ctx, 0, OBJECT_NONEXISTENT);
+ expect_object_map_update(mock_image_ctx, CEPH_NOSNAP, 0, OBJECT_EXISTS, true,
+ -EINVAL);
+
+ auto req = new MockCopyupRequest(&mock_image_ctx, "oid", 0,
+ {{0, 4096}}, {});
+ mock_image_ctx.copyup_list[0] = req;
+ req->append_request(&mock_write_request);
+ req->send();
+
+ ASSERT_EQ(-EINVAL, mock_write_request.ctx.wait());
+}
+
+TEST_F(TestMockIoCopyupRequest, CopyupError) {
+ REQUIRE_FEATURE(RBD_FEATURE_LAYERING);
+
+ librbd::ImageCtx *ictx;
+ ASSERT_EQ(0, open_image(m_image_name, &ictx));
+ ictx->snap_lock.get_write();
+ ictx->add_snap(cls::rbd::UserSnapshotNamespace(), "1", 1, ictx->size,
+ ictx->parent_md, RBD_PROTECTION_STATUS_UNPROTECTED,
+ 0, {});
+ ictx->snapc = {1, {1}};
+ ictx->snap_lock.put_write();
+
+ MockTestImageCtx mock_parent_image_ctx(*ictx->parent);
+ MockTestImageCtx mock_image_ctx(*ictx, &mock_parent_image_ctx);
+
+ MockExclusiveLock mock_exclusive_lock;
+ MockJournal mock_journal;
+ MockObjectMap mock_object_map;
+ initialize_features(ictx, mock_image_ctx, mock_exclusive_lock, mock_journal,
+ mock_object_map);
+
+ expect_test_features(mock_image_ctx);
+ expect_op_work_queue(mock_image_ctx);
+ expect_is_lock_owner(mock_image_ctx);
+
+ InSequence seq;
+
+ MockImageRequest mock_image_request;
+ std::string data(4096, '1');
+ expect_read_parent(mock_parent_image_ctx, mock_image_request, {{0, 4096}},
+ data, 0);
+
+ MockAbstractObjectWriteRequest mock_write_request;
+ expect_get_pre_write_object_map_state(mock_image_ctx, mock_write_request,
+ OBJECT_EXISTS);
+ expect_object_map_at(mock_image_ctx, 0, OBJECT_NONEXISTENT);
+ expect_object_map_update(mock_image_ctx, 1, 0, OBJECT_EXISTS, true, 0);
+ expect_object_map_update(mock_image_ctx, CEPH_NOSNAP, 0, OBJECT_EXISTS, true,
+ 0);
+
+ expect_add_copyup_ops(mock_write_request);
+ expect_copyup(mock_image_ctx, 0, "oid", data, -EPERM);
+ expect_write(mock_image_ctx, CEPH_NOSNAP, "oid", 0);
+
+ auto req = new MockCopyupRequest(&mock_image_ctx, "oid", 0,
+ {{0, 4096}}, {});
+ mock_image_ctx.copyup_list[0] = req;
+ req->append_request(&mock_write_request);
+ req->send();
+
+ ASSERT_EQ(-EPERM, mock_write_request.ctx.wait());
+ ictx->flush_async_operations();
+}
+
+} // namespace io
+} // namespace librbd