[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