[libvirt] [PATCHv5 04/23] blockjob: add new API flags

Eric Blake eblake at redhat.com
Tue Apr 17 05:05:55 UTC 2012


This patch introduces a new block job, useful for live storage
migration using pre-copy streaming.  Justification for including
this under virDomainBlockRebase rather than adding a new command
includes: 1) there are now two possible block jobs in qemu, with
virDomainBlockRebase starting either type of command, and
virDomainBlockJobInfo and virDomainBlockJobAbort working to end
either type; 2) reusing this command allows distros to backport
this feature to the libvirt 0.9.10 API without a .so bump.

Note that a future patch may add a more powerful interface named
virDomainBlockJobCopy, dedicated to just the block copy job, in
order to expose even more options (such as setting an arbitrary
format type for the destination without having to probe it from a
pre-existing destination file); adding a new command for targetting
just block copy would be similar to how we already have
virDomainBlockPull for targetting just the block pull job.

Using a live VM with the backing chain:
  base <- snap1 <- snap2
as the starting point, we have:

- virDomainBlockRebase(dom, disk, "/path/to/copy", 0,
    VIR_DOMAIN_BLOCK_REBASE_COPY)
creates /path/to/copy with the same format as snap2, with no backing
file, so entire chain is copied and flattened

- virDomainBlockRebase(dom, disk, "/path/to/copy", 0,
    VIR_DOMAIN_BLOCK_REBASE_COPY|VIR_DOMAIN_BLOCK_REBASE_COPY_RAW)
creates /path/to/copy as a raw file, so entire chain is copied and
flattened

- virDomainBlockRebase(dom, disk, "/path/to/copy", 0,
    VIR_DOMAIN_BLOCK_REBASE_COPY|VIR_DOMAIN_BLOCK_REBASE_SHALLOW)
creates /path/to/copy with the same format as snap2, but with snap1 as
a backing file, so only snap2 is copied.

- virDomainBlockRebase(dom, disk, "/path/to/copy", 0,
    VIR_DOMAIN_BLOCK_REBASE_COPY|VIR_DOMAIN_BLOCK_REBASE_REUSE_EXT)
reuse existing /path/to/copy (must have empty contents, and format is
probed[*] from the metadata), and copy the full chain

- virDomainBlockRebase(dom, disk, "/path/to/copy", 0,
    VIR_DOMAIN_BLOCK_REBASE_COPY|VIR_DOMAIN_BLOCK_REBASE_REUSE_EXT|
    VIR_DOMAIN_BLOCK_REBASE_SHALLOW)
reuse existing /path/to/copy (contents must be identical to snap1,
and format is probed[*] from the metadata), and copy only the contents
of snap2

- virDomainBlockRebase(dom, disk, "/path/to/copy", 0,
    VIR_DOMAIN_BLOCK_REBASE_COPY|VIR_DOMAIN_BLOCK_REBASE_REUSE_EXT|
    VIR_DOMAIN_BLOCK_REBASE_SHALLOW|VIR_DOMAIN_BLOCK_REBASE_COPY_RAW)
reuse existing /path/to/copy (must be raw volume with contents
identical to snap1), and copy only the contents of snap2

Less useful combinations:

- virDomainBlockRebase(dom, disk, "/path/to/copy", 0,
    VIR_DOMAIN_BLOCK_REBASE_COPY|VIR_DOMAIN_BLOCK_REBASE_SHALLOW|
    VIR_DOMAIN_BLOCK_REBASE_COPY_RAW)
fail if source is not raw, otherwise create /path/to/copy as raw and
the single file is copied (no chain involved)

- virDomainBlockRebase(dom, disk, "/path/to/copy", 0,
    VIR_DOMAIN_BLOCK_REBASE_COPY|VIR_DOMAIN_BLOCK_REBASE_REUSE_EXT|
    VIR_DOMAIN_BLOCK_REBASE_COPY_RAW)
makes little sense: the destination must be raw but have no contents,
meaning that it is an empty file, so there is nothing to reuse

