Introduce support for virDomainGetJobInfo in the QEMU driver. This
allows for monitoring of any API that uses the 'info migrate' monitor
command. ie virDomainMigrate, virDomainSave and virDomainCoreDump
Unfortunately QEMU does not provide a way to monitor incoming migration
so we can't wire up virDomainRestore yet.
The virsh tool gets a new command 'domjobinfo' to query status
* src/qemu/qemu_driver.c: Record virDomainJobInfo and start time
in qemuDomainObjPrivatePtr objects. Add generic shared handler
for calling 'info migrate' with all migration based APIs.
* src/qemu/qemu_monitor_text.c: Fix parsing of 'info migration' reply
* tools/virsh.c: add new 'domjobinfo' command to query progress
---
src/qemu/qemu_driver.c | 205 +++++++++++++++++++++++++++++++++++-------
src/qemu/qemu_monitor_text.c | 7 +-
tools/virsh.c | 75 +++++++++++++++
3 files changed, 252 insertions(+), 35 deletions(-)
diff --git a/src/qemu/qemu_driver.c b/src/qemu/qemu_driver.c
index 1e3a8b7..540558f 100644
--- a/src/qemu/qemu_driver.c
+++ b/src/qemu/qemu_driver.c
@@ -86,6 +86,8 @@ struct _qemuDomainObjPrivate {
int jobActive; /* Non-zero if a job is active. Only 1 job is allowed at any time
* A job includes *all* monitor commands, even those just querying
* information, not merely actions */
+ virDomainJobInfo jobInfo;
+ unsigned long long jobStart;
qemuMonitorPtr mon;
virDomainChrDefPtr monConfig;
@@ -329,6 +331,8 @@ static int qemuDomainObjBeginJob(virDomainObjPtr obj)
}
}
priv->jobActive = 1;
+ priv->jobStart = (now.tv_sec * 1000ull) + (now.tv_usec / 1000);
+ memset(&priv->jobInfo, 0, sizeof(priv->jobInfo));
return 0;
}
@@ -373,6 +377,8 @@ static int qemuDomainObjBeginJobWithDriver(struct qemud_driver
*driver,
}
}
priv->jobActive = 1;
+ priv->jobStart = (now.tv_sec * 1000ull) + (now.tv_usec / 1000);
+ memset(&priv->jobInfo, 0, sizeof(priv->jobInfo));
virDomainObjUnlock(obj);
qemuDriverLock(driver);
@@ -395,6 +401,8 @@ static int ATTRIBUTE_RETURN_CHECK qemuDomainObjEndJob(virDomainObjPtr
obj)
qemuDomainObjPrivatePtr priv = obj->privateData;
priv->jobActive = 0;
+ priv->jobStart = 0;
+ memset(&priv->jobInfo, 0, sizeof(priv->jobInfo));
virCondSignal(&priv->jobCond);
return virDomainObjUnref(obj);
@@ -3800,6 +3808,93 @@ cleanup:
}
+static int
+qemuDomainWaitForMigrationComplete(struct qemud_driver *driver, virDomainObjPtr vm)
+{
+ int ret = -1;
+ int status;
+ unsigned long long memProcessed;
+ unsigned long long memRemaining;
+ unsigned long long memTotal;
+ qemuDomainObjPrivatePtr priv = vm->privateData;
+
+ priv->jobInfo.type = VIR_DOMAIN_JOB_UNBOUNDED;
+
+ while (priv->jobInfo.type != VIR_DOMAIN_JOB_NONE) {
+ /* Poll every 1/2 second for progress & to allow cancellation */
+ struct timespec ts = { .tv_sec = 0, .tv_nsec = 500 * 1000ull };
+ struct timeval now;
+ int rc;
+
+ qemuDomainObjEnterMonitorWithDriver(driver, vm);
+ rc = qemuMonitorGetMigrationStatus(priv->mon,
+ &status,
+ &memProcessed,
+ &memRemaining,
+ &memTotal);
+ qemuDomainObjExitMonitorWithDriver(driver, vm);
+
+ if (rc < 0)
+ goto cleanup;
+
+ if (gettimeofday(&now, NULL) < 0) {
+ virReportSystemError(NULL, errno, "%s",
+ _("cannot get time of day"));
+ goto cleanup;
+ }
+ priv->jobInfo.timeElapsed =
+ ((now.tv_sec * 1000ull) + (now.tv_usec / 1000)) -
+ priv->jobStart;
+
+ switch (status) {
+ case QEMU_MONITOR_MIGRATION_STATUS_INACTIVE:
+ priv->jobInfo.type = VIR_DOMAIN_JOB_NONE;
+ qemudReportError(NULL, NULL, NULL, VIR_ERR_OPERATION_FAILED,
+ "%s", _("Migration is not active"));
+ break;
+
+ case QEMU_MONITOR_MIGRATION_STATUS_ACTIVE:
+ priv->jobInfo.dataTotal = memTotal;
+ priv->jobInfo.dataRemaining = memRemaining;
+ priv->jobInfo.dataProcessed = memProcessed;
+
+ priv->jobInfo.memTotal = memTotal;
+ priv->jobInfo.memRemaining = memRemaining;
+ priv->jobInfo.memProcessed = memProcessed;
+ break;
+
+ case QEMU_MONITOR_MIGRATION_STATUS_COMPLETED:
+ priv->jobInfo.type = VIR_DOMAIN_JOB_NONE;
+ ret = 0;
+ break;
+
+ case QEMU_MONITOR_MIGRATION_STATUS_ERROR:
+ priv->jobInfo.type = VIR_DOMAIN_JOB_NONE;
+ qemudReportError(NULL, NULL, NULL, VIR_ERR_OPERATION_FAILED,
+ "%s", _("Migration unexpectedly
failed"));
+ break;
+
+ case QEMU_MONITOR_MIGRATION_STATUS_CANCELLED:
+ priv->jobInfo.type = VIR_DOMAIN_JOB_NONE;
+ qemudReportError(NULL, NULL, NULL, VIR_ERR_OPERATION_FAILED,
+ "%s", _("Migration was cancelled by
client"));
+ break;
+ }
+
+ virDomainObjUnlock(vm);
+ qemuDriverUnlock(driver);
+
+ nanosleep(&ts, NULL);
+
+ qemuDriverLock(driver);
+ virDomainObjLock(vm);
+ }
+
+cleanup:
+ return ret;
+}
+
+
#define QEMUD_SAVE_MAGIC "LibvirtQemudSave"
#define QEMUD_SAVE_VERSION 2
@@ -3848,6 +3943,7 @@ static int qemudDomainSave(virDomainPtr dom,
int ret = -1;
int rc;
virDomainEventPtr event = NULL;
+ qemuDomainObjPrivatePtr priv;
memset(&header, 0, sizeof(header));
memcpy(header.magic, QEMUD_SAVE_MAGIC, sizeof(header.magic));
@@ -3876,6 +3972,7 @@ static int qemudDomainSave(virDomainPtr dom,
_("no domain with matching uuid '%s'"),
uuidstr);
goto cleanup;
}
+ priv = vm->privateData;
if (qemuDomainObjBeginJobWithDriver(driver, vm) < 0)
goto cleanup;
@@ -3886,9 +3983,11 @@ static int qemudDomainSave(virDomainPtr dom,
goto endjob;
}
+ memset(&priv->jobInfo, 0, sizeof(priv->jobInfo));
+ priv->jobInfo.type = VIR_DOMAIN_JOB_UNBOUNDED;
+
/* Pause */
if (vm->state == VIR_DOMAIN_RUNNING) {
- qemuDomainObjPrivatePtr priv = vm->privateData;
header.was_running = 1;
qemuDomainObjEnterMonitorWithDriver(driver, vm);
if (qemuMonitorStopCPUs(priv->mon) < 0) {
@@ -3942,26 +4041,29 @@ static int qemudDomainSave(virDomainPtr dom,
if (header.compressed == QEMUD_SAVE_FORMAT_RAW) {
const char *args[] = { "cat", NULL };
- qemuDomainObjPrivatePtr priv = vm->privateData;
qemuDomainObjEnterMonitorWithDriver(driver, vm);
- rc = qemuMonitorMigrateToCommand(priv->mon, 0, args, path);
+ rc = qemuMonitorMigrateToCommand(priv->mon, 1, args, path);
qemuDomainObjExitMonitorWithDriver(driver, vm);
} else {
const char *prog = qemudSaveCompressionTypeToString(header.compressed);
- qemuDomainObjPrivatePtr priv = vm->privateData;
const char *args[] = {
prog,
"-c",
NULL
};
qemuDomainObjEnterMonitorWithDriver(driver, vm);
- rc = qemuMonitorMigrateToCommand(priv->mon, 0, args, path);
+ rc = qemuMonitorMigrateToCommand(priv->mon, 1, args, path);
qemuDomainObjExitMonitorWithDriver(driver, vm);
}
if (rc < 0)
goto endjob;
+ rc = qemuDomainWaitForMigrationComplete(driver, vm);
+
+ if (rc < 0)
+ goto endjob;
+
if (driver->securityDriver &&
driver->securityDriver->domainRestoreSavedStateLabel &&
driver->securityDriver->domainRestoreSavedStateLabel(dom->conn, vm,
path) == -1)
@@ -3984,7 +4086,7 @@ static int qemudDomainSave(virDomainPtr dom,
endjob:
if (vm &&
qemuDomainObjEndJob(vm) == 0)
- vm = NULL;
+ vm = NULL;
cleanup:
if (fd != -1)
@@ -4013,6 +4115,7 @@ static int qemudDomainCoreDump(virDomainPtr dom,
"cat",
NULL,
};
+ qemuDomainObjPrivatePtr priv;
qemuDriverLock(driver);
vm = virDomainFindByUUID(&driver->domains, dom->uuid);
@@ -4025,6 +4128,7 @@ static int qemudDomainCoreDump(virDomainPtr dom,
_("no domain with matching uuid '%s'"),
uuidstr);
goto cleanup;
}
+ priv = vm->privateData;
if (qemuDomainObjBeginJob(vm) < 0)
goto cleanup;
@@ -4058,8 +4162,6 @@ static int qemudDomainCoreDump(virDomainPtr dom,
independent of whether the stop command is issued. */
resume = (vm->state == VIR_DOMAIN_RUNNING);
- qemuDomainObjPrivatePtr priv = vm->privateData;
-
/* Pause domain for non-live dump */
if (!(flags & VIR_DUMP_LIVE) && vm->state == VIR_DOMAIN_RUNNING) {
qemuDomainObjEnterMonitor(vm);
@@ -4072,9 +4174,18 @@ static int qemudDomainCoreDump(virDomainPtr dom,
}
qemuDomainObjEnterMonitor(vm);
- ret = qemuMonitorMigrateToCommand(priv->mon, 0, args, path);
+ ret = qemuMonitorMigrateToCommand(priv->mon, 1, args, path);
qemuDomainObjExitMonitor(vm);
- paused |= (ret == 0);
+
+ if (ret < 0)
+ goto endjob;
+
+ ret = qemuDomainWaitForMigrationComplete(driver, vm);
+
+ if (ret < 0)
+ goto endjob;
+
+ paused = 1;
if (driver->securityDriver &&
driver->securityDriver->domainRestoreSavedStateLabel &&
@@ -7763,8 +7874,6 @@ static int doNativeMigrate(virDomainPtr dom,
{
int ret = -1;
xmlURIPtr uribits = NULL;
- int status;
- unsigned long long transferred, remaining, total;
qemuDomainObjPrivatePtr priv = vm->privateData;
/* Issue the migrate command. */
@@ -7793,29 +7902,14 @@ static int doNativeMigrate(virDomainPtr dom,
goto cleanup;
}
- if (qemuMonitorMigrateToHost(priv->mon, 0, uribits->server, uribits->port)
< 0) {
- qemuDomainObjExitMonitorWithDriver(driver, vm);
- goto cleanup;
- }
-
- /* it is also possible that the migrate didn't fail initially, but
- * rather failed later on. Check the output of "info migrate"
- */
- if (qemuMonitorGetMigrationStatus(priv->mon,
- &status,
- &transferred,
- &remaining,
- &total) < 0) {
+ if (qemuMonitorMigrateToHost(priv->mon, 1, uribits->server, uribits->port)
< 0) {
qemuDomainObjExitMonitorWithDriver(driver, vm);
goto cleanup;
}
qemuDomainObjExitMonitorWithDriver(driver, vm);
- if (status != QEMU_MONITOR_MIGRATION_STATUS_COMPLETED) {
- qemudReportError (dom->conn, dom, NULL, VIR_ERR_OPERATION_FAILED,
- "%s", _("migrate did not successfully
complete"));
+ if (qemuDomainWaitForMigrationComplete(driver, vm) < 0)
goto cleanup;
- }
ret = 0;
@@ -8167,6 +8261,7 @@ qemudDomainMigratePerform (virDomainPtr dom,
virDomainEventPtr event = NULL;
int ret = -1;
int paused = 0;
+ qemuDomainObjPrivatePtr priv;
qemuDriverLock(driver);
vm = virDomainFindByUUID(&driver->domains, dom->uuid);
@@ -8177,6 +8272,7 @@ qemudDomainMigratePerform (virDomainPtr dom,
_("no domain with matching uuid '%s'"),
uuidstr);
goto cleanup;
}
+ priv = vm->privateData;
if (qemuDomainObjBeginJobWithDriver(driver, vm) < 0)
goto cleanup;
@@ -8187,8 +8283,10 @@ qemudDomainMigratePerform (virDomainPtr dom,
goto endjob;
}
+ memset(&priv->jobInfo, 0, sizeof(priv->jobInfo));
+ priv->jobInfo.type = VIR_DOMAIN_JOB_UNBOUNDED;
+
if (!(flags & VIR_MIGRATE_LIVE) && vm->state == VIR_DOMAIN_RUNNING) {
- qemuDomainObjPrivatePtr priv = vm->privateData;
/* Pause domain for non-live migration */
qemuDomainObjEnterMonitorWithDriver(driver, vm);
if (qemuMonitorStopCPUs(priv->mon) < 0) {
@@ -8233,7 +8331,6 @@ qemudDomainMigratePerform (virDomainPtr dom,
endjob:
if (paused) {
- qemuDomainObjPrivatePtr priv = vm->privateData;
/* we got here through some sort of failure; start the domain again */
qemuDomainObjEnterMonitorWithDriver(driver, vm);
if (qemuMonitorStartCPUs(priv->mon, dom->conn) < 0) {
@@ -8530,6 +8627,50 @@ qemuCPUCompare(virConnectPtr conn,
return ret;
}
+
+static int qemuDomainGetJobInfo(virDomainPtr dom,
+ virDomainJobInfoPtr info) {
+ struct qemud_driver *driver = dom->conn->privateData;
+ virDomainObjPtr vm;
+ int ret = -1;
+ qemuDomainObjPrivatePtr priv;
+
+ qemuDriverLock(driver);
+ vm = virDomainFindByUUID(&driver->domains, dom->uuid);
+ qemuDriverUnlock(driver);
+ if (!vm) {
+ char uuidstr[VIR_UUID_STRING_BUFLEN];
+ virUUIDFormat(dom->uuid, uuidstr);
+ qemudReportError(dom->conn, dom, NULL, VIR_ERR_NO_DOMAIN,
+ _("no domain with matching uuid '%s'"),
uuidstr);
+ goto cleanup;
+ }
+
+ priv = vm->privateData;
+
+ if (virDomainObjIsActive(vm)) {
+ if (priv->jobActive) {
+ memcpy(info, &priv->jobInfo, sizeof(*info));
+ } else {
+ memset(info, 0, sizeof(*info));
+ info->type = VIR_DOMAIN_JOB_NONE;
+ }
+ } else {
+ qemudReportError(dom->conn, NULL, NULL, VIR_ERR_OPERATION_INVALID,
+ "%s", _("domain is not running"));
+ goto cleanup;
+ }
+
+ ret = 0;
+
+cleanup:
+ if (vm)
+ virDomainObjUnlock(vm);
+ return ret;
+}
+
+
+
static virDriver qemuDriver = {
VIR_DRV_QEMU,
"QEMU",
@@ -8606,7 +8747,7 @@ static virDriver qemuDriver = {
qemuDomainIsActive,
qemuDomainIsPersistent,
qemuCPUCompare, /* cpuCompare */
- NULL, /* domainGetJobInfo */
+ qemuDomainGetJobInfo, /* domainGetJobInfo */
};
diff --git a/src/qemu/qemu_monitor_text.c b/src/qemu/qemu_monitor_text.c
index 42ce363..ac90d85 100644
--- a/src/qemu/qemu_monitor_text.c
+++ b/src/qemu/qemu_monitor_text.c
@@ -997,18 +997,19 @@ int qemuMonitorTextGetMigrationStatus(qemuMonitorPtr mon,
goto done;
tmp += strlen(MIGRATION_TRANSFER_PREFIX);
- if (virStrToLong_ull(tmp, NULL, 10, transferred) < 0) {
+ if (virStrToLong_ull(tmp, &end, 10, transferred) < 0 || !end) {
qemudReportError(NULL, NULL, NULL, VIR_ERR_INTERNAL_ERROR,
_("cannot parse migration data transferred
statistic %s"), tmp);
goto cleanup;
}
*transferred *= 1024;
+ tmp = end;
if (!(tmp = strstr(tmp, MIGRATION_REMAINING_PREFIX)))
goto done;
tmp += strlen(MIGRATION_REMAINING_PREFIX);
- if (virStrToLong_ull(tmp, NULL, 10, remaining) < 0) {
+ if (virStrToLong_ull(tmp, &end, 10, remaining) < 0 || !end) {
qemudReportError(NULL, NULL, NULL, VIR_ERR_INTERNAL_ERROR,
_("cannot parse migration data remaining statistic
%s"), tmp);
goto cleanup;
@@ -1019,7 +1020,7 @@ int qemuMonitorTextGetMigrationStatus(qemuMonitorPtr mon,
goto done;
tmp += strlen(MIGRATION_TOTAL_PREFIX);
- if (virStrToLong_ull(tmp, NULL, 10, total) < 0) {
+ if (virStrToLong_ull(tmp, &end, 10, total) < 0 || !end) {
qemudReportError(NULL, NULL, NULL, VIR_ERR_INTERNAL_ERROR,
_("cannot parse migration data total statistic
%s"), tmp);
goto cleanup;
diff --git a/tools/virsh.c b/tools/virsh.c
index 01d2038..bb8190c 100644
--- a/tools/virsh.c
+++ b/tools/virsh.c
@@ -1791,6 +1791,80 @@ cmdDominfo(vshControl *ctl, const vshCmd *cmd)
}
/*
+ * "domjobinfo" command
+ */
+static const vshCmdInfo info_domjobinfo[] = {
+ {"help", gettext_noop("domain job information")},
+ {"desc", gettext_noop("Returns information about jobs running on a
domain.")},
+ {NULL, NULL}
+};
+
+static const vshCmdOptDef opts_domjobinfo[] = {
+ {"domain", VSH_OT_DATA, VSH_OFLAG_REQ, gettext_noop("domain name, id
or uuid")},
+ {NULL, 0, 0, NULL}
+};
+
+static int
+cmdDomjobinfo(vshControl *ctl, const vshCmd *cmd)
+{
+ virDomainJobInfo info;
+ virDomainPtr dom;
+ int ret = TRUE, autostart;
+ unsigned int id;
+ char *str, uuid[VIR_UUID_STRING_BUFLEN];
+
+ if (!vshConnectionUsability(ctl, ctl->conn, TRUE))
+ return FALSE;
+
+ if (!(dom = vshCommandOptDomain(ctl, cmd, NULL)))
+ return FALSE;
+
+ if (virDomainGetJobInfo(dom, &info) == 0) {
+ vshPrint(ctl, "%-17s ", _("Job type:"));
+ switch (info.type) {
+ case VIR_DOMAIN_JOB_BOUNDED:
+ vshPrint(ctl, "%-12s\n", _("Bounded"));
+ break;
+
+ case VIR_DOMAIN_JOB_UNBOUNDED:
+ vshPrint(ctl, "%-12s\n", _("Unbounded"));
+ break;
+
+ case VIR_DOMAIN_JOB_NONE:
+ default:
+ vshPrint(ctl, "%-12s\n", _("None"));
+ goto cleanup;
+ }
+
+ if (info.type == VIR_DOMAIN_JOB_BOUNDED)
+ vshPrint(ctl, "%-17s %-12d%%\n", _("Completion:"),
info.percentComplete);
+ vshPrint(ctl, "%-17s %-12llu ms\n", _("Time elapsed:"),
info.timeElapsed);
+ if (info.type == VIR_DOMAIN_JOB_BOUNDED)
+ vshPrint(ctl, "%-17s %-12llu ms\n", _("Time remaining:"),
info.timeRemaining);
+ if (info.dataTotal || info.dataRemaining || info.dataProcessed) {
+ vshPrint(ctl, "%-17s %-12llu bytes\n", _("Data
processed:"), info.dataProcessed);
+ vshPrint(ctl, "%-17s %-12llu bytes\n", _("Data
remaining:"), info.dataRemaining);
+ vshPrint(ctl, "%-17s %-12llu bytes\n", _("Data total:"),
info.dataTotal);
+ }
+ if (info.memTotal || info.memRemaining || info.memProcessed) {
+ vshPrint(ctl, "%-17s %-12llu bytes\n", _("Memory
processed:"), info.memProcessed);
+ vshPrint(ctl, "%-17s %-12llu bytes\n", _("Memory
remaining:"), info.memRemaining);
+ vshPrint(ctl, "%-17s %-12llu bytes\n", _("Memory
total:"), info.memTotal);
+ }
+ if (info.fileTotal || info.fileRemaining || info.fileProcessed) {
+ vshPrint(ctl, "%-17s %-12llu bytes\n", _("File
processed:"), info.fileProcessed);
+ vshPrint(ctl, "%-17s %-12llu bytes\n", _("File
remaining:"), info.fileRemaining);
+ vshPrint(ctl, "%-17s %-12llu bytes\n", _("File total:"),
info.fileTotal);
+ }
+ } else {
+ ret = FALSE;
+ }
+cleanup:
+ virDomainFree(dom);
+ return ret;
+}
+
+/*
* "freecell" command
*/
static const vshCmdInfo info_freecell[] = {
@@ -7340,6 +7414,7 @@ static const vshCmdDef commands[] = {
{"domid", cmdDomid, opts_domid, info_domid},
{"domuuid", cmdDomuuid, opts_domuuid, info_domuuid},
{"dominfo", cmdDominfo, opts_dominfo, info_dominfo},
+ {"domjobinfo", cmdDomjobinfo, opts_domjobinfo, info_domjobinfo},
{"domname", cmdDomname, opts_domname, info_domname},
{"domstate", cmdDomstate, opts_domstate, info_domstate},
{"domblkstat", cmdDomblkstat, opts_domblkstat, info_domblkstat},
--
1.6.6