Signed-off-by: Chris Lalancette <clalance@xxxxxxxxxx> --- src/qemu/qemu_conf.c | 9 +- src/qemu/qemu_conf.h | 4 +- src/qemu/qemu_driver.c | 757 ++++++++++++++++++++++++++++++++++++++++- src/qemu/qemu_monitor.c | 39 +++ src/qemu/qemu_monitor.h | 4 + src/qemu/qemu_monitor_json.c | 24 ++ src/qemu/qemu_monitor_json.h | 4 + src/qemu/qemu_monitor_text.c | 126 +++++++ src/qemu/qemu_monitor_text.h | 4 + tests/qemuxml2argvtest.c | 2 +- 10 files changed, 951 insertions(+), 22 deletions(-) diff --git a/src/qemu/qemu_conf.c b/src/qemu/qemu_conf.c index 5d0b211..1ebb29a 100644 --- a/src/qemu/qemu_conf.c +++ b/src/qemu/qemu_conf.c @@ -3362,7 +3362,9 @@ int qemudBuildCommandLine(virConnectPtr conn, const char ***retenv, int **tapfds, int *ntapfds, - const char *migrateFrom) { + const char *migrateFrom, + int restore_snapshot) +{ int i; char memory[50]; char boot[VIR_DOMAIN_BOOT_LAST]; @@ -4567,6 +4569,11 @@ int qemudBuildCommandLine(virConnectPtr conn, ADD_ARG_LIT("virtio"); } + if (def->current_snapshot && restore_snapshot) { + ADD_ARG_LIT("-loadvm"); + ADD_ARG_LIT(def->current_snapshot->name); + } + ADD_ARG(NULL); ADD_ENV(NULL); diff --git a/src/qemu/qemu_conf.h b/src/qemu/qemu_conf.h index 39518ca..30d9d69 100644 --- a/src/qemu/qemu_conf.h +++ b/src/qemu/qemu_conf.h @@ -120,6 +120,7 @@ struct qemud_driver { * the QEMU user/group */ char *libDir; char *cacheDir; + char *snapshotDir; unsigned int vncTLS : 1; unsigned int vncTLSx509verify : 1; unsigned int vncSASL : 1; @@ -198,7 +199,8 @@ int qemudBuildCommandLine (virConnectPtr conn, const char ***retenv, int **tapfds, int *ntapfds, - const char *migrateFrom) + const char *migrateFrom, + int restore_snapshot) ATTRIBUTE_NONNULL(1); /* With vlan == -1, use netdev syntax, else old hostnet */ diff --git a/src/qemu/qemu_driver.c b/src/qemu/qemu_driver.c index 695e5ba..7df15a6 100644 --- a/src/qemu/qemu_driver.c +++ b/src/qemu/qemu_driver.c @@ -148,7 +148,8 @@ static int qemudStartVMDaemon(virConnectPtr conn, struct qemud_driver *driver, virDomainObjPtr vm, const char *migrateFrom, - int stdin_fd); + int stdin_fd, + int restore_snapshot); static void qemudShutdownVMDaemon(struct qemud_driver *driver, virDomainObjPtr vm); @@ -629,7 +630,7 @@ qemuAutostartDomain(void *payload, const char *name ATTRIBUTE_UNUSED, void *opaq int ret; virResetLastError(); - ret = qemudStartVMDaemon(data->conn, data->driver, vm, NULL, -1); + ret = qemudStartVMDaemon(data->conn, data->driver, vm, NULL, -1, 0); if (ret < 0) { virErrorPtr err = virGetLastError(); VIR_ERROR(_("Failed to autostart VM '%s': %s"), @@ -1342,6 +1343,89 @@ no_memory: return NULL; } +static void qemuDomainSnapshotLoad(void *payload, + const char *name ATTRIBUTE_UNUSED, + void *data) +{ + virDomainObjPtr vm = (virDomainObjPtr)payload; + char *baseDir = (char *)data; + char *snapDir = NULL; + DIR *dir = NULL; + struct dirent *entry; + char *xmlStr; + int ret; + char *fullpath; + virDomainSnapshotObjPtr snap = NULL; + virDomainSnapshotDefPtr def = NULL; + + virDomainObjLock(vm); + if (virAsprintf(&snapDir, "%s/%s", baseDir, vm->def->name) < 0) { + VIR_ERROR("Failed to allocate memory for snapshot directory for domain %s", + vm->def->name); + goto cleanup; + } + + VIR_INFO("Scanning for snapshots for domain %s in %s", vm->def->name, + snapDir); + + if (!(dir = opendir(snapDir))) { + if (errno != ENOENT) + VIR_ERROR("Failed to open snapshot directory %s for domain %s: %s", + snapDir, vm->def->name, strerror(errno)); + goto cleanup; + } + + while ((entry = readdir(dir))) { + if (entry->d_name[0] == '.') + continue; + + /* NB: ignoring errors, so one malformed config doesn't + kill the whole process */ + VIR_INFO("Loading snapshot file '%s'", entry->d_name); + + if (virAsprintf(&fullpath, "%s/%s", snapDir, entry->d_name) < 0) + continue; + + ret = virFileReadAll(fullpath, 1024*1024*1, &xmlStr); + VIR_FREE(fullpath); + if (ret < 0) { + /* Nothing we can do here, skip this one */ + VIR_ERROR("Failed to read snapshot file %s: %s", fullpath, + strerror(errno)); + continue; + } + + def = virDomainSnapshotDefParseString(xmlStr, 0); + if (def == NULL) { + /* Nothing we can do here, skip this one */ + VIR_ERROR("Failed to parse snapshot XML from file '%s'", fullpath); + VIR_FREE(xmlStr); + continue; + } + + snap = virDomainSnapshotAssignDef(&vm->snapshots, def); + + VIR_FREE(xmlStr); + } + + /* FIXME: qemu keeps internal track of snapshots. We can get access + * to this info via the "info snapshots" monitor command for running + * domains, or via "qemu-img snapshot -l" for shutoff domains. It would + * be nice to update our internal state based on that, but there is a + * a problem. qemu doesn't track all of the same metadata that we do. + * In particular we wouldn't be able to fill in the <parent>, which is + * pretty important in our metadata. + */ + + virResetLastError(); + +cleanup: + if (dir) + closedir(dir); + VIR_FREE(snapDir); + virDomainObjUnlock(vm); +} + /** * qemudStartup: * @@ -1399,6 +1483,9 @@ qemudStartup(int privileged) { if (virAsprintf(&qemu_driver->cacheDir, "%s/cache/libvirt/qemu", LOCAL_STATE_DIR) == -1) goto out_of_memory; + if (virAsprintf(&qemu_driver->snapshotDir, + "%s/run/libvirt/qemu/snapshot", LOCAL_STATE_DIR) == -1) + goto out_of_memory; } else { uid_t uid = geteuid(); char *userdir = virGetUserDirectory(uid); @@ -1423,6 +1510,8 @@ qemudStartup(int privileged) { goto out_of_memory; if (virAsprintf(&qemu_driver->cacheDir, "%s/qemu/cache", base) == -1) goto out_of_memory; + if (virAsprintf(&qemu_driver->snapshotDir, "%s/qemu/snapshot", base) == -1) + goto out_of_memory; } if (virFileMakePath(qemu_driver->stateDir) != 0) { @@ -1443,6 +1532,12 @@ qemudStartup(int privileged) { qemu_driver->cacheDir, virStrerror(errno, ebuf, sizeof ebuf)); goto error; } + if (virFileMakePath(qemu_driver->snapshotDir) != 0) { + char ebuf[1024]; + VIR_ERROR(_("Failed to create save dir '%s': %s"), + qemu_driver->snapshotDir, virStrerror(errno, ebuf, sizeof ebuf)); + goto error; + } /* Configuration paths are either ~/.libvirt/qemu/... (session) or * /etc/libvirt/qemu/... (system). @@ -1493,6 +1588,12 @@ qemudStartup(int privileged) { qemu_driver->cacheDir, qemu_driver->user, qemu_driver->group); goto error; } + if (chown(qemu_driver->snapshotDir, qemu_driver->user, qemu_driver->group) < 0) { + virReportSystemError(errno, + _("unable to set ownership of '%s' to %d:%d"), + qemu_driver->snapshotDir, qemu_driver->user, qemu_driver->group); + goto error; + } } /* If hugetlbfs is present, then we need to create a sub-directory within @@ -1543,6 +1644,11 @@ qemudStartup(int privileged) { qemu_driver->autostartDir, 0, NULL, NULL) < 0) goto error; + + + virHashForEach(qemu_driver->domains.objs, qemuDomainSnapshotLoad, + qemu_driver->snapshotDir); + qemuDriverUnlock(qemu_driver); qemudAutostartConfigs(qemu_driver); @@ -1645,6 +1751,7 @@ qemudShutdown(void) { VIR_FREE(qemu_driver->stateDir); VIR_FREE(qemu_driver->libDir); VIR_FREE(qemu_driver->cacheDir); + VIR_FREE(qemu_driver->snapshotDir); VIR_FREE(qemu_driver->vncTLSx509certdir); VIR_FREE(qemu_driver->vncListen); VIR_FREE(qemu_driver->vncPassword); @@ -3012,7 +3119,9 @@ static int qemudStartVMDaemon(virConnectPtr conn, struct qemud_driver *driver, virDomainObjPtr vm, const char *migrateFrom, - int stdin_fd) { + int stdin_fd, + int restore_snapshot) +{ const char **argv = NULL, **tmp; const char **progenv = NULL; int i, ret; @@ -3175,7 +3284,7 @@ static int qemudStartVMDaemon(virConnectPtr conn, vm->def->id = driver->nextvmid++; if (qemudBuildCommandLine(conn, driver, vm->def, priv->monConfig, priv->monJSON, qemuCmdFlags, &argv, &progenv, - &tapfds, &ntapfds, migrateFrom) < 0) + &tapfds, &ntapfds, migrateFrom, restore_snapshot) < 0) goto cleanup; /* now that we know it is about to start call the hook if present */ @@ -3917,7 +4026,7 @@ static virDomainPtr qemudDomainCreate(virConnectPtr conn, const char *xml, if (qemuDomainObjBeginJobWithDriver(driver, vm) < 0) goto cleanup; /* XXXX free the 'vm' we created ? */ - if (qemudStartVMDaemon(conn, driver, vm, NULL, -1) < 0) { + if (qemudStartVMDaemon(conn, driver, vm, NULL, -1, 0) < 0) { if (qemuDomainObjEndJob(vm) > 0) virDomainRemoveInactive(&driver->domains, vm); @@ -5600,7 +5709,7 @@ static int qemudDomainRestore(virConnectPtr conn, } } /* Set the migration source and start it up. */ - ret = qemudStartVMDaemon(conn, driver, vm, "stdio", fd); + ret = qemudStartVMDaemon(conn, driver, vm, "stdio", fd, 0); if (intermediate_pid != -1) { /* Wait for intermediate process to exit */ while (waitpid(intermediate_pid, &childstat, 0) == -1 && @@ -5906,7 +6015,7 @@ static char *qemuDomainXMLToNative(virConnectPtr conn, &monConfig, 0, qemuCmdFlags, &retargv, &retenv, NULL, NULL, /* Don't want it to create TAP devices */ - NULL) < 0) { + NULL, 0) < 0) { goto cleanup; } @@ -5995,7 +6104,7 @@ static int qemudDomainStart(virDomainPtr dom) { goto endjob; } - ret = qemudStartVMDaemon(dom->conn, driver, vm, NULL, -1); + ret = qemudStartVMDaemon(dom->conn, driver, vm, NULL, -1, 0); if (ret != -1) event = virDomainEventNewFromObj(vm, VIR_DOMAIN_EVENT_STARTED, @@ -9060,7 +9169,7 @@ qemudDomainMigratePrepareTunnel(virConnectPtr dconn, /* Start the QEMU daemon, with the same command-line arguments plus * -incoming unix:/path/to/file or exec:nc -U /path/to/file */ - internalret = qemudStartVMDaemon(dconn, driver, vm, migrateFrom, -1); + internalret = qemudStartVMDaemon(dconn, driver, vm, migrateFrom, -1, 0); VIR_FREE(migrateFrom); if (internalret < 0) { /* Note that we don't set an error here because qemudStartVMDaemon @@ -9265,7 +9374,7 @@ qemudDomainMigratePrepare2 (virConnectPtr dconn, * -incoming tcp:0.0.0.0:port */ snprintf (migrateFrom, sizeof (migrateFrom), "tcp:0.0.0.0:%d", this_port); - if (qemudStartVMDaemon (dconn, driver, vm, migrateFrom, -1) < 0) { + if (qemudStartVMDaemon (dconn, driver, vm, migrateFrom, -1, 0) < 0) { /* Note that we don't set an error here because qemudStartVMDaemon * should have already done that. */ @@ -10221,6 +10330,616 @@ cleanup: return ret; } +static char *qemuFindQemuImgBinary(void) +{ + char *ret; + + ret = virFindFileInPath("kvm-img"); + if (ret == NULL) + ret = virFindFileInPath("qemu-img"); + if (ret == NULL) + qemuReportError(VIR_ERR_INTERNAL_ERROR, + "%s", _("unable to find kvm-img or qemu-img")); + + return ret; +} + +static int qemuDomainSnapshotWriteSnapshotMetadata(virDomainObjPtr vm, + virDomainSnapshotDefPtr def, + char *snapshotDir) +{ + int fd = -1; + char *newxml = NULL; + int ret = -1; + char *snapDir = NULL; + char *snapFile = NULL; + int err; + char uuidstr[VIR_UUID_STRING_BUFLEN]; + + virUUIDFormat(vm->def->uuid, uuidstr); + newxml = virDomainSnapshotDefFormat(uuidstr, def); + if (newxml == NULL) { + virReportOOMError(); + return -1; + } + + if (virAsprintf(&snapDir, "%s/%s", snapshotDir, vm->def->name) < 0) { + virReportOOMError(); + goto cleanup; + } + err = virFileMakePath(snapDir); + if (err < 0) { + virReportSystemError(err, _("cannot create snapshot directory '%s'"), + snapDir); + goto cleanup; + } + + if (virAsprintf(&snapFile, "%s/%s.snap", snapDir, def->name) < 0) { + virReportOOMError(); + goto cleanup; + } + fd = open(snapFile, O_CREAT|O_TRUNC|O_WRONLY, S_IRUSR|S_IWUSR); + if (fd < 0) { + qemuReportError(VIR_ERR_OPERATION_FAILED, + _("failed to create snapshot file '%s'"), snapFile); + goto cleanup; + } + if (safewrite(fd, newxml, strlen(newxml)) != strlen(newxml)) { + virReportSystemError(errno, _("Failed to write snapshot data to %s"), + snapFile); + goto cleanup; + } + + ret = 0; + +cleanup: + VIR_FREE(snapFile); + VIR_FREE(snapDir); + VIR_FREE(newxml); + if (fd != -1) + close(fd); + return ret; +} + +static int qemuDomainSnapshotIsAllowed(virDomainObjPtr vm) +{ + int i; + + /* FIXME: we need to figure out what else here might succeed; in + * particular, if it's a raw device but on LVM, we could probably make + * that succeed as well + */ + /* FIXME: we also need to handle devices that are using pools */ + for (i = 0; i < vm->def->ndisks; i++) { + if (vm->def->disks[i]->device == VIR_DOMAIN_DISK_DEVICE_DISK && + (!vm->def->disks[i]->driverType || + STRNEQ(vm->def->disks[i]->driverType, "qcow2"))) { + qemuReportError(VIR_ERR_OPERATION_INVALID, + _("Disk device '%s' does not support snapshotting"), + vm->def->disks[i]->info.alias); + return 0; + } + } + + return 1; +} + +static virDomainSnapshotPtr qemuDomainSnapshotCreateXML(virDomainPtr domain, + const char *xmlDesc, + unsigned int flags ATTRIBUTE_UNUSED) +{ + struct qemud_driver *driver = domain->conn->privateData; + virDomainObjPtr vm = NULL; + virDomainSnapshotObjPtr snap = NULL; + virDomainSnapshotPtr snapshot = NULL; + char uuidstr[VIR_UUID_STRING_BUFLEN]; + virDomainSnapshotDefPtr def; + qemuDomainObjPrivatePtr priv; + const char *qemuimgarg[] = { NULL, "snapshot", "-c", NULL, NULL, NULL }; + int i; + + qemuDriverLock(driver); + virUUIDFormat(domain->uuid, uuidstr); + vm = virDomainFindByUUID(&driver->domains, domain->uuid); + if (!vm) { + qemuReportError(VIR_ERR_NO_DOMAIN, + _("no domain with matching uuid '%s'"), uuidstr); + goto cleanup; + } + + /* in a perfect world, we would allow qemu to tell us this. The problem + * is that qemu only does this check device-by-device; so if you had a + * domain that booted from a large qcow2 device, but had a secondary raw + * device attached, you wouldn't find out that you can't snapshot your + * guest until *after* it had spent the time to snapshot the boot device. + * This is probably a bug in qemu, but we'll work around it here for now. + */ + if (!qemuDomainSnapshotIsAllowed(vm)) + goto cleanup; + + if (!(def = virDomainSnapshotDefParseString(xmlDesc, 1))) + goto cleanup; + + if (!(snap = virDomainSnapshotAssignDef(&vm->snapshots, def))) + goto cleanup; + + /* actually do the snapshot */ + if (!virDomainObjIsActive(vm)) { + qemuimgarg[0] = qemuFindQemuImgBinary(); + if (qemuimgarg[0] == NULL) + /* qemuFindQemuImgBinary set the error */ + goto cleanup; + + qemuimgarg[3] = snap->def->name; + + for (i = 0; i < vm->def->ndisks; i++) { + /* FIXME: we also need to handle LVM here */ + /* FIXME: if we fail halfway through this loop, we are in an + * inconsistent state. I'm not quite sure what to do about that + */ + if (vm->def->disks[i]->device == VIR_DOMAIN_DISK_DEVICE_DISK) { + if (!vm->def->disks[i]->driverType || + STRNEQ(vm->def->disks[i]->driverType, "qcow2")) { + qemuReportError(VIR_ERR_OPERATION_INVALID, + _("Disk device '%s' does not support snapshotting"), + vm->def->disks[i]->info.alias); + goto cleanup; + } + + qemuimgarg[4] = vm->def->disks[i]->src; + + if (virRun(qemuimgarg, NULL) < 0) { + virReportSystemError(errno, + _("Failed to run '%s' to create snapshot '%s' from disk '%s'"), + qemuimgarg[0], snap->def->name, + vm->def->disks[i]->src); + goto cleanup; + } + } + } + } + else { + priv = vm->privateData; + qemuDomainObjEnterMonitorWithDriver(driver, vm); + if (qemuMonitorCreateSnapshot(priv->mon, def->name) < 0) { + qemuDomainObjExitMonitorWithDriver(driver, vm); + goto cleanup; + } + qemuDomainObjExitMonitorWithDriver(driver, vm); + + } + + snap->def->state = vm->state; + + /* FIXME: if we fail after this point, there's not a whole lot we can + * do; we've successfully taken the snapshot, and we are now running + * on it, so we have to go forward the best we can + */ + + if (vm->def->current_snapshot) { + def->parent = strdup(vm->def->current_snapshot->name); + if (def->parent == NULL) { + virReportOOMError(); + goto cleanup; + } + } + + /* Now we set the new current_snapshot for the domain */ + vm->def->current_snapshot = def; + /* FIXME: Do we have to take an additional reference? */ + + if (qemuDomainSnapshotWriteSnapshotMetadata(vm, def, + driver->snapshotDir) < 0) + /* qemuDomainSnapshotWriteSnapshotMetadata set the error */ + goto cleanup; + + snapshot = virGetDomainSnapshot(domain, snap->def->name); + +cleanup: + VIR_FREE(qemuimgarg[0]); + if (vm) + virDomainObjUnlock(vm); + qemuDriverUnlock(driver); + return snapshot; +} + +static int qemuDomainSnapshotListNames(virDomainPtr domain, char **names, + int nameslen, + unsigned int flags ATTRIBUTE_UNUSED) +{ + struct qemud_driver *driver = domain->conn->privateData; + virDomainObjPtr vm = NULL; + int n = -1; + + qemuDriverLock(driver); + vm = virDomainFindByUUID(&driver->domains, domain->uuid); + if (!vm) { + char uuidstr[VIR_UUID_STRING_BUFLEN]; + virUUIDFormat(domain->uuid, uuidstr); + qemuReportError(VIR_ERR_NO_DOMAIN, + _("no domain with matching uuid '%s'"), uuidstr); + goto cleanup; + } + + n = virDomainSnapshotObjListGetNames(&vm->snapshots, names, nameslen); + +cleanup: + if (vm) + virDomainObjUnlock(vm); + qemuDriverUnlock(driver); + return n; +} + +static int qemuDomainSnapshotNum(virDomainPtr domain, + unsigned int flags ATTRIBUTE_UNUSED) +{ + struct qemud_driver *driver = domain->conn->privateData; + virDomainObjPtr vm = NULL; + int n = -1; + + qemuDriverLock(driver); + vm = virDomainFindByUUID(&driver->domains, domain->uuid); + if (!vm) { + char uuidstr[VIR_UUID_STRING_BUFLEN]; + virUUIDFormat(domain->uuid, uuidstr); + qemuReportError(VIR_ERR_NO_DOMAIN, + _("no domain with matching uuid '%s'"), uuidstr); + goto cleanup; + } + + n = virDomainSnapshotObjListNum(&vm->snapshots); + +cleanup: + if (vm) + virDomainObjUnlock(vm); + qemuDriverUnlock(driver); + return n; +} + +static virDomainSnapshotPtr qemuDomainSnapshotLookupByName(virDomainPtr domain, + const char *name, + unsigned int flags ATTRIBUTE_UNUSED) +{ + struct qemud_driver *driver = domain->conn->privateData; + virDomainObjPtr vm; + virDomainSnapshotObjPtr snap = NULL; + virDomainSnapshotPtr snapshot = NULL; + + qemuDriverLock(driver); + vm = virDomainFindByUUID(&driver->domains, domain->uuid); + if (!vm) { + char uuidstr[VIR_UUID_STRING_BUFLEN]; + virUUIDFormat(domain->uuid, uuidstr); + qemuReportError(VIR_ERR_NO_DOMAIN, + _("no domain with matching uuid '%s'"), uuidstr); + goto cleanup; + } + + snap = virDomainSnapshotFindByName(&vm->snapshots, name); + if (!snap) { + qemuReportError(VIR_ERR_NO_DOMAIN_SNAPSHOT, + _("no snapshot with matching name '%s'"), name); + goto cleanup; + } + + snapshot = virGetDomainSnapshot(domain, snap->def->name); + +cleanup: + if (vm) + virDomainObjUnlock(vm); + qemuDriverUnlock(driver); + return snapshot; +} + +static int qemuDomainHasCurrentSnapshot(virDomainPtr domain, + unsigned int flags ATTRIBUTE_UNUSED) +{ + struct qemud_driver *driver = domain->conn->privateData; + virDomainObjPtr vm; + int ret = -1; + + qemuDriverLock(driver); + vm = virDomainFindByUUID(&driver->domains, domain->uuid); + if (!vm) { + char uuidstr[VIR_UUID_STRING_BUFLEN]; + virUUIDFormat(domain->uuid, uuidstr); + qemuReportError(VIR_ERR_NO_DOMAIN, + _("no domain with matching uuid '%s'"), uuidstr); + goto cleanup; + } + + ret = (vm->def->current_snapshot != NULL); + +cleanup: + if (vm) + virDomainObjUnlock(vm); + qemuDriverUnlock(driver); + return ret; +} + +static virDomainSnapshotPtr qemuDomainSnapshotCurrent(virDomainPtr domain, + unsigned int flags ATTRIBUTE_UNUSED) +{ + struct qemud_driver *driver = domain->conn->privateData; + virDomainObjPtr vm; + virDomainSnapshotPtr snapshot = NULL; + + qemuDriverLock(driver); + vm = virDomainFindByUUID(&driver->domains, domain->uuid); + if (!vm) { + char uuidstr[VIR_UUID_STRING_BUFLEN]; + virUUIDFormat(domain->uuid, uuidstr); + qemuReportError(VIR_ERR_NO_DOMAIN, + _("no domain with matching uuid '%s'"), uuidstr); + goto cleanup; + } + + if (!vm->def->current_snapshot) { + qemuReportError(VIR_ERR_NO_DOMAIN_SNAPSHOT, "%s", + _("the domain does not have a current snapshot")); + } + + snapshot = virGetDomainSnapshot(domain, vm->def->current_snapshot->name); + +cleanup: + if (vm) + virDomainObjUnlock(vm); + qemuDriverUnlock(driver); + return snapshot; +} + +static char *qemuDomainSnapshotDumpXML(virDomainSnapshotPtr snapshot, + unsigned int flags ATTRIBUTE_UNUSED) +{ + struct qemud_driver *driver = snapshot->domain->conn->privateData; + virDomainObjPtr vm = NULL; + char *xml = NULL; + virDomainSnapshotObjPtr snap = NULL; + char uuidstr[VIR_UUID_STRING_BUFLEN]; + + qemuDriverLock(driver); + virUUIDFormat(snapshot->domain->uuid, uuidstr); + vm = virDomainFindByUUID(&driver->domains, snapshot->domain->uuid); + if (!vm) { + qemuReportError(VIR_ERR_NO_DOMAIN, + _("no domain with matching uuid '%s'"), uuidstr); + goto cleanup; + } + + snap = virDomainSnapshotFindByName(&vm->snapshots, snapshot->name); + if (!snap) { + qemuReportError(VIR_ERR_NO_DOMAIN_SNAPSHOT, + _("no domain snapshot with matching name '%s'"), + snapshot->name); + goto cleanup; + } + + xml = virDomainSnapshotDefFormat(uuidstr, snap->def); + +cleanup: + if (vm) + virDomainObjUnlock(vm); + qemuDriverUnlock(driver); + return xml; +} + +static int qemuDomainCreateFromSnapshot(virDomainSnapshotPtr snapshot, + unsigned int flags ATTRIBUTE_UNUSED) +{ + struct qemud_driver *driver = snapshot->domain->conn->privateData; + virDomainObjPtr vm = NULL; + int ret = -1; + virDomainSnapshotObjPtr snap = NULL; + char uuidstr[VIR_UUID_STRING_BUFLEN]; + virDomainEventPtr event = NULL; + qemuDomainObjPrivatePtr priv; + + qemuDriverLock(driver); + virUUIDFormat(snapshot->domain->uuid, uuidstr); + vm = virDomainFindByUUID(&driver->domains, snapshot->domain->uuid); + if (!vm) { + qemuReportError(VIR_ERR_NO_DOMAIN, + _("no domain with matching uuid '%s'"), uuidstr); + goto cleanup; + } + + snap = virDomainSnapshotFindByName(&vm->snapshots, snapshot->name); + if (!snap) { + qemuReportError(VIR_ERR_NO_DOMAIN_SNAPSHOT, + _("no domain snapshot with matching name '%s'"), + snapshot->name); + goto cleanup; + } + + vm->def->current_snapshot = snap->def; + + if (virDomainObjIsActive(vm)) { + if (snap->def->state != VIR_DOMAIN_RUNNING + && snap->def->state != VIR_DOMAIN_PAUSED) { + qemuReportError(VIR_ERR_OPERATION_INVALID, "%s", + _("domain is running, but snapshot was taken while domain was off")); + goto cleanup; + } + priv = vm->privateData; + qemuDomainObjEnterMonitorWithDriver(driver, vm); + if (qemuMonitorLoadSnapshot(priv->mon, snap->def->name) < 0) { + qemuDomainObjExitMonitorWithDriver(driver, vm); + goto cleanup; + } + qemuDomainObjExitMonitorWithDriver(driver, vm); + + ret = 0; + } + else { + if (qemuDomainObjBeginJobWithDriver(driver, vm) < 0) + goto cleanup; + ret = qemudStartVMDaemon(snapshot->domain->conn, driver, vm, NULL, -1, 1); + if (ret != -1) + event = virDomainEventNewFromObj(vm, + VIR_DOMAIN_EVENT_STARTED, + VIR_DOMAIN_EVENT_STARTED_BOOTED); + if (qemuDomainObjEndJob(vm) == 0) + vm = NULL; + } + +cleanup: + if (vm) + virDomainObjUnlock(vm); + qemuDriverUnlock(driver); + + return ret; +} + +static int qemuDomainSnapshotDiscard(struct qemud_driver *driver, + virDomainObjPtr vm, + virDomainSnapshotObjPtr snap, + int delete_children ATTRIBUTE_UNUSED) +{ + const char *qemuimgarg[] = { NULL, "snapshot", "-d", NULL, NULL, NULL }; + char *snapFile = NULL; + int ret = -1; + int i; + qemuDomainObjPrivatePtr priv; + virDomainSnapshotObjPtr parentsnap = NULL; + + if (!virDomainObjIsActive(vm)) { + qemuimgarg[0] = qemuFindQemuImgBinary(); + if (qemuimgarg[0] == NULL) + /* qemuFindQemuImgBinary set the error */ + goto cleanup; + + qemuimgarg[3] = snap->def->name; + + for (i = 0; i < vm->def->ndisks; i++) { + /* FIXME: we also need to handle LVM here */ + /* FIXME: if we fail halfway through this loop, we are in an + * inconsistent state. I'm not quite sure what to do about that + */ + if (vm->def->disks[i]->device == VIR_DOMAIN_DISK_DEVICE_DISK) { + if (!vm->def->disks[i]->driverType || + STRNEQ(vm->def->disks[i]->driverType, "qcow2")) { + qemuReportError(VIR_ERR_OPERATION_INVALID, + _("Disk device '%s' does not support snapshotting"), + vm->def->disks[i]->info.alias); + goto cleanup; + } + + qemuimgarg[4] = vm->def->disks[i]->src; + + if (virRun(qemuimgarg, NULL) < 0) { + virReportSystemError(errno, + _("Failed to run '%s' to delete snapshot '%s' from disk '%s'"), + qemuimgarg[0], snap->def->name, + vm->def->disks[i]->src); + goto cleanup; + } + } + } + } + else { + priv = vm->privateData; + qemuDomainObjEnterMonitorWithDriver(driver, vm); + if (qemuMonitorDeleteSnapshot(priv->mon, snap->def->name) < 0) { + qemuDomainObjExitMonitorWithDriver(driver, vm); + goto cleanup; + } + qemuDomainObjExitMonitorWithDriver(driver, vm); + } + + if (snap->def == vm->def->current_snapshot) { + if (snap->def->parent) { + parentsnap = virDomainSnapshotFindByName(&vm->snapshots, + snap->def->parent); + if (!parentsnap) { + qemuReportError(VIR_ERR_NO_DOMAIN_SNAPSHOT, + _("no domain snapshot parent with matching name '%s'"), + snap->def->parent); + goto cleanup; + } + + /* Now we set the new current_snapshot for the domain */ + vm->def->current_snapshot = parentsnap->def; + } + else + vm->def->current_snapshot = NULL; + } + + if (virAsprintf(&snapFile, "%s/%s/%s.snap", driver->snapshotDir, + vm->def->name, snap->def->name) < 0) { + virReportOOMError(); + goto cleanup; + } + unlink(snapFile); + + virDomainSnapshotObjListRemove(&vm->snapshots, snap); + + ret = 0; + +cleanup: + VIR_FREE(snapFile); + VIR_FREE(qemuimgarg[0]); + + return ret; +} + +static int qemuDomainSnapshotDelete(virDomainSnapshotPtr snapshot, + unsigned int flags) +{ + struct qemud_driver *driver = snapshot->domain->conn->privateData; + virDomainObjPtr vm = NULL; + int ret = -1; + virDomainSnapshotObjPtr snap = NULL; + char uuidstr[VIR_UUID_STRING_BUFLEN]; + + qemuDriverLock(driver); + virUUIDFormat(snapshot->domain->uuid, uuidstr); + vm = virDomainFindByUUID(&driver->domains, snapshot->domain->uuid); + if (!vm) { + qemuReportError(VIR_ERR_NO_DOMAIN, + _("no domain with matching uuid '%s'"), uuidstr); + goto cleanup; + } + + snap = virDomainSnapshotFindByName(&vm->snapshots, snapshot->name); + if (!snap) { + qemuReportError(VIR_ERR_NO_DOMAIN_SNAPSHOT, + _("no domain snapshot with matching name '%s'"), + snapshot->name); + goto cleanup; + } + + if (flags & VIR_DOMAIN_SNAPSHOT_DELETE_DISCARD) { + if (qemuDomainSnapshotDiscard(driver, vm, snap, 0) < 0) + goto cleanup; + } + else if (flags & VIR_DOMAIN_SNAPSHOT_DELETE_DISCARD_FORCE) { + if (qemuDomainSnapshotDiscard(driver, vm, snap, 1) < 0) + goto cleanup; + } + else if (flags & VIR_DOMAIN_SNAPSHOT_DELETE_MERGE) { + /* FIXME: is this even possible with qemu? */ + if (virDomainSnapshotHasChildren(snap, &vm->snapshots)) { + /* FIXME: we should come up with a list of snapshots that would + * be merged/removed + */ + qemuReportError(VIR_ERR_INTERNAL_ERROR, + _("merging snapshot '%s' would also remove children snapshots"), + snap->def->name); + goto cleanup; + } + } + else if (flags & VIR_DOMAIN_SNAPSHOT_DELETE_MERGE_FORCE) { + /* FIXME: is this even possible with qemu? */ + } + + ret = 0; + +cleanup: + if (vm) + virDomainObjUnlock(vm); + qemuDriverUnlock(driver); + return ret; +} static virDriver qemuDriver = { VIR_DRV_QEMU, @@ -10307,15 +11026,15 @@ static virDriver qemuDriver = { qemuDomainMigrateSetMaxDowntime, /* domainMigrateSetMaxDowntime */ qemuDomainEventRegisterAny, /* domainEventRegisterAny */ qemuDomainEventDeregisterAny, /* domainEventDeregisterAny */ - NULL, /* domainSnapshotCreateXML */ - NULL, /* domainSnapshotDumpXML */ - NULL, /* domainSnapshotNum */ - NULL, /* domainSnapshotListNames */ - NULL, /* domainSnapshotLookupByName */ - NULL, /* domainHasCurrentSnapshot */ - NULL, /* domainSnapshotCurrent */ - NULL, /* domainCreateFromSnapshot */ - NULL, /* domainSnapshotDelete */ + qemuDomainSnapshotCreateXML, /* domainSnapshotCreateXML */ + qemuDomainSnapshotDumpXML, /* domainSnapshotDumpXML */ + qemuDomainSnapshotNum, /* domainSnapshotNum */ + qemuDomainSnapshotListNames, /* domainSnapshotListNames */ + qemuDomainSnapshotLookupByName, /* domainSnapshotLookupByName */ + qemuDomainHasCurrentSnapshot, /* domainHasCurrentSnapshot */ + qemuDomainSnapshotCurrent, /* domainSnapshotCurrent */ + qemuDomainCreateFromSnapshot, /* domainCreateFromSnapshot */ + qemuDomainSnapshotDelete, /* domainSnapshotDelete */ }; diff --git a/src/qemu/qemu_monitor.c b/src/qemu/qemu_monitor.c index 64779ac..7011821 100644 --- a/src/qemu/qemu_monitor.c +++ b/src/qemu/qemu_monitor.c @@ -1491,3 +1491,42 @@ int qemuMonitorSetDrivePassphrase(qemuMonitorPtr mon, ret = qemuMonitorTextSetDrivePassphrase(mon, alias, passphrase); return ret; } + +int qemuMonitorCreateSnapshot(qemuMonitorPtr mon, const char *name) +{ + int ret; + + DEBUG("mon=%p, name=%s",mon,name); + + if (mon->json) + ret = qemuMonitorJSONCreateSnapshot(mon, name); + else + ret = qemuMonitorTextCreateSnapshot(mon, name); + return ret; +} + +int qemuMonitorLoadSnapshot(qemuMonitorPtr mon, const char *name) +{ + int ret; + + DEBUG("mon=%p, name=%s",mon,name); + + if (mon->json) + ret = qemuMonitorJSONLoadSnapshot(mon, name); + else + ret = qemuMonitorTextLoadSnapshot(mon, name); + return ret; +} + +int qemuMonitorDeleteSnapshot(qemuMonitorPtr mon, const char *name) +{ + int ret; + + DEBUG("mon=%p, name=%s",mon,name); + + if (mon->json) + ret = qemuMonitorJSONDeleteSnapshot(mon, name); + else + ret = qemuMonitorTextDeleteSnapshot(mon, name); + return ret; +} diff --git a/src/qemu/qemu_monitor.h b/src/qemu/qemu_monitor.h index 07773bd..21b8989 100644 --- a/src/qemu/qemu_monitor.h +++ b/src/qemu/qemu_monitor.h @@ -344,4 +344,8 @@ int qemuMonitorSetDrivePassphrase(qemuMonitorPtr mon, const char *alias, const char *passphrase); +int qemuMonitorCreateSnapshot(qemuMonitorPtr mon, const char *name); +int qemuMonitorLoadSnapshot(qemuMonitorPtr mon, const char *name); +int qemuMonitorDeleteSnapshot(qemuMonitorPtr mon, const char *name); + #endif /* QEMU_MONITOR_H */ diff --git a/src/qemu/qemu_monitor_json.c b/src/qemu/qemu_monitor_json.c index eac3aca..1b00f22 100644 --- a/src/qemu/qemu_monitor_json.c +++ b/src/qemu/qemu_monitor_json.c @@ -2156,3 +2156,27 @@ int qemuMonitorJSONSetDrivePassphrase(qemuMonitorPtr mon, virJSONValueFree(reply); return ret; } + +int qemuMonitorJSONCreateSnapshot(qemuMonitorPtr mon ATTRIBUTE_UNUSED, + const char *name ATTRIBUTE_UNUSED) +{ + qemuReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("savevm not suppported in JSON mode")); + return -1; +} + +int qemuMonitorJSONLoadSnapshot(qemuMonitorPtr mon ATTRIBUTE_UNUSED, + const char *name ATTRIBUTE_UNUSED) +{ + qemuReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("loadvm not suppported in JSON mode")); + return -1; +} + +int qemuMonitorJSONDeleteSnapshot(qemuMonitorPtr mon ATTRIBUTE_UNUSED, + const char *name ATTRIBUTE_UNUSED) +{ + qemuReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("delvm not suppported in JSON mode")); + return -1; +} diff --git a/src/qemu/qemu_monitor_json.h b/src/qemu/qemu_monitor_json.h index fc05153..e7baf84 100644 --- a/src/qemu/qemu_monitor_json.h +++ b/src/qemu/qemu_monitor_json.h @@ -175,4 +175,8 @@ int qemuMonitorJSONSetDrivePassphrase(qemuMonitorPtr mon, const char *alias, const char *passphrase); +int qemuMonitorJSONCreateSnapshot(qemuMonitorPtr mon, const char *name); +int qemuMonitorJSONLoadSnapshot(qemuMonitorPtr mon, const char *name); +int qemuMonitorJSONDeleteSnapshot(qemuMonitorPtr mon, const char *name); + #endif /* QEMU_MONITOR_JSON_H */ diff --git a/src/qemu/qemu_monitor_text.c b/src/qemu/qemu_monitor_text.c index a199de7..5d7d124 100644 --- a/src/qemu/qemu_monitor_text.c +++ b/src/qemu/qemu_monitor_text.c @@ -2290,3 +2290,129 @@ cleanup: VIR_FREE(safe_str); return ret; } + +int qemuMonitorTextCreateSnapshot(qemuMonitorPtr mon, const char *name) +{ + char *cmd; + char *reply = NULL; + int ret = -1; + + if (virAsprintf(&cmd, "savevm \"%s\"", name) < 0) { + virReportOOMError(); + return -1; + } + + if (qemuMonitorCommand(mon, cmd, &reply)) { + qemuReportError(VIR_ERR_OPERATION_FAILED, + _("failed to take snapshot using command '%s'"), cmd); + goto cleanup; + } + + if (strstr(reply, "Error while creating snapshot") != NULL) { + qemuReportError(VIR_ERR_OPERATION_FAILED, + _("Failed to take snapshot: %s"), reply); + goto cleanup; + } + + /* If there is no valid drive on which to store snapshots, this is the + * string that qemu will reply with */ + if (strstr(reply, "No block device can accept snapshots") != NULL) { + qemuReportError(VIR_ERR_OPERATION_INVALID, "%s", + _("this domain does not have a device to take snapshots")); + goto cleanup; + } + + ret = 0; + +cleanup: + VIR_FREE(cmd); + VIR_FREE(reply); + return ret; +} + +int qemuMonitorTextLoadSnapshot(qemuMonitorPtr mon, const char *name) +{ + char *cmd; + char *reply = NULL; + int ret = -1; + + if (virAsprintf(&cmd, "loadvm \"%s\"", name) < 0) { + virReportOOMError(); + return -1; + } + + if (qemuMonitorCommand(mon, cmd, &reply)) { + qemuReportError(VIR_ERR_OPERATION_FAILED, + _("failed to restore snapshot using command '%s'"), + cmd); + goto cleanup; + } + + if (strstr(reply, "No block device supports snapshots") != NULL) { + qemuReportError(VIR_ERR_OPERATION_INVALID, "%s", + _("this domain does not have a device to load snapshots")); + goto cleanup; + } + else if (strstr(reply, "Could not find snapshot") != NULL) { + qemuReportError(VIR_ERR_OPERATION_INVALID, + _("the snapshot '%s' does not exist, and was not loaded"), + name); + goto cleanup; + } + else if (strstr(reply, "Snapshots not supported on device") != NULL) { + /* FIXME: we really should parse out which device it was */ + qemuReportError(VIR_ERR_OPERATION_INVALID, + _("snapshots are not supported on one of the devices")); + goto cleanup; + } + /* FIXME: there is also an "Error %d while activating snapshot" that we + * should handle + */ + + ret = 0; + +cleanup: + VIR_FREE(cmd); + VIR_FREE(reply); + return ret; +} + +int qemuMonitorTextDeleteSnapshot(qemuMonitorPtr mon, const char *name) +{ + char *cmd; + char *reply = NULL; + int ret = -1; + + if (virAsprintf(&cmd, "delvm \"%s\"", name) < 0) { + virReportOOMError(); + return -1; + } + if (qemuMonitorCommand(mon, cmd, &reply)) { + qemuReportError(VIR_ERR_OPERATION_FAILED, + _("failed to delete snapshot using command '%s'"), + cmd); + goto cleanup; + } + + if (strstr(reply, "No block device supports snapshots") != NULL) { + qemuReportError(VIR_ERR_OPERATION_INVALID, "%s", + _("this domain does not have a device to delete snapshots")); + goto cleanup; + } + else if (strstr(reply, "Snapshots not supported on device") != NULL) { + /* FIXME: we really should parse out which device it was */ + qemuReportError(VIR_ERR_OPERATION_INVALID, + _("snapshots are not supported on one of the devices")); + goto cleanup; + } + /* FIXME: there is also an "Error %d while deleting snapshot" that we + * should handle + */ + + ret = 0; + +cleanup: + VIR_FREE(cmd); + VIR_FREE(reply); + return ret; +} diff --git a/src/qemu/qemu_monitor_text.h b/src/qemu/qemu_monitor_text.h index 4e1939c..fb7d08b 100644 --- a/src/qemu/qemu_monitor_text.h +++ b/src/qemu/qemu_monitor_text.h @@ -177,4 +177,8 @@ int qemuMonitorTextSetDrivePassphrase(qemuMonitorPtr mon, const char *alias, const char *passphrase); +int qemuMonitorTextCreateSnapshot(qemuMonitorPtr mon, const char *name); +int qemuMonitorTextLoadSnapshot(qemuMonitorPtr mon, const char *name); +int qemuMonitorTextDeleteSnapshot(qemuMonitorPtr mon, const char *name); + #endif /* QEMU_MONITOR_TEXT_H */ diff --git a/tests/qemuxml2argvtest.c b/tests/qemuxml2argvtest.c index c98de19..4ee56cd 100644 --- a/tests/qemuxml2argvtest.c +++ b/tests/qemuxml2argvtest.c @@ -83,7 +83,7 @@ static int testCompareXMLToArgvFiles(const char *xml, if (qemudBuildCommandLine(conn, &driver, vmdef, &monitor_chr, 0, flags, &argv, &qenv, - NULL, NULL, migrateFrom) < 0) + NULL, NULL, migrateFrom, 0) < 0) goto fail; len = 1; /* for trailing newline */ -- 1.6.6.1 -- libvir-list mailing list libvir-list@xxxxxxxxxx https://www.redhat.com/mailman/listinfo/libvir-list