diff --git a/src/conf/domain_conf.c b/src/conf/domain_conf.c index 52a4fb704ebd10941b7de237bb102471d7ef0757..0d6384559875df9babff508dca39a428742d2a9e 100644 --- a/src/conf/domain_conf.c +++ b/src/conf/domain_conf.c @@ -13517,7 +13517,7 @@ virDomainDefCheckABIStability(virDomainDefPtr src, virArchToString(src->os.arch)); return false; } - if (STRNEQ(src->os.machine, dst->os.machine)) { + if (STRNEQ_NULLABLE(src->os.machine, dst->os.machine)) { virReportError(VIR_ERR_CONFIG_UNSUPPORTED, _("Target domain OS type %s does not match source %s"), dst->os.machine, src->os.machine); diff --git a/src/test/test_driver.c b/src/test/test_driver.c index 7349d26ff9108aea6faeee84d5e456abf27fa392..60c77e4face4e78bef0b0e9c2866d842020ea47e 100644 --- a/src/test/test_driver.c +++ b/src/test/test_driver.c @@ -2955,9 +2955,11 @@ static int testDomainUndefineFlags(virDomainPtr domain, testConnPtr privconn = domain->conn->privateData; virDomainObjPtr privdom; virDomainEventPtr event = NULL; + int nsnapshots; int ret = -1; - virCheckFlags(VIR_DOMAIN_UNDEFINE_MANAGED_SAVE, -1); + virCheckFlags(VIR_DOMAIN_UNDEFINE_MANAGED_SAVE | + VIR_DOMAIN_UNDEFINE_SNAPSHOTS_METADATA, -1); testDriverLock(privconn); privdom = virDomainObjListFindByName(privconn->domains, @@ -2976,6 +2978,24 @@ static int testDomainUndefineFlags(virDomainPtr domain, goto cleanup; } + /* Requiring an inactive VM is part of the documented API for + * UNDEFINE_SNAPSHOTS_METADATA + */ + if (!virDomainObjIsActive(privdom) && + (nsnapshots = virDomainSnapshotObjListNum(privdom->snapshots, + NULL, 0))) { + if (!(flags & VIR_DOMAIN_UNDEFINE_SNAPSHOTS_METADATA)) { + virReportError(VIR_ERR_OPERATION_INVALID, + _("cannot delete inactive domain with %d " + "snapshots"), + nsnapshots); + goto cleanup; + } + + /* There isn't actually anything to do, we are just emulating qemu + * behavior here. */ + } + event = virDomainEventNewFromObj(privdom, VIR_DOMAIN_EVENT_UNDEFINED, VIR_DOMAIN_EVENT_UNDEFINED_REMOVED); @@ -6571,6 +6591,482 @@ cleanup: return ret; } +static int +testDomainSnapshotAlignDisks(virDomainObjPtr vm, + virDomainSnapshotDefPtr def, + unsigned int flags) +{ + int align_location = VIR_DOMAIN_SNAPSHOT_LOCATION_INTERNAL; + int align_match = true; + + if (flags & VIR_DOMAIN_SNAPSHOT_CREATE_DISK_ONLY) { + align_location = VIR_DOMAIN_SNAPSHOT_LOCATION_EXTERNAL; + align_match = false; + if (virDomainObjIsActive(vm)) + def->state = VIR_DOMAIN_DISK_SNAPSHOT; + else + def->state = VIR_DOMAIN_SHUTOFF; + def->memory = VIR_DOMAIN_SNAPSHOT_LOCATION_NONE; + } else if (def->memory == VIR_DOMAIN_SNAPSHOT_LOCATION_EXTERNAL) { + def->state = virDomainObjGetState(vm, NULL); + align_location = VIR_DOMAIN_SNAPSHOT_LOCATION_EXTERNAL; + align_match = false; + } else { + def->state = virDomainObjGetState(vm, NULL); + def->memory = def->state == VIR_DOMAIN_SHUTOFF ? + VIR_DOMAIN_SNAPSHOT_LOCATION_NONE : + VIR_DOMAIN_SNAPSHOT_LOCATION_INTERNAL; + } + + return virDomainSnapshotAlignDisks(def, align_location, align_match); +} + +static virDomainSnapshotPtr +testDomainSnapshotCreateXML(virDomainPtr domain, + const char *xmlDesc, + unsigned int flags) +{ + testConnPtr privconn = domain->conn->privateData; + virDomainObjPtr vm = NULL; + virDomainSnapshotDefPtr def = NULL; + virDomainSnapshotObjPtr snap = NULL; + virDomainSnapshotPtr snapshot = NULL; + virDomainEventPtr event = NULL; + char *xml = NULL; + unsigned int parse_flags = VIR_DOMAIN_SNAPSHOT_PARSE_DISKS; + + /* + * REDEFINE + CURRENT: Not implemented yet + * DISK_ONLY: Not implemented yet + * REUSE_EXT: Not implemented yet + * + * NO_METADATA: Explicitly not implemented + * + * HALT: Implemented + * QUIESCE: Nothing to do + * ATOMIC: Nothing to do + * LIVE: Nothing to do + */ + virCheckFlags( + VIR_DOMAIN_SNAPSHOT_CREATE_HALT | + VIR_DOMAIN_SNAPSHOT_CREATE_QUIESCE | + VIR_DOMAIN_SNAPSHOT_CREATE_ATOMIC | + VIR_DOMAIN_SNAPSHOT_CREATE_LIVE, NULL); + + if (!(vm = testDomObjFromDomain(domain))) + goto cleanup; + + if (!vm->persistent && (flags & VIR_DOMAIN_SNAPSHOT_CREATE_HALT)) { + virReportError(VIR_ERR_OPERATION_INVALID, "%s", + _("cannot halt after transient domain snapshot")); + goto cleanup; + } + + if (!(def = virDomainSnapshotDefParseString(xmlDesc, + privconn->caps, + privconn->xmlopt, + 1 << VIR_DOMAIN_VIRT_TEST, + parse_flags))) + goto cleanup; + + if (!(def->dom = virDomainDefCopy(vm->def, + privconn->caps, + privconn->xmlopt, + true))) + goto cleanup; + + if (testDomainSnapshotAlignDisks(vm, def, flags) < 0) + goto cleanup; + + if (!(snap = virDomainSnapshotAssignDef(vm->snapshots, def))) + goto cleanup; + def = NULL; + + if (vm->current_snapshot) { + if (!(flags & VIR_DOMAIN_SNAPSHOT_CREATE_REDEFINE) && + VIR_STRDUP(snap->def->parent, vm->current_snapshot->def->name) < 0) + goto cleanup; + } + + if ((flags & VIR_DOMAIN_SNAPSHOT_CREATE_HALT) && + virDomainObjIsActive(vm)) { + testDomainShutdownState(domain, vm, VIR_DOMAIN_SHUTOFF_FROM_SNAPSHOT); + event = virDomainEventNewFromObj(vm, VIR_DOMAIN_EVENT_STOPPED, + VIR_DOMAIN_EVENT_STOPPED_FROM_SNAPSHOT); + } + + + snapshot = virGetDomainSnapshot(domain, snap->def->name); +cleanup: + VIR_FREE(xml); + if (vm) { + if (snapshot) { + virDomainSnapshotObjPtr other; + vm->current_snapshot = snap; + other = virDomainSnapshotFindByName(vm->snapshots, + snap->def->parent); + snap->parent = other; + other->nchildren++; + snap->sibling = other->first_child; + other->first_child = snap; + } + virObjectUnlock(vm); + } + if (event) { + testDriverLock(privconn); + testDomainEventQueue(privconn, event); + testDriverUnlock(privconn); + } + virDomainSnapshotDefFree(def); + return snapshot; +} + + +typedef struct _testSnapRemoveData testSnapRemoveData; +typedef testSnapRemoveData *testSnapRemoveDataPtr; +struct _testSnapRemoveData { + virDomainObjPtr vm; + bool current; +}; + +static void +testDomainSnapshotDiscardAll(void *payload, + const void *name ATTRIBUTE_UNUSED, + void *data) +{ + virDomainSnapshotObjPtr snap = payload; + testSnapRemoveDataPtr curr = data; + + if (snap->def->current) + curr->current = true; + virDomainSnapshotObjListRemove(curr->vm->snapshots, snap); +} + +typedef struct _testSnapReparentData testSnapReparentData; +typedef testSnapReparentData *testSnapReparentDataPtr; +struct _testSnapReparentData { + virDomainSnapshotObjPtr parent; + virDomainObjPtr vm; + int err; + virDomainSnapshotObjPtr last; +}; + +static void +testDomainSnapshotReparentChildren(void *payload, + const void *name ATTRIBUTE_UNUSED, + void *data) +{ + virDomainSnapshotObjPtr snap = payload; + testSnapReparentDataPtr rep = data; + + if (rep->err < 0) { + return; + } + + VIR_FREE(snap->def->parent); + snap->parent = rep->parent; + + if (rep->parent->def && + VIR_STRDUP(snap->def->parent, rep->parent->def->name) < 0) { + rep->err = -1; + return; + } + + if (!snap->sibling) + rep->last = snap; +} + +static int +testDomainSnapshotDelete(virDomainSnapshotPtr snapshot, + unsigned int flags) +{ + virDomainObjPtr vm = NULL; + virDomainSnapshotObjPtr snap = NULL; + virDomainSnapshotObjPtr parentsnap = NULL; + int ret = -1; + + virCheckFlags(VIR_DOMAIN_SNAPSHOT_DELETE_CHILDREN | + VIR_DOMAIN_SNAPSHOT_DELETE_CHILDREN_ONLY, -1); + + if (!(vm = testDomObjFromSnapshot(snapshot))) + return -1; + + if (!(snap = testSnapObjFromSnapshot(vm, snapshot))) + goto cleanup; + + if (flags & (VIR_DOMAIN_SNAPSHOT_DELETE_CHILDREN | + VIR_DOMAIN_SNAPSHOT_DELETE_CHILDREN_ONLY)) { + testSnapRemoveData rem; + rem.vm = vm; + rem.current = false; + virDomainSnapshotForEachDescendant(snap, + testDomainSnapshotDiscardAll, + &rem); + if (rem.current) { + if (flags & VIR_DOMAIN_SNAPSHOT_DELETE_CHILDREN_ONLY) { + snap->def->current = true; + } + vm->current_snapshot = snap; + } + } else if (snap->nchildren) { + testSnapReparentData rep; + rep.parent = snap->parent; + rep.vm = vm; + rep.err = 0; + rep.last = NULL; + virDomainSnapshotForEachChild(snap, + testDomainSnapshotReparentChildren, + &rep); + if (rep.err < 0) + goto cleanup; + + /* Can't modify siblings during ForEachChild, so do it now. */ + snap->parent->nchildren += snap->nchildren; + rep.last->sibling = snap->parent->first_child; + snap->parent->first_child = snap->first_child; + } + + if (flags & VIR_DOMAIN_SNAPSHOT_DELETE_CHILDREN_ONLY) { + snap->nchildren = 0; + snap->first_child = NULL; + } else { + virDomainSnapshotDropParent(snap); + if (snap == vm->current_snapshot) { + if (snap->def->parent) { + parentsnap = virDomainSnapshotFindByName(vm->snapshots, + snap->def->parent); + if (!parentsnap) { + VIR_WARN("missing parent snapshot matching name '%s'", + snap->def->parent); + } else { + parentsnap->def->current = true; + } + } + vm->current_snapshot = parentsnap; + } + virDomainSnapshotObjListRemove(vm->snapshots, snap); + } + + ret = 0; +cleanup: + if (vm) + virObjectUnlock(vm); + return ret; +} + +static int +testDomainRevertToSnapshot(virDomainSnapshotPtr snapshot, + unsigned int flags) +{ + testConnPtr privconn = snapshot->domain->conn->privateData; + virDomainObjPtr vm = NULL; + virDomainSnapshotObjPtr snap = NULL; + virDomainEventPtr event = NULL; + virDomainEventPtr event2 = NULL; + virDomainDefPtr config = NULL; + int ret = -1; + + virCheckFlags(VIR_DOMAIN_SNAPSHOT_REVERT_RUNNING | + VIR_DOMAIN_SNAPSHOT_REVERT_PAUSED | + VIR_DOMAIN_SNAPSHOT_REVERT_FORCE, -1); + + /* We have the following transitions, which create the following events: + * 1. inactive -> inactive: none + * 2. inactive -> running: EVENT_STARTED + * 3. inactive -> paused: EVENT_STARTED, EVENT_PAUSED + * 4. running -> inactive: EVENT_STOPPED + * 5. running -> running: none + * 6. running -> paused: EVENT_PAUSED + * 7. paused -> inactive: EVENT_STOPPED + * 8. paused -> running: EVENT_RESUMED + * 9. paused -> paused: none + * Also, several transitions occur even if we fail partway through, + * and use of FORCE can cause multiple transitions. + */ + + if (!(vm = testDomObjFromSnapshot(snapshot))) + return -1; + + if (!(snap = testSnapObjFromSnapshot(vm, snapshot))) + goto cleanup; + + testDriverLock(privconn); + + if (!vm->persistent && + snap->def->state != VIR_DOMAIN_RUNNING && + snap->def->state != VIR_DOMAIN_PAUSED && + (flags & (VIR_DOMAIN_SNAPSHOT_REVERT_RUNNING | + VIR_DOMAIN_SNAPSHOT_REVERT_PAUSED)) == 0) { + virReportError(VIR_ERR_OPERATION_INVALID, "%s", + _("transient domain needs to request run or pause " + "to revert to inactive snapshot")); + goto cleanup; + } + + if (!(flags & VIR_DOMAIN_SNAPSHOT_REVERT_FORCE)) { + if (!snap->def->dom) { + virReportError(VIR_ERR_SNAPSHOT_REVERT_RISKY, + _("snapshot '%s' lacks domain '%s' rollback info"), + snap->def->name, vm->def->name); + goto cleanup; + } + if (virDomainObjIsActive(vm) && + !(snap->def->state == VIR_DOMAIN_RUNNING + || snap->def->state == VIR_DOMAIN_PAUSED) && + (flags & (VIR_DOMAIN_SNAPSHOT_REVERT_RUNNING | + VIR_DOMAIN_SNAPSHOT_REVERT_PAUSED))) { + virReportError(VIR_ERR_SNAPSHOT_REVERT_RISKY, "%s", + _("must respawn guest to start inactive snapshot")); + goto cleanup; + } + } + + + if (vm->current_snapshot) { + vm->current_snapshot->def->current = false; + vm->current_snapshot = NULL; + } + + snap->def->current = true; + config = virDomainDefCopy(snap->def->dom, + privconn->caps, privconn->xmlopt, true); + if (!config) + goto cleanup; + + if (snap->def->state == VIR_DOMAIN_RUNNING || + snap->def->state == VIR_DOMAIN_PAUSED) { + /* Transitions 2, 3, 5, 6, 8, 9 */ + bool was_running = false; + bool was_stopped = false; + + if (virDomainObjIsActive(vm)) { + /* Transitions 5, 6, 8, 9 */ + /* Check for ABI compatibility. */ + if (!virDomainDefCheckABIStability(vm->def, config)) { + virErrorPtr err = virGetLastError(); + + if (!(flags & VIR_DOMAIN_SNAPSHOT_REVERT_FORCE)) { + /* Re-spawn error using correct category. */ + if (err->code == VIR_ERR_CONFIG_UNSUPPORTED) + virReportError(VIR_ERR_SNAPSHOT_REVERT_RISKY, "%s", + err->str2); + goto cleanup; + } + + virResetError(err); + testDomainShutdownState(snapshot->domain, vm, + VIR_DOMAIN_SHUTOFF_FROM_SNAPSHOT); + event = virDomainEventNewFromObj(vm, + VIR_DOMAIN_EVENT_STOPPED, + VIR_DOMAIN_EVENT_STOPPED_FROM_SNAPSHOT); + if (event) + testDomainEventQueue(privconn, event); + goto load; + } + + if (virDomainObjGetState(vm, NULL) == VIR_DOMAIN_RUNNING) { + /* Transitions 5, 6 */ + was_running = true; + virDomainObjSetState(vm, VIR_DOMAIN_PAUSED, + VIR_DOMAIN_PAUSED_FROM_SNAPSHOT); + /* Create an event now in case the restore fails, so + * that user will be alerted that they are now paused. + * If restore later succeeds, we might replace this. */ + event = virDomainEventNewFromObj(vm, + VIR_DOMAIN_EVENT_SUSPENDED, + VIR_DOMAIN_EVENT_SUSPENDED_FROM_SNAPSHOT); + } + virDomainObjAssignDef(vm, config, false, NULL); + + } else { + /* Transitions 2, 3 */ + load: + was_stopped = true; + virDomainObjAssignDef(vm, config, false, NULL); + if (testDomainStartState(privconn, vm, + VIR_DOMAIN_RUNNING_FROM_SNAPSHOT) < 0) + goto cleanup; + event = virDomainEventNewFromObj(vm, + VIR_DOMAIN_EVENT_STARTED, + VIR_DOMAIN_EVENT_STARTED_FROM_SNAPSHOT); + } + + /* Touch up domain state. */ + if (!(flags & VIR_DOMAIN_SNAPSHOT_REVERT_RUNNING) && + (snap->def->state == VIR_DOMAIN_PAUSED || + (flags & VIR_DOMAIN_SNAPSHOT_REVERT_PAUSED))) { + /* Transitions 3, 6, 9 */ + virDomainObjSetState(vm, VIR_DOMAIN_PAUSED, + VIR_DOMAIN_PAUSED_FROM_SNAPSHOT); + if (was_stopped) { + /* Transition 3, use event as-is and add event2 */ + event2 = virDomainEventNewFromObj(vm, + VIR_DOMAIN_EVENT_SUSPENDED, + VIR_DOMAIN_EVENT_SUSPENDED_FROM_SNAPSHOT); + } /* else transition 6 and 9 use event as-is */ + } else { + /* Transitions 2, 5, 8 */ + virDomainEventFree(event); + event = NULL; + + if (was_stopped) { + /* Transition 2 */ + event = virDomainEventNewFromObj(vm, + VIR_DOMAIN_EVENT_STARTED, + VIR_DOMAIN_EVENT_STARTED_FROM_SNAPSHOT); + } else if (was_running) { + /* Transition 8 */ + event = virDomainEventNewFromObj(vm, + VIR_DOMAIN_EVENT_RESUMED, + VIR_DOMAIN_EVENT_RESUMED); + } + } + } else { + /* Transitions 1, 4, 7 */ + virDomainObjAssignDef(vm, config, false, NULL); + + if (virDomainObjIsActive(vm)) { + /* Transitions 4, 7 */ + testDomainShutdownState(snapshot->domain, vm, + VIR_DOMAIN_SHUTOFF_FROM_SNAPSHOT); + event = virDomainEventNewFromObj(vm, + VIR_DOMAIN_EVENT_STOPPED, + VIR_DOMAIN_EVENT_STOPPED_FROM_SNAPSHOT); + } + + if (flags & (VIR_DOMAIN_SNAPSHOT_REVERT_RUNNING | + VIR_DOMAIN_SNAPSHOT_REVERT_PAUSED)) { + /* Flush first event, now do transition 2 or 3 */ + bool paused = (flags & VIR_DOMAIN_SNAPSHOT_REVERT_PAUSED) != 0; + + if (event) + testDomainEventQueue(privconn, event); + event = virDomainEventNewFromObj(vm, + VIR_DOMAIN_EVENT_STARTED, + VIR_DOMAIN_EVENT_STARTED_FROM_SNAPSHOT); + if (paused) { + event2 = virDomainEventNewFromObj(vm, + VIR_DOMAIN_EVENT_SUSPENDED, + VIR_DOMAIN_EVENT_SUSPENDED_FROM_SNAPSHOT); + } + } + } + + vm->current_snapshot = snap; + ret = 0; +cleanup: + if (event) { + testDomainEventQueue(privconn, event); + if (event2) + testDomainEventQueue(privconn, event2); + } + virObjectUnlock(vm); + testDriverUnlock(privconn); + + return ret; +} + + static virDriver testDriver = { .no = VIR_DRV_TEST, @@ -6662,6 +7158,9 @@ static virDriver testDriver = { .domainSnapshotCurrent = testDomainSnapshotCurrent, /* 1.1.4 */ .domainSnapshotIsCurrent = testDomainSnapshotIsCurrent, /* 1.1.4 */ .domainSnapshotHasMetadata = testDomainSnapshotHasMetadata, /* 1.1.4 */ + .domainSnapshotCreateXML = testDomainSnapshotCreateXML, /* 1.1.4 */ + .domainRevertToSnapshot = testDomainRevertToSnapshot, /* 1.1.4 */ + .domainSnapshotDelete = testDomainSnapshotDelete, /* 1.1.4 */ }; static virNetworkDriver testNetworkDriver = {