[libvirt] [PATCH] BlockJob: Support sync/async virDomainBlockJobAbort

Dave Allan dallan at redhat.com
Mon Jan 16 19:19:03 UTC 2012


On Fri, Jan 13, 2012 at 04:30:56PM -0600, Adam Litke wrote:
> On Fri, Jan 13, 2012 at 04:48:30PM -0500, Dave Allan wrote:
> > On Fri, Jan 13, 2012 at 02:51:23PM -0600, Adam Litke wrote:
> > > Qemu has changed the semantics of the "block_job_cancel" API.  Originally, the
> > > operation was synchronous (ie. upon command completion, the operation was
> > > guaranteed to be completely stopped).  With the new semantics, a
> > > "block_job_cancel" merely requests that the operation be cancelled and an event
> > > is triggered once the cancellation request has been honored.
> > > 
> > > To adopt the new semantics while preserving compatibility I propose the
> > > following updates to the virDomainBlockJob API:
> > > 
> > > A new block job event type VIR_DOMAIN_BLOCK_JOB_CANCELLED will be recognized by
> > > libvirt.  Regardless of the flags used with virDomainBlockJobAbort, this event
> > > will be raised whenever it is received from qemu.  This event indicates that a
> > > block job has been successfully cancelled.
> > > 
> > > A new extension flag VIR_DOMAIN_BLOCK_JOB_ABORT_ASYNC will be added to the
> > > virDomainBlockJobAbort API.  When enabled, this function will operate
> > > asynchronously (ie, it can return before the job has actually been cancelled).
> > > When the API is used in this mode, it is the responsibility of the caller to
> > > wait for a VIR_DOMAIN_BLOCK_JOB_CANCELLED event or poll via the
> > > virDomainGetBlockJobInfo API to check the cancellation status.
> > > 
> > > Without the VIR_DOMAIN_BLOCK_JOB_ABORT_ASYNC flag, libvirt will internally poll
> > > using qemu's "query-block-jobs" API and will not return until the operation has
> > > been completed.  API users are advised that this operation is unbounded and
> > > further interaction with the domain during this period may block.
> > > 
> > > This patch implements the new event type, the API flag, and the polling.  The
> > > main outstanding issue is whether we should bound the amount of time we will
> > > wait for cancellation and return an error.
> > > 
> > > Comments on this proposal?
> > 
> > Hi Adam,
> > 
> > Thanks for the patch.  That approach seems reasonable.  Re: bounding
> > the amount of time we wait for cancellation, if the time isn't
> > bounded, what happens if the cancellation never happens?  IMO the most
> > important thing is to make sure that the caller has a way to return to
> > a normally functioning state even if the virDomainBlockJobAbort call
> > is unable to cancel the job.
> 
> Yes, agreed.  But this brings up the familiar problem with timeouts: selecting
> the right amount of time to wait.  I don't have a good answer here, but it's not
> really all that bad if we bail out too early.  In that case we can just return
> VIR_ERR_OPERATION_TIMEOUT to the caller and they can retry the operation again.
> Unfortunately, these semantics were not present in the initial API.  Is adding a
> new error condition (timeout) to an existing API allowed?

Yes, a new error return value is fine for existing API.

