[libvirt] [PATCHv2 04/15] blockjob: add new API flags

Eric Blake eblake at redhat.com
Fri Apr 6 04:36:50 UTC 2012


This patch introduces a new block job, useful for live storage
migration using pre-copy streaming.

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.

It would be nice if we could issue an event when pivoting from phase 1
to phase 2, but qemu hasn't implemented that, so 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.
---
 include/libvirt/libvirt.h.in |   24 ++++++++++-
 include/libvirt/virterror.h  |    1 +
 src/libvirt.c                |   95 ++++++++++++++++++++++++++++++++++++++----
 src/util/virterror.c         |    6 +++
 4 files changed, 116 insertions(+), 10 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 af22232..d4b648c 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,13 +17930,24 @@ 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 complete, and during this time
  * further domain interactions may be unresponsive.  To avoid this problem,
  * pass VIR_DOMAIN_BLOCK_JOB_ABORT_ASYNC in the @flags argument to enable
  * asynchronous behavior.  Either way, when the job has been cancelled, a
  * BlockJob event will be emitted, with status VIR_DOMAIN_BLOCK_JOB_CANCELLED.
+ * In this usage, @flags must not contain VIR_DOMAIN_BLOCK_JOB_ABORT_PIVOT.
+ *
+ * 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_SYNC to control whether this command waits
+ * for the completion of the job.
  *
  * Returns -1 in case of failure, 0 when successful.
  */
@@ -18169,19 +18197,55 @@ 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.
+ *
+ * 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; it is
+ * generally used with the shallow flag, since otherwise the destination
+ * file must start with empty contents).
+ *
+ * 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.
+ *
+ * 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
@@ -18195,7 +18259,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.
  */
@@ -18228,6 +18293,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