mirror of
https://github.com/ceph/ceph
synced 2025-01-19 17:41:39 +00:00
librbd/migration: add support for reading from QCOW2 snapshots
Tweak the IO read path to now utilize the L1 table associated with the specified snapshot id. This will cause the IO to properly read from the specific snapshot. Signed-off-by: Jason Dillaman <dillaman@redhat.com>
This commit is contained in:
parent
1594b53d75
commit
1f4b5aeb0e
@ -195,7 +195,27 @@ test_import_qcow2_format() {
|
||||
local base_image=$1
|
||||
local dest_image=$2
|
||||
|
||||
qemu-img convert -f raw -O qcow2 rbd:rbd/${base_image} ${TEMPDIR}/${base_image}.qcow2
|
||||
# create new image via qemu-img and its bench tool since we cannot
|
||||
# import snapshot deltas into QCOW2
|
||||
qemu-img create -f qcow2 ${TEMPDIR}/${base_image}.qcow2 1G
|
||||
|
||||
qemu-img bench -f qcow2 -w -c 65536 -d 16 --pattern 65 -s 4096 \
|
||||
-S $((($RANDOM % 262144) * 4096)) ${TEMPDIR}/${base_image}.qcow2
|
||||
qemu-img convert -f qcow2 -O raw ${TEMPDIR}/${base_image}.qcow2 \
|
||||
"${TEMPDIR}/${base_image}@snap1"
|
||||
qemu-img snapshot -c "snap1" ${TEMPDIR}/${base_image}.qcow2
|
||||
|
||||
qemu-img bench -f qcow2 -w -c 16384 -d 16 --pattern 66 -s 4096 \
|
||||
-S $((($RANDOM % 262144) * 4096)) ${TEMPDIR}/${base_image}.qcow2
|
||||
qemu-img convert -f qcow2 -O raw ${TEMPDIR}/${base_image}.qcow2 \
|
||||
"${TEMPDIR}/${base_image}@snap2"
|
||||
qemu-img snapshot -c "snap2" ${TEMPDIR}/${base_image}.qcow2
|
||||
|
||||
qemu-img bench -f qcow2 -w -c 32768 -d 16 --pattern 67 -s 4096 \
|
||||
-S $((($RANDOM % 262144) * 4096)) ${TEMPDIR}/${base_image}.qcow2
|
||||
qemu-img convert -f qcow2 -O raw ${TEMPDIR}/${base_image}.qcow2 \
|
||||
${TEMPDIR}/${base_image}
|
||||
|
||||
qemu-img info -f qcow2 ${TEMPDIR}/${base_image}.qcow2
|
||||
|
||||
cat > ${TEMPDIR}/spec.json <<EOF
|
||||
@ -212,6 +232,8 @@ EOF
|
||||
rbd migration prepare --import-only \
|
||||
--source-spec-path ${TEMPDIR}/spec.json ${dest_image}
|
||||
|
||||
compare_images "${base_image}@snap1" "${dest_image}@snap1"
|
||||
compare_images "${base_image}@snap2" "${dest_image}@snap2"
|
||||
compare_images "${base_image}" "${dest_image}"
|
||||
|
||||
rbd migration abort ${dest_image}
|
||||
@ -219,6 +241,8 @@ EOF
|
||||
rbd migration prepare --import-only \
|
||||
--source-spec-path ${TEMPDIR}/spec.json ${dest_image}
|
||||
|
||||
compare_images "${base_image}@snap1" "${dest_image}@snap1"
|
||||
compare_images "${base_image}@snap2" "${dest_image}@snap2"
|
||||
compare_images "${base_image}" "${dest_image}"
|
||||
|
||||
rbd migration execute ${dest_image}
|
||||
@ -320,7 +344,7 @@ export_base_image ${IMAGE1}
|
||||
|
||||
test_import_native_format ${IMAGE1} ${IMAGE2}
|
||||
test_import_qcow_format ${IMAGE1} ${IMAGE2}
|
||||
test_import_qcow2_format ${IMAGE1} ${IMAGE2}
|
||||
test_import_qcow2_format ${IMAGE2} ${IMAGE3}
|
||||
test_import_raw_format ${IMAGE1} ${IMAGE2}
|
||||
|
||||
echo OK
|
||||
|
@ -241,15 +241,17 @@ public:
|
||||
l2_cache_entries(QCOW_L2_CACHE_SIZE) {
|
||||
}
|
||||
|
||||
void get_cluster_offset(uint64_t image_offset, uint64_t* cluster_offset,
|
||||
void get_cluster_offset(uint32_t l1_size, const uint64_t* l1_table,
|
||||
uint64_t image_offset, uint64_t* cluster_offset,
|
||||
Context* on_finish) {
|
||||
auto cct = qcow_format->m_image_ctx->cct;
|
||||
ldout(cct, 20) << "image_offset=" << image_offset << dendl;
|
||||
|
||||
// cache state machine runs in a single strand thread
|
||||
Request request{l1_size, l1_table, image_offset, cluster_offset, on_finish};
|
||||
boost::asio::dispatch(
|
||||
m_strand, [this, image_offset, cluster_offset, on_finish]() {
|
||||
requests.emplace_back(image_offset, cluster_offset, on_finish);
|
||||
m_strand, [this, request=std::move(request)]() {
|
||||
requests.push_back(std::move(request));
|
||||
});
|
||||
dispatch_get_cluster_offset();
|
||||
}
|
||||
@ -262,13 +264,17 @@ private:
|
||||
boost::asio::io_context::strand m_strand;
|
||||
|
||||
struct Request {
|
||||
uint32_t l1_size;
|
||||
const uint64_t* l1_table;
|
||||
|
||||
uint64_t image_offset;
|
||||
uint64_t* cluster_offset;
|
||||
Context* on_finish;
|
||||
|
||||
Request(uint64_t image_offset, uint64_t* cluster_offset, Context* on_finish)
|
||||
: image_offset(image_offset), cluster_offset(cluster_offset),
|
||||
on_finish(on_finish) {
|
||||
Request(uint32_t l1_size, const uint64_t* l1_table, uint64_t image_offset,
|
||||
uint64_t* cluster_offset, Context* on_finish)
|
||||
: l1_size(l1_size), l1_table(l1_table), image_offset(image_offset),
|
||||
cluster_offset(cluster_offset), on_finish(on_finish) {
|
||||
}
|
||||
};
|
||||
typedef std::deque<Request> Requests;
|
||||
@ -300,7 +306,8 @@ private:
|
||||
auto request = requests.front();
|
||||
auto l1_index = request.image_offset >>
|
||||
(l2_bits + qcow_format->m_cluster_bits);
|
||||
auto l2_offset = qcow_format->m_l1_table[l1_index] &
|
||||
auto l2_offset = request.l1_table[
|
||||
std::min<uint32_t>(l1_index, request.l1_size - 1)] &
|
||||
qcow_format->m_cluster_mask;
|
||||
auto l2_index = (request.image_offset >> qcow_format->m_cluster_bits) &
|
||||
(l2_size - 1);
|
||||
@ -310,7 +317,10 @@ private:
|
||||
<< "l2_index=" << l2_index << dendl;
|
||||
|
||||
int r = 0;
|
||||
if (l2_offset == 0) {
|
||||
if (l1_index >= request.l1_size) {
|
||||
lderr(cct) << "L1 index " << l1_index << " out-of-bounds" << dendl;
|
||||
r = -ERANGE;
|
||||
} else if (l2_offset == 0) {
|
||||
// L2 table has not been allocated for specified offset
|
||||
ldout(cct, 20) << "image_offset=" << request.image_offset << ", "
|
||||
<< "cluster_offset=DNE" << dendl;
|
||||
@ -459,9 +469,10 @@ template <typename I>
|
||||
class QCOWFormat<I>::ReadRequest {
|
||||
public:
|
||||
ReadRequest(QCOWFormat* qcow_format, io::AioCompletion* aio_comp,
|
||||
uint32_t l1_size, const uint64_t* l1_table,
|
||||
io::Extents&& image_extents)
|
||||
: qcow_format(qcow_format), aio_comp(aio_comp),
|
||||
image_extents(std::move(image_extents)) {
|
||||
: qcow_format(qcow_format), aio_comp(aio_comp), l1_size(l1_size),
|
||||
l1_table(l1_table), image_extents(std::move(image_extents)) {
|
||||
}
|
||||
|
||||
void send() {
|
||||
@ -472,7 +483,10 @@ private:
|
||||
QCOWFormat* qcow_format;
|
||||
io::AioCompletion* aio_comp;
|
||||
|
||||
uint32_t l1_size;
|
||||
const uint64_t* l1_table;
|
||||
io::Extents image_extents;
|
||||
|
||||
size_t image_extents_idx = 0;
|
||||
uint32_t image_extent_offset = 0;
|
||||
|
||||
@ -493,7 +507,8 @@ private:
|
||||
[this, &cluster_extent, on_finish=gather_ctx->new_sub()](int r) {
|
||||
handle_get_cluster_offset(r, cluster_extent, on_finish); });
|
||||
qcow_format->m_l2_table_cache->get_cluster_offset(
|
||||
cluster_extent.image_offset, &cluster_extent.cluster_offset, sub_ctx);
|
||||
l1_size, l1_table, cluster_extent.image_offset,
|
||||
&cluster_extent.cluster_offset, sub_ctx);
|
||||
}
|
||||
|
||||
gather_ctx->activate();
|
||||
@ -592,9 +607,11 @@ private:
|
||||
template <typename I>
|
||||
class QCOWFormat<I>::ListSnapsRequest {
|
||||
public:
|
||||
ListSnapsRequest(QCOWFormat* qcow_format, io::Extents&& image_extents,
|
||||
ListSnapsRequest(QCOWFormat* qcow_format, uint32_t l1_size,
|
||||
const uint64_t* l1_table, io::Extents&& image_extents,
|
||||
io::SparseExtents* sparse_extents, Context* on_finish)
|
||||
: qcow_format(qcow_format), image_extents(std::move(image_extents)),
|
||||
: qcow_format(qcow_format), l1_size(l1_size),
|
||||
l1_table(l1_table), image_extents(std::move(image_extents)),
|
||||
sparse_extents(sparse_extents), on_finish(on_finish) {
|
||||
}
|
||||
|
||||
@ -604,6 +621,8 @@ public:
|
||||
|
||||
private:
|
||||
QCOWFormat* qcow_format;
|
||||
uint32_t l1_size;
|
||||
const uint64_t* l1_table;
|
||||
io::Extents image_extents;
|
||||
io::SparseExtents* sparse_extents;
|
||||
Context* on_finish;
|
||||
@ -630,7 +649,8 @@ private:
|
||||
});
|
||||
});
|
||||
qcow_format->m_l2_table_cache->get_cluster_offset(
|
||||
cluster_extent.image_offset, &cluster_extent.cluster_offset, ctx);
|
||||
l1_size, l1_table, cluster_extent.image_offset,
|
||||
&cluster_extent.cluster_offset, ctx);
|
||||
}
|
||||
|
||||
gather_ctx->activate();
|
||||
@ -1256,17 +1276,28 @@ bool QCOWFormat<I>::read(
|
||||
ldout(cct, 20) << "snap_id=" << snap_id << ", "
|
||||
<< "image_extents=" << image_extents << dendl;
|
||||
|
||||
if (snap_id != CEPH_NOSNAP) {
|
||||
// TODO add QCOW2 snapshot support
|
||||
lderr(cct) << "snapshots are not supported" << dendl;
|
||||
aio_comp->fail(-EINVAL);
|
||||
return true;
|
||||
uint32_t l1_size;
|
||||
uint64_t* l1_table;
|
||||
if (snap_id == CEPH_NOSNAP) {
|
||||
l1_size = m_l1_size;
|
||||
l1_table = m_l1_table;
|
||||
} else {
|
||||
auto snapshot_it = m_snapshots.find(snap_id);
|
||||
if (snapshot_it == m_snapshots.end()) {
|
||||
aio_comp->fail(-ENOENT);
|
||||
return true;
|
||||
}
|
||||
|
||||
auto& snapshot = snapshot_it->second;
|
||||
l1_size = snapshot.l1_size;
|
||||
l1_table = snapshot.l1_table;
|
||||
}
|
||||
|
||||
aio_comp->read_result = std::move(read_result);
|
||||
aio_comp->read_result.set_image_extents(image_extents);
|
||||
|
||||
auto read_request = new ReadRequest(this, aio_comp, std::move(image_extents));
|
||||
auto read_request = new ReadRequest(this, aio_comp, l1_size, l1_table,
|
||||
std::move(image_extents));
|
||||
read_request->send();
|
||||
|
||||
return true;
|
||||
@ -1286,7 +1317,8 @@ void QCOWFormat<I>::list_snaps(io::Extents&& image_extents,
|
||||
// QCOW does support snapshots so just use cluster existence for delta
|
||||
auto snapshot = &(*snapshot_delta)[{CEPH_NOSNAP, CEPH_NOSNAP}];
|
||||
auto list_snaps_request = new ListSnapsRequest(
|
||||
this, io::Extents{image_extents}, snapshot, on_finish);
|
||||
this, m_l1_size, m_l1_table, io::Extents{image_extents}, snapshot,
|
||||
on_finish);
|
||||
list_snaps_request->send();
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user