[libvirt] [PATCHv2 0.5/4] network: fix virNetworkObjAssignDef and persistence

John Ferlan jferlan at redhat.com
Fri Apr 25 18:24:37 UTC 2014



On 04/25/2014 01:55 PM, Laine Stump wrote:
> On 04/24/2014 07:09 PM, John Ferlan wrote:
>>
>> On 04/23/2014 09:49 AM, Laine Stump wrote:
>>> Experimentation showed that if virNetworkCreateXML() was called for a
>>> network that was already defined, and then the network was
>>> subsequently shutdown, the network would continue to be persistent
>>> after the shutdown (expected/desired), but the original config would
>>> be lost in favor of the transient config sent in with
>>> virNetworkCreateXML() (which would then be the new persistent config)
>>> (obviously unexpected/not desired).
>>>
>>> To fix this, virNetworkObjAssignDef() has been changed to
>>>
>>> 1) properly save/free network->def and network->newDef for all the
>>> various combinations of live/active/persistent, including some
>>> combinations that were previously considered to be an error but didn't
>>> need to be (e.g. setting a "live" config for a network that isn't yet
>>> active but soon will be - that was previously considered an error,
>>> even though in practice it can be very useful).
>>>
>>> 2) automatically set the persistent flag whenever a new non-live
>>> config is assigned to the network (and clear it when the non-live
>>> config is set to NULL). the libvirt network driver no longer directly
>>> manipulates network->persistent, but instead relies entirely on
>>> virNetworkObjAssignDef() to do the right thing automatically.
>>>
>>> After this patch, the following sequence will behave as expected:
>>>
>>> virNetworkDefineXML(X)
>>> virNetworkCreateXML(X') (same name but some config different)
>>> virNetworkDestroy(X)
>>>
>>> At the end of these calls, the network config will remain as it was
>>> after the initial virNetworkDefine(), whereas previously it would take
>>> on the changes given during virNetworkCreateXML().
>>>
>>> Another effect of this tighter coupling between a) setting a !live def
>>> and b) setting/clearing the "persistent" flag, is that future patches
>>> which change the details of network lifecycle management
>>> (e.g. upcoming patches to fix detection of "active" networks when
>>> libvirtd is restarted) will find it much more difficult to break
>>> persistence functionality.
>>> ---
>>>  src/conf/network_conf.c           | 78 +++++++++++++++++++++++----------------
>>>  src/conf/network_conf.h           |  6 +--
>>>  src/network/bridge_driver.c       | 34 +++++++----------
>>>  src/parallels/parallels_network.c |  4 +-
>>>  src/test/test_driver.c            |  5 +--
>>>  5 files changed, 64 insertions(+), 63 deletions(-)
>>>
>>> diff --git a/src/conf/network_conf.c b/src/conf/network_conf.c
>>> index 56c4a09..66945ce 100644
>>> --- a/src/conf/network_conf.c
>>> +++ b/src/conf/network_conf.c
>>> @@ -302,46 +302,63 @@ void virNetworkObjListFree(virNetworkObjListPtr nets)
>>>  /*
>>>   * virNetworkObjAssignDef:
>>>   * @network: the network object to update
>>> - * @def: the new NetworkDef (will be consumed by this function iff successful)
>>> + * @def: the new NetworkDef (will be consumed by this function)
>>>   * @live: is this new def the "live" version, or the "persistent" version
>>>   *
>>> - * Replace the appropriate copy of the given network's NetworkDef
>>> + * Replace the appropriate copy of the given network's def or newDef
>>>   * with def. Use "live" and current state of the network to determine
>>> - * which to replace.
>>> + * which to replace and what to do with the old defs. When a non-live
>>> + * def is set, indicate that the network is now persistent.
>>> + *
>>> + * NB: a persistent network can be made transient by calling with:
>>> + * virNetworkObjAssignDef(network, NULL, false) (i.e. set the
>>> + * persistent def to NULL)
>>>   *
>>>   * Returns 0 on success, -1 on failure.
>>>   */
>>> -int
>>> +void
>> Well this doesn't jive with the comment above about what it returns...
> 
> Yeah, when all the failure paths disappeared I changed the function to
> void, but forgot to remove that comment. I'll fix that now.
> 
>>>  virNetworkObjAssignDef(virNetworkObjPtr network,
>>>                         virNetworkDefPtr def,
>>>                         bool live)
>>>  {
>>> -    if (virNetworkObjIsActive(network)) {
>>> -        if (live) {
>>> +    if (live) {
>>> +        /* before setting new live def, save (into newDef) any
>>> +         * existing persistent (!live) def to be restored when the
>>> +         * network is destroyed, unless there is one already saved.
>>> +         */
>>> +        if (network->persistent && !network->newDef)
>>> +            network->newDef = network->def;
>>> +        else
>>>              virNetworkDefFree(network->def);
>>> -            network->def = def;
>>> -        } else if (network->persistent) {
>>> -            /* save current configuration to be restored on network shutdown */
>>> -            virNetworkDefFree(network->newDef);
>>> +        network->def = def;
>>> +    } else { /* !live */
>>> +        virNetworkDefFree(network->newDef);
>>> +        if (virNetworkObjIsActive(network)) {
>>> +            /* save new configuration to be restored on network
>>> +             * shutdown, leaving current live def alone
>>> +             */
>>>              network->newDef = def;
>>> -        } else {
>>> -            virReportError(VIR_ERR_OPERATION_INVALID,
>>> -                           _("cannot save persistent config of transient "
>>> -                             "network '%s'"), network->def->name);
>>> -            return -1;
>>> +        } else { /* !live and !active */
>>> +            if (network->def && !network->persistent) {
>>> +                /* network isn't (yet) marked active or persistent,
>>> +                 * but already has a "live" def set. This means we are
>>> +                 * currently setting the persistent def as a part of
>>> +                 * the process of starting the network, so we need to
>>> +                 * preserve the "not yet live" def in network->def.
>>> +                 */
>>> +                network->newDef = def;
>>> +            } else {
>>> +                /* either there is no live def set, or this network
>>> +                 * was already set as persistent, so the proper thing
>>> +                 * is to overwrite network->def.
>>> +                 */
>>> +                network->newDef = NULL;
>> If I'm reading correctly - the newDef is virNetworkDefFree()'d above as
>> we enter the "} else { /* !live */" clause, which is probably where this
>> statement should be moved.
> 
> No, I just did some reduction of common code by moving it above the if
> statement. You'll notice that network->newDef is set to *something* in
> every path through those ifs and elses, so at the end of the function,
> it is either NULL or pointing to a valid object.
> 
>>
>>> +                virNetworkDefFree(network->def);
>>> +                network->def = def;
>>
>> NOTE: I think this is what is causing issues in networkUndefine() -
>> that'll call this function with '!live' - thus you'll do the free the
>> 'def' and set it to NULL... Back in the caller there's all sorts of
>> touching of the network->def-> structure
> 
> Yes, indirectly. But the *real* problem is that we shouldn't be calling
> this function until after we're finished using anything in network->def.
> See below for the small diff I'm squashing in to fix this.
> 
>>
>>
>>> +            }
>>>          }
>>> -    } else if (!live) {
>>> -        virNetworkDefFree(network->newDef);
>>> -        virNetworkDefFree(network->def);
>>> -        network->newDef = NULL;
>>> -        network->def = def;
>>> -    } else {
>>> -        virReportError(VIR_ERR_OPERATION_INVALID,
>>> -                       _("cannot save live config of inactive "
>>> -                         "network '%s'"), network->def->name);
>>> -        return -1;
>>> +        network->persistent = !!def;
>>>      }
>>> -    return 0;
>>>  }
>>>  
>>>  /*
>>> @@ -365,10 +382,7 @@ virNetworkAssignDef(virNetworkObjListPtr nets,
>>>      virNetworkObjPtr network;
>>>  
>>>      if ((network = virNetworkFindByName(nets, def->name))) {
>>> -        if (virNetworkObjAssignDef(network, def, live) < 0) {
>>> -            virNetworkObjUnlock(network);
>>> -            return NULL;
>>> -        }
>>> +        virNetworkObjAssignDef(network, def, live);
>>>          return network;
>>>      }
>>>  
>>> @@ -392,8 +406,9 @@ virNetworkAssignDef(virNetworkObjListPtr nets,
>>>      ignore_value(virBitmapSetBit(network->class_id, 2));
>>>  
>>>      network->def = def;
>>> -
>>> +    network->persistent = !live;
>>>      return network;
>>> +
>>>   error:
>>>      virNetworkObjUnlock(network);
>>>      virNetworkObjFree(network);
>>> @@ -3118,7 +3133,6 @@ virNetworkObjPtr virNetworkLoadConfig(virNetworkObjListPtr nets,
>>>          goto error;
>>>  
>>>      net->autostart = autostart;
>>> -    net->persistent = 1;
>>>  
>>>      VIR_FREE(configFile);
>>>      VIR_FREE(autostartLink);
>>> diff --git a/src/conf/network_conf.h b/src/conf/network_conf.h
>>> index 1a864de..90da16f 100644
>>> --- a/src/conf/network_conf.h
>>> +++ b/src/conf/network_conf.h
>>> @@ -335,9 +335,9 @@ typedef bool (*virNetworkObjListFilter)(virConnectPtr conn,
>>>  virNetworkObjPtr virNetworkAssignDef(virNetworkObjListPtr nets,
>>>                                       virNetworkDefPtr def,
>>>                                       bool live);
>>> -int virNetworkObjAssignDef(virNetworkObjPtr network,
>>> -                           virNetworkDefPtr def,
>>> -                           bool live);
>>> +void virNetworkObjAssignDef(virNetworkObjPtr network,
>>> +                            virNetworkDefPtr def,
>>> +                            bool live);
>>>  int virNetworkObjSetDefTransient(virNetworkObjPtr network, bool live);
>>>  void virNetworkObjUnsetDefTransient(virNetworkObjPtr network);
>>>  virNetworkDefPtr virNetworkObjGetPersistentDef(virNetworkObjPtr network);
>>> diff --git a/src/network/bridge_driver.c b/src/network/bridge_driver.c
>>> index eb276cd..62d1c25 100644
>>> --- a/src/network/bridge_driver.c
>>> +++ b/src/network/bridge_driver.c
>>> @@ -2667,10 +2667,11 @@ static virNetworkPtr networkCreateXML(virConnectPtr conn, const char *xml)
>>>      if (networkValidate(driver, def, true) < 0)
>>>         goto cleanup;
>>>  
>>> -    /* NB: "live" is false because this transient network hasn't yet
>>> -     * been started
>>> +    /* NB: even though this transient network hasn't yet been started,
>>> +     * we assign the def with live = true in anticipation that it will
>>> +     * be started momentarily.
>>>       */
>>> -    if (!(network = virNetworkAssignDef(&driver->networks, def, false)))
>>> +    if (!(network = virNetworkAssignDef(&driver->networks, def, true)))
>>>          goto cleanup;
>>>      def = NULL;
>>>  
>>> @@ -2719,19 +2720,10 @@ static virNetworkPtr networkDefineXML(virConnectPtr conn, const char *xml)
>>>      if (networkValidate(driver, def, false) < 0)
>>>         goto cleanup;
>>>  
>>> -    if ((network = virNetworkFindByName(&driver->networks, def->name))) {
>>> -        network->persistent = 1;
>>> -        if (virNetworkObjAssignDef(network, def, false) < 0)
>>> -            goto cleanup;
>>> -    } else {
>>> -        if (!(network = virNetworkAssignDef(&driver->networks, def, false)))
>>> -            goto cleanup;
>>> -    }
>>> -
>>> -    /* define makes the network persistent - always */
>>> -    network->persistent = 1;
>>> +    if (!(network = virNetworkAssignDef(&driver->networks, def, false)))
>>> +       goto cleanup;
>>>  
>>> -    /* def was asigned */
>>> +    /* def was assigned to network object */
>>>      freeDef = false;
>>>  
>>>      if (virNetworkSaveConfig(driver->networkConfigDir, def) < 0) {
>>> @@ -2740,9 +2732,11 @@ static virNetworkPtr networkDefineXML(virConnectPtr conn, const char *xml)
>>>              network = NULL;
>>>              goto cleanup;
>>>          }
>>> -        network->persistent = 0;
>>> -        virNetworkDefFree(network->newDef);
>>> -        network->newDef = NULL;
>>> +        /* if network was active already, just undo new persistent
>>> +         * definition by making it transient.
>>> +         * XXX - this isn't necessarily the correct thing to do.
>>> +         */
>> Leaving the XXX comment in here leaves me wondering what is the correct
>> thing to do...
> 
> The correct thing to do is... well, to exactly rollback to what you had
> before the failure.
> 
> However, the current code (both before *and* after this patch) does the
> (same) wrong thing in this case - if you fail during an attempt to
> redefine an already persistent+active network, you'll end up with a
> transient+active network (so it will disappear as soon as the network is
> stopped).
> 
> It would be better if such a failure caused us to revert back to the
> previous persistent def. That would require saving off the existing def
> before trying to assign the new one or writing to disk. But that is a
> separate problem and should be in a separate patch anyway. The only
> reason I touched that area of the code in this patch was that I was
> replacing direct assignment of network->persistent with calling
> virNetworkObjAssignDef() (while preserving the existing functionality).
> I did think it was worth noting the problem with the existing
> functionality while touching the code, however.
> 
> So, while I think this needs fixing, it's pre-existing and I'd rather
> not have these other patches delayed because of it.
> 
>>
>>> +        virNetworkObjAssignDef(network, NULL, false);
>>>          goto cleanup;
>>>      }
>>>  
>>> @@ -2794,10 +2788,8 @@ networkUndefine(virNetworkPtr net)
>>>          goto cleanup;
>>>  
>>>      /* make the network transient */
>>> -    network->persistent = 0;
>>>      network->autostart = 0;
>>> -    virNetworkDefFree(network->newDef);
>>> -    network->newDef = NULL;
>>> +    virNetworkObjAssignDef(network, NULL, false);
>> This seems to be the cause of the crash I noted in private chat. If I
>> restore just these lines and not make the ObjAssignDef() call, then
>> things work again.  That includes issues I was seeing in virt-test for
>> patches 3 & 4 as well.
> 
> Yep. That call needs to be moved down to below the call to
> networkRemoveInactive. I've done that and all the tests complete without
> failure (as you've also separately reported on IRC). Here is what I
> squashed into this patch:
> 
> diff --git a/src/network/bridge_driver.c b/src/network/bridge_driver.c
> index 309d967..2a54aa3 100644
> --- a/src/network/bridge_driver.c
> +++ b/src/network/bridge_driver.c
> @@ -2899,14 +2899,12 @@ networkUndefine(virNetworkPtr net)
>      if (virNetworkObjIsActive(network))
>          active = true;
>  
> +    /* remove autostart link */
>      if (virNetworkDeleteConfig(driver->networkConfigDir,
>                                 driver->networkAutostartDir,
>                                 network) < 0)
>          goto cleanup;
> -
> -    /* make the network transient */
>      network->autostart = 0;
> -    virNetworkObjAssignDef(network, NULL, false);
>  
>      event = virNetworkEventLifecycleNew(network->def->name,
>                                          network->def->uuid,
> @@ -2920,6 +2918,12 @@ networkUndefine(virNetworkPtr net)
>              goto cleanup;
>          }
>          network = NULL;
> +    } else {
> +
> +        /* if the network still exists, it was active, and we need to make
> +         * it transient (by deleting the persistent def

s/def/def)/

> +         */
> +        virNetworkObjAssignDef(network, NULL, false);
>      }
>  
>      ret = 0;
> --
> 
>>
>> Just figured I'd close the loop on this review at least...
> 
> Do my responses clear up your concerns?
> 
> 
> 

Yep - all set, thanks for the extra details...

You have an ACK with that extra ) above



John




More information about the libvir-list mailing list