[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