The other three flags are rejected without VIR_DOMAIN_BLOCK_COPY.

[*] Note that probing an existing file for its format can be a security
risk _if_ there is a possibility that the existing file is 'raw', in
which case the guest can manipulate the file to appear like some other
format.  But, by virtue of the VIR_DOMAIN_BLOCK_REBASE_COPY_RAW flag,
it is possible to avoid probing of raw files, at which point, probing
of any remaining file type is no longer a security risk.

It would be nice if we could issue an event when pivoting from phase 1
to phase 2, but qemu hasn't implemented that, and we would have to poll
in order to synthesize it ourselves.  Meanwhile, qemu will give us a
distinct job info and completion event when we either cancel or pivot
to end the job.  Pivoting is accomplished via the new:

virDomainBlockJobAbort(dom, disk, VIR_DOMAIN_BLOCK_JOB_ABORT_PIVOT)

Management applications can pre-create the copy with a relative
backing file name, and use the VIR_DOMAIN_BLOCK_REBASE_REUSE_EXT
flag to have qemu reuse the metadata; if the management application
also copies the backing files to a new location, this can be used
to perform live storage migration of an entire backing chain.

* include/libvirt/libvirt.h.in (VIR_DOMAIN_BLOCK_JOB_TYPE_COPY):
New block job type.
(virDomainBlockJobAbortFlags, virDomainBlockRebaseFlags): New enums.
* src/libvirt.c (virDomainBlockRebase): Document the new flags,
and implement general restrictions on flag combinations.
(virDomainBlockJobAbort): Document the new flag.
(virDomainSaveFlags, virDomainSnapshotCreateXML)
(virDomainRevertToSnapshot, virDomainDetachDeviceFlags): Document
restrictions.
* include/libvirt/virterror.h (VIR_ERR_BLOCK_COPY_ACTIVE): New
error.
* src/util/virterror.c (virErrorMsg): Define it.
---

was 5/18 in v4
v5: enhance commit message, fix typos in docs

 include/libvirt/libvirt.h.in |   24 +++++++++-
 include/libvirt/virterror.h  |    1 +
 src/libvirt.c                |  100 ++++++++++++++++++++++++++++++++++++++----
 src/util/virterror.c         |    6 +++
 4 files changed, 120 insertions(+), 11 deletions(-)

