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 | 208 +++++++++++++++++++++++++++++++++++-------
src/qemu/qemu_monitor_text.c | 7 +-
tools/virsh.c | 129 ++++++++++++++++++++++----
3 files changed, 288 insertions(+), 56 deletions(-)
diff --git a/src/qemu/qemu_driver.c b/src/qemu/qemu_driver.c
index a6dc4f9..b245eb2 100644
--- a/src/qemu/qemu_driver.c
+++ b/src/qemu/qemu_driver.c
@@ -87,6 +87,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);
@@ -3919,6 +3927,96 @@ 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_UNBOUNDED) {
+ /* 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) {
+ priv->jobInfo.type = VIR_DOMAIN_JOB_FAILED;
+ goto cleanup;
+ }
+
+ if (gettimeofday(&now, NULL) < 0) {
+ priv->jobInfo.type = VIR_DOMAIN_JOB_FAILED;
+ virReportSystemError(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;
+ qemuReportError(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_COMPLETED;
+ ret = 0;
+ break;
+
+ case QEMU_MONITOR_MIGRATION_STATUS_ERROR:
+ priv->jobInfo.type = VIR_DOMAIN_JOB_FAILED;
+ qemuReportError(VIR_ERR_OPERATION_FAILED,
+ "%s", _("Migration unexpectedly
failed"));
+ break;
+
+ case QEMU_MONITOR_MIGRATION_STATUS_CANCELLED:
+ priv->jobInfo.type = VIR_DOMAIN_JOB_CANCELLED;
+ qemuReportError(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
@@ -3967,6 +4065,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));
@@ -3995,6 +4094,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;
@@ -4005,9 +4105,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) {
@@ -4061,26 +4163,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(vm, path) == -1)
@@ -4103,7 +4208,7 @@ static int qemudDomainSave(virDomainPtr dom,
endjob:
if (vm &&
qemuDomainObjEndJob(vm) == 0)
- vm = NULL;
+ vm = NULL;
cleanup:
if (fd != -1)
@@ -4132,6 +4237,7 @@ static int qemudDomainCoreDump(virDomainPtr dom,
"cat",
NULL,
};
+ qemuDomainObjPrivatePtr priv;
qemuDriverLock(driver);
vm = virDomainFindByUUID(&driver->domains, dom->uuid);
@@ -4144,6 +4250,7 @@ static int qemudDomainCoreDump(virDomainPtr dom,
_("no domain with matching uuid '%s'"),
uuidstr);
goto cleanup;
}
+ priv = vm->privateData;
if (qemuDomainObjBeginJob(vm) < 0)
goto cleanup;
@@ -4177,8 +4284,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);
@@ -4191,9 +4296,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 &&
@@ -7888,8 +8002,6 @@ static int doNativeMigrate(struct qemud_driver *driver,
{
int ret = -1;
xmlURIPtr uribits = NULL;
- int status;
- unsigned long long transferred, remaining, total;
qemuDomainObjPrivatePtr priv = vm->privateData;
/* Issue the migrate command. */
@@ -7918,29 +8030,14 @@ static int doNativeMigrate(struct qemud_driver *driver,
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) {
- qemuReportError(VIR_ERR_OPERATION_FAILED,
- "%s", _("migrate did not successfully
complete"));
+ if (qemuDomainWaitForMigrationComplete(driver, vm) < 0)
goto cleanup;
- }
ret = 0;
@@ -8291,6 +8388,7 @@ qemudDomainMigratePerform (virDomainPtr dom,
virDomainEventPtr event = NULL;
int ret = -1;
int paused = 0;
+ qemuDomainObjPrivatePtr priv;
qemuDriverLock(driver);
vm = virDomainFindByUUID(&driver->domains, dom->uuid);
@@ -8301,6 +8399,7 @@ qemudDomainMigratePerform (virDomainPtr dom,
_("no domain with matching uuid '%s'"),
uuidstr);
goto cleanup;
}
+ priv = vm->privateData;
if (qemuDomainObjBeginJobWithDriver(driver, vm) < 0)
goto cleanup;
@@ -8311,8 +8410,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) {
@@ -8357,7 +8458,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) {
@@ -8654,6 +8754,7 @@ qemuCPUCompare(virConnectPtr conn,
return ret;
}
+
static char *
qemuCPUBaseline(virConnectPtr conn ATTRIBUTE_UNUSED,
const char **xmlCPUs,
@@ -8667,6 +8768,49 @@ qemuCPUBaseline(virConnectPtr conn ATTRIBUTE_UNUSED,
return cpu;
}
+
+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);
+ qemuReportError(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 {
+ qemuReportError(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",
@@ -8746,7 +8890,7 @@ static virDriver qemuDriver = {
qemuDomainIsPersistent,
qemuCPUCompare, /* cpuCompare */
qemuCPUBaseline, /* cpuBaseline */
- NULL, /* domainGetJobInfo */
+ qemuDomainGetJobInfo, /* domainGetJobInfo */
};
diff --git a/src/qemu/qemu_monitor_text.c b/src/qemu/qemu_monitor_text.c
index 62ffcc6..3f27e49 100644
--- a/src/qemu/qemu_monitor_text.c
+++ b/src/qemu/qemu_monitor_text.c
@@ -1004,18 +1004,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) {
qemuReportError(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) {
qemuReportError(VIR_ERR_INTERNAL_ERROR,
_("cannot parse migration data remaining statistic
%s"), tmp);
goto cleanup;
@@ -1026,7 +1027,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) {
qemuReportError(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 e1d1300..0bf03df 100644
--- a/tools/virsh.c
+++ b/tools/virsh.c
@@ -327,6 +327,28 @@ static int namesorter(const void *a, const void *b) {
return strcasecmp(*sa, *sb);
}
+static double
+prettyCapacity(unsigned long long val,
+ const char **unit) {
+ if (val < 1024) {
+ *unit = "";
+ return (double)val;
+ } else if (val < (1024.0l * 1024.0l)) {
+ *unit = "KB";
+ return (((double)val / 1024.0l));
+ } else if (val < (1024.0l * 1024.0l * 1024.0l)) {
+ *unit = "MB";
+ return ((double)val / (1024.0l * 1024.0l));
+ } else if (val < (1024.0l * 1024.0l * 1024.0l * 1024.0l)) {
+ *unit = "GB";
+ return ((double)val / (1024.0l * 1024.0l * 1024.0l));
+ } else {
+ *unit = "TB";
+ return ((double)val / (1024.0l * 1024.0l * 1024.0l * 1024.0l));
+ }
+}
+
+
static virErrorPtr last_error;
/*
@@ -1792,6 +1814,91 @@ 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) {
+ const char *unit;
+ double val;
+
+ 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;
+ }
+
+ 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) {
+ val = prettyCapacity(info.dataProcessed, &unit);
+ vshPrint(ctl, "%-17s %-0.3ld %s\n", _("Data processed:"),
val, unit);
+ val = prettyCapacity(info.dataRemaining, &unit);
+ vshPrint(ctl, "%-17s %-0.3ld %s\n", _("Data remaining:"),
val, unit);
+ val = prettyCapacity(info.dataTotal, &unit);
+ vshPrint(ctl, "%-17s %-0.3ld %s\n", _("Data total:"),
val, unit);
+ }
+ if (info.memTotal || info.memRemaining || info.memProcessed) {
+ val = prettyCapacity(info.memProcessed, &unit);
+ vshPrint(ctl, "%-17s %-0.3ld %s\n", _("Memory
processed:"), val, unit);
+ val = prettyCapacity(info.memRemaining, &unit);
+ vshPrint(ctl, "%-17s %-0.3ld %s\n", _("Memory
remaining:"), val, unit);
+ val = prettyCapacity(info.memTotal, &unit);
+ vshPrint(ctl, "%-17s %-0.3ld %s\n", _("Memory total:"),
val, unit);
+ }
+ if (info.fileTotal || info.fileRemaining || info.fileProcessed) {
+ val = prettyCapacity(info.fileProcessed, &unit);
+ vshPrint(ctl, "%-17s %-0.3ld %s\n", _("File processed:"),
val, unit);
+ val = prettyCapacity(info.fileRemaining, &unit);
+ vshPrint(ctl, "%-17s %-0.3ld %s\n", _("File remaining:"),
val, unit);
+ val = prettyCapacity(info.fileTotal, &unit);
+ vshPrint(ctl, "%-17s %-0.3ld %s\n", _("File total:"),
val, unit);
+ }
+ } else {
+ ret = FALSE;
+ }
+cleanup:
+ virDomainFree(dom);
+ return ret;
+}
+
+/*
* "freecell" command
*/
static const vshCmdInfo info_freecell[] = {
@@ -4453,27 +4560,6 @@ cmdPoolDiscoverSources(vshControl * ctl, const vshCmd * cmd
ATTRIBUTE_UNUSED)
}
-static double
-prettyCapacity(unsigned long long val,
- const char **unit) {
- if (val < 1024) {
- *unit = "";
- return (double)val;
- } else if (val < (1024.0l * 1024.0l)) {
- *unit = "KB";
- return (((double)val / 1024.0l));
- } else if (val < (1024.0l * 1024.0l * 1024.0l)) {
- *unit = "MB";
- return ((double)val / (1024.0l * 1024.0l));
- } else if (val < (1024.0l * 1024.0l * 1024.0l * 1024.0l)) {
- *unit = "GB";
- return ((double)val / (1024.0l * 1024.0l * 1024.0l));
- } else {
- *unit = "TB";
- return ((double)val / (1024.0l * 1024.0l * 1024.0l * 1024.0l));
- }
-}
-
/*
* "pool-info" command
*/
@@ -7526,6 +7612,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