[libvirt] [PATCH v3 4/8] test: Implement snapshot create/delete/revert APIs
Cole Robinson
crobinso at redhat.com
Fri Oct 4 12:47:29 UTC 2013
On 10/04/2013 07:05 AM, John Ferlan wrote:
> On 09/25/2013 03:15 PM, Cole Robinson wrote:
>> Again stolen from qemu_driver.c, but dropping all the unneeded bits.
>> This aims to copy all the current qemu validation checks since that's
>> the most commonly used real driver, but some of the checks are
>> completely artificial in the test driver.
>>
>> This only supports creation of internal snapshots for initial
>> simplicity.
>> ---
>>
>> v3:
>> Use STRNEQ_NULLABLE for domain_conf.c change
>>
>> src/conf/domain_conf.c | 2 +-
>> src/test/test_driver.c | 504 ++++++++++++++++++++++++++++++++++++++++++++++++-
>> 2 files changed, 504 insertions(+), 2 deletions(-)
> ...
>
> A RESOURCE_LEAK Coverity issue - it takes a bit to set up though...
>
>> +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;
>> +
> 6953 goto cleanup;
> 6954
>
> (20) Event cond_false: Condition "snap->def->state == VIR_DOMAIN_RUNNING", taking false branch
> (21) Event cond_false: Condition "snap->def->state == VIR_DOMAIN_PAUSED", taking false branch
>
> 6955 if (snap->def->state == VIR_DOMAIN_RUNNING ||
> 6956 snap->def->state == VIR_DOMAIN_PAUSED) {
>
>
>> + 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);
>> + }
>> + }
>
> 7042 }
>
> (22) Event else_branch: Reached else branch
>
> 7043 } else {
>
>> + } else {
>> + /* Transitions 1, 4, 7 */
>> + virDomainObjAssignDef(vm, config, false, NULL);
>> +
>
> (23) Event cond_false:
>
>> + 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);
>
> (24) Event if_end: End of if statement
>
>> + }
>> +
>
> (25) Event cond_true: Condition "flags & (3U /* VIR_DOMAIN_SNAPSHOT_REVERT_RUNNING | VIR_DOMAIN_SNAPSHOT_REVERT_PAUSED */)", taking true branch
>
>> + 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;
>> +
>
> (26) Event cond_false: Condition "event", taking false branch
>
>> + if (event)
>
> (27) Event if_end: End of if statement
>
>> + testDomainEventQueue(privconn, event);
>> + event = virDomainEventNewFromObj(vm,
>> + VIR_DOMAIN_EVENT_STARTED,
>> + VIR_DOMAIN_EVENT_STARTED_FROM_SNAPSHOT);
>
> (28) Event cond_true: Condition "paused", taking true branch
>
>> + if (paused) {
>
> (29) Event alloc_fn: Storage is returned from allocation function "virDomainEventNewFromObj(virDomainObjPtr, int, int)". [details]
> (30) Event var_assign: Assigning: "event2" = storage returned from "virDomainEventNewFromObj(vm, 3, 5)".
> Also see events: [leaked_storage]
>
>> + 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);
>> + }
>
>
> Leads to the following Coverity issue -
>
> 7076 cleanup:
>
> (31) Event cond_false: Condition "event", taking false branch
>
> 7077 if (event) {
> 7078 testDomainEventQueue(privconn, event);
> 7079 if (event2)
> 7080 testDomainEventQueue(privconn, event2);
>
> (32) Event if_end: End of if statement
>
> 7081 }
> 7082 virObjectUnlock(vm);
> 7083 testDriverUnlock(privconn);
> 7084
>
> (33) Event leaked_storage: Variable "event2" going out of scope leaks the storage it points to.
> Also see events: [alloc_fn][var_assign]
>
> 7085 return ret;
> 7086 }
> 7087
>
>
> This one is a bit more "difficult" to see, but I think the complaint
> is that it's possible that 'event' is NULL at line 7063, but it's never
> checked and that 'event2' being allocated is based on 'paused' and not
> 'event' being non NULL, thus when we come to this point in the code the
> event == NULL will cause event2 to be leaked.
>
Hmm, the logic in this code is pretty intense :) In the followup patch I've added:
@@ -7078,6 +7076,8 @@ cleanup:
testDomainEventQueue(privconn, event);
if (event2)
testDomainEventQueue(privconn, event2);
+ } else {
+ virDomainEventFree(event2);
}
virObjectUnlock(vm);
testDriverUnlock(privconn);
Hopefully that covers it.
Thanks,
Cole
More information about the libvir-list
mailing list