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
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}
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}
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
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();
}
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;
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);
<< "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;
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() {
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;
[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();
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) {
}
private:
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->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();
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;
// 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();
}