diff --git a/include/libvirt/libvirt.h.in b/include/libvirt/libvirt.h.in
index 97ad99d..ac5df95 100644
--- a/include/libvirt/libvirt.h.in
+++ b/include/libvirt/libvirt.h.in
@@ -1934,12 +1934,15 @@ int virDomainUpdateDeviceFlags(virDomainPtr domain,
 /**
  * virDomainBlockJobType:
  *
- * VIR_DOMAIN_BLOCK_JOB_TYPE_PULL: Block Pull (virDomainBlockPull or
- * virDomainBlockRebase)
+ * VIR_DOMAIN_BLOCK_JOB_TYPE_PULL: Block Pull (virDomainBlockPull, or
+ * virDomainBlockRebase without flags), job ends on completion
+ * VIR_DOMAIN_BLOCK_JOB_TYPE_COPY: Block Copy (virDomainBlockRebase with
+ * flags), job exists as long as mirroring is active
  */
 typedef enum {
     VIR_DOMAIN_BLOCK_JOB_TYPE_UNKNOWN = 0,
     VIR_DOMAIN_BLOCK_JOB_TYPE_PULL = 1,
+    VIR_DOMAIN_BLOCK_JOB_TYPE_COPY = 2,

 #ifdef VIR_ENUM_SENTINELS
     VIR_DOMAIN_BLOCK_JOB_TYPE_LAST
@@ -1950,9 +1953,11 @@ typedef enum {
  * virDomainBlockJobAbortFlags:
  *
  * VIR_DOMAIN_BLOCK_JOB_ABORT_ASYNC: Request only, do not wait for completion
+ * VIR_DOMAIN_BLOCK_JOB_ABORT_PIVOT: Pivot to mirror when ending a copy job
  */
 typedef enum {
     VIR_DOMAIN_BLOCK_JOB_ABORT_ASYNC = 1 << 0,
+    VIR_DOMAIN_BLOCK_JOB_ABORT_PIVOT = 1 << 1,
 } virDomainBlockJobAbortFlags;

 /* An iterator for monitoring block job operations */
@@ -1983,6 +1988,21 @@ int    virDomainBlockJobSetSpeed(virDomainPtr dom, const char *disk,

 int           virDomainBlockPull(virDomainPtr dom, const char *disk,
                                  unsigned long bandwidth, unsigned int flags);
+
+/**
+ * virDomainBlockRebaseFlags:
+ *
+ * Flags available for virDomainBlockRebase().
+ */
+typedef enum {
+    VIR_DOMAIN_BLOCK_REBASE_SHALLOW   = 1 << 0, /* Limit copy to top of source
+                                                   backing chain */
+    VIR_DOMAIN_BLOCK_REBASE_REUSE_EXT = 1 << 1, /* Reuse existing external
+                                                   file for a copy */
+    VIR_DOMAIN_BLOCK_REBASE_COPY_RAW  = 1 << 2, /* Make destination file raw */
+    VIR_DOMAIN_BLOCK_REBASE_COPY      = 1 << 3, /* Start a copy job */
+} virDomainBlockRebaseFlags;
+
 int           virDomainBlockRebase(virDomainPtr dom, const char *disk,
                                    const char *base, unsigned long bandwidth,
                                    unsigned int flags);
diff --git a/include/libvirt/virterror.h b/include/libvirt/virterror.h
index e04d29e..070fdb5 100644
--- a/include/libvirt/virterror.h
+++ b/include/libvirt/virterror.h
@@ -249,6 +249,7 @@ typedef enum {
     VIR_ERR_NO_DOMAIN_METADATA = 80,    /* The metadata is not present */
     VIR_ERR_MIGRATE_UNSAFE = 81,        /* Migration is not safe */
     VIR_ERR_OVERFLOW = 82,              /* integer overflow */
+    VIR_ERR_BLOCK_COPY_ACTIVE = 83,     /* action prevented by block copy job */
 } virErrorNumber;

 /**
diff --git a/src/libvirt.c b/src/libvirt.c
index 93ec817..af42d3b 100644
--- a/src/libvirt.c
+++ b/src/libvirt.c
@@ -2696,6 +2696,10 @@ error:
  * A save file can be inspected or modified slightly with
  * virDomainSaveImageGetXMLDesc() and virDomainSaveImageDefineXML().
  *
+ * Some hypervisors may prevent this operation if there is a current
+ * block copy operation; in that case, use virDomainBlockJobAbort()
+ * to stop the block copy first.
+ *
  * Returns 0 in case of success and -1 in case of failure.
  */
 int
@@ -7891,6 +7895,11 @@ error:
  * virDomainUndefine(). A previous definition for this domain would be
  * overriden if it already exists.
  *
+ * Some hypervisors may prevent this operation if there is a current
+ * block copy operation on a transient domain with the same id as the
+ * domain being defined; in that case, use virDomainBlockJobAbort() to
+ * stop the block copy first.
+ *
  * Returns NULL in case of error, a pointer to the domain otherwise
  */
 virDomainPtr
@@ -9424,6 +9433,10 @@ error:
  * return failure if LIVE is specified but it only supports removing the
  * persisted device allocation.
  *
+ * Some hypervisors may prevent this operation if there is a current
+ * block copy operation on the device being detached; in that case,
+ * use virDomainBlockJobAbort() to stop the block copy first.
+ *
  * Returns 0 in case of success, -1 in case of failure.
  */
 int
@@ -17124,6 +17137,10 @@ virDomainSnapshotGetConnect(virDomainSnapshotPtr snapshot)
  * that it is still possible to fail after disks have changed, but only
  * in the much rarer cases of running out of memory or disk space).
  *
+ * Some hypervisors may prevent this operation if there is a current
+ * block copy operation; in that case, use virDomainBlockJobAbort()
+ * to stop the block copy first.
+ *
  * Returns an (opaque) virDomainSnapshotPtr on success, NULL on failure.
  */
 virDomainSnapshotPtr
@@ -17913,7 +17930,8 @@ error:
  * can be found by calling virDomainGetXMLDesc() and inspecting
  * elements within //domain/devices/disk.
  *
- * By default, this function performs a synchronous operation and the caller
+ * If the current block job for @disk is VIR_DOMAIN_BLOCK_JOB_TYPE_PULL, then
+ * by default, this function performs a synchronous operation and the caller
  * may assume that the operation has completed when 0 is returned.  However,
  * BlockJob operations may take a long time to cancel, and during this time
  * further domain interactions may be unresponsive.  To avoid this problem,
@@ -17922,7 +17940,18 @@ error:
  * been canceled, a BlockJob event will be emitted, with status
  * VIR_DOMAIN_BLOCK_JOB_CANCELED (even if the ABORT_ASYNC flag was not
  * used); it is also possible to poll virDomainBlockJobInfo() to see if
- * the job cancellation is still pending.
+ * the job cancellation is still pending.  This type of job can be restarted
+ * to pick up from where it left off.
+ *
+ * If the current block job for @disk is VIR_DOMAIN_BLOCK_JOB_TYPE_COPY, then
+ * the default is to abort the mirroring and revert to the source disk;
+ * adding @flags of VIR_DOMAIN_BLOCK_JOB_ABORT_PIVOT causes this call to
+ * fail with VIR_ERR_BLOCK_COPY_ACTIVE if the copy is not fully populated,
+ * otherwise it will swap the disk over to the copy to end the mirroring.  An
+ * event will be issued when the job is ended, and it is possible to use
+ * VIR_DOMAIN_BLOCK_JOB_ABORT_ASYNC to control whether this command waits
+ * for the completion of the job.  Restarting this job requires starting
+ * over from the beginning of the first phase.
  *
  * Returns -1 in case of failure, 0 when successful.
  */
@@ -18172,19 +18201,57 @@ error:
  * @disk: path to the block device, or device shorthand
  * @base: path to backing file to keep, or NULL for no backing file
  * @bandwidth: (optional) specify copy bandwidth limit in Mbps
- * @flags: extra flags; not used yet, so callers should always pass 0
+ * @flags: bitwise-OR of virDomainBlockRebaseFlags
  *
  * Populate a disk image with data from its backing image chain, and
- * setting the backing image to @base.  @base must be the absolute
+ * setting the backing image to @base, or alternatively copy an entire
+ * backing chain to a new file @base.
+ *
+ * When @flags is 0, this starts a pull, where @base must be the absolute
  * path of one of the backing images further up the chain, or NULL to
  * convert the disk image so that it has no backing image.  Once all
  * data from its backing image chain has been pulled, the disk no
  * longer depends on those intermediate backing images.  This function
  * pulls data for the entire device in the background.  Progress of
- * the operation can be checked with virDomainGetBlockJobInfo() and
- * the operation can be aborted with virDomainBlockJobAbort().  When
- * finished, an asynchronous event is raised to indicate the final
- * status.
+ * the operation can be checked with virDomainGetBlockJobInfo() with a
+ * job type of VIR_DOMAIN_BLOCK_JOB_TYPE_PULL, and the operation can be
+ * aborted with virDomainBlockJobAbort().  When finished, an asynchronous
+ * event is raised to indicate the final status, and the job no longer
+ * exists.  If the job is aborted, a new one can be started later to
+ * resume from the same point.
+ *
+ * When @flags includes VIR_DOMAIN_BLOCK_REBASE_COPY, this starts a copy,
+ * where @base must be the name of a new file to copy the chain to.  By
+ * default, the copy will pull the entire source chain into the destination
+ * file, but if @flags also contains VIR_DOMAIN_BLOCK_REBASE_SHALLOW, then
+ * only the top of the source chain will be copied (the source and
+ * destination have a common backing file).  By default, @base will be
+ * created with the same file format as the source, but this can be altered
+ * by adding VIR_DOMAIN_BLOCK_REBASE_COPY_RAW to force the copy to be raw
+ * (does not make sense with the shallow flag unless the source is also raw),
+ * or by using VIR_DOMAIN_BLOCK_REBASE_REUSE_EXT to reuse an existing file
+ * with initial contents identical to the backing file of the source (this
+ * allows a management app to pre-create files with relative backing file
+ * names, rather than the default of absolute backing file names; as a
+ * security precaution, you should generally only use reuse_ext with the
+ * shallow flag and a non-raw destination file).
+ *
+ * A copy job has two parts; in the first phase, the @bandwidth parameter
+ * affects how fast the source is pulled into the destination, and the job
+ * can only be canceled by reverting to the source file; progress in this
+ * phase can be tracked via the virDomainBlockJobInfo() command, with a
+ * job type of VIR_DOMAIN_BLOCK_JOB_TYPE_COPY.  The job transitions to the
+ * second phase when the job info states cur == end, and remains alive to
+ * mirror all further changes to both source and destination.  The user
+ * must call virDomainBlockJobAbort() to end the mirroring while choosing
+ * whether to revert to source or pivot to the destination.  An event is
+ * issued when the job ends, and in the future, an event may be added when
+ * the job transitions from pulling to mirroring.  If the job is aborted,
+ * a new job will have to start over from the beginning of the first phase.
+ *
+ * Some hypervisors will restrict certain actions, such as virDomainSave()
+ * or virDomainDetachDevice(), while a copy job is active; they may
+ * also restrict a copy job to transient domains.
  *
  * The @disk parameter is either an unambiguous source name of the
  * block device (the <source file='...'/> sub-element, such as
@@ -18198,7 +18265,8 @@ error:
  * suitable default.  Some hypervisors do not support this feature and will
  * return an error if bandwidth is not 0.
  *
- * When @base is NULL, this is identical to virDomainBlockPull().
+ * When @base is NULL and @flags is 0, this is identical to
+ * virDomainBlockPull().
  *
  * Returns 0 if the operation has started, -1 on failure.
  */
@@ -18231,6 +18299,20 @@ int virDomainBlockRebase(virDomainPtr dom, const char *disk,
         goto error;
     }

+    if (flags & VIR_DOMAIN_BLOCK_REBASE_COPY) {
+        if (!base) {
+            virLibDomainError(VIR_ERR_INVALID_ARG,
+                              _("base is required when starting a copy"));
+            goto error;
+        }
+    } else if (flags & (VIR_DOMAIN_BLOCK_REBASE_SHALLOW |
+                        VIR_DOMAIN_BLOCK_REBASE_REUSE_EXT |
+                        VIR_DOMAIN_BLOCK_REBASE_COPY_RAW)) {
+        virLibDomainError(VIR_ERR_INVALID_ARG,
+                          _("use of flags requires a copy job"));
+        goto error;
+    }
+
     if (conn->driver->domainBlockRebase) {
         int ret;
         ret = conn->driver->domainBlockRebase(dom, disk, base, bandwidth,
diff --git a/src/util/virterror.c b/src/util/virterror.c
index ff9a36f..845081e 100644
--- a/src/util/virterror.c
+++ b/src/util/virterror.c
@@ -1250,6 +1250,12 @@ virErrorMsg(virErrorNumber error, const char *info)
             else
                 errmsg = _("numerical overflow: %s");
             break;
+        case VIR_ERR_BLOCK_COPY_ACTIVE:
+            if (!info)
+                errmsg = _("block copy still active");
+            else
+                errmsg = _("block copy still active: %s");
+            break;
     }
     return errmsg;
 }
-- 
1.7.7.6




More information about the libvir-list mailing list