<div dir="ltr"><br><br><div class="gmail_quote"><div dir="ltr">On Fri, Oct 5, 2018 at 7:58 AM Eric Blake <<a href="mailto:eblake@redhat.com">eblake@redhat.com</a>> wrote:<br></div><blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex">On 10/4/18 12:05 AM, Eric Blake wrote:<br>
> The following (long) email describes a portion of the work-flow of how <br>
> my proposed incremental backup APIs will work, along with the backend <br>
> QMP commands that each one executes.  I will reply to this thread with <br>
> further examples (the first example is long enough to be its own email). <br>
> This is an update to a thread last posted here:<br>
> <a href="https://www.redhat.com/archives/libvir-list/2018-June/msg01066.html" rel="noreferrer" target="_blank">https://www.redhat.com/archives/libvir-list/2018-June/msg01066.html</a><br>
> <br>
<br>
> More to come in part 2.<br>
> <br>
<br>
- Second example: a sequence of incremental backups via pull model<br>
<br>
In the first example, we did not create a checkpoint at the time of the <br>
full pull. That means we have no way to track a delta of changes since <br>
that point in time. </blockquote><div><br></div><div>Why do we want to support backup without creating a checkpoint?</div><div><br></div><div>If we don't have any real use case, I suggest to always require a checkpoint.</div><div> </div><blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex">Let's repeat the full backup (reusing the same <br>
backup.xml from before), but this time, we'll add a new parameter, a <br>
second XML file for describing the checkpoint we want to create.<br>
<br>
Actually, it was easy enough to get virsh to write the XML for me <br>
(because it was very similar to existing code in virsh that creates XML <br>
for snapshot creation):<br>
<br>
$ $virsh checkpoint-create-as --print-xml $dom check1 testing \<br>
    --diskspec sdc --diskspec sdd | tee check1.xml<br>
<domaincheckpoint><br>
   <name>check1</name><br></blockquote><div><br></div><div>We should use an id, not a name, even of name is name is also unique like</div><div>in most libvirt apis.</div><div><br></div><div>In RHV we will use always use a UUID for this.</div><div> </div><blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex">
   <description>testing</description><br>
   <disks><br>
     <disk name='sdc'/><br>
     <disk name='sdd'/><br>
   </disks><br>
</domaincheckpoint><br>
<br>
I had to supply two --diskspec arguments to virsh to select just the two <br>
qcow2 disks that I am using in my example (rather than every disk in the <br>
domain, which is the default when <disks> is not present). </blockquote><div><br></div><div>So <disks /> is valid configuration, selecting all disks, or not having "disks" element</div><div>selects all disks?</div><div> </div><blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex">I also picked <br>
a name (mandatory) and description (optional) to be associated with the <br>
checkpoint.<br>
<br>
The backup.xml file that we plan to reuse still mentions scratch1.img <br>
and scratch2.img as files needed for staging the pull request. However, <br>
any contents in those files could interfere with our second backup <br>
(after all, every cluster written into that file from the first backup <br>
represents a point in time that was frozen at the first backup; but our <br>
second backup will want to read the data as the guest sees it now rather <br>
than what it was at the first backup), so we MUST regenerate the scratch <br>
files. (Perhaps I should have just deleted them at the end of example 1 <br>
in my previous email, had I remembered when typing that mail).<br>
<br>
$ $qemu_img create -f qcow2 -b $orig1 -F qcow2 scratch1.img<br>
$ $qemu_img create -f qcow2 -b $orig2 -F qcow2 scratch2.img<br>
<br>
Now, to begin the full backup and create a checkpoint at the same time. <br>
Also, this time around, it would be nice if the guest had a chance to <br>
freeze I/O to the disks prior to the point chosen as the checkpoint. <br>
Assuming the guest is trusted, and running the qemu guest agent (qga), <br>
we can do that with:<br>
<br>
$ $virsh fsfreeze $dom<br>
$ $virsh backup-begin $dom backup.xml check1.xml<br>
Backup id 1 started<br>
backup used description from 'backup.xml'<br>
checkpoint used description from 'check1.xml'<br>
$ $virsh fsthaw $dom<br></blockquote><div><br></div><div>Great, this answer my (unsent) question about freeze/thaw from part 1 :-) <br></div><blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex">
<br>
and eventually, we may decide to add a VIR_DOMAIN_BACKUP_BEGIN_QUIESCE <br>
flag to combine those three steps into a single API (matching what we've <br>
done on some other existing API).  In other words, the sequence of QMP <br>
operations performed during virDomainBackupBegin are quick enough that <br>
they won't stall a freeze operation (at least Windows is picky if you <br>
stall a freeze operation longer than 10 seconds).<br></blockquote><div><br></div><div>We use fsFreeze/fsThaw directly in RHV since we need to support external</div><div>snapshots (e.g. ceph), so we don't need this functionality, but it sounds good</div><div>idea to make it work like snapshot.</div><div> </div><blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex">
<br>
The tweaked $virsh backup-begin now results in a call to:<br>
  virDomainBackupBegin(dom, "<domainbackup ...>",<br>
    "<domaincheckpoint ...", 0)<br>
and in turn libvirt makes a similar sequence of QMP calls as before, <br>
with a slight modification in the middle:<br>
{"execute":"nbd-server-start",...<br>
{"execute":"blockdev-add",...<br></blockquote><div><br></div><div>This does not work yet for network disks like "rbd" and "glusterfs"</div><div>does it mean that they will not be supported for backup?</div><div> </div><blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex">
{"execute":"transaction",<br>
  "arguments":{"actions":[<br>
   {"type":"blockdev-backup", "data":{<br>
    "device":"$node1", "target":"backup-sdc", "sync":"none",<br>
    "job-id":"backup-sdc" }},<br>
   {"type":"blockdev-backup", "data":{<br>
    "device":"$node2", "target":"backup-sdd", "sync":"none",<br>
    "job-id":"backup-sdd" }}<br>
   {"type":"block-dirty-bitmap-add", "data":{<br>
    "node":"$node1", "name":"check1", "persistent":true}},<br>
   {"type":"block-dirty-bitmap-add", "data":{<br>
    "node":"$node2", "name":"check1", "persistent":true}}<br>
  ]}}<br>
{"execute":"nbd-server-add",...<br></blockquote><div><br></div><div><br></div><div>What if this sequence fail in the middle? will libvirt handle all failures</div><div>and rollback to the previous state?</div><div><br></div><div>What is the semantics of "execute": "transaction"? does it mean that qemu</div><div>will handle all possible failures in one of the actions?</div><div><br></div><div>(Will continue later)</div><div> </div><blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex">
<br>
The only change was adding more actions to the "transaction" command - <br>
in addition to kicking off the fleece image in the scratch nodes, it <br>
ALSO added a persistent bitmap to each of the original images, to track <br>
all changes made after the point of the transaction.  The bitmaps are <br>
persistent - at this point (well, it's better if you wait until after <br>
backup-end), you could shut the guest down and restart it, and libvirt <br>
will still remember that the checkpoint exists, and qemu will continue <br>
track guest writes via the bitmap. However, the backup job itself is <br>
currently live-only, and shutting down the guest while a backup <br>
operation is in effect will lose track of the backup job.  What that <br>
really means is that if the guest shuts down, your current backup job is <br>
hosed (you cannot ever get back the point-in-time data from your API <br>
request - as your next API request will be a new point in time) - but <br>
you have not permanently ruined the guest, and your recovery is to just <br>
start a new backup.<br>
<br>
Pulling the data out from the backup is unchanged from example 1; virsh <br>
backup-dumpxml will show details about the job (yes, the job id is still <br>
1 for now), and when ready, virsh backup-end will end the job and <br>
gracefully take down the NBD server with no difference in QMP commands <br>
from before.  Thus, the creation of a checkpoint didn't change any of <br>
the fundamentals of capturing the current backup, but rather is in <br>
preparation for the next step.<br>
<br>
$ $virsh backup-end $dom 1<br>
Backup id 1 completed<br>
$ rm scratch1.img scratch2.img<br>
<br>
[We have not yet designed how qemu bitmaps will interact with external <br>
snapshots - but I see two likely scenarios:<br>
  1. Down the road, I add a virDomainSnapshotCheckpointCreateXML() API, <br>
which adds a checkpointXML parameter but otherwise behaves like the <br>
existing virDomainSnapshotCreateXML - if that API is added in a <br>
different release than my current API proposals, that's yet another <br>
libvirt.so rebase to pickup the new API.<br>
  2. My current proposal of virDomainBackupBegin(dom, "<domainbackup>", <br>
"<domaincheckpoint>", flags) could instead be tweaked to a single XML <br>
parameter, virDomainBackupBegin(dom, "<br>
<domainbackup><br>
   <domaincheckpoint> ... </domaincheckpoint><br>
</domainbackup>", flags) prior to adding my APIs to libvirt 4.9, then <br>
down the road, we also tweak <domainsnapshot> to take an optional <br>
<domaincheckpoint> sub-element, and thus reuse the existing <br>
virDomainSnapshotCreateXML() to now also create checkpoints without a <br>
further API addition.<br>
Speak up now if you have a preference between the two ideas]<br>
<br>
Now that we have concluded the full backup and created a checkpoint, we <br>
can do more things with the checkpoint (it is persistent, after all). <br>
For example:<br>
<br>
$ $virsh checkpoint-list $dom<br>
  Name                 Creation Time<br>
--------------------------------------------<br>
  check1               2018-10-04 15:02:24 -0500<br>
<br>
called virDomainListCheckpoints(dom, &array, 0) under the hood to get a <br>
list of virDomainCheckpointPtr objects, then called <br>
virDomainCheckpointGetXMLDesc(array[0], 0) to scrape the XML describing <br>
that checkpoint in order to display information.  Or another approach, <br>
using virDomainCheckpointGetXMLDesc(virDomainCheckpointCurrent(dom, 0), 0):<br>
<br>
$ $virsh checkpoint-current $dom | head<br>
<domaincheckpoint><br>
   <name>check1</name><br>
   <description>testing</description><br>
   <creationTime>1538683344</creationTime><br>
   <disks><br>
     <disk name='vda' checkpoint='no'/><br>
     <disk name='sdc' checkpoint='bitmap' bitmap='check1'/><br>
     <disk name='sdd' checkpoint='bitmap' bitmap='check1'/><br>
   </disks><br>
   <domain type='kvm'><br>
<br>
which shows the current checkpoint (that is, the checkpoint owning the <br>
bitmap that is still receiving live updates), and which bitmap names in <br>
the qcow2 files are in use. For convenience, it also recorded the full <br>
<domain> description at the time the checkpoint was captured (I used <br>
head to limit the size of this email), so that if you later hot-plug <br>
things, you still have a record of what state the machine had at the <br>
time the checkpoint was created.<br>
<br>
The XML output of a checkpoint description is normally static, but <br>
sometimes it is useful to know an approximate size of the guest data <br>
that has been dirtied since a checkpoint was created (a dynamic value <br>
that grows as a guest dirties more clusters).  For that, it makes sense <br>
to have a flag to request the dynamic data; it's also useful to have a <br>
flag that suppresses the (length) <domain> output:<br>
<br>
$ $virsh checkpoint-current $dom --size --no-domain<br>
<domaincheckpoint><br>
   <name>check1</name><br>
   <description>testing</description><br>
   <creationTime>1538683344</creationTime><br>
   <disks><br>
     <disk name='vda' checkpoint='no'/><br>
     <disk name='sdc' checkpoint='bitmap' bitmap='check1' size='1048576'/><br>
     <disk name='sdd' checkpoint='bitmap' bitmap='check1' size='65536'/><br>
   </disks><br>
</domaincheckpoint><br>
<br>
This maps to virDomainCheckpointGetXMLDesc(chk, <br>
VIR_DOMAIN_CHECKPOINT_XML_NO_DOMAIN | VIR_DOMAIN_CHECKPOINT_XML_SIZE). <br>
Under the hood, libvirt calls<br>
{"execute":"query-block"}<br>
and converts the bitmap size reported by qemu into an estimate of the <br>
number of bytes that would be required if you were to start a backup <br>
from that checkpoint right now.  Note that the result is just an <br>
estimate of the storage taken by guest-visible data; you'll probably <br>
want to use 'qemu-img measure' to convert that into a size of how much a <br>
matching qcow2 image would require when metadata is added in; also <br>
remember that the number is constantly growing as the guest writes and <br>
causes more of the image to become dirty.  But having a feel for how <br>
much has changed can be useful for determining if continuing a chain of <br>
incremental backups still makes more sense, or if enough of the guest <br>
data has changed that doing a full backup is smarter; it is also useful <br>
for preallocating how much storage you will need for an incremental backup.<br>
<br>
Technically, libvirt mapping that a checkpoint size request to a single <br>
{"execute":"query-block"} works only when querying the size of the <br>
current bitmap. The command also works when querying the cumulative size <br>
since an older checkpoint, but under the hood, libvirt must juggle <br>
things to create a temporary bitmap, call a few <br>
x-block-dirty-bitmap-merge, query the size of that temporary bitmap, <br>
then clean things back up again (after all, size(A) + size(B) >= <br>
size(A|B), depending on how many clusters were touched during both A and <br>
B's tracking of dirty clusters).  Again, a nice benefit of having <br>
libvirt manage multiple qemu bitmaps under a single libvirt API.<br>
<br>
Of course, the real reason we created a checkpoint with our full backup <br>
is that we want to take an incremental backup next, rather than <br>
repeatedly taking full backups. For this, we need a one-line <br>
modification to our backup XML to add an <incremental> element; we also <br>
want to update our checkpoint XML to start yet another checkpoint when <br>
we run our first incremental backup.<br>
<br>
$ cat > backup.xml <<EOF<br>
<domainbackup mode='pull'><br>
   <server transport='tcp' name='localhost' port='10809'/><br>
   <incremental>check1</incremental><br>
   <disks><br>
     <disk name='$orig1' type='file'><br>
       <scratch file='$PWD/scratch1.img'/><br>
     </disk><br>
     <disk name='sdd' type='file'><br>
       <scratch file='$PWD/scratch2.img'/><br>
     </disk><br>
   </disks><br>
</domainbackup><br>
EOF<br>
$ $virsh checkpoint-create-as --print-xml $dom check2 \<br>
    --diskspec sdc --diskspec sdd | tee check2.xml<br>
<domaincheckpoint><br>
   <name>check2</name><br>
   <disks><br>
     <disk name='sdc'/><br>
     <disk name='sdd'/><br>
   </disks><br>
</domaincheckpoint><br>
$ $qemu_img create -f qcow2 -b $orig1 -F qcow2 scratch1.img<br>
$ $qemu_img create -f qcow2 -b $orig2 -F qcow2 scratch2.img<br>
<br>
And again, it's time to kick off the backup job:<br>
<br>
$ $virsh backup-begin $dom backup.xml check2.xml<br>
Backup id 1 started<br>
backup used description from 'backup.xml'<br>
checkpoint used description from 'check2.xml'<br>
<br>
This time, the incremental backup causes libvirt to do a bit more work <br>
under the hood:<br>
<br>
{"execute":"nbd-server-start",<br>
  "arguments":{"addr":{"type":"inet",<br>
   "data":{"host":"localhost", "port":"10809"}}}}<br>
{"execute":"blockdev-add",<br>
  "arguments":{"driver":"qcow2", "node-name":"backup-sdc",<br>
   "file":{"driver":"file",<br>
    "filename":"$PWD/scratch1.img"},<br>
    "backing":"'$node1'"}}<br>
{"execute":"blockdev-add",<br>
  "arguments":{"driver":"qcow2", "node-name":"backup-sdd",<br>
   "file":{"driver":"file",<br>
    "filename":"$PWD/scratch2.img"},<br>
    "backing":"'$node2'"}}<br>
{"execute":"block-dirty-bitmap-add",<br>
  "arguments":{"node":"$node1", "name":"backup-sdc"}}<br>
{"execute":"x-block-dirty-bitmap-merge",<br>
  "arguments":{"node":"$node1", "src_name":"check1",<br>
  "dst_name":"backup-sdc"}}'<br>
{"execute":"block-dirty-bitmap-add",<br>
  "arguments":{"node":"$node2", "name":"backup-sdd"}}<br>
{"execute":"x-block-dirty-bitmap-merge",<br>
  "arguments":{"node":"$node2", "src_name":"check1",<br>
  "dst_name":"backup-sdd"}}'<br>
{"execute":"transaction",<br>
  "arguments":{"actions":[<br>
   {"type":"blockdev-backup", "data":{<br>
    "device":"$node1", "target":"backup-sdc", "sync":"none",<br>
    "job-id":"backup-sdc" }},<br>
   {"type":"blockdev-backup", "data":{<br>
    "device":"$node2", "target":"backup-sdd", "sync":"none",<br>
    "job-id":"backup-sdd" }},<br>
   {"type":"x-block-dirty-bitmap-disable", "data":{<br>
    "node":"$node1", "name":"backup-sdc"}},<br>
   {"type":"x-block-dirty-bitmap-disable", "data":{<br>
    "node":"$node2", "name":"backup-sdd"}},<br>
   {"type":"x-block-dirty-bitmap-disable", "data":{<br>
    "node":"$node1", "name":"check1"}},<br>
   {"type":"x-block-dirty-bitmap-disable", "data":{<br>
    "node":"$node2", "name":"check1"}},<br>
   {"type":"block-dirty-bitmap-add", "data":{<br>
    "node":"$node1", "name":"check2", "persistent":true}},<br>
   {"type":"block-dirty-bitmap-add", "data":{<br>
    "node":"$node2", "name":"check2", "persistent":true}}<br>
  ]}}<br>
{"execute":"nbd-server-add",<br>
  "arguments":{"device":"backup-sdc", "name":"sdc"}}<br>
{"execute":"nbd-server-add",<br>
  "arguments":{"device":"backup-sdd", "name":"sdd"}}<br>
{"execute":"x-nbd-server-add-bitmap",<br>
  "arguments":{"name":"sdc", "bitmap":"backup-sdc"}}<br>
{"execute":"x-nbd-server-add-bitmap",<br>
  "arguments":{"name":"sdd", "bitmap":"backup-sdd"}}<br>
<br>
Two things stand out here, different from the earlier full backup. First <br>
is that libvirt is now creating a temporary non-persistent bitmap, <br>
merging all data fom check1 into the temporary, then freezing writes <br>
into the temporary bitmap during the transaction, and telling NBD to <br>
expose the bitmap to clients. The second is that since we want this <br>
backup to start a new checkpoint, we disable the old bitmap and create a <br>
new one. The two additions are independent - it is possible to create an <br>
incremental backup [<incremental> in backup XML]) without triggering a <br>
new checkpoint [presence of non-null checkpoint XML].  In fact, taking <br>
an incremental backup without creating a checkpoint is effectively doing <br>
differential backups, where multiple backups started at different times <br>
each contain all cumulative changes since the same original point in <br>
time, such that later backups are larger than earlier backups, but you <br>
no longer have to chain those backups to one another to reconstruct the <br>
state in any one of the backups).<br>
<br>
Now that the pull-model backup job is running, we want to scrape the <br>
data off the NBD server.  Merely reading nbd://localhost:10809/sdc will <br>
read the full contents of the disk - but that defeats the purpose of <br>
using the checkpoint in the first place to reduce the amount of data to <br>
be backed up. So, let's modify our image-scraping loop from the first <br>
example, to now have one client utilizing the x-dirty-bitmap command <br>
line extension to drive other clients.  Note: that extension is marked <br>
experimental in part because it has screwy semantics: if you use it, you <br>
can't reliably read any data from the NBD server, but instead can <br>
interpret 'qemu-img map' output by treating any "data":false lines as <br>
dirty, and "data":true entries as unchanged.<br>
<br>
$ image_opts=driver=nbd,export=sdc,server.type=inet,<br>
$ image_opts+=server.host=localhost,server.port=10809,<br>
$ image_opts+=x-dirty-bitmap=qemu:dirty-bitmap:backup-sdc<br>
$ $qemu_img create -f qcow2 inc12.img $size_of_orig1<br>
$ $qemu_img rebase -u -f qcow2 -F raw -b nbd://localhost:10809/sdc \<br>
   inc12.img<br>
$ while read line; do<br>
   [[ $line =~ .*start.:.([0-9]*).*length.:.([0-9]*).*data.:.false.* ]] ||<br>
     continue<br>
   start=${BASH_REMATCH[1]} len=${BASH_REMATCH[2]}<br>
   qemu-io -C -c "r $start $len" -f qcow2 inc12.img<br>
done < <($qemu_img map --output=json --image-opts <br>
$image_optsdriver=nbd,export=sdc,server.type=inet,server.host=localhost,server.port=10809,x-dirty-bitmap=qemu:dirty-bitmap:backup-sdc)<br>
$ $qemu_img rebase -u -f qcow2 -b '' inc12.img<br>
<br>
As captured, inc12.img is an incomplete qcow2 file (it only includes <br>
clusters touched by the guest since the last incremental or full <br>
backup); but since we output into a qcow2 file, we can easily repair the <br>
damage:<br>
<br>
$ $qemu_img rebase -u -f qcow2 -F qcow2 -b full1.img inc12.img<br>
<br>
creating the qcow2 chain 'full1.img <- inc12.img' that contains <br>
identical guest-visible contents as would be present in a full backup <br>
done at the same moment.<br>
<br>
Of course, with the backups now captured, we clean up:<br>
<br>
$ $virsh backup-end $dom 1<br>
Backup id 1 completed<br>
$ rm scratch1.img scratch2.img<br>
<br>
and this time, virDomainBackupEnd() had to do one additional bit of work <br>
to delete the temporary bitmaps:<br>
<br>
{"execute":"nbd-server-remove",<br>
  "arguments":{"name":"sdc"}}<br>
{"execute":"nbd-server-remove",<br>
  "arguments":{"name":"sdd"}}<br>
{"execute":"nbd-server-stop"}<br>
{"execute":"block-job-cancel",<br>
  "arguments":{"device":"backup-sdc"}}<br>
{"execute":"block-job-cancel",<br>
  "arguments":{"device":"backup-sdd"}}<br>
{"execute":"blockdev-del",<br>
  "arguments":{"node-name":"backup-sdc"}}<br>
{"execute":"blockdev-del",<br>
  "arguments":{"node-name":"backup-sdd"}}<br>
{"execute":"block-dirty-bitmap-remove",<br>
  "arguments":{"node":"$node1", "name":"backup-sdc"}}<br>
{"execute":"block-dirty-bitmap-remove",<br>
  "arguments":{"node":"$node2", "name":"backup-sdd"}}<br>
<br>
At this point, it should be fairly obvious that you can create more <br>
incremental backups, by repeatedly updating the <incremental> line in <br>
backup.xml, and adjusting the checkpoint XML to move on to a successive <br>
name.  And while incremental backups are the most common (using the <br>
current active checkpoint as the <incremental> when starting the next), <br>
the scheme is also set up to permit differential backups from any <br>
existing checkpoint to the current point in time (since libvirt is <br>
already creating a temporary bitmap as its basis for the <br>
x-nbd-server-add-bitmap, all it has to do is just add an appropriate <br>
number of x-block-dirty-bitmap-merge calls to collect all bitmaps in the <br>
chain from the requested checkpoint to the current checkpoint).<br>
<br>
More to come in part 3.<br>
<br>
-- <br>
Eric Blake, Principal Software Engineer<br>
Red Hat, Inc.           +1-919-301-3266<br>
Virtualization:  <a href="http://qemu.org" rel="noreferrer" target="_blank">qemu.org</a> | <a href="http://libvirt.org" rel="noreferrer" target="_blank">libvirt.org</a><br>
</blockquote></div></div>