Notes on blockdev-backup and blockdev-mirror

This document discusses the two block primitives blockdev-backup and blockdev-mirror.

Table of Contents

Introduction to blockdev-backup

The blockdev-backup command allows you to create a point-in-time copy of a disk image chain. Where the point-in-time is when you start the blockdev-backup command.

It is equivalent in functionality to drive-backup, the difference being, blockdev-backup operates at node-level in a Block Driver State (BDS) graph.

As an example, the sequence of actions to create a point-in-time backup of an entire disk image chain, to a target, using blockdev-backup would be:

  1. Create the QCOW2 overlays, to arrive at a backing chain of desired depth
  2. Create the target image (using qemu-img), say, backup.qcow2
  3. Attach the above created backup.qcow2 file, run-time, using blockdev-add to QEMU
  4. Perform blockdev-backup (use "sync": "full" to copy the entire chain to the target). And observe for the event BLOCK_JOB_COMPLETED
  5. Shutdown the guest, by issuing the QMP quit command, so that caches are flushed
  6. Then, finally, compare the contents of the disk image chain, and the target copy with qemu-img compare. You would notice: "Images are identical"

QMP invocation for blockdev-backup

Given, a disk image chain of depth 1, where image [B] is the active overlay (live QEMU is writing to it):

[A] <-- [B]

The following is the procedure to copy the content from the entire chain to a target image (say, [E]), which has the full content from [A] and [B].

Create the overlay, [B]:

(QEMU) blockdev-snapshot-sync node-name=node-A snapshot-file=b.qcow2 snapshot-node-name=node-B format=qcow2
{
    "execute": "blockdev-snapshot-sync",
    "arguments": {
        "node-name": "node-A",
        "snapshot-file": "b.qcow2",
        "format": "qcow2",
        "snapshot-node-name": "node-B"
    }
}

Create a target image, that will contain the copy:

$ qemu-img create -f qcow2 e.qcow2 39M

Then, add it to QEMU via blockdev-add:

(QEMU) blockdev-add driver=qcow2 node-name=node-E file={"driver":"file","filename":"e.qcow2"}
{
    "execute": "blockdev-add",
    "arguments": {
        "node-name": "node-E",
        "driver": "qcow2",
        "file": {
            "driver": "file",
            "filename": "e.qcow2"
        }
    }
}

Then, invoke blockdev-backup, to copy the contents from the entire image chain, consisting of images [A], and [B], to the target image 'e.qcow2':

(QEMU) blockdev-backup device=node-B target=node-E sync=full job-id=job0
{
    "execute": "blockdev-backup",
    "arguments": {
        "device": "node-B",
        "job-id": "job0",
        "target": "node-E",
        "sync": "full"
    }
}

Once the above 'backup' operation has completed, an event, BLOCK_JOB_COMPLETED will be emitted, signalling successful completion.

Next, query for any active block device jobs (there should be none):

(QEMU) query-block-jobs
{
    "execute": "query-block-jobs",
    "arguments": {}
}

Shutdown the guest (NB: the following step is really important; if not done, an error, "Failed to get shared "write" lock on e.qcow2", will be thrown when you do qemu-img compare):

(QEMU) quit
{
        "execute": "quit",
            "arguments": {}
}
        "return": {}
}
(QEMU)
{u'timestamp': {u'seconds': 1496072942, u'microseconds': 685292},
u'event': u'SHUTDOWN'}

The end result will be, the image 'e.qcow2' containing a point-in-time backup of the disk image chain -- i.e. contents from images [A], and [B] at the time the blockdev-backup command was initiated.

One way to confirm the backup disk image contains the identical content with the disk image chain is to compare the backup, and the contents of the chain, you should see "Images are identical". (NB: this is assuming QEMU was launched with -S option, which will not start the CPUs at guest boot up):

$ qemu-img compare b.qcow2 e.qcow2
Warning: Image size mismatch!
Images are identical.

NOTE: The "Warning: Image size mismatch!" is expected, as we created the target image (e.qcow2) with 39M size.

Introduction to blockdev-mirror

The blockdev-mirror command allows you to synchronize a running disk image chain (all or part of it) to a target image.

Once a 'mirror' job has started, there are two possible actions when a drive-mirror job is active:

  1. Issuing the command block-job-cancel: will -- after completing synchronization of the content from the disk image chain to the target image, [E] -- create a point-in-time (which is at the time of triggering the cancel command) copy, contained in image [E], of the backing file.
  2. Issuing the command block-job-complete: will, after completing synchronization of the content, adjust the guest device (i.e. live QEMU) to point to the target image, and, causing all the new writes from this point on to happen there. One use case for this is live storage migration.

As an example, the sequence of actions to create a point-in-time backup of an entire disk image chain, to a target, using blockdev-mirror would be:

  1. Create the QCOW2 overlays, to arrive at a backing chain of desired depth
  2. Create the target image (using qemu-img), say, backup.qcow2
  3. Attach the above created backup.qcow2 file, run-time, using blockdev-add to QEMU
  4. Perform blockdev-mirror (use "sync": "full" to copy the entire chain to the target). And observe for the event BLOCK_JOB_READY
  5. Optionally, query for active block jobs, there should be a 'mirror' job ready to be completed
  6. Gracefully complete the 'mirror' block device job, and observe for the event BLOCK_JOB_COMPLETED
  7. Shutdown the guest, by issuing the QMP quit command, so that caches are flushed
  8. Then, finally, compare the contents of the disk image chain, and the target copy with qemu-img compare. You should notice: "Images are identical"

QMP invocation for blockdev-mirror

Given the disk image chain:

[A] <-- [B]

To copy the contents of the entire disk image chain, from [A] all the way to [D], to a new target, call it [E]. The following is the flow.

Create the overlay, [B]:

(QEMU) blockdev-snapshot-sync node-name=node-A snapshot-file=b.qcow2 snapshot-node-name=node-B format=qcow2
{
    "execute": "blockdev-snapshot-sync",
    "arguments": {
        "node-name": "node-A",
        "snapshot-file": "b.qcow2",
        "format": "qcow2",
        "snapshot-node-name": "node-B"
    }
}

Create the target image, [E]:

$ qemu-img create -f qcow2 e.qcow2 39M

Add the above created target image to QEMU, via blockdev-add:

(QEMU) blockdev-add driver=qcow2 node-name=node-E file={"driver":"file","filename":"e.qcow2"}
{
    "execute": "blockdev-add",
    "arguments": {
        "node-name": "node-E",
        "driver": "qcow2",
        "file": {
            "driver": "file",
            "filename": "e.qcow2"
        }
    }
}

Perform the blockdev-mirror, and observe for the event BLOCK_JOB_READY:

(QEMU) blockdev-mirror device=node-B target=node-E sync=full job-id=job0
{
    "execute": "blockdev-mirror",
    "arguments": {
        "device": "node-D",
        "job-id": "job0",
        "target": "node-E",
        "sync": "full"
    }
}

Query for active block jobs, there should be a 'mirror' job ready:

(QEMU) query-block-jobs
{
    "execute": "query-block-jobs",
    "arguments": {}
}
{
    "return": [
        {
            "busy": false,
            "type": "mirror",
            "len": 21561344,
            "paused": false,
            "ready": true,
            "io-status": "ok",
            "offset": 21561344,
            "device": "job0",
            "speed": 0
        }
    ]
}

Gracefully complete the block device job operation, and observe for the event BLOCK_JOB_COMPLETED:

(QEMU) block-job-complete device=job0
{
    "execute": "block-job-complete",
    "arguments": {
        "device": "job0"
    }
}
{
    "return": {}
}

Shutdown the guest, by issuing the quit QMP command:

(QEMU) quit
{
    "execute": "quit",
    "arguments": {}
}