> > I'll defer to the other folks on the code, since I am at this point
> > likely to do more harm than good.  :)
> > 
> > Dave
> > 
> > 
> > > Signed-off-by: Adam Litke <agl at us.ibm.com>
> > > Cc: Stefan Hajnoczi <stefanha at gmail.com>
> > > Cc: Eric Blake <eblake at redhat.com>
> > > ---
> > >  include/libvirt/libvirt.h.in |   10 ++++++++++
> > >  src/libvirt.c                |    9 ++++++++-
> > >  src/qemu/qemu_driver.c       |   24 +++++++++++++++++-------
> > >  src/qemu/qemu_monitor.c      |   24 ++++++++++++++++++++++++
> > >  src/qemu/qemu_monitor.h      |    2 ++
> > >  src/qemu/qemu_monitor_json.c |   36 +++++++++++++++++++++++++++++-------
> > >  6 files changed, 90 insertions(+), 15 deletions(-)
> > > 
> > > diff --git a/include/libvirt/libvirt.h.in b/include/libvirt/libvirt.h.in
> > > index e436f3c..fc7f028 100644
> > > --- a/include/libvirt/libvirt.h.in
> > > +++ b/include/libvirt/libvirt.h.in
> > > @@ -1776,6 +1776,15 @@ typedef enum {
> > >      VIR_DOMAIN_BLOCK_JOB_TYPE_PULL = 1,
> > >  } virDomainBlockJobType;
> > >  
> > > +/**
> > > + * virDomainBlockJobAbortFlags:
> > > + *
> > > + * VIR_DOMAIN_BLOCK_JOB_ABORT_ASYNC: Request only, do not wait for completion
> > > + */
> > > +typedef enum {
> > > +    VIR_DOMAIN_BLOCK_JOB_ABORT_ASYNC = 1,
> > > +} virDomainBlockJobAbortFlags;
> > > +
> > >  /* An iterator for monitoring block job operations */
> > >  typedef unsigned long long virDomainBlockJobCursor;
> > >  
> > > @@ -3293,6 +3302,7 @@ typedef void (*virConnectDomainEventGraphicsCallback)(virConnectPtr conn,
> > >  typedef enum {
> > >      VIR_DOMAIN_BLOCK_JOB_COMPLETED = 0,
> > >      VIR_DOMAIN_BLOCK_JOB_FAILED = 1,
> > > +    VIR_DOMAIN_BLOCK_JOB_CANCELLED = 2,
> > >  } virConnectDomainEventBlockJobStatus;
> > >  
> > >  /**
> > > diff --git a/src/libvirt.c b/src/libvirt.c
> > > index a540424..0e886f5 100644
> > > --- a/src/libvirt.c
> > > +++ b/src/libvirt.c
> > > @@ -17299,7 +17299,7 @@ error:
> > >   * virDomainBlockJobAbort:
> > >   * @dom: pointer to domain object
> > >   * @disk: path to the block device, or device shorthand
> > > - * @flags: extra flags; not used yet, so callers should always pass 0
> > > + * @flags: bitwise or of virDomainBlockJobAbortFlags
> > >   *
> > >   * Cancel the active block job on the given disk.
> > >   *
> > > @@ -17310,6 +17310,13 @@ 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
> > > + * 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.  When the job has been cancelled, a BlockJob event will be emitted.
> > > + * 
> > >   * Returns -1 in case of failure, 0 when successful.
> > >   */
> > >  int virDomainBlockJobAbort(virDomainPtr dom, const char *disk,
> > > diff --git a/src/qemu/qemu_driver.c b/src/qemu/qemu_driver.c
> > > index 712f1fc..d971a5f 100644
> > > --- a/src/qemu/qemu_driver.c
> > > +++ b/src/qemu/qemu_driver.c
> > > @@ -11379,7 +11379,7 @@ cleanup:
> > >  static int
> > >  qemuDomainBlockJobImpl(virDomainPtr dom, const char *path,
> > >                         unsigned long bandwidth, virDomainBlockJobInfoPtr info,
> > > -                       int mode)
> > > +                       int flags, int mode)
> > >  {
> > >      struct qemud_driver *driver = dom->conn->privateData;
> > >      virDomainObjPtr vm = NULL;
> > > @@ -11420,6 +11420,15 @@ qemuDomainBlockJobImpl(virDomainPtr dom, const char *path,
> > >      qemuDomainObjEnterMonitorWithDriver(driver, vm);
> > >      priv = vm->privateData;
> > >      ret = qemuMonitorBlockJob(priv->mon, device, bandwidth, info, mode);
> > > +    /*
> > > +     * Qemu provides asynchronous block job cancellation but without the 
> > > +     * VIR_DOMAIN_BLOCK_JOB_ABORT_ASYNC flag libvirt guarantees a synchronous
> > > +     * operation.  Provide this behavior by waiting here (with the monitor
> > > +     * locked) so we don't get confused by newly scheduled block jobs.
> > > +     */
> > > +    if (ret == 0 && mode == BLOCK_JOB_ABORT &&
> > > +                    !(flags & VIR_DOMAIN_BLOCK_JOB_ABORT_ASYNC))
> > > +        ret = qemuMonitorBlockJobCancelWait(priv->mon, device);
> > >      qemuDomainObjExitMonitorWithDriver(driver, vm);
> > >  
> > >  endjob:
> > > @@ -11439,8 +11448,7 @@ cleanup:
> > >  static int
> > >  qemuDomainBlockJobAbort(virDomainPtr dom, const char *path, unsigned int flags)
> > >  {
> > > -    virCheckFlags(0, -1);
> > > -    return qemuDomainBlockJobImpl(dom, path, 0, NULL, BLOCK_JOB_ABORT);
> > > +    return qemuDomainBlockJobImpl(dom, path, 0, NULL, flags, BLOCK_JOB_ABORT);
> > >  }
> > >  
> > >  static int
> > > @@ -11448,7 +11456,7 @@ qemuDomainGetBlockJobInfo(virDomainPtr dom, const char *path,
> > >                             virDomainBlockJobInfoPtr info, unsigned int flags)
> > >  {
> > >      virCheckFlags(0, -1);
> > > -    return qemuDomainBlockJobImpl(dom, path, 0, info, BLOCK_JOB_INFO);
> > > +    return qemuDomainBlockJobImpl(dom, path, 0, info, flags, BLOCK_JOB_INFO);
> > >  }
> > >  
> > >  static int
> > > @@ -11456,7 +11464,8 @@ qemuDomainBlockJobSetSpeed(virDomainPtr dom, const char *path,
> > >                             unsigned long bandwidth, unsigned int flags)
> > >  {
> > >      virCheckFlags(0, -1);
> > > -    return qemuDomainBlockJobImpl(dom, path, bandwidth, NULL, BLOCK_JOB_SPEED);
> > > +    return qemuDomainBlockJobImpl(dom, path, bandwidth, NULL, flags,
> > > +                                  BLOCK_JOB_SPEED);
> > >  }
> > >  
> > >  static int
> > > @@ -11466,9 +11475,10 @@ qemuDomainBlockPull(virDomainPtr dom, const char *path, unsigned long bandwidth,
> > >      int ret;
> > >  
> > >      virCheckFlags(0, -1);
> > > -    ret = qemuDomainBlockJobImpl(dom, path, bandwidth, NULL, BLOCK_JOB_PULL);
> > > +    ret = qemuDomainBlockJobImpl(dom, path, bandwidth, NULL, flags,
> > > +                                 BLOCK_JOB_PULL);
> > >      if (ret == 0 && bandwidth != 0)
> > > -        ret = qemuDomainBlockJobImpl(dom, path, bandwidth, NULL,
> > > +        ret = qemuDomainBlockJobImpl(dom, path, bandwidth, NULL, flags,
> > >                                       BLOCK_JOB_SPEED);
> > >      return ret;
> > >  }
> > > diff --git a/src/qemu/qemu_monitor.c b/src/qemu/qemu_monitor.c
> > > index ad7e2a5..5c22486 100644
> > > --- a/src/qemu/qemu_monitor.c
> > > +++ b/src/qemu/qemu_monitor.c
> > > @@ -2585,6 +2585,30 @@ int qemuMonitorScreendump(qemuMonitorPtr mon,
> > >      return ret;
> > >  }
> > >  
> > > +/* Poll the monitor to wait for the block job on a given disk to end.
> > > + * We don't need to worry about another block job starting since we have the
> > > + * driver locked. */
> > > +int
> > > +qemuMonitorBlockJobCancelWait(qemuMonitorPtr mon, const char *device)
> > > +{
> > > +    VIR_DEBUG("mon=%p, device=%p", mon, device);
> > > +    /* XXX: Should we provide some sort of escape hatch for this wait? */
> > > +    while (1) {
> > > +        /* Poll every 100ms */
> > > +        int ret;
> > > +        struct timespec ts = { .tv_sec = 0, .tv_nsec = 100 * 1000 * 1000ull };
> > > +        virDomainBlockJobInfo info;
> > > +
> > > +        ret = qemuMonitorBlockJob(mon, device, 0, &info, BLOCK_JOB_INFO);
> > > +        if (ret < 0)
> > > +            return -1;
> > > +        else if (ret == 0)
> > > +            return 0;
> > > +        else
> > > +            nanosleep(&ts, NULL); 
> > > +    }
> > > +}
> > > +
> > >  int qemuMonitorBlockJob(qemuMonitorPtr mon,
> > >                          const char *device,
> > >                          unsigned long bandwidth,
> > > diff --git a/src/qemu/qemu_monitor.h b/src/qemu/qemu_monitor.h
> > > index 15acf8b..afc081e 100644
> > > --- a/src/qemu/qemu_monitor.h
> > > +++ b/src/qemu/qemu_monitor.h
> > > @@ -510,6 +510,8 @@ typedef enum {
> > >      BLOCK_JOB_PULL = 3,
> > >  } BLOCK_JOB_CMD;
> > >  
> > > +int qemuMonitorBlockJobCancelWait(qemuMonitorPtr mon, const char *device);
> > > +
> > >  int qemuMonitorBlockJob(qemuMonitorPtr mon,
> > >                          const char *device,
> > >                          unsigned long bandwidth,
> > > diff --git a/src/qemu/qemu_monitor_json.c b/src/qemu/qemu_monitor_json.c
> > > index 7eb2a92..2ca8eeb 100644
> > > --- a/src/qemu/qemu_monitor_json.c
> > > +++ b/src/qemu/qemu_monitor_json.c
> > > @@ -57,7 +57,8 @@ static void qemuMonitorJSONHandleIOError(qemuMonitorPtr mon, virJSONValuePtr dat
> > >  static void qemuMonitorJSONHandleVNCConnect(qemuMonitorPtr mon, virJSONValuePtr data);
> > >  static void qemuMonitorJSONHandleVNCInitialize(qemuMonitorPtr mon, virJSONValuePtr data);
> > >  static void qemuMonitorJSONHandleVNCDisconnect(qemuMonitorPtr mon, virJSONValuePtr data);
> > > -static void qemuMonitorJSONHandleBlockJob(qemuMonitorPtr mon, virJSONValuePtr data);
> > > +static void qemuMonitorJSONHandleBlockJobCompleted(qemuMonitorPtr mon, virJSONValuePtr data);
> > > +static void qemuMonitorJSONHandleBlockJobCancelled(qemuMonitorPtr mon, virJSONValuePtr data);
> > >  
> > >  struct {
> > >      const char *type;
> > > @@ -73,7 +74,8 @@ struct {
> > >      { "VNC_CONNECTED", qemuMonitorJSONHandleVNCConnect, },
> > >      { "VNC_INITIALIZED", qemuMonitorJSONHandleVNCInitialize, },
> > >      { "VNC_DISCONNECTED", qemuMonitorJSONHandleVNCDisconnect, },
> > > -    { "BLOCK_JOB_COMPLETED", qemuMonitorJSONHandleBlockJob, },
> > > +    { "BLOCK_JOB_COMPLETED", qemuMonitorJSONHandleBlockJobCompleted, },
> > > +    { "BLOCK_JOB_CANCELLED", qemuMonitorJSONHandleBlockJobCancelled, },
> > >  };
> > >  
> > >  
> > > @@ -685,13 +687,14 @@ static void qemuMonitorJSONHandleVNCDisconnect(qemuMonitorPtr mon, virJSONValueP
> > >      qemuMonitorJSONHandleVNC(mon, data, VIR_DOMAIN_EVENT_GRAPHICS_DISCONNECT);
> > >  }
> > >  
> > > -static void qemuMonitorJSONHandleBlockJob(qemuMonitorPtr mon, virJSONValuePtr data)
> > > +static void qemuMonitorJSONHandleBlockJobImpl(qemuMonitorPtr mon,
> > > +                                              virJSONValuePtr data,
> > > +                                              int event)
> > >  {
> > >      const char *device;
> > >      const char *type_str;
> > >      int type = VIR_DOMAIN_BLOCK_JOB_TYPE_UNKNOWN;
> > >      unsigned long long offset, len;
> > > -    int status = VIR_DOMAIN_BLOCK_JOB_FAILED;
> > >  
> > >      if ((device = virJSONValueObjectGetString(data, "device")) == NULL) {
> > >          VIR_WARN("missing device in block job event");
> > > @@ -716,13 +719,32 @@ static void qemuMonitorJSONHandleBlockJob(qemuMonitorPtr mon, virJSONValuePtr da
> > >      if (STREQ(type_str, "stream"))
> > >          type = VIR_DOMAIN_BLOCK_JOB_TYPE_PULL;
> > >  
> > > -    if (offset != 0 && offset == len)
> > > -        status = VIR_DOMAIN_BLOCK_JOB_COMPLETED;
> > > +    switch (event) {
> > > +        case VIR_DOMAIN_BLOCK_JOB_COMPLETED:
> > > +            /* Make sure the whole device has been processed */
> > > +            if (offset != len)
> > > +                event = VIR_DOMAIN_BLOCK_JOB_FAILED;
> > > +            break;
> > > +        case VIR_DOMAIN_BLOCK_JOB_FAILED:
> > > +        case VIR_DOMAIN_BLOCK_JOB_CANCELLED:
> > > +            break;
> > > +    }
> > >  
> > >  out:
> > > -    qemuMonitorEmitBlockJob(mon, device, type, status);
> > > +    qemuMonitorEmitBlockJob(mon, device, type, event);
> > > +}
> > > +
> > > +static void qemuMonitorJSONHandleBlockJobCompleted(qemuMonitorPtr mon,
> > > +                                                   virJSONValuePtr data)
> > > +{
> > > +    qemuMonitorJSONHandleBlockJobImpl(mon, data, VIR_DOMAIN_BLOCK_JOB_COMPLETED);
> > >  }
> > >  
> > > +static void qemuMonitorJSONHandleBlockJobCancelled(qemuMonitorPtr mon,
> > > +                                                   virJSONValuePtr data)
> > > +{
> > > +    qemuMonitorJSONHandleBlockJobImpl(mon, data, VIR_DOMAIN_BLOCK_JOB_CANCELLED);
> > > +}
> > >  
> > >  int
> > >  qemuMonitorJSONHumanCommandWithFd(qemuMonitorPtr mon,
> > > -- 
> > > 1.7.5.rc1
> > > 
> > 
> 
> -- 
> Adam Litke <agl at us.ibm.com>
> IBM Linux Technology Center
> 




More information about the libvir-list mailing list