[libvirt] [PATCH 00/10] new virNetworkUpdate API

This patchset implements a new API function called virNetworkUpdate which enables updating certain parts of a libvirt network's definition without the need to destroy/re-start the network. This is especially useful, for example, to add/remove hosts from the dhcp static hosts table, or change portgroup settings. This was previously discussed in this thread: https://www.redhat.com/archives/libvir-list/2012-August/msg01535.html continuing here in September: https://www.redhat.com/archives/libvir-list/2012-September/msg00328.html with the final form here: https://www.redhat.com/archives/libvir-list/2012-September/msg00465.html In short, the single function has a "section" specifier which tells the part of the network definition to be updated, a "parentIndex" that gives the index of the *parent* element containing this section (when there are multiples - in particular in the case of the <ip> element), and a fully formed XML element which will be added as-is in the case of VIR_NETWORK_UPDATE_ADD_* (after checking for a duplicate), used to search for the specific element to delete in case of VIR_NETWORK_UPDATE_DELETE, and used both to find the existing element and replace its current contents in the case of VIR_UPDATE_EXISTING (this implies that you can't change the change the attribute used for indexing, e.g. the name of a portgroup, or mac address of a dhcp host entry). An example of use: to add a dhcp host entry to network "net", you would do this: virNetworkUpdate(net, VIR_NETWORK_SECTION_IP_DHCP_HOST, -1, "<host mac='00:11:22:33:44:55' ip='192.168.122.5'/>", VIR_NETWORK_UPDATE_AFFECT_LIVE | VIR_NETWORK_UPDATE_AFFECT_CONFIG | VIR_NETWORK_UPDATE_ADD_LAST); To delete that same entry: virNetworkUpdate(net, VIR_NETWORK_SECTION_IP_DHCP_HOST, -1, "<host mac='00:11:22:33:44:55'/>", VIR_NETWORK_UPDATE_AFFECT_LIVE | VIR_NETWORK_UPDATE_AFFECT_CONFIG | VIR_NETWORK_UPDATE_DELETE); If you wanted to force any of these to affect the dhcp host list in the 3rd <ip> element of the network, you would replace "-1" with "2". Another example: to modify the portgroup named "engineering" (e.g. to increase the inbound average bandwidth from 1000 to 2000): virNetworkUpdate(net, VIR_NETWORK_SECTION_PORTGROUP, -1, "<portgroup name='engineering' default='yes'>" " <virtualport type='802.1Qbh'>" " <parameters profileid='test'/>" " </virtualport>" " <bandwidth>" " <inbound average='2000' peak='5000' burst='5120'/>" " <outbound average='1000' peak='5000' burst='5120'/>" " </bandwidth>" "</portgroup>", VIR_NETWORK_UPDATE_EXISTING | VIR_NETWORK_UPDATE_LIVE | VIR_NETWORK_UPDATE_CONFIG) (note that parentIndex is irrelevant for PORTGROUP, since they are in the toplevel of <network>, so there aren't multiple instances of parents. In such cases, the caller *must* set parentIndex to -1 or 0 - any other value indicates that they don't understand the purpose/usage of parentIndex, so it must result in an error. Also note that the above function would fail if it couldn't find an existing portgroup with name='engineering' (i.e. it wouldn't automatically add a new one).) Adding support for each of the different sections has been reduced to a single function that handles the update of a virNetworkDef; all the logic to determine which virNetworkDef (def or newDef) and to restart/SIGHUP the appropriate daemons is in higher levels and is 100% complete. The low level functions aren't yet finished, although the function for IP_DHCP_HOST is nearly done. As usual, several of the patches are re-factoring existing code, and a couple are bugfixes that are only peripherally related: 1/10 - splits out parsing of dhcp related elements to separate functions 2/10 - fixes a small bug that would probably never be encountered IRL anyway 3/10+4/10 - actual API 5/10 - utility functions to simplify API implementation 6/10 - framework for backend that updates the virNetworkDef 7/10 - refactoring in bridge_driver 8/10 - virNetworkUpdate for bridge_driver 9/10 - virNetworkUpdate for test_driver 10/10 - simple troubleshooting aid - restart dnsmasq/radvd when libvirtd is restarted (if its process is missing). I'll try to have the following additional patches posted later today: 11/10 - backend for IP_DHCP_HOST update 12/10 - backend for PORTGROUP update 13/10 - virsh "net-update" command Differences from email thread: 1) Aside from being actual code instead of just pseudo code, I've changed the "index" arg into "parentIndex" to avoid confusion between the index of the element we're looking at (e.g. which host in the list of hosts in a <dhcp> element) and the index of the parent (e.g. which <ip> element will contain the <dhcp> with the list of <hosts> we want to update). I also changed this from a unsigned into to an int so that it can hold the special value "-1", which means "pick the most useful/only item". Again, in the case of VIR_NETWORK_SECTION_IP_DHCP_HOST, this would mean to pick the one <ip> element that actually has a <dhcp> section (may not be the first, but there can only be one). 2) added VIR_NETWORK_UPDATE_AFFECT_CURRENT similar to VIR_DOMAIN_AFFECT_CURRENT

These two objects were previously always parsed as a part of an IpDef, but we will now need to be able to parse them on their own for virNetworkUpdate(). Split the parsing functions out, with no functional changes. --- src/conf/network_conf.c | 255 +++++++++++++++++++++++++++++------------------- 1 file changed, 154 insertions(+), 101 deletions(-) diff --git a/src/conf/network_conf.c b/src/conf/network_conf.c index 1ee0951..d916427 100644 --- a/src/conf/network_conf.c +++ b/src/conf/network_conf.c @@ -111,6 +111,13 @@ virNetworkForwardPfDefClear(virNetworkForwardPfDefPtr def) VIR_FREE(def->dev); } +static void +virNetworkDHCPHostDefClear(virNetworkDHCPHostDefPtr def) +{ + VIR_FREE(def->mac); + VIR_FREE(def->name); +} + static void virNetworkIpDefClear(virNetworkIpDefPtr def) { int ii; @@ -118,10 +125,8 @@ static void virNetworkIpDefClear(virNetworkIpDefPtr def) VIR_FREE(def->family); VIR_FREE(def->ranges); - for (ii = 0 ; ii < def->nhosts && def->hosts ; ii++) { - VIR_FREE(def->hosts[ii].mac); - VIR_FREE(def->hosts[ii].name); - } + for (ii = 0 ; ii < def->nhosts && def->hosts ; ii++) + virNetworkDHCPHostDefClear(&def->hosts[ii]); VIR_FREE(def->hosts); VIR_FREE(def->tftproot); @@ -370,9 +375,140 @@ int virNetworkIpDefNetmask(const virNetworkIpDefPtr def, static int -virNetworkDHCPRangeDefParseXML(const char *networkName, - virNetworkIpDefPtr def, - xmlNodePtr node) +virNetworkDHCPRangeDefParse(const char *networkName, + xmlNodePtr node, + virNetworkDHCPRangeDefPtr range) +{ + + + char *start = NULL, *end = NULL; + int ret = -1; + + if (!(start = virXMLPropString(node, "start"))) { + virReportError(VIR_ERR_XML_ERROR, + _("Missing 'start' attribute in dhcp range for network '%s'"), + networkName); + goto cleanup; + } + if (virSocketAddrParse(&range->start, start, AF_UNSPEC) < 0) + goto cleanup; + + if (!(end = virXMLPropString(node, "end"))) { + virReportError(VIR_ERR_XML_ERROR, + _("Missing 'end' attribute in dhcp range for network '%s'"), + networkName); + goto cleanup; + } + if (virSocketAddrParse(&range->end, end, AF_UNSPEC) < 0) + goto cleanup; + + /* do a sanity check of the range */ + if (virSocketAddrGetRange(&range->start, &range->end) < 0) { + virReportError(VIR_ERR_XML_ERROR, + _("Invalid dhcp range '%s' to '%s' in network '%s'"), + start, end, networkName); + goto cleanup; + } + + ret = 0; + +cleanup: + VIR_FREE(start); + VIR_FREE(end); + return ret; +} + +static int +virNetworkDHCPHostDefParse(const char *networkName, + xmlNodePtr node, + virNetworkDHCPHostDefPtr host, + bool partialOkay) +{ + char *mac = NULL, *name = NULL, *ip = NULL; + virMacAddr addr; + virSocketAddr inaddr; + int ret = -1; + + mac = virXMLPropString(node, "mac"); + if (mac != NULL) { + if (virMacAddrParse(mac, &addr) < 0) { + virReportError(VIR_ERR_XML_ERROR, + _("Cannot parse MAC address '%s' in network '%s'"), + mac, networkName); + goto cleanup; + } + if (virMacAddrIsMulticast(&addr)) { + virReportError(VIR_ERR_XML_ERROR, + _("expected unicast mac address, found " + "multicast '%s' in network '%s'"), + (const char *)mac, networkName); + goto cleanup; + } + } + + name = virXMLPropString(node, "name"); + if (name && (!c_isalpha(name[0]))) { + virReportError(VIR_ERR_XML_ERROR, + _("Cannot use name address '%s' in network '%s'"), + name, networkName); + goto cleanup; + } + + ip = virXMLPropString(node, "ip"); + if (ip && (virSocketAddrParse(&inaddr, ip, AF_UNSPEC) < 0)) { + virReportError(VIR_ERR_XML_ERROR, + _("Invalid IP address in static host definition " + "for network '%s'"), + networkName); + goto cleanup; + } + + if (partialOkay) { + /* for search/match, you just need one of the three */ + if (!(mac || name || ip)) { + virReportError(VIR_ERR_XML_ERROR, + _("At least one of name, mac, or ip attribute " + "must be specified for static host definition " + "in network '%s' "), + networkName); + } + } else { + /* normal usage - you need at least one MAC address or one host name */ + if (!(mac || name)) { + virReportError(VIR_ERR_XML_ERROR, + _("Static host definition in network '%s' " + "must have mac or name attribute"), + networkName); + goto cleanup; + } + if (!ip) { + virReportError(VIR_ERR_XML_ERROR, + _("Missing IP address in static host definition " + "for network '%s'"), + networkName); + goto cleanup; + } + } + + host->mac = mac; + mac = NULL; + host->name = name; + name = NULL; + if (ip) + host->ip = inaddr; + ret = 0; + +cleanup: + VIR_FREE(mac); + VIR_FREE(name); + VIR_FREE(ip); + return ret; +} + +static int +virNetworkDHCPDefParse(const char *networkName, + virNetworkIpDefPtr def, + xmlNodePtr node) { xmlNodePtr cur; @@ -381,112 +517,29 @@ virNetworkDHCPRangeDefParseXML(const char *networkName, while (cur != NULL) { if (cur->type == XML_ELEMENT_NODE && xmlStrEqual(cur->name, BAD_CAST "range")) { - char *start, *end; - virSocketAddr saddr, eaddr; - int range; - - if (!(start = virXMLPropString(cur, "start"))) { - cur = cur->next; - continue; - } - if (!(end = virXMLPropString(cur, "end"))) { - VIR_FREE(start); - cur = cur->next; - continue; - } - - if (virSocketAddrParse(&saddr, start, AF_UNSPEC) < 0) { - VIR_FREE(start); - VIR_FREE(end); - return -1; - } - if (virSocketAddrParse(&eaddr, end, AF_UNSPEC) < 0) { - VIR_FREE(start); - VIR_FREE(end); - return -1; - } - - range = virSocketAddrGetRange(&saddr, &eaddr); - if (range < 0) { - virReportError(VIR_ERR_XML_ERROR, - _("Invalid dhcp range '%s' to '%s' in network '%s'"), - start, end, networkName); - VIR_FREE(start); - VIR_FREE(end); - return -1; - } - VIR_FREE(start); - VIR_FREE(end); if (VIR_REALLOC_N(def->ranges, def->nranges + 1) < 0) { virReportOOMError(); return -1; } - def->ranges[def->nranges].start = saddr; - def->ranges[def->nranges].end = eaddr; + if (virNetworkDHCPRangeDefParse(networkName, cur, + &def->ranges[def->nranges]) < 0) { + return -1; + } def->nranges++; + } else if (cur->type == XML_ELEMENT_NODE && xmlStrEqual(cur->name, BAD_CAST "host")) { - char *mac = NULL, *name = NULL, *ip; - virMacAddr addr; - virSocketAddr inaddr; - mac = virXMLPropString(cur, "mac"); - if (mac != NULL) { - if (virMacAddrParse(mac, &addr) < 0) { - virReportError(VIR_ERR_XML_ERROR, - _("Cannot parse MAC address '%s' in network '%s'"), - mac, networkName); - VIR_FREE(mac); - return -1; - } - if (virMacAddrIsMulticast(&addr)) { - virReportError(VIR_ERR_XML_ERROR, - _("expected unicast mac address, found multicast '%s' in network '%s'"), - (const char *)mac, networkName); - VIR_FREE(mac); - return -1; - } - } - name = virXMLPropString(cur, "name"); - if ((name != NULL) && (!c_isalpha(name[0]))) { - virReportError(VIR_ERR_XML_ERROR, - _("Cannot use name address '%s' in network '%s'"), - name, networkName); - VIR_FREE(mac); - VIR_FREE(name); - return -1; - } - /* - * You need at least one MAC address or one host name - */ - if ((mac == NULL) && (name == NULL)) { - virReportError(VIR_ERR_XML_ERROR, - _("Static host definition in network '%s' must have mac or name attribute"), - networkName); - return -1; - } - ip = virXMLPropString(cur, "ip"); - if ((ip == NULL) || - (virSocketAddrParse(&inaddr, ip, AF_UNSPEC) < 0)) { - virReportError(VIR_ERR_XML_ERROR, - _("Missing IP address in static host definition for network '%s'"), - networkName); - VIR_FREE(ip); - VIR_FREE(mac); - VIR_FREE(name); - return -1; - } - VIR_FREE(ip); if (VIR_REALLOC_N(def->hosts, def->nhosts + 1) < 0) { - VIR_FREE(mac); - VIR_FREE(name); virReportOOMError(); return -1; } - def->hosts[def->nhosts].mac = mac; - def->hosts[def->nhosts].name = name; - def->hosts[def->nhosts].ip = inaddr; + if (virNetworkDHCPHostDefParse(networkName, cur, + &def->hosts[def->nhosts], + false) < 0) { + return -1; + } def->nhosts++; } else if (cur->type == XML_ELEMENT_NODE && @@ -853,7 +906,7 @@ virNetworkIPParseXML(const char *networkName, while (cur != NULL) { if (cur->type == XML_ELEMENT_NODE && xmlStrEqual(cur->name, BAD_CAST "dhcp")) { - result = virNetworkDHCPRangeDefParseXML(networkName, def, cur); + result = virNetworkDHCPDefParse(networkName, def, cur); if (result) goto error; -- 1.7.11.4

On 09/17/2012 03:48 AM, Laine Stump wrote:
These two objects were previously always parsed as a part of an IpDef, but we will now need to be able to parse them on their own for virNetworkUpdate(). Split the parsing functions out, with no functional changes. --- src/conf/network_conf.c | 255 +++++++++++++++++++++++++++++------------------- 1 file changed, 154 insertions(+), 101 deletions(-)
ACK. -- Eric Blake eblake@redhat.com +1-919-301-3266 Libvirt virtualization library http://libvirt.org

On 09/17/2012 05:17 PM, Eric Blake wrote:
On 09/17/2012 03:48 AM, Laine Stump wrote:
These two objects were previously always parsed as a part of an IpDef, but we will now need to be able to parse them on their own for virNetworkUpdate(). Split the parsing functions out, with no functional changes. --- src/conf/network_conf.c | 255 +++++++++++++++++++++++++++++------------------- 1 file changed, 154 insertions(+), 101 deletions(-) ACK.
Thanks. Since this stands on its own without the rest of the series, I'm pushing it now.

virNetworkAssignDef was allocating a new network object, initing and grabbing its lock, then potentially freeing it without unlocking or destroying the lock. In practice 1) this will probably never happen, and 2) even if it did, the lock implementation used on most (all?) platforms doesn't actually hold any resources for an initialized or held lock, but it still bothered me, so I moved the realloc that could lead to this bad situation earlier in the function, and now the mutex isn't inited or locked until we are assured of complete success. --- src/conf/network_conf.c | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/conf/network_conf.c b/src/conf/network_conf.c index d916427..88e1492 100644 --- a/src/conf/network_conf.c +++ b/src/conf/network_conf.c @@ -245,6 +245,11 @@ virNetworkObjPtr virNetworkAssignDef(virNetworkObjListPtr nets, return network; } + if (VIR_REALLOC_N(nets->objs, nets->count + 1) < 0) { + virReportOOMError(); + return NULL; + } + if (VIR_ALLOC(network) < 0) { virReportOOMError(); return NULL; @@ -258,12 +263,6 @@ virNetworkObjPtr virNetworkAssignDef(virNetworkObjListPtr nets, virNetworkObjLock(network); network->def = def; - if (VIR_REALLOC_N(nets->objs, nets->count + 1) < 0) { - virReportOOMError(); - VIR_FREE(network); - return NULL; - } - nets->objs[nets->count] = network; nets->count++; -- 1.7.11.4

On 09/17/2012 03:48 AM, Laine Stump wrote:
virNetworkAssignDef was allocating a new network object, initing and grabbing its lock, then potentially freeing it without unlocking or destroying the lock. In practice 1) this will probably never happen, and 2) even if it did, the lock implementation used on most (all?)
This is probably a safe claim for Unix-like systems, but mingw can be rather surprising, and I think actually leaks resources in this scenario.
platforms doesn't actually hold any resources for an initialized or held lock, but it still bothered me, so I moved the realloc that could lead to this bad situation earlier in the function, and now the mutex isn't inited or locked until we are assured of complete success. --- src/conf/network_conf.c | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-)
ACK. -- Eric Blake eblake@redhat.com +1-919-301-3266 Libvirt virtualization library http://libvirt.org

On 09/17/2012 05:50 PM, Eric Blake wrote:
On 09/17/2012 03:48 AM, Laine Stump wrote:
virNetworkAssignDef was allocating a new network object, initing and grabbing its lock, then potentially freeing it without unlocking or destroying the lock. In practice 1) this will probably never happen, and 2) even if it did, the lock implementation used on most (all?) This is probably a safe claim for Unix-like systems, but mingw can be rather surprising, and I think actually leaks resources in this scenario.
Yeah, I can easily imagine that, which is why it bothered me.
platforms doesn't actually hold any resources for an initialized or held lock, but it still bothered me, so I moved the realloc that could lead to this bad situation earlier in the function, and now the mutex isn't inited or locked until we are assured of complete success. --- src/conf/network_conf.c | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) ACK.
This also doesn't need the rest of the series, so I'll push now to avoid the rush. Thanks!

This patch adds a new public API virNetworkUpdate that will permit updating an existing network configuration without requiring that the network be destroyed/restarted for the changes to take effect. --- include/libvirt/libvirt.h.in | 50 +++++++++++++++++++++++++++++++++++++ src/driver.h | 7 ++++++ src/libvirt.c | 59 ++++++++++++++++++++++++++++++++++++++++++++ src/libvirt_public.syms | 1 + 4 files changed, 117 insertions(+) diff --git a/include/libvirt/libvirt.h.in b/include/libvirt/libvirt.h.in index b12f7e3..fae0848 100644 --- a/include/libvirt/libvirt.h.in +++ b/include/libvirt/libvirt.h.in @@ -2328,6 +2328,56 @@ virNetworkPtr virNetworkDefineXML (virConnectPtr conn, int virNetworkUndefine (virNetworkPtr network); /* + * virNetworkUpdateSection: + * + * describes which section of a <network> definition the provided + * xml should be applied to. + * + */ +typedef enum { + VIR_NETWORK_SECTION_BRIDGE = 0, + VIR_NETWORK_SECTION_DOMAIN = 1, + VIR_NETWORK_SECTION_IP = 2, + VIR_NETWORK_SECTION_IP_DHCP_HOST = 3, + VIR_NETWORK_SECTION_IP_DHCP_RANGE = 4, + VIR_NETWORK_SECTION_FORWARD = 5, + VIR_NETWORK_SECTION_FORWARD_INTERFACE = 6, + VIR_NETWORK_SECTION_FORWARD_PF = 7, + VIR_NETWORK_SECTION_PORTGROUP = 8, + VIR_NETWORK_SECTION_DNS_HOST = 9, + VIR_NETWORK_SECTION_DNS_TXT = 10, + VIR_NETWORK_SECTION_DNS_SRV = 11, +#ifdef VIR_ENUM_SENTINELS + VIR_NETWORK_SECTION_LAST +#endif +} virNetworkUpdateSection; + +/* + * virNetworkUpdateFlags: + * + * Used to specify what type of operation to perform, and whether to + * affect live network, persistent config, or both. + */ +typedef enum { + VIR_NETWORK_UPDATE_AFFECT_CURRENT = 0, + VIR_NETWORK_UPDATE_AFFECT_LIVE = 1 << 0, + VIR_NETWORK_UPDATE_AFFECT_CONFIG = 1 << 1, + VIR_NETWORK_UPDATE_EXISTING = 1 << 2, + VIR_NETWORK_UPDATE_DELETE = 1 << 3, + VIR_NETWORK_UPDATE_ADD_LAST = 1 << 4, + VIR_NETWORK_UPDATE_ADD_FIRST = 1 << 5, + } virNetworkUpdateFlags; + +/* + * Update an existing network definition + */ +int virNetworkUpdate(virNetworkPtr network, + unsigned int section, /* virNetworkUpdateSection */ + int parentIndex, + const char *xml, + unsigned int flags); + +/* * Activate persistent network */ int virNetworkCreate (virNetworkPtr network); diff --git a/src/driver.h b/src/driver.h index bb470fe..cd82c34 100644 --- a/src/driver.h +++ b/src/driver.h @@ -1115,6 +1115,12 @@ typedef virNetworkPtr typedef int (*virDrvNetworkUndefine) (virNetworkPtr network); typedef int + (*virDrvNetworkUpdate) (virNetworkPtr network, + unsigned int section, /* virNetworkUpdateSection */ + int parentIndex, + const char *xml, + unsigned int flags); +typedef int (*virDrvNetworkCreate) (virNetworkPtr network); typedef int (*virDrvNetworkDestroy) (virNetworkPtr network); @@ -1164,6 +1170,7 @@ struct _virNetworkDriver { virDrvNetworkCreateXML networkCreateXML; virDrvNetworkDefineXML networkDefineXML; virDrvNetworkUndefine networkUndefine; + virDrvNetworkUpdate networkUpdate; virDrvNetworkCreate networkCreate; virDrvNetworkDestroy networkDestroy; virDrvNetworkGetXMLDesc networkGetXMLDesc; diff --git a/src/libvirt.c b/src/libvirt.c index dc8f4e4..bcf5c48 100644 --- a/src/libvirt.c +++ b/src/libvirt.c @@ -10407,6 +10407,65 @@ error: } /** + * virNetworkUpdate: + * @network: pointer to a defined network + * @section: which section of the network to update + * (virNetworkUpdateSection) + * @parentIndex: which parent element, if there are multiples parents + * of the same type (e.g. which <ip> element when modifying + * a <dhcp>/<host> element), or "-1" for "don't care" or + * "automatically find appropriate one". + * @xml: the XML description for the network, preferably in UTF-8 + * @flags: bitwise OR of virNetworkUpdateFlags. + * + * Update the definition of an existing network, either its live + * running state, its persistent configuration, or both. + * + * Returns 0 in case of success, -1 in case of error + */ +int +virNetworkUpdate(virNetworkPtr network, + unsigned int section, /* virNetworkUpdateSection */ + int parentIndex, + const char *xml, + unsigned int flags) +{ + virConnectPtr conn; + VIR_DEBUG("network=%p, section=%d, parentIndex=%d, xml=%s, flags=0x%x", + network, section, parentIndex, xml, flags); + + virResetLastError(); + + if (!VIR_IS_CONNECTED_NETWORK(network)) { + virLibNetworkError(VIR_ERR_INVALID_NETWORK, __FUNCTION__); + virDispatchError(NULL); + return -1; + } + conn = network->conn; + if (conn->flags & VIR_CONNECT_RO) { + virLibNetworkError(VIR_ERR_OPERATION_DENIED, __FUNCTION__); + goto error; + } + + virCheckNonNullArgGoto(xml, error); + + if (conn->networkDriver && conn->networkDriver->networkUpdate) { + int ret; + ret = conn->networkDriver->networkUpdate(network, section, + parentIndex, xml, flags); + if (ret < 0) + goto error; + return ret; + } + + virLibConnError(VIR_ERR_NO_SUPPORT, __FUNCTION__); + +error: + virDispatchError(network->conn); + return -1; +} + +/** * virNetworkCreate: * @network: pointer to a defined network * diff --git a/src/libvirt_public.syms b/src/libvirt_public.syms index 28b92ad..77cc4f2 100644 --- a/src/libvirt_public.syms +++ b/src/libvirt_public.syms @@ -565,6 +565,7 @@ LIBVIRT_0.10.2 { virNodeGetMemoryParameters; virNodeSetMemoryParameters; virStoragePoolListAllVolumes; + virNetworkUpdate; } LIBVIRT_0.10.0; # .... define new API here using predicted next version number .... -- 1.7.11.4

On 09/17/2012 03:48 AM, Laine Stump wrote:
This patch adds a new public API virNetworkUpdate that will permit updating an existing network configuration without requiring that the network be destroyed/restarted for the changes to take effect. --- include/libvirt/libvirt.h.in | 50 +++++++++++++++++++++++++++++++++++++ src/driver.h | 7 ++++++ src/libvirt.c | 59 ++++++++++++++++++++++++++++++++++++++++++++ src/libvirt_public.syms | 1 + 4 files changed, 117 insertions(+)
This one is the clincher - either we take this API as written for the 0.10.2 freeze tomorrow (guaranteeing it will be in the .so, even if the implementation is still baking), or we wait another release cycle to come up with something better (and given the churn we have already been through, I don't know that we'll have any breakthroughs for a nicer API). Dan's already given a weak ACK to the API, and I will also throw in my assent - this is better than any of the other 3 earlier designs we tried (reusing the define XML to redefine, but no way to limit how much was redefined; dealing with an explosion in new APIs; or exposing XPath selections to the user); it is at least extensible, and your cover letter demonstrated several valid use cases where this was sufficient for the task. You may want to wait on DV's opinion before pushing (or DV may want to push before cutting the rc1 release if he's online when you aren't - that would also serve as consent). Also, although I'm agreeing to the API, I have some review comments that might warrant a v2. As for the rest of the series, I'm okay if you wait for the review of all of the patches as a unit before pushing any implementation, even if that means missing the rc1 freeze. Once the API is in place, we can then treat the rest of the series as a bug fix to an incomplete API as justification for including it in rc2 if we miss rc1. Of course, the sooner it is in, the more testing we can give it. Not to mention I already noticed you posted a v2 on patch 6/10, so like me, you are in the same boat of trying to get implementation to match the interface.
+++ b/include/libvirt/libvirt.h.in @@ -2328,6 +2328,56 @@ virNetworkPtr virNetworkDefineXML (virConnectPtr conn, int virNetworkUndefine (virNetworkPtr network);
/* + * virNetworkUpdateSection: + * + * describes which section of a <network> definition the provided + * xml should be applied to. + * + */ +typedef enum { + VIR_NETWORK_SECTION_BRIDGE = 0,
I'm fine with this as-is, but maybe we want to start at 1, to ensure that the user is always passing in a section number and not blindly passing 0 without realizing how to use the API?
+/* + * virNetworkUpdateFlags: + * + * Used to specify what type of operation to perform, and whether to + * affect live network, persistent config, or both. + */ +typedef enum { + VIR_NETWORK_UPDATE_AFFECT_CURRENT = 0, + VIR_NETWORK_UPDATE_AFFECT_LIVE = 1 << 0, + VIR_NETWORK_UPDATE_AFFECT_CONFIG = 1 << 1, + VIR_NETWORK_UPDATE_EXISTING = 1 << 2, + VIR_NETWORK_UPDATE_DELETE = 1 << 3, + VIR_NETWORK_UPDATE_ADD_LAST = 1 << 4, + VIR_NETWORK_UPDATE_ADD_FIRST = 1 << 5,
Am I correct that EXISTING, DELETE, ADD_LAST, and ADD_FIRST are mutually exclusive, and that exactly one has to be provided?
+++ b/src/libvirt.c @@ -10407,6 +10407,65 @@ error: }
/** + * virNetworkUpdate: + * @network: pointer to a defined network + * @section: which section of the network to update + * (virNetworkUpdateSection) + * @parentIndex: which parent element, if there are multiples parents
s/multiples/multiple/
+ * of the same type (e.g. which <ip> element when modifying + * a <dhcp>/<host> element), or "-1" for "don't care" or + * "automatically find appropriate one". + * @xml: the XML description for the network, preferably in UTF-8 + * @flags: bitwise OR of virNetworkUpdateFlags. + * + * Update the definition of an existing network, either its live + * running state, its persistent configuration, or both. + * + * Returns 0 in case of success, -1 in case of error
Where do you document the four modes (existing, delete, add_first, add_last)? Since the modes are mutually exclusive, is it worth passing them in as a separate argument (with values 0, 1, 2, 3), instead of as bits within flags (with values 4, 8, 16, 32)? But adding a new argument would cause you more rebase work, so I can live with them as flags for now. Another possibility might be passing them as 2-bit combinations within the flags parameter (that will automatically enforce the mutual exclusion as well as necessarily supplying exactly one of the four choices), by encoding: VIR_NETWORK_UPDATE_ACTION_MASK = 0xc, VIR_NETWORK_UPDATE_EXISTING = 0x0, VIR_NETWORK_UPDATE_DELETE = 0x4, VIR_NETWORK_UPDATE_ADD_LAST = 0x8, VIR_NETWORK_UPDATE_ADD_FIRST = 0xc,
+ */ +int +virNetworkUpdate(virNetworkPtr network, + unsigned int section, /* virNetworkUpdateSection */ + int parentIndex, + const char *xml, + unsigned int flags) +{ + virConnectPtr conn; + VIR_DEBUG("network=%p, section=%d, parentIndex=%d, xml=%s, flags=0x%x", + network, section, parentIndex, xml, flags); + + virResetLastError(); + + if (!VIR_IS_CONNECTED_NETWORK(network)) { + virLibNetworkError(VIR_ERR_INVALID_NETWORK, __FUNCTION__); + virDispatchError(NULL); + return -1; + } + conn = network->conn; + if (conn->flags & VIR_CONNECT_RO) { + virLibNetworkError(VIR_ERR_OPERATION_DENIED, __FUNCTION__); + goto error; + } + + virCheckNonNullArgGoto(xml, error);
If you _don't_ encode the four modes as a separate argument or as a two-bit field within flags, then it might be worth enforcing here at the API entry point that exactly one of the four flags is always specified, as in: if ((!!(flags & _EXISTING) + !!(flags & _DELETE) + !!(flags & _ADD_FIRST) + !!(flags & _ADD_LAST)) != 1) raise error; But this is bike-shedding - whether or not we error out on invalid mode combinations can be viewed as implementation, not interface, and therefore something we could add later if we want to take this commit as-is for the interface.
+++ b/src/libvirt_public.syms @@ -565,6 +565,7 @@ LIBVIRT_0.10.2 { virNodeGetMemoryParameters; virNodeSetMemoryParameters; virStoragePoolListAllVolumes; + virNetworkUpdate;
You probably guessed I would catch this, but I'll say it anyways :) Sorting :) -- Eric Blake eblake@redhat.com +1-919-301-3266 Libvirt virtualization library http://libvirt.org

On 09/17/2012 06:42 PM, Eric Blake wrote:
This patch adds a new public API virNetworkUpdate that will permit updating an existing network configuration without requiring that the network be destroyed/restarted for the changes to take effect. --- include/libvirt/libvirt.h.in | 50 +++++++++++++++++++++++++++++++++++++ src/driver.h | 7 ++++++ src/libvirt.c | 59 ++++++++++++++++++++++++++++++++++++++++++++ src/libvirt_public.syms | 1 + 4 files changed, 117 insertions(+) This one is the clincher - either we take this API as written for the 0.10.2 freeze tomorrow (guaranteeing it will be in the .so, even if the implementation is still baking), or we wait another release cycle to come up with something better (and given the churn we have already been
On 09/17/2012 03:48 AM, Laine Stump wrote: through, I don't know that we'll have any breakthroughs for a nicer API). Dan's already given a weak ACK to the API, and I will also throw in my assent - this is better than any of the other 3 earlier designs we tried (reusing the define XML to redefine, but no way to limit how much was redefined; dealing with an explosion in new APIs; or exposing XPath selections to the user); it is at least extensible, and your cover letter demonstrated several valid use cases where this was sufficient for the task.
You may want to wait on DV's opinion before pushing (or DV may want to push before cutting the rc1 release if he's online when you aren't - that would also serve as consent). Also, although I'm agreeing to the API, I have some review comments that might warrant a v2.
As for the rest of the series, I'm okay if you wait for the review of all of the patches as a unit before pushing any implementation, even if that means missing the rc1 freeze. Once the API is in place, we can then treat the rest of the series as a bug fix to an incomplete API as justification for including it in rc2 if we miss rc1. Of course, the sooner it is in, the more testing we can give it. Not to mention I already noticed you posted a v2 on patch 6/10, so like me, you are in the same boat of trying to get implementation to match the interface.
+++ b/include/libvirt/libvirt.h.in @@ -2328,6 +2328,56 @@ virNetworkPtr virNetworkDefineXML (virConnectPtr conn, int virNetworkUndefine (virNetworkPtr network);
/* + * virNetworkUpdateSection: + * + * describes which section of a <network> definition the provided + * xml should be applied to. + * + */ +typedef enum { + VIR_NETWORK_SECTION_BRIDGE = 0, I'm fine with this as-is, but maybe we want to start at 1, to ensure that the user is always passing in a section number and not blindly passing 0 without realizing how to use the API?
I like that idea! I'll be posting a V2 in a bit, and that will be one of the changes.
+/* + * virNetworkUpdateFlags: + * + * Used to specify what type of operation to perform, and whether to + * affect live network, persistent config, or both. + */ +typedef enum { + VIR_NETWORK_UPDATE_AFFECT_CURRENT = 0, + VIR_NETWORK_UPDATE_AFFECT_LIVE = 1 << 0, + VIR_NETWORK_UPDATE_AFFECT_CONFIG = 1 << 1, + VIR_NETWORK_UPDATE_EXISTING = 1 << 2, + VIR_NETWORK_UPDATE_DELETE = 1 << 3, + VIR_NETWORK_UPDATE_ADD_LAST = 1 << 4, + VIR_NETWORK_UPDATE_ADD_FIRST = 1 << 5, Am I correct that EXISTING, DELETE, ADD_LAST, and ADD_FIRST are mutually exclusive, and that exactly one has to be provided?
Yes.
+++ b/src/libvirt.c @@ -10407,6 +10407,65 @@ error: }
/** + * virNetworkUpdate: + * @network: pointer to a defined network + * @section: which section of the network to update + * (virNetworkUpdateSection) + * @parentIndex: which parent element, if there are multiples parents s/multiples/multiple/
+ * of the same type (e.g. which <ip> element when modifying + * a <dhcp>/<host> element), or "-1" for "don't care" or + * "automatically find appropriate one". + * @xml: the XML description for the network, preferably in UTF-8 + * @flags: bitwise OR of virNetworkUpdateFlags. + * + * Update the definition of an existing network, either its live + * running state, its persistent configuration, or both. + * + * Returns 0 in case of success, -1 in case of error Where do you document the four modes (existing, delete, add_first, add_last)?
I'll try to put in better documentation in the next round.
Since the modes are mutually exclusive, is it worth passing them in as a separate argument (with values 0, 1, 2, 3), instead of as bits within flags (with values 4, 8, 16, 32)?
Actually, yes. It ended up the way it is just because the arguments grew organically from my virNetworkDefineFlags idea. But when you compare what's there now with an API that has a separate "command" arg, this version is no more compact in the source, and one more unsigned int arg is no big deal. So, before I move on to the virsh command (I finished the first backend section implementation a little while ago), I'll do another pass through git rebase -i, adding in the extra arg.
But adding a new argument would cause you more rebase work, so I can live with them as flags for now. Another possibility might be passing them as 2-bit combinations within the flags parameter (that will automatically enforce the mutual exclusion as well as necessarily supplying exactly one of the four choices), by encoding:
VIR_NETWORK_UPDATE_ACTION_MASK = 0xc, VIR_NETWORK_UPDATE_EXISTING = 0x0, VIR_NETWORK_UPDATE_DELETE = 0x4, VIR_NETWORK_UPDATE_ADD_LAST = 0x8, VIR_NETWORK_UPDATE_ADD_FIRST = 0xc,
+ */ +int +virNetworkUpdate(virNetworkPtr network, + unsigned int section, /* virNetworkUpdateSection */ + int parentIndex, + const char *xml, + unsigned int flags) +{ + virConnectPtr conn; + VIR_DEBUG("network=%p, section=%d, parentIndex=%d, xml=%s, flags=0x%x", + network, section, parentIndex, xml, flags); + + virResetLastError(); + + if (!VIR_IS_CONNECTED_NETWORK(network)) { + virLibNetworkError(VIR_ERR_INVALID_NETWORK, __FUNCTION__); + virDispatchError(NULL); + return -1; + } + conn = network->conn; + if (conn->flags & VIR_CONNECT_RO) { + virLibNetworkError(VIR_ERR_OPERATION_DENIED, __FUNCTION__); + goto error; + } + + virCheckNonNullArgGoto(xml, error); If you _don't_ encode the four modes as a separate argument or as a two-bit field within flags, then it might be worth enforcing here at the API entry point that exactly one of the four flags is always specified, as in:
if ((!!(flags & _EXISTING) + !!(flags & _DELETE) + !!(flags & _ADD_FIRST) + !!(flags & _ADD_LAST)) != 1) raise error;
But this is bike-shedding - whether or not we error out on invalid mode combinations can be viewed as implementation, not interface, and therefore something we could add later if we want to take this commit as-is for the interface.
+++ b/src/libvirt_public.syms @@ -565,6 +565,7 @@ LIBVIRT_0.10.2 { virNodeGetMemoryParameters; virNodeSetMemoryParameters; virStoragePoolListAllVolumes; + virNetworkUpdate; You probably guessed I would catch this, but I'll say it anyways :) Sorting :)
Actually, when I added that line, I was thinking more about the RPC numbers and how the latest one is always last, for some reason. I guess that's what happens when you're running on fumes...

This is very short, because almost everything is autogenerated. All that's needed are: * src/remote/remote_driver.c: add pointer to autogenerated remoteNetworkUpdate to the function table for the remote network driver. * src/remote/remote_protocol.x: add the "args" struct and add one more item to the remote_procedure enum for this function. * src/remote_protocol-struct: update to match remote_protocol.x --- src/remote/remote_driver.c | 1 + src/remote/remote_protocol.x | 11 ++++++++++- src/remote_protocol-structs | 8 ++++++++ 3 files changed, 19 insertions(+), 1 deletion(-) diff --git a/src/remote/remote_driver.c b/src/remote/remote_driver.c index 8f3895d..2a30096 100644 --- a/src/remote/remote_driver.c +++ b/src/remote/remote_driver.c @@ -6075,6 +6075,7 @@ static virNetworkDriver network_driver = { .networkCreateXML = remoteNetworkCreateXML, /* 0.3.0 */ .networkDefineXML = remoteNetworkDefineXML, /* 0.3.0 */ .networkUndefine = remoteNetworkUndefine, /* 0.3.0 */ + .networkUpdate = remoteNetworkUpdate, /* 0.10.2 */ .networkCreate = remoteNetworkCreate, /* 0.3.0 */ .networkDestroy = remoteNetworkDestroy, /* 0.3.0 */ .networkGetXMLDesc = remoteNetworkGetXMLDesc, /* 0.3.0 */ diff --git a/src/remote/remote_protocol.x b/src/remote/remote_protocol.x index 6201ff7..2db09f2 100644 --- a/src/remote/remote_protocol.x +++ b/src/remote/remote_protocol.x @@ -1328,6 +1328,14 @@ struct remote_network_undefine_args { remote_nonnull_network net; }; +struct remote_network_update_args { + remote_nonnull_network net; + unsigned int section; + int parentIndex; + remote_nonnull_string xml; + unsigned int flags; +}; + struct remote_network_create_args { remote_nonnull_network net; }; @@ -2988,7 +2996,8 @@ enum remote_procedure { REMOTE_PROC_CONNECT_LIST_ALL_NWFILTERS = 286, /* skipgen skipgen priority:high */ REMOTE_PROC_CONNECT_LIST_ALL_SECRETS = 287, /* skipgen skipgen priority:high */ REMOTE_PROC_NODE_SET_MEMORY_PARAMETERS = 288, /* autogen autogen */ - REMOTE_PROC_NODE_GET_MEMORY_PARAMETERS = 289 /* skipgen skipgen */ + REMOTE_PROC_NODE_GET_MEMORY_PARAMETERS = 289, /* skipgen skipgen */ + REMOTE_PROC_NETWORK_UPDATE = 290 /* autogen autogen priority:high */ /* * Notice how the entries are grouped in sets of 10 ? diff --git a/src/remote_protocol-structs b/src/remote_protocol-structs index 52ccf80..a84ed86 100644 --- a/src/remote_protocol-structs +++ b/src/remote_protocol-structs @@ -969,6 +969,13 @@ struct remote_network_define_xml_ret { struct remote_network_undefine_args { remote_nonnull_network net; }; +struct remote_network_update_args { + remote_nonnull_network net; + u_int section; + int index; + remote_nonnull_string xml; + u_int flags; +}; struct remote_network_create_args { remote_nonnull_network net; }; @@ -2397,4 +2404,5 @@ enum remote_procedure { REMOTE_PROC_CONNECT_LIST_ALL_SECRETS = 287, REMOTE_PROC_NODE_SET_MEMORY_PARAMETERS = 288, REMOTE_PROC_NODE_GET_MEMORY_PARAMETERS = 289, + REMOTE_PROC_NETWORK_UPDATE = 290, }; -- 1.7.11.4

These new functions are highly inspired by those in domain_conf.c (but not identical), and are intended to make it simpler to update the various combinations of live/persistent network configs. The network driver wasn't previously as careful about the separation between the live "status" in network->def and the persistent "config" in network->newDef (or sometimes in network->def). This series attempts to remedy some of that, but probably doesn't go all the way (enough to get these functions working and enable continued work on virNetworkUpdate though). bridge_driver.c and test_driver.c were updated in a few places to take advantage of the new functions and/or account for changes in argument lists. --- src/conf/network_conf.c | 234 ++++++++++++++++++++++++++++++++++++++++++-- src/conf/network_conf.h | 16 ++- src/libvirt_private.syms | 10 +- src/network/bridge_driver.c | 14 ++- src/test/test_driver.c | 9 +- 5 files changed, 262 insertions(+), 21 deletions(-) diff --git a/src/conf/network_conf.c b/src/conf/network_conf.c index 88e1492..a48eb9e 100644 --- a/src/conf/network_conf.c +++ b/src/conf/network_conf.c @@ -228,20 +228,74 @@ void virNetworkObjListFree(virNetworkObjListPtr nets) nets->count = 0; } -virNetworkObjPtr virNetworkAssignDef(virNetworkObjListPtr nets, - const virNetworkDefPtr def) +/* + * virNetworkObjAssignDef: + * @network: the network object to update + * @def: the new NetworkDef (will be consumed by this function iff successful) + * @live: is this new def the "live" version, or the "persistent" version + * + * Replace the appropriate copy of the given network's NetworkDef + * with def. Use "live" and current state of the network to determine + * which to replace. + * + * Returns 0 on success, -1 on failure. + */ +int +virNetworkObjAssignDef(virNetworkObjPtr network, + const virNetworkDefPtr def, + bool live) { - virNetworkObjPtr network; - - if ((network = virNetworkFindByName(nets, def->name))) { - if (!virNetworkObjIsActive(network)) { + if (virNetworkObjIsActive(network)) { + if (live) { virNetworkDefFree(network->def); network->def = def; - } else { + } else if (network->persistent) { + /* save current configuration to be restored on network shutdown */ virNetworkDefFree(network->newDef); network->newDef = def; + } else { + virReportError(VIR_ERR_OPERATION_INVALID, + _("cannot save persistent config of transient " + "network '%s'"), network->def->name); + return -1; } + } else if (!live) { + virNetworkDefFree(network->newDef); /* should be unnecessary */ + virNetworkDefFree(network->def); + network->def = def; + } else { + virReportError(VIR_ERR_OPERATION_INVALID, + _("cannot save live config of inactive " + "network '%s'"), network->def->name); + return -1; + } + return 0; +} + +/* + * virNetworkAssignDef: + * @nets: list of all networks + * @def: the new NetworkDef (will be consumed by this function iff successful) + * @live: is this new def the "live" version, or the "persistent" version + * + * Either replace the appropriate copy of the NetworkDef with name + * matching def->name or, if not found, create a new NetworkObj with + * def. For an existing network, use "live" and current state of the + * network to determine which to replace. + * + * Returns -1 on failure, 0 on success. + */ +virNetworkObjPtr +virNetworkAssignDef(virNetworkObjListPtr nets, + const virNetworkDefPtr def, + bool live) +{ + virNetworkObjPtr network; + if ((network = virNetworkFindByName(nets, def->name))) { + if (virNetworkObjAssignDef(network, def, live) < 0) { + return NULL; + } return network; } @@ -270,6 +324,150 @@ virNetworkObjPtr virNetworkAssignDef(virNetworkObjListPtr nets, } +/* + * virNetworkObjSetDefTransient: + * @network: network object pointer + * @live: if true, run this operation even for an inactive network. + * this allows freely updated network->def with runtime defaults + * before starting the network, which will be discarded on network + * shutdown. Any cleanup paths need to be sure to handle newDef if + * the network is never started. + * + * Mark the active network config as transient. Ensures live-only update + * operations do not persist past network destroy. + * + * Returns 0 on success, -1 on failure + */ +int +virNetworkObjSetDefTransient(virNetworkObjPtr network, bool live) +{ + if (!virNetworkObjIsActive(network) && !live) + return 0; + + if (!network->persistent || network->newDef) + return 0; + + network->newDef = virNetworkDefCopy(network->def, VIR_NETWORK_XML_INACTIVE); + return network->newDef ? 0 : -1; +} + +/* + * virNetworkObjGetPersistentDef: + * @network: network object pointer + * + * Return the persistent network configuration. If network is transient, + * return the running config. + * + * Returns NULL on error, virNetworkDefPtr on success. + */ +virNetworkDefPtr +virNetworkObjGetPersistentDef(virNetworkObjPtr network) +{ + if (network->newDef) + return network->newDef; + else + return network->def; +} + +/* + * virNetworkObjReplacePersistentDef: + * @network: network object pointer + * @def: new virNetworkDef to replace current persistent config + * + * Replace the "persistent" network configuration with the given new + * virNetworkDef. This pays attention to whether or not the network + * is active. + * + * Returns -1 on error, 0 on success + */ +int +virNetworkObjReplacePersistentDef(virNetworkObjPtr network, + virNetworkDefPtr def) +{ + if (virNetworkObjIsActive(network)) { + virNetworkDefFree(network->newDef); + network->newDef = def; + } else { + virNetworkDefFree(network->def); + network->def = def; + } + return 0; +} + +/* + * virNetworkDefCopy: + * @def: NetworkDef to copy + * @flags: VIR_NETWORK_XML_INACTIVE if appropriate + * + * make a deep copy of the given NetworkDef + * + * Returns a new NetworkDef on success, or NULL on failure. + */ +virNetworkDefPtr +virNetworkDefCopy(virNetworkDefPtr def, unsigned int flags) +{ + char *xml = NULL; + virNetworkDefPtr newDef = NULL; + + if (!def) { + virReportError(VIR_ERR_INTERNAL_ERROR, + "%s", _("NULL NetworkDef")); + return NULL; + } + + /* deep copy with a format/parse cycle */ + if (!(xml = virNetworkDefFormat(def, flags))) + goto cleanup; + newDef = virNetworkDefParseString(xml); +cleanup: + VIR_FREE(xml); + return newDef; +} + +/* + * virNetworkConfigChangeSetup: + * + * 1) checks whether network state is consistent with the requested + * type of modification. + * + * 3) make sure there are separate "def" and "newDef" copies of + * networkDef if appropriate. + * + * Returns 0 on success, -1 on error. + */ +int +virNetworkConfigChangeSetup(virNetworkObjPtr network, unsigned int flags) +{ + bool isActive; + int ret = -1; + + isActive = virNetworkObjIsActive(network); + + if (!isActive && (flags & VIR_NETWORK_UPDATE_AFFECT_LIVE)) { + virReportError(VIR_ERR_OPERATION_INVALID, "%s", + _("network is not running")); + goto cleanup; + } + + if (flags & VIR_NETWORK_UPDATE_AFFECT_CONFIG) { + if (!network->persistent) { + virReportError(VIR_ERR_OPERATION_INVALID, "%s", + _("cannot change persistent config of a " + "transient network")); + goto cleanup; + } + /* this should already have been done by the driver, but do it + * anyway just in case. + */ + if (isActive && (virNetworkObjSetDefTransient(network, false) < 0)) + goto cleanup; + } + + ret = 0; +cleanup: + return ret; +} + void virNetworkRemoveInactive(virNetworkObjListPtr nets, const virNetworkObjPtr net) { @@ -1791,7 +1989,7 @@ int virNetworkSaveConfig(const char *configDir, int ret = -1; char *xml; - if (!(xml = virNetworkDefFormat(def, 0))) + if (!(xml = virNetworkDefFormat(def, VIR_NETWORK_XML_INACTIVE))) goto cleanup; if (virNetworkSaveXML(configDir, def, xml)) @@ -1804,6 +2002,24 @@ cleanup: } +int virNetworkSaveStatus(const char *statusDir, + virNetworkObjPtr network) +{ + int ret = -1; + char *xml; + + if (!(xml = virNetworkDefFormat(network->def, 0))) + goto cleanup; + + if (virNetworkSaveXML(statusDir, network->def, xml)) + goto cleanup; + + ret = 0; +cleanup: + VIR_FREE(xml); + return ret; +} + virNetworkObjPtr virNetworkLoadConfig(virNetworkObjListPtr nets, const char *configDir, const char *autostartDir, @@ -1844,7 +2060,7 @@ virNetworkObjPtr virNetworkLoadConfig(virNetworkObjListPtr nets, goto error; } - if (!(net = virNetworkAssignDef(nets, def))) + if (!(net = virNetworkAssignDef(nets, def, false))) goto error; net->autostart = autostart; diff --git a/src/conf/network_conf.h b/src/conf/network_conf.h index 5dbf50d..0d37a8b 100644 --- a/src/conf/network_conf.h +++ b/src/conf/network_conf.h @@ -247,7 +247,18 @@ void virNetworkObjFree(virNetworkObjPtr net); void virNetworkObjListFree(virNetworkObjListPtr vms); virNetworkObjPtr virNetworkAssignDef(virNetworkObjListPtr nets, - const virNetworkDefPtr def); + const virNetworkDefPtr def, + bool live); +int virNetworkObjAssignDef(virNetworkObjPtr network, + const virNetworkDefPtr def, + bool live); +int virNetworkObjSetDefTransient(virNetworkObjPtr network, bool live); +virNetworkDefPtr virNetworkObjGetPersistentDef(virNetworkObjPtr network); +int virNetworkObjReplacePersistentDef(virNetworkObjPtr network, + virNetworkDefPtr def); +virNetworkDefPtr virNetworkDefCopy(virNetworkDefPtr def, unsigned int flags); +int virNetworkConfigChangeSetup(virNetworkObjPtr dom, unsigned int flags); + void virNetworkRemoveInactive(virNetworkObjListPtr nets, const virNetworkObjPtr net); @@ -283,6 +294,9 @@ int virNetworkSaveXML(const char *configDir, int virNetworkSaveConfig(const char *configDir, virNetworkDefPtr def); +int virNetworkSaveStatus(const char *statusDir, + virNetworkObjPtr net) ATTRIBUTE_RETURN_CHECK; + virNetworkObjPtr virNetworkLoadConfig(virNetworkObjListPtr nets, const char *configDir, const char *autostartDir, diff --git a/src/libvirt_private.syms b/src/libvirt_private.syms index db08c09..33763fc 100644 --- a/src/libvirt_private.syms +++ b/src/libvirt_private.syms @@ -829,6 +829,8 @@ virNetDevVPortTypeToString; # network_conf.h virNetworkAssignDef; virNetworkConfigFile; +virNetworkConfigChangeSetup; +virNetworkDefCopy; virNetworkDefFormat; virNetworkDefFree; virNetworkDefGetIpByIndex; @@ -842,15 +844,21 @@ virNetworkIpDefNetmask; virNetworkIpDefPrefix; virNetworkList; virNetworkLoadAllConfigs; +virNetworkObjAssignDef; +virNetworkObjFree; +virNetworkObjGetPersistentDef; virNetworkObjIsDuplicate; virNetworkObjListFree; virNetworkObjLock; +virNetworkObjReplacePersistentDef; +virNetworkObjSetDefTransient; virNetworkObjUnlock; -virNetworkObjFree; virNetworkRemoveInactive; virNetworkSaveConfig; +virNetworkSaveStatus; virNetworkSetBridgeMacAddr; virNetworkSetBridgeName; +virNetworkSetDefTransient; virPortGroupFindByName; diff --git a/src/network/bridge_driver.c b/src/network/bridge_driver.c index 4faad5d..8dbb050 100644 --- a/src/network/bridge_driver.c +++ b/src/network/bridge_driver.c @@ -2023,6 +2023,9 @@ networkStartNetwork(struct network_driver *driver, return -1; } + if (virNetworkObjSetDefTransient(network, true) < 0) + return -1; + switch (network->def->forwardType) { case VIR_NETWORK_FORWARD_NONE: @@ -2046,7 +2049,7 @@ networkStartNetwork(struct network_driver *driver, /* Persist the live configuration now that anything autogenerated * is setup. */ - if ((ret = virNetworkSaveConfig(NETWORK_STATE_DIR, network->def)) < 0) { + if ((ret = virNetworkSaveStatus(NETWORK_STATE_DIR, network)) < 0) { goto error; } @@ -2389,8 +2392,10 @@ static virNetworkPtr networkCreate(virConnectPtr conn, const char *xml) { if (networkValidate(def) < 0) goto cleanup; - if (!(network = virNetworkAssignDef(&driver->networks, - def))) + /* NB: "live" is false because this transient network hasn't yet + * been started + */ + if (!(network = virNetworkAssignDef(&driver->networks, def, false))) goto cleanup; def = NULL; @@ -2465,8 +2470,7 @@ static virNetworkPtr networkDefine(virConnectPtr conn, const char *xml) { if (networkValidate(def) < 0) goto cleanup; - if (!(network = virNetworkAssignDef(&driver->networks, - def))) + if (!(network = virNetworkAssignDef(&driver->networks, def, false))) goto cleanup; freeDef = false; diff --git a/src/test/test_driver.c b/src/test/test_driver.c index e15833b..d635f3d 100644 --- a/src/test/test_driver.c +++ b/src/test/test_driver.c @@ -576,7 +576,7 @@ static int testOpenDefault(virConnectPtr conn) { if (!(netdef = virNetworkDefParseString(defaultNetworkXML))) goto error; - if (!(netobj = virNetworkAssignDef(&privconn->networks, netdef))) { + if (!(netobj = virNetworkAssignDef(&privconn->networks, netdef, false))) { virNetworkDefFree(netdef); goto error; } @@ -945,8 +945,7 @@ static int testOpenFromFile(virConnectPtr conn, if ((def = virNetworkDefParseNode(xml, networks[i])) == NULL) goto error; } - if (!(net = virNetworkAssignDef(&privconn->networks, - def))) { + if (!(net = virNetworkAssignDef(&privconn->networks, def, false))) { virNetworkDefFree(def); goto error; } @@ -3109,7 +3108,7 @@ static virNetworkPtr testNetworkCreate(virConnectPtr conn, const char *xml) { if ((def = virNetworkDefParseString(xml)) == NULL) goto cleanup; - if ((net = virNetworkAssignDef(&privconn->networks, def)) == NULL) + if (!(net = virNetworkAssignDef(&privconn->networks, def, false))) goto cleanup; def = NULL; net->active = 1; @@ -3134,7 +3133,7 @@ static virNetworkPtr testNetworkDefine(virConnectPtr conn, const char *xml) { if ((def = virNetworkDefParseString(xml)) == NULL) goto cleanup; - if ((net = virNetworkAssignDef(&privconn->networks, def)) == NULL) + if (!(net = virNetworkAssignDef(&privconn->networks, def, false))) goto cleanup; def = NULL; net->persistent = 1; -- 1.7.11.4

virNetworkObjUpdate takes care of all virNetworkUpdate-related changes to the data stored in the in-memory virNetworkObj list. It should be called by network drivers that use this in-memory list. virNetworkObjUpdate *does not* take care of updating any disk-based copies of the config, nor does it perform any other operations necessary to have the new config data take effect (e.g. it won't re-write dnsmasq host files, nor will it send a SIGHUP to dnsmasq) - those things should all be taken care of in the network driver function that calls virNetworkObjUpdate (assuming that it returns success). --- src/conf/network_conf.c | 262 +++++++++++++++++++++++++++++++++++++++++++++++ src/conf/network_conf.h | 7 ++ src/libvirt_private.syms | 1 + 3 files changed, 270 insertions(+) diff --git a/src/conf/network_conf.c b/src/conf/network_conf.c index a48eb9e..8f65c84 100644 --- a/src/conf/network_conf.c +++ b/src/conf/network_conf.c @@ -2251,6 +2251,268 @@ void virNetworkSetBridgeMacAddr(virNetworkDefPtr def) } } +/* NetworkObj backend of the virNetworkUpdate API */ + +static void +virNetworkDefUpdateNoSupport(virNetworkDefPtr def, const char *section) +{ + virReportError(VIR_ERR_NO_SUPPORT, + _("can't update '%s' section of network '%s'"), + section, def->name); +} + +static int +virNetworkDefUpdateBridge(virNetworkDefPtr def, + int parentIndex ATTRIBUTE_UNUSED, + const char *xml ATTRIBUTE_UNUSED, + /* virNetworkUpdateFlags */ + unsigned int fflags ATTRIBUTE_UNUSED) +{ + virNetworkDefUpdateNoSupport(def, "bridge"); + return -1; +} + +static int +virNetworkDefUpdateDomain(virNetworkDefPtr def, + int parentIndex ATTRIBUTE_UNUSED, + const char *xml ATTRIBUTE_UNUSED, + /* virNetworkUpdateFlags */ + unsigned int fflags ATTRIBUTE_UNUSED) +{ + virNetworkDefUpdateNoSupport(def, "domain"); + return -1; +} + +static int +virNetworkDefUpdateIP(virNetworkDefPtr def, + int parentIndex ATTRIBUTE_UNUSED, + const char *xml ATTRIBUTE_UNUSED, + /* virNetworkUpdateFlags */ + unsigned int fflags ATTRIBUTE_UNUSED) +{ + virNetworkDefUpdateNoSupport(def, "ip"); + return -1; +} + +static int +virNetworkDefUpdateIPDHCPHost(virNetworkDefPtr def, + int parentIndex ATTRIBUTE_UNUSED, + const char *xml ATTRIBUTE_UNUSED, + /* virNetworkUpdateFlags */ + unsigned int fflags ATTRIBUTE_UNUSED) +{ + virNetworkDefUpdateNoSupport(def, "ip dhcp host"); + return -1; +} + +static int +virNetworkDefUpdateIPDHCPRange(virNetworkDefPtr def, + int parentIndex ATTRIBUTE_UNUSED, + const char *xml ATTRIBUTE_UNUSED, + /* virNetworkUpdateFlags */ + unsigned int fflags ATTRIBUTE_UNUSED) +{ + virNetworkDefUpdateNoSupport(def, "ip dhcp range"); + return -1; +} + +static int +virNetworkDefUpdateForward(virNetworkDefPtr def, + int parentIndex ATTRIBUTE_UNUSED, + const char *xml ATTRIBUTE_UNUSED, + /* virNetworkUpdateFlags */ + unsigned int fflags ATTRIBUTE_UNUSED) +{ + virNetworkDefUpdateNoSupport(def, "forward"); + return -1; +} + +static int +virNetworkDefUpdateForwardInterface(virNetworkDefPtr def, + int parentIndex ATTRIBUTE_UNUSED, + const char *xml ATTRIBUTE_UNUSED, + /* virNetworkUpdateFlags */ + unsigned int fflags ATTRIBUTE_UNUSED) +{ + virNetworkDefUpdateNoSupport(def, "forward interface"); + return -1; +} + +static int +virNetworkDefUpdateForwardPF(virNetworkDefPtr def, + int parentIndex ATTRIBUTE_UNUSED, + const char *xml ATTRIBUTE_UNUSED, + /* virNetworkUpdateFlags */ + unsigned int fflags ATTRIBUTE_UNUSED) +{ + virNetworkDefUpdateNoSupport(def, "forward pf"); + return -1; +} + +static int +virNetworkDefUpdatePortgroup(virNetworkDefPtr def, + int parentIndex ATTRIBUTE_UNUSED, + const char *xml ATTRIBUTE_UNUSED, + /* virNetworkUpdateFlags */ + unsigned int fflags ATTRIBUTE_UNUSED) +{ + virNetworkDefUpdateNoSupport(def, "portgroup"); + return -1; +} + +static int +virNetworkDefUpdateDNSHost(virNetworkDefPtr def, + int parentIndex ATTRIBUTE_UNUSED, + const char *xml ATTRIBUTE_UNUSED, + /* virNetworkUpdateFlags */ + unsigned int fflags ATTRIBUTE_UNUSED) +{ + virNetworkDefUpdateNoSupport(def, "dns host"); + return -1; +} + +static int +virNetworkDefUpdateDNSTxt(virNetworkDefPtr def, + int parentIndex ATTRIBUTE_UNUSED, + const char *xml ATTRIBUTE_UNUSED, + /* virNetworkUpdateFlags */ + unsigned int fflags ATTRIBUTE_UNUSED) +{ + virNetworkDefUpdateNoSupport(def, "dns txt"); + return -1; +} + +static int +virNetworkDefUpdateDNSSrv(virNetworkDefPtr def, + int parentIndex ATTRIBUTE_UNUSED, + const char *xml ATTRIBUTE_UNUSED, + /* virNetworkUpdateFlags */ + unsigned int fflags ATTRIBUTE_UNUSED) +{ + virNetworkDefUpdateNoSupport(def, "dns txt"); + return -1; +} + +static int +virNetworkDefUpdateSection(virNetworkDefPtr def, + unsigned int section, /* virNetworkUpdateSection */ + int parentIndex, + const char *xml, + unsigned int flags) /* virNetworkUpdateFlags */ +{ + int ret = -1; + + switch (section) { + case VIR_NETWORK_SECTION_BRIDGE: + ret = virNetworkDefUpdateBridge(def, parentIndex, xml, flags); + break; + + case VIR_NETWORK_SECTION_DOMAIN: + ret = virNetworkDefUpdateDomain(def, parentIndex, xml, flags); + break; + case VIR_NETWORK_SECTION_IP: + ret = virNetworkDefUpdateIP(def, parentIndex, xml, flags); + break; + case VIR_NETWORK_SECTION_IP_DHCP_HOST: + ret = virNetworkDefUpdateIPDHCPHost(def, parentIndex, xml, flags); + break; + case VIR_NETWORK_SECTION_IP_DHCP_RANGE: + ret = virNetworkDefUpdateIPDHCPRange(def, parentIndex, xml, flags); + break; + case VIR_NETWORK_SECTION_FORWARD: + ret = virNetworkDefUpdateForward(def, parentIndex, xml, flags); + break; + case VIR_NETWORK_SECTION_FORWARD_INTERFACE: + ret = virNetworkDefUpdateForwardInterface(def, parentIndex, xml, flags); + break; + case VIR_NETWORK_SECTION_FORWARD_PF: + ret = virNetworkDefUpdateForwardPF(def, parentIndex, xml, flags); + break; + case VIR_NETWORK_SECTION_PORTGROUP: + ret = virNetworkDefUpdatePortgroup(def, parentIndex, xml, flags); + break; + case VIR_NETWORK_SECTION_DNS_HOST: + ret = virNetworkDefUpdateDNSHost(def, parentIndex, xml, flags); + break; + case VIR_NETWORK_SECTION_DNS_TXT: + ret = virNetworkDefUpdateDNSTxt(def, parentIndex, xml, flags); + break; + case VIR_NETWORK_SECTION_DNS_SRV: + ret = virNetworkDefUpdateDNSSrv(def, parentIndex, xml, flags); + break; + default: + virReportError(VIR_ERR_NO_SUPPORT, "%s", + _("can't update unrecognized section of network")); + break; + } + + return ret; +} + +/* + * virNetworkObjUpdate: + * + * Apply the supplied update to the given virNetworkObj. Except for + * @network pointing to an actual network object rather than the + * opaque virNetworkPtr, parameters are identical to the public API + * virNetworkUpdate. + * + * The original virNetworkDefs are copied, and all modifications made + * to these copies. The originals are replaced with the copies only + * after success has been guaranteed. + * + * Returns: -1 on error, 0 on success. + */ +int +virNetworkObjUpdate(virNetworkObjPtr network, + unsigned int section, /* virNetworkUpdateSection */ + int parentIndex, + const char *xml, + unsigned int flags) /* virNetworkUpdateFlags */ +{ + int ret = -1; + virNetworkDefPtr def = NULL; + + /* normalize config data, and check for common invalid requests. */ + if (virNetworkConfigChangeSetup(network, flags) < 0) + goto cleanup; + + if (flags & VIR_NETWORK_UPDATE_AFFECT_LIVE) { + /* work on a copy of the def */ + if (!(def = virNetworkDefCopy(network->def, 0))) + goto cleanup; + if (virNetworkDefUpdateSection(def, section, + parentIndex, xml, flags) < 0) { + goto cleanup; + } + /* successfully modified copy, now replace original */ + virNetworkDefFree(network->def); + network->def = def; + def = NULL; + } + + if (flags & VIR_NETWORK_UPDATE_AFFECT_CONFIG) { + /* work on a copy of the def */ + if (!(def = virNetworkDefCopy(virNetworkObjGetPersistentDef(network), + VIR_NETWORK_XML_INACTIVE))) { + goto cleanup; + } + if (virNetworkDefUpdateSection(def, section, + parentIndex, xml, flags) < 0) { + goto cleanup; + } + /* successfully modified copy, now replace original */ + if (virNetworkObjReplacePersistentDef(network, def) < 0) + goto cleanup; + def = NULL; + } + + ret = 0; +cleanup: + virNetworkDefFree(def); + return ret; +} + /* * virNetworkObjIsDuplicate: * @doms : virNetworkObjListPtr to search diff --git a/src/conf/network_conf.h b/src/conf/network_conf.h index 0d37a8b..3d12f58 100644 --- a/src/conf/network_conf.h +++ b/src/conf/network_conf.h @@ -326,6 +326,13 @@ int virNetworkSetBridgeName(const virNetworkObjListPtr nets, void virNetworkSetBridgeMacAddr(virNetworkDefPtr def); +int +virNetworkObjUpdate(virNetworkObjPtr obj, + unsigned int section, /* virNetworkUpdateSection */ + int parentIndex, + const char *xml, + unsigned int flags); /* virNetworkUpdateFlags */ + int virNetworkObjIsDuplicate(virNetworkObjListPtr doms, virNetworkDefPtr def, unsigned int check_active); diff --git a/src/libvirt_private.syms b/src/libvirt_private.syms index 33763fc..29511f2 100644 --- a/src/libvirt_private.syms +++ b/src/libvirt_private.syms @@ -853,6 +853,7 @@ virNetworkObjLock; virNetworkObjReplacePersistentDef; virNetworkObjSetDefTransient; virNetworkObjUnlock; +virNetworkObjUpdate; virNetworkRemoveInactive; virNetworkSaveConfig; virNetworkSaveStatus; -- 1.7.11.4

virNetworkObjUpdate takes care of all virNetworkUpdate-related changes to the data stored in the in-memory virNetworkObj list. It should be called by network drivers that use this in-memory list. virNetworkObjUpdate *does not* take care of updating any disk-based copies of the config, nor does it perform any other operations necessary to have the new config data take effect (e.g. it won't re-write dnsmasq host files, nor will it send a SIGHUP to dnsmasq) - those things should all be taken care of in the network driver function that calls virNetworkObjUpdate (assuming that it returns success). --- I'm resending this patch because, while implementing the backend for a specific section (IP_DHCP_HOST), I realized that it will be much more useful for virNetworkObjUpdate() to create an xmlDoc and xmlXPathContext, and send that down to the section-specific functions rather than the character string - they're all going to need an xmlDoc anyway, so I may as well write the code once. src/conf/network_conf.c | 287 +++++++++++++++++++++++++++++++++++++++++++++++ src/conf/network_conf.h | 7 ++ src/libvirt_private.syms | 1 + 3 files changed, 295 insertions(+) diff --git a/src/conf/network_conf.c b/src/conf/network_conf.c index a48eb9e..d991b2a 100644 --- a/src/conf/network_conf.c +++ b/src/conf/network_conf.c @@ -2251,6 +2251,293 @@ void virNetworkSetBridgeMacAddr(virNetworkDefPtr def) } } +/* NetworkObj backend of the virNetworkUpdate API */ + +static void +virNetworkDefUpdateNoSupport(virNetworkDefPtr def, const char *section) +{ + virReportError(VIR_ERR_NO_SUPPORT, + _("can't update '%s' section of network '%s'"), + section, def->name); +} + +#if 0 +static int +virNetworkDefUpdateCheckElementName(virNetworkDefPtr def, + xmlNodePtr node, + const char *section) +{ + if (!xmlStrEqual(node->name, BAD_CAST section)) { + virReportError(VIR_ERR_XML_ERROR, + _("unexpected element <%s>, expecting <%s>, " + "while updating network '%s'"), + node->name, section, def->name); + return -1; + } + return 0; +} +#endif + +static int +virNetworkDefUpdateBridge(virNetworkDefPtr def, + int parentIndex ATTRIBUTE_UNUSED, + xmlXPathContextPtr ctxt ATTRIBUTE_UNUSED, + /* virNetworkUpdateFlags */ + unsigned int fflags ATTRIBUTE_UNUSED) +{ + virNetworkDefUpdateNoSupport(def, "bridge"); + return -1; +} + +static int +virNetworkDefUpdateDomain(virNetworkDefPtr def, + int parentIndex ATTRIBUTE_UNUSED, + xmlXPathContextPtr ctxt ATTRIBUTE_UNUSED, + /* virNetworkUpdateFlags */ + unsigned int fflags ATTRIBUTE_UNUSED) +{ + virNetworkDefUpdateNoSupport(def, "domain"); + return -1; +} + +static int +virNetworkDefUpdateIP(virNetworkDefPtr def, + int parentIndex ATTRIBUTE_UNUSED, + xmlXPathContextPtr ctxt ATTRIBUTE_UNUSED, + /* virNetworkUpdateFlags */ + unsigned int fflags ATTRIBUTE_UNUSED) +{ + virNetworkDefUpdateNoSupport(def, "ip"); + return -1; +} + +static int +virNetworkDefUpdateIPDHCPHost(virNetworkDefPtr def, + int parentIndex ATTRIBUTE_UNUSED, + xmlXPathContextPtr ctxt ATTRIBUTE_UNUSED, + /* virNetworkUpdateFlags */ + unsigned int fflags ATTRIBUTE_UNUSED) +{ + virNetworkDefUpdateNoSupport(def, "ip dhcp host"); + return -1; +} + +static int +virNetworkDefUpdateIPDHCPRange(virNetworkDefPtr def, + int parentIndex ATTRIBUTE_UNUSED, + xmlXPathContextPtr ctxt ATTRIBUTE_UNUSED, + /* virNetworkUpdateFlags */ + unsigned int fflags ATTRIBUTE_UNUSED) +{ + virNetworkDefUpdateNoSupport(def, "ip dhcp range"); + return -1; +} + +static int +virNetworkDefUpdateForward(virNetworkDefPtr def, + int parentIndex ATTRIBUTE_UNUSED, + xmlXPathContextPtr ctxt ATTRIBUTE_UNUSED, + /* virNetworkUpdateFlags */ + unsigned int fflags ATTRIBUTE_UNUSED) +{ + virNetworkDefUpdateNoSupport(def, "forward"); + return -1; +} + +static int +virNetworkDefUpdateForwardInterface(virNetworkDefPtr def, + int parentIndex ATTRIBUTE_UNUSED, + xmlXPathContextPtr ctxt ATTRIBUTE_UNUSED, + /* virNetworkUpdateFlags */ + unsigned int fflags ATTRIBUTE_UNUSED) +{ + virNetworkDefUpdateNoSupport(def, "forward interface"); + return -1; +} + +static int +virNetworkDefUpdateForwardPF(virNetworkDefPtr def, + int parentIndex ATTRIBUTE_UNUSED, + xmlXPathContextPtr ctxt ATTRIBUTE_UNUSED, + /* virNetworkUpdateFlags */ + unsigned int fflags ATTRIBUTE_UNUSED) +{ + virNetworkDefUpdateNoSupport(def, "forward pf"); + return -1; +} + +static int +virNetworkDefUpdatePortgroup(virNetworkDefPtr def, + int parentIndex ATTRIBUTE_UNUSED, + xmlXPathContextPtr ctxt ATTRIBUTE_UNUSED, + /* virNetworkUpdateFlags */ + unsigned int fflags ATTRIBUTE_UNUSED) +{ + virNetworkDefUpdateNoSupport(def, "portgroup"); + return -1; +} + +static int +virNetworkDefUpdateDNSHost(virNetworkDefPtr def, + int parentIndex ATTRIBUTE_UNUSED, + xmlXPathContextPtr ctxt ATTRIBUTE_UNUSED, + /* virNetworkUpdateFlags */ + unsigned int fflags ATTRIBUTE_UNUSED) +{ + virNetworkDefUpdateNoSupport(def, "dns host"); + return -1; +} + +static int +virNetworkDefUpdateDNSTxt(virNetworkDefPtr def, + int parentIndex ATTRIBUTE_UNUSED, + xmlXPathContextPtr ctxt ATTRIBUTE_UNUSED, + /* virNetworkUpdateFlags */ + unsigned int fflags ATTRIBUTE_UNUSED) +{ + virNetworkDefUpdateNoSupport(def, "dns txt"); + return -1; +} + +static int +virNetworkDefUpdateDNSSrv(virNetworkDefPtr def, + int parentIndex ATTRIBUTE_UNUSED, + xmlXPathContextPtr ctxt ATTRIBUTE_UNUSED, + /* virNetworkUpdateFlags */ + unsigned int fflags ATTRIBUTE_UNUSED) +{ + virNetworkDefUpdateNoSupport(def, "dns txt"); + return -1; +} + +static int +virNetworkDefUpdateSection(virNetworkDefPtr def, + unsigned int section, /* virNetworkUpdateSection */ + int parentIndex, + const char *xml, + unsigned int flags) /* virNetworkUpdateFlags */ +{ + int ret = -1; + xmlDocPtr doc; + xmlXPathContextPtr ctxt = NULL; + + if (!(doc = virXMLParseStringCtxt(xml, _("network_update_xml"), &ctxt))) + goto cleanup; + + switch (section) { + case VIR_NETWORK_SECTION_BRIDGE: + ret = virNetworkDefUpdateBridge(def, parentIndex, ctxt, flags); + break; + + case VIR_NETWORK_SECTION_DOMAIN: + ret = virNetworkDefUpdateDomain(def, parentIndex, ctxt, flags); + break; + case VIR_NETWORK_SECTION_IP: + ret = virNetworkDefUpdateIP(def, parentIndex, ctxt, flags); + break; + case VIR_NETWORK_SECTION_IP_DHCP_HOST: + ret = virNetworkDefUpdateIPDHCPHost(def, parentIndex, ctxt, flags); + break; + case VIR_NETWORK_SECTION_IP_DHCP_RANGE: + ret = virNetworkDefUpdateIPDHCPRange(def, parentIndex, ctxt, flags); + break; + case VIR_NETWORK_SECTION_FORWARD: + ret = virNetworkDefUpdateForward(def, parentIndex, ctxt, flags); + break; + case VIR_NETWORK_SECTION_FORWARD_INTERFACE: + ret = virNetworkDefUpdateForwardInterface(def, parentIndex, ctxt, flags); + break; + case VIR_NETWORK_SECTION_FORWARD_PF: + ret = virNetworkDefUpdateForwardPF(def, parentIndex, ctxt, flags); + break; + case VIR_NETWORK_SECTION_PORTGROUP: + ret = virNetworkDefUpdatePortgroup(def, parentIndex, ctxt, flags); + break; + case VIR_NETWORK_SECTION_DNS_HOST: + ret = virNetworkDefUpdateDNSHost(def, parentIndex, ctxt, flags); + break; + case VIR_NETWORK_SECTION_DNS_TXT: + ret = virNetworkDefUpdateDNSTxt(def, parentIndex, ctxt, flags); + break; + case VIR_NETWORK_SECTION_DNS_SRV: + ret = virNetworkDefUpdateDNSSrv(def, parentIndex, ctxt, flags); + break; + default: + virReportError(VIR_ERR_NO_SUPPORT, "%s", + _("can't update unrecognized section of network")); + break; + } + +cleanup: + xmlFreeDoc(doc); + xmlXPathFreeContext(ctxt); + return ret; +} + +/* + * virNetworkObjUpdate: + * + * Apply the supplied update to the given virNetworkObj. Except for + * @network pointing to an actual network object rather than the + * opaque virNetworkPtr, parameters are identical to the public API + * virNetworkUpdate. + * + * The original virNetworkDefs are copied, and all modifications made + * to these copies. The originals are replaced with the copies only + * after success has been guaranteed. + * + * Returns: -1 on error, 0 on success. + */ +int +virNetworkObjUpdate(virNetworkObjPtr network, + unsigned int section, /* virNetworkUpdateSection */ + int parentIndex, + const char *xml, + unsigned int flags) /* virNetworkUpdateFlags */ +{ + int ret = -1; + virNetworkDefPtr def = NULL; + + /* normalize config data, and check for common invalid requests. */ + if (virNetworkConfigChangeSetup(network, flags) < 0) + goto cleanup; + + if (flags & VIR_NETWORK_UPDATE_AFFECT_LIVE) { + /* work on a copy of the def */ + if (!(def = virNetworkDefCopy(network->def, 0))) + goto cleanup; + if (virNetworkDefUpdateSection(def, section, + parentIndex, xml, flags) < 0) { + goto cleanup; + } + /* successfully modified copy, now replace original */ + virNetworkDefFree(network->def); + network->def = def; + def = NULL; + } + + if (flags & VIR_NETWORK_UPDATE_AFFECT_CONFIG) { + /* work on a copy of the def */ + if (!(def = virNetworkDefCopy(virNetworkObjGetPersistentDef(network), + VIR_NETWORK_XML_INACTIVE))) { + goto cleanup; + } + if (virNetworkDefUpdateSection(def, section, + parentIndex, xml, flags) < 0) { + goto cleanup; + } + /* successfully modified copy, now replace original */ + if (virNetworkObjReplacePersistentDef(network, def) < 0) + goto cleanup; + def = NULL; + } + + ret = 0; +cleanup: + virNetworkDefFree(def); + return ret; +} + /* * virNetworkObjIsDuplicate: * @doms : virNetworkObjListPtr to search diff --git a/src/conf/network_conf.h b/src/conf/network_conf.h index 0d37a8b..3d12f58 100644 --- a/src/conf/network_conf.h +++ b/src/conf/network_conf.h @@ -326,6 +326,13 @@ int virNetworkSetBridgeName(const virNetworkObjListPtr nets, void virNetworkSetBridgeMacAddr(virNetworkDefPtr def); +int +virNetworkObjUpdate(virNetworkObjPtr obj, + unsigned int section, /* virNetworkUpdateSection */ + int parentIndex, + const char *xml, + unsigned int flags); /* virNetworkUpdateFlags */ + int virNetworkObjIsDuplicate(virNetworkObjListPtr doms, virNetworkDefPtr def, unsigned int check_active); diff --git a/src/libvirt_private.syms b/src/libvirt_private.syms index 9e87357..a24f626 100644 --- a/src/libvirt_private.syms +++ b/src/libvirt_private.syms @@ -863,6 +863,7 @@ virNetworkObjLock; virNetworkObjReplacePersistentDef; virNetworkObjSetDefTransient; virNetworkObjUnlock; +virNetworkObjUpdate; virNetworkRemoveInactive; virNetworkSaveConfig; virNetworkSaveStatus; -- 1.7.11.4

This patch splits the starting of dnsmasq and radvd into multiple files, and adds new networkRefreshXX() and networkRestartXX() functions for each. These new functions are currently commented out because they won't be used until the next commit, and the compile options require all static functions to be used. networkRefreshXX() - rewrites any file-based config for dnsmasq/radvd, and sends SIGHUP to the process to make it reread its config. If the program isn't already running, it's just started. networkRestartXX() - kills the given program, waits for it to exit (see the comments in the function networkKillDaemon()), then calls networkStartXX(). This commit is here mostly as a checkpoint to verify no change in functional behavior after refactoring networkStartXX() functions to fit in with these new functions. --- src/network/bridge_driver.c | 326 ++++++++++++++++++++++++++++++++++++++------ 1 file changed, 282 insertions(+), 44 deletions(-) diff --git a/src/network/bridge_driver.c b/src/network/bridge_driver.c index 8dbb050..5f7f31e 100644 --- a/src/network/bridge_driver.c +++ b/src/network/bridge_driver.c @@ -468,6 +468,68 @@ networkShutdown(void) { } +#if 0 +/* currently unused, so it causes a build error unless we #if it out */ +/* networkKillDaemon: + * + * kill the specified pid/name, and wait a bit to make sure it's dead. + */ +static int +networkKillDaemon(pid_t pid, const char *daemonName, const char *networkName) +{ + int ii, ret = -1; + const char *signame = "TERM"; + + /* send SIGTERM, then wait up to 3 seconds for the process to + * disappear, send SIGKILL, then wait for up to another 2 + * seconds. If that fails, log a warning and continue, hoping + * for the best. + */ + for (ii = 0; ii < 25; ii++) { + int signum = 0; + if (ii == 0) + signum = SIGTERM; + else if (ii == 15) { + signum = SIGKILL; + signame = "KILL"; + } + if (kill(pid, signum) < 0) { + if (errno == ESRCH) { + ret = 0; + } else { + char ebuf[1024]; + VIR_WARN("Failed to terminate %s process %d " + "for network '%s' with SIG%s: %s", + daemonName, pid, networkName, signame, + virStrerror(errno, ebuf, sizeof(ebuf))); + } + goto cleanup; + } + /* NB: since networks have no reference count like + * domains, there is no safe way to unlock the network + * object temporarily, and so we can't follow the + * procedure used by the qemu driver of 1) unlock driver + * 2) sleep, 3) add ref to object 4) unlock object, 5) + * re-lock driver, 6) re-lock object. We may need to add + * that functionality eventually, but for now this + * function is rarely used and, at worst, leaving the + * network driver locked during this loop of sleeps will + * have the effect of holding up any other thread trying + * to make modifications to a network for up to 5 seconds; + * since modifications to networks are much less common + * than modifications to domains, this seems a reasonable + * tradeoff in exchange for less code disruption. + */ + usleep(20 * 1000); + } + VIR_WARN("Timed out waiting after SIG%s to %s process %d " + "(network '%s')", + signame, daemonName, pid, networkName); +cleanup: + return ret; +} +#endif /* #if 0 */ + static int networkBuildDnsmasqHostsfile(dnsmasqContext *dctx, virNetworkIpDefPtr ipdef, @@ -777,6 +839,12 @@ networkStartDhcpDaemon(virNetworkObjPtr network) int ret = -1; dnsmasqContext *dctx = NULL; + if (!virNetworkDefGetIpByIndex(network->def, AF_UNSPEC, 0)) { + /* no IPv6 addresses, so we don't need to run radvd */ + ret = 0; + goto cleanup; + } + if (virFileMakePath(NETWORK_PID_DIR) < 0) { virReportSystemError(errno, _("cannot create directory %s"), @@ -840,50 +908,87 @@ cleanup: return ret; } +#if 0 +/* currently unused, so they cause a build error unless we #if them out */ +/* networkRefreshDhcpDaemon: + * Update dnsmasq config files, then send a SIGHUP so that it rereads + * them. + * + * Returns 0 on success, -1 on failure. + */ static int -networkStartRadvd(virNetworkObjPtr network) +networkRefreshDhcpDaemon(virNetworkObjPtr network) { - char *pidfile = NULL; - char *radvdpidbase = NULL; - virBuffer configbuf = VIR_BUFFER_INITIALIZER;; - char *configstr = NULL; - char *configfile = NULL; - virCommandPtr cmd = NULL; int ret = -1, ii; virNetworkIpDefPtr ipdef; + dnsmasqContext *dctx = NULL; - network->radvdPid = -1; + /* if there's no running dnsmasq, just start it */ + if (network->dnsmasqPid <= 0 || (kill(network->dnsmasqPid, 0) < 0)) + return networkStartDhcpDaemon(network); - if (!virFileIsExecutable(RADVD)) { - virReportSystemError(errno, - _("Cannot find %s - " - "Possibly the package isn't installed"), - RADVD); - goto cleanup; + /* Look for first IPv4 address that has dhcp defined. */ + /* We support dhcp config on 1 IPv4 interface only. */ + for (ii = 0; + (ipdef = virNetworkDefGetIpByIndex(network->def, AF_INET, ii)); + ii++) { + if (ipdef->nranges || ipdef->nhosts) + break; } + /* If no IPv4 addresses had dhcp info, pick the first (if there were any). */ + if (!ipdef) + ipdef = virNetworkDefGetIpByIndex(network->def, AF_INET, 0); - if (virFileMakePath(NETWORK_PID_DIR) < 0) { - virReportSystemError(errno, - _("cannot create directory %s"), - NETWORK_PID_DIR); - goto cleanup; - } - if (virFileMakePath(RADVD_STATE_DIR) < 0) { - virReportSystemError(errno, - _("cannot create directory %s"), - RADVD_STATE_DIR); - goto cleanup; + if (!ipdef) { + /* no <ip> elements, so nothing to do */ + return 0; } - /* construct pidfile name */ - if (!(radvdpidbase = networkRadvdPidfileBasename(network->def->name))) { - virReportOOMError(); + if (!(dctx = dnsmasqContextNew(network->def->name, DNSMASQ_STATE_DIR))) goto cleanup; - } - if (!(pidfile = virPidFileBuildPath(NETWORK_PID_DIR, radvdpidbase))) { - virReportOOMError(); + + if (networkBuildDnsmasqHostsfile(dctx, ipdef, network->def->dns) < 0) + goto cleanup; + + if ((ret = dnsmasqSave(dctx)) < 0) goto cleanup; + + ret = kill(network->dnsmasqPid, SIGHUP); +cleanup: + dnsmasqContextFree(dctx); + return ret; +} + +/* networkRestartDhcpDaemon: + * + * kill and restart dnsmasq, in order to update any config that is on + * the dnsmasq commandline (and any placed in separate config files). + * + * Returns 0 on success, -1 on failure. + */ +static int +networkRestartDhcpDaemon(virNetworkObjPtr network) +{ + /* if there is a running dnsmasq, kill it */ + if (network->dnsmasqPid > 0) { + networkKillDaemon(network->dnsmasqPid, "dnsmasq", + network->def->name); + network->dnsmasqPid = -1; } + /* now start dnsmasq if it should be started */ + return networkStartDhcpDaemon(network); +} +#endif /* #if 0 */ + +static int +networkRadvdConfContents(virNetworkObjPtr network, char **configstr) +{ + virBuffer configbuf = VIR_BUFFER_INITIALIZER;; + int ret = -1, ii; + virNetworkIpDefPtr ipdef; + bool v6present = false; + + *configstr = NULL; /* create radvd config file appropriate for this network */ virBufferAsprintf(&configbuf, "interface %s\n" @@ -893,12 +998,15 @@ networkStartRadvd(virNetworkObjPtr network) " AdvOtherConfigFlag off;\n" "\n", network->def->bridge); + + /* add a section for each IPv6 address in the config */ for (ii = 0; (ipdef = virNetworkDefGetIpByIndex(network->def, AF_INET6, ii)); ii++) { int prefix; char *netaddr; + v6present = true; prefix = virNetworkIpDefPrefix(ipdef); if (prefix < 0) { virReportError(VIR_ERR_INTERNAL_ERROR, @@ -919,30 +1027,118 @@ networkStartRadvd(virNetworkObjPtr network) VIR_FREE(netaddr); } - virBufferAddLit(&configbuf, "};\n"); + /* only create the string if we found at least one IPv6 address */ + if (v6present) { + virBufferAddLit(&configbuf, "};\n"); - if (virBufferError(&configbuf)) { - virReportOOMError(); - goto cleanup; + if (virBufferError(&configbuf)) { + virReportOOMError(); + goto cleanup; + } + if (!(*configstr = virBufferContentAndReset(&configbuf))) { + virReportOOMError(); + goto cleanup; + } } - if (!(configstr = virBufferContentAndReset(&configbuf))) { - virReportOOMError(); + + ret = 0; +cleanup: + virBufferFreeAndReset(&configbuf); + return ret; +} + +/* write file and return it's name (which must be freed by caller) */ +static int +networkRadvdConfWrite(virNetworkObjPtr network, char **configFile) +{ + int ret = -1; + char *configStr = NULL; + char *myConfigFile = NULL; + + if (!configFile) + configFile = &myConfigFile; + + *configFile = NULL; + + if (networkRadvdConfContents(network, &configStr) < 0) + goto cleanup; + + if (!configStr) { + ret = 0; goto cleanup; } /* construct the filename */ - if (!(configfile = networkRadvdConfigFileName(network->def->name))) { + if (!(*configFile = networkRadvdConfigFileName(network->def->name))) { virReportOOMError(); goto cleanup; } /* write the file */ - if (virFileWriteStr(configfile, configstr, 0600) < 0) { + if (virFileWriteStr(*configFile, configStr, 0600) < 0) { virReportSystemError(errno, _("couldn't write radvd config file '%s'"), - configfile); + *configFile); + goto cleanup; + } + + ret = 0; +cleanup: + VIR_FREE(configStr); + VIR_FREE(myConfigFile); + return ret; +} + +static int +networkStartRadvd(virNetworkObjPtr network) +{ + char *pidfile = NULL; + char *radvdpidbase = NULL; + char *configfile = NULL; + virCommandPtr cmd = NULL; + int ret = -1; + + network->radvdPid = -1; + + if (!virNetworkDefGetIpByIndex(network->def, AF_INET6, 0)) { + /* no IPv6 addresses, so we don't need to run radvd */ + ret = 0; + goto cleanup; + } + + if (!virFileIsExecutable(RADVD)) { + virReportSystemError(errno, + _("Cannot find %s - " + "Possibly the package isn't installed"), + RADVD); goto cleanup; } + if (virFileMakePath(NETWORK_PID_DIR) < 0) { + virReportSystemError(errno, + _("cannot create directory %s"), + NETWORK_PID_DIR); + goto cleanup; + } + if (virFileMakePath(RADVD_STATE_DIR) < 0) { + virReportSystemError(errno, + _("cannot create directory %s"), + RADVD_STATE_DIR); + goto cleanup; + } + + /* construct pidfile name */ + if (!(radvdpidbase = networkRadvdPidfileBasename(network->def->name))) { + virReportOOMError(); + goto cleanup; + } + if (!(pidfile = virPidFileBuildPath(NETWORK_PID_DIR, radvdpidbase))) { + virReportOOMError(); + goto cleanup; + } + + if (networkRadvdConfWrite(network, &configfile) < 0) + goto cleanup; + /* prevent radvd from daemonizing itself with "--debug 1", and use * a dummy pidfile name - virCommand will create the pidfile we * want to use (this is necessary because radvd's internal @@ -963,21 +1159,63 @@ networkStartRadvd(virNetworkObjPtr network) if (virCommandRun(cmd, NULL) < 0) goto cleanup; - if (virPidFileRead(NETWORK_PID_DIR, radvdpidbase, - &network->radvdPid) < 0) + if (virPidFileRead(NETWORK_PID_DIR, radvdpidbase, &network->radvdPid) < 0) goto cleanup; ret = 0; cleanup: virCommandFree(cmd); VIR_FREE(configfile); - VIR_FREE(configstr); - virBufferFreeAndReset(&configbuf); VIR_FREE(radvdpidbase); VIR_FREE(pidfile); return ret; } +#if 0 +/* currently unused, so they cause a build error unless we #if them out */ +static int +networkRefreshRadvd(virNetworkObjPtr network) +{ + /* if there's no running radvd, just start it */ + if (network->radvdPid <= 0 || (kill(network->radvdPid, 0) < 0)) + return networkStartRadvd(network); + + if (!virNetworkDefGetIpByIndex(network->def, AF_INET6, 0)) { + /* no IPv6 addresses, so we don't need to run radvd */ + return 0; + } + + if (networkRadvdConfWrite(network, NULL) < 0) + return -1; + + return kill(network->radvdPid, SIGHUP); +} + +static int +networkRestartRadvd(virNetworkObjPtr network) +{ + char *radvdpidbase; + + /* if there is a running radvd, kill it */ + if (network->radvdPid > 0) { + /* essentially ignore errors from the following two functions, + * since there's really no better recovery to be done than to + * just push ahead (and that may be exactly what's needed). + */ + if ((networkKillDaemon(network->dnsmasqPid, "radvd", + network->def->name) >= 0) && + ((radvdpidbase = networkRadvdPidfileBasename(network->def->name)) + != NULL)) { + virPidFileDelete(NETWORK_PID_DIR, radvdpidbase); + VIR_FREE(radvdpidbase); + } + network->radvdPid = -1; + } + /* now start radvd if it should be started */ + return networkStartRadvd(network); +} +#endif /* #if 0 */ + static int networkAddMasqueradingIptablesRules(struct network_driver *driver, virNetworkObjPtr network, -- 1.7.11.4

On 09/17/2012 05:48 AM, Laine Stump wrote:
This patch splits the starting of dnsmasq and radvd into multiple files, and adds new networkRefreshXX() and networkRestartXX()
Heh. s/files/functions/ (As a side note - I have run make check && make syntax-check on each step of applying these patches, and also done basic functional checks (e.g. starting and stopping networks with both ipv4 and ipv6 addresses, static hosts, etc. defined, so I'm fairly certain that no existing functionality has been changed.)

Call the network_conf function that modifies the live/persistent/both config, then refresh/restart dnsmasq/radvd if necessary, and finally save the config in the proper place(s). This patch also needed to uncomment a few utility functions that were added inside #if 0 in the previous commit (to avoid compiler errors due to unreferenced static functions). --- src/network/bridge_driver.c | 123 +++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 115 insertions(+), 8 deletions(-) diff --git a/src/network/bridge_driver.c b/src/network/bridge_driver.c index 5f7f31e..37447e8 100644 --- a/src/network/bridge_driver.c +++ b/src/network/bridge_driver.c @@ -468,8 +468,6 @@ networkShutdown(void) { } -#if 0 -/* currently unused, so it causes a build error unless we #if it out */ /* networkKillDaemon: * * kill the specified pid/name, and wait a bit to make sure it's dead. @@ -528,7 +526,6 @@ networkKillDaemon(pid_t pid, const char *daemonName, const char *networkName) cleanup: return ret; } -#endif /* #if 0 */ static int networkBuildDnsmasqHostsfile(dnsmasqContext *dctx, @@ -908,8 +905,6 @@ cleanup: return ret; } -#if 0 -/* currently unused, so they cause a build error unless we #if them out */ /* networkRefreshDhcpDaemon: * Update dnsmasq config files, then send a SIGHUP so that it rereads * them. @@ -978,7 +973,6 @@ networkRestartDhcpDaemon(virNetworkObjPtr network) /* now start dnsmasq if it should be started */ return networkStartDhcpDaemon(network); } -#endif /* #if 0 */ static int networkRadvdConfContents(virNetworkObjPtr network, char **configstr) @@ -1171,8 +1165,6 @@ cleanup: return ret; } -#if 0 -/* currently unused, so they cause a build error unless we #if them out */ static int networkRefreshRadvd(virNetworkObjPtr network) { @@ -1191,6 +1183,8 @@ networkRefreshRadvd(virNetworkObjPtr network) return kill(network->radvdPid, SIGHUP); } +#if 0 +/* currently unused, so it causes a build error unless we #if it out */ static int networkRestartRadvd(virNetworkObjPtr network) { @@ -2830,6 +2824,118 @@ cleanup: return ret; } +static int +networkUpdate(virNetworkPtr net, + unsigned int section, + int parentIndex, + const char *xml, + unsigned int flags) +{ + struct network_driver *driver = net->conn->networkPrivateData; + virNetworkObjPtr network = NULL; + int isActive, ret = -1; + + virCheckFlags(VIR_NETWORK_UPDATE_AFFECT_LIVE | + VIR_NETWORK_UPDATE_AFFECT_CONFIG | + VIR_NETWORK_UPDATE_EXISTING | + VIR_NETWORK_UPDATE_DELETE | + VIR_NETWORK_UPDATE_ADD_LAST | + VIR_NETWORK_UPDATE_ADD_FIRST, + -1); + + networkDriverLock(driver); + + network = virNetworkFindByUUID(&driver->networks, net->uuid); + if (!network) { + virReportError(VIR_ERR_NO_NETWORK, + "%s", _("no network with matching uuid")); + goto cleanup; + } + + /* VIR_NETWORK_UPDATE_AFFECT_CURRENT means "change LIVE if network + * is active, else change CONFIG + */ + isActive = virNetworkObjIsActive(network); + if ((flags & (VIR_NETWORK_UPDATE_AFFECT_LIVE + | VIR_NETWORK_UPDATE_AFFECT_CONFIG)) == + VIR_NETWORK_UPDATE_AFFECT_CURRENT) { + if (isActive) + flags |= VIR_NETWORK_UPDATE_AFFECT_LIVE; + else + flags |= VIR_NETWORK_UPDATE_AFFECT_CONFIG; + } + + /* update the network config in memory/on disk */ + if (virNetworkObjUpdate(network, section, parentIndex, xml, flags) < 0) + goto cleanup; + + if (flags & VIR_NETWORK_UPDATE_AFFECT_CONFIG) { + /* save updated persistent config to disk */ + if (virNetworkSaveConfig(driver->networkConfigDir, + virNetworkObjGetPersistentDef(network)) < 0) { + goto cleanup; + } + } + + if (isActive && (flags & VIR_NETWORK_UPDATE_AFFECT_LIVE)) { + /* rewrite dnsmasq host files, restart dnsmasq, update iptables + * rules, etc, according to which section was modified. Note that + * some sections require multiple actions, so a single switch + * statement is inadequate. + */ + if (section == VIR_NETWORK_SECTION_BRIDGE || + section == VIR_NETWORK_SECTION_DOMAIN || + section == VIR_NETWORK_SECTION_IP || + section == VIR_NETWORK_SECTION_IP_DHCP_RANGE) { + /* these sections all change things on the dnsmasq commandline, + * so we need to kill and restart dnsmasq. + */ + if (networkRestartDhcpDaemon(network) < 0) + goto cleanup; + + } else if (section == VIR_NETWORK_SECTION_IP_DHCP_HOST || + section == VIR_NETWORK_SECTION_DNS_HOST || + section == VIR_NETWORK_SECTION_DNS_TXT || + section == VIR_NETWORK_SECTION_DNS_SRV) { + /* these sections only change things in config files, so we + * can just update the config files and send SIGHUP to + * dnsmasq. + */ + if (networkRefreshDhcpDaemon(network) < 0) + goto cleanup; + + } + + if (section == VIR_NETWORK_SECTION_IP) { + /* only a change in IP addresses will affect radvd, and all of radvd's + * config is stored in the conf file which will be re-read with a SIGHUP. + */ + if (networkRefreshRadvd(network) < 0) + goto cleanup; + } + + if (section == VIR_NETWORK_SECTION_IP || + section == VIR_NETWORK_SECTION_FORWARD || + section == VIR_NETWORK_SECTION_FORWARD_INTERFACE) { + /* these could affect the iptables rules */ + networkRemoveIptablesRules(driver, network); + if (networkAddIptablesRules(driver, network) < 0) + goto cleanup; + + } + + /* save current network state to disk */ + if ((ret = virNetworkSaveStatus(NETWORK_STATE_DIR, network)) < 0) + goto cleanup; + } + ret = 0; +cleanup: + if (network) + virNetworkObjUnlock(network); + networkDriverUnlock(driver); + return ret; +} + static int networkStart(virNetworkPtr net) { struct network_driver *driver = net->conn->networkPrivateData; virNetworkObjPtr network; @@ -3057,6 +3163,7 @@ static virNetworkDriver networkDriver = { .networkCreateXML = networkCreate, /* 0.2.0 */ .networkDefineXML = networkDefine, /* 0.2.0 */ .networkUndefine = networkUndefine, /* 0.2.0 */ + .networkUpdate = networkUpdate, /* 0.10.2 */ .networkCreate = networkStart, /* 0.2.0 */ .networkDestroy = networkDestroy, /* 0.2.0 */ .networkGetXMLDesc = networkGetXMLDesc, /* 0.2.0 */ -- 1.7.11.4

The test driver does nothing outside of keeping track of each network's config/state in the in-memory database maintained by network_conf functions, so all we have to do is call the function that updates the network's entry in the in-memory database. --- src/test/test_driver.c | 56 +++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 55 insertions(+), 1 deletion(-) diff --git a/src/test/test_driver.c b/src/test/test_driver.c index d635f3d..017708b 100644 --- a/src/test/test_driver.c +++ b/src/test/test_driver.c @@ -3123,7 +3123,9 @@ cleanup: return ret; } -static virNetworkPtr testNetworkDefine(virConnectPtr conn, const char *xml) { +static +virNetworkPtr testNetworkDefine(virConnectPtr conn, const char *xml) +{ testConnPtr privconn = conn->privateData; virNetworkDefPtr def; virNetworkObjPtr net = NULL; @@ -3180,6 +3182,57 @@ cleanup: return ret; } +static int +testNetworkUpdate(virNetworkPtr net, + unsigned int section, + int parentIndex ATTRIBUTE_UNUSED, + const char *xml ATTRIBUTE_UNUSED, + unsigned int flags) +{ + testConnPtr privconn = net->conn->privateData; + virNetworkObjPtr network = NULL; + int isActive, ret = -1; + + virCheckFlags(VIR_NETWORK_UPDATE_AFFECT_LIVE | + VIR_NETWORK_UPDATE_AFFECT_CONFIG | + VIR_NETWORK_UPDATE_EXISTING | + VIR_NETWORK_UPDATE_DELETE | + VIR_NETWORK_UPDATE_ADD_LAST | + VIR_NETWORK_UPDATE_ADD_FIRST, + -1); + + testDriverLock(privconn); + + network = virNetworkFindByUUID(&privconn->networks, net->uuid); + if (!network) { + virReportError(VIR_ERR_NO_NETWORK, + "%s", _("no network with matching uuid")); + goto cleanup; + } + + /* VIR_NETWORK_UPDATE_AFFECT_CURRENT means "change LIVE if network + * is active, else change CONFIG + */ + isActive = virNetworkObjIsActive(network); + if ((flags & (VIR_NETWORK_UPDATE_AFFECT_LIVE + | VIR_NETWORK_UPDATE_AFFECT_CONFIG)) == + VIR_NETWORK_UPDATE_AFFECT_CURRENT) { + if (isActive) + flags |= VIR_NETWORK_UPDATE_AFFECT_LIVE; + else + flags |= VIR_NETWORK_UPDATE_AFFECT_CONFIG; + } + + /* update the network config in memory/on disk */ + if (virNetworkObjUpdate(network, section, parentIndex, xml, flags) < 0) + goto cleanup; + + ret = 0; +cleanup: + testDriverUnlock(privconn); + return ret; +} + static int testNetworkStart(virNetworkPtr network) { testConnPtr privconn = network->conn->privateData; virNetworkObjPtr privnet; @@ -5719,6 +5772,7 @@ static virNetworkDriver testNetworkDriver = { .networkCreateXML = testNetworkCreate, /* 0.3.2 */ .networkDefineXML = testNetworkDefine, /* 0.3.2 */ .networkUndefine = testNetworkUndefine, /* 0.3.2 */ + .networkUpdate = testNetworkUpdate, /* 0.10.2 */ .networkCreate = testNetworkStart, /* 0.3.2 */ .networkDestroy = testNetworkDestroy, /* 0.3.2 */ .networkGetXMLDesc = testNetworkGetXMLDesc, /* 0.3.2 */ -- 1.7.11.4

A user on IRC had accidentally killed all of his libvirt-started dnsmasq instances (due to a buggy dnsmasq service script in Fedora 16), and had hoped that libvirtd would notice this on restart and reload all the dnsmasq daemons (as it does with iptables rules). Unfortunately this was not the case - as long as the network object had a pid registered for dnsmasq and/or radvd, it assumed that the processes were running. This patch takes advantage of the new utility functions in bridge_driver.c to do a "refresh" of all radvd and dnsmasq processes started by libvirt each time libvirtd is restarted - this function attempts to do a SIGHUP of each existing process, and if that fails, it restarts the process, rebuilding all the associated config files and commandline parameters in the process. This normally has no effect, but will be useful in solving the occasional "odd situation" without needing to take the drastic step of destroying/re-starting the network. --- src/network/bridge_driver.c | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/src/network/bridge_driver.c b/src/network/bridge_driver.c index 37447e8..84a8510 100644 --- a/src/network/bridge_driver.c +++ b/src/network/bridge_driver.c @@ -117,6 +117,7 @@ static int networkShutdownNetworkExternal(struct network_driver *driver, virNetworkObjPtr network); static void networkReloadIptablesRules(struct network_driver *driver); +static void networkRefreshDaemons(struct network_driver *driver); static struct network_driver *driverState = NULL; @@ -344,6 +345,7 @@ networkStartup(int privileged) { networkFindActiveConfigs(driverState); networkReloadIptablesRules(driverState); + networkRefreshDaemons(driverState); networkAutostartConfigs(driverState); networkDriverUnlock(driverState); @@ -404,6 +406,7 @@ networkReload(void) { driverState->networkConfigDir, driverState->networkAutostartDir); networkReloadIptablesRules(driverState); + networkRefreshDaemons(driverState); networkAutostartConfigs(driverState); networkDriverUnlock(driverState); return 0; @@ -1210,6 +1213,37 @@ networkRestartRadvd(virNetworkObjPtr network) } #endif /* #if 0 */ +/* SIGHUP/restart any dnsmasq or radvd daemons. + * This should be called when libvirtd is restarted. + */ +static void +networkRefreshDaemons(struct network_driver *driver) +{ + unsigned int i; + + VIR_INFO("Refreshing network daemons"); + + for (i = 0 ; i < driver->networks.count ; i++) { + virNetworkObjPtr network = driver->networks.objs[i]; + + virNetworkObjLock(network); + if (virNetworkObjIsActive(network) && + ((network->def->forwardType == VIR_NETWORK_FORWARD_NONE) || + (network->def->forwardType == VIR_NETWORK_FORWARD_NAT) || + (network->def->forwardType == VIR_NETWORK_FORWARD_ROUTE))) { + /* Only the three L3 network types that are configured by + * libvirt will have a dnsmasq or radvd daemon associated + * with them. Here we send a SIGHUP to an existing + * dnsmasq and/or radvd, or restart them if they've + * disappeared. + */ + networkRefreshDhcpDaemon(network); + networkRefreshRadvd(network); + } + virNetworkObjUnlock(network); + } +} + static int networkAddMasqueradingIptablesRules(struct network_driver *driver, virNetworkObjPtr network, -- 1.7.11.4

On Mon, Sep 17, 2012 at 05:48:45AM -0400, Laine Stump wrote:
This patchset implements a new API function called virNetworkUpdate which enables updating certain parts of a libvirt network's definition without the need to destroy/re-start the network. This is especially useful, for example, to add/remove hosts from the dhcp static hosts table, or change portgroup settings.
This was previously discussed in this thread:
https://www.redhat.com/archives/libvir-list/2012-August/msg01535.html
continuing here in September:
https://www.redhat.com/archives/libvir-list/2012-September/msg00328.html
with the final form here:
https://www.redhat.com/archives/libvir-list/2012-September/msg00465.html
In short, the single function has a "section" specifier which tells the part of the network definition to be updated, a "parentIndex" that gives the index of the *parent* element containing this section (when there are multiples - in particular in the case of the <ip> element), and a fully formed XML element which will be added as-is in the case of VIR_NETWORK_UPDATE_ADD_* (after checking for a duplicate), used to search for the specific element to delete in case of VIR_NETWORK_UPDATE_DELETE, and used both to find the existing element and replace its current contents in the case of VIR_UPDATE_EXISTING (this implies that you can't change the change the attribute used for indexing, e.g. the name of a portgroup, or mac address of a dhcp host entry).
An example of use: to add a dhcp host entry to network "net", you would do this:
virNetworkUpdate(net, VIR_NETWORK_SECTION_IP_DHCP_HOST, -1, "<host mac='00:11:22:33:44:55' ip='192.168.122.5'/>", VIR_NETWORK_UPDATE_AFFECT_LIVE | VIR_NETWORK_UPDATE_AFFECT_CONFIG | VIR_NETWORK_UPDATE_ADD_LAST);
To delete that same entry:
virNetworkUpdate(net, VIR_NETWORK_SECTION_IP_DHCP_HOST, -1, "<host mac='00:11:22:33:44:55'/>", VIR_NETWORK_UPDATE_AFFECT_LIVE | VIR_NETWORK_UPDATE_AFFECT_CONFIG | VIR_NETWORK_UPDATE_DELETE);
If you wanted to force any of these to affect the dhcp host list in the 3rd <ip> element of the network, you would replace "-1" with "2".
Another example: to modify the portgroup named "engineering" (e.g. to increase the inbound average bandwidth from 1000 to 2000):
virNetworkUpdate(net, VIR_NETWORK_SECTION_PORTGROUP, -1, "<portgroup name='engineering' default='yes'>" " <virtualport type='802.1Qbh'>" " <parameters profileid='test'/>" " </virtualport>" " <bandwidth>" " <inbound average='2000' peak='5000' burst='5120'/>" " <outbound average='1000' peak='5000' burst='5120'/>" " </bandwidth>" "</portgroup>", VIR_NETWORK_UPDATE_EXISTING | VIR_NETWORK_UPDATE_LIVE | VIR_NETWORK_UPDATE_CONFIG)
(note that parentIndex is irrelevant for PORTGROUP, since they are in the toplevel of <network>, so there aren't multiple instances of parents. In such cases, the caller *must* set parentIndex to -1 or 0 - any other value indicates that they don't understand the purpose/usage of parentIndex, so it must result in an error. Also note that the above function would fail if it couldn't find an existing portgroup with name='engineering' (i.e. it wouldn't automatically add a new one).)
I've been trying to think about how this might all map into the LibvirtGObject/LibvirtGConfig APIs, and the thing I'm struggling with is the parentIndex parameter. First of all, in the GConfig API I won't be exposing the virNetworkUpdate API as it is. To be typesafe, there will be separate APIs for each possible operation. eg gvir_network_add_portgroup gvir_network_remove_portgroup gvir_network_update_portgroup Consider how the <network> schema will eventually map into objects, <network> == GVirConfigNetwork <name>test1</name> <uuid>2d39a0ba-ac4b-6097-114c-50f8bccc277c</uuid> <forward mode='bridge'/> <bridge name='virbr5' stp='on' delay='0' /> <mac address='52:54:00:38:81:4D'/> <domain name='example.com'/> <forward mode='private'/> <interface dev="eth20"/> == GVirConfigNetworkInterface <interface dev="eth21"/> == GVirConfigNetworkInterface <interface dev="eth22"/> == GVirConfigNetworkInterface <interface dev="eth23"/> == GVirConfigNetworkInterface <interface dev="eth24"/> == GVirConfigNetworkInterface </forward> <portgroup name='engineering' default='yes'> == GVirConfigNetworkPortGroup <virtualport type='802.1Qbh'> <parameters profileid='test'/> </virtualport> <bandwidth> <inbound average='1000' peak='5000' burst='5120'/> <outbound average='1000' peak='5000' burst='5120'/> </bandwidth> </portgroup> <portgroup name='sales'> == GVirConfigNetworkPortGroup <virtualport type='802.1Qbh'> <parameters profileid='salestest'/> </virtualport> <bandwidth> <inbound average='500' peak='2000' burst='2560'/> <outbound average='128' peak='256' burst='256'/> </bandwidth> </portgroup> <dns> == GVirConfigNetworkDNS <txt name="example" value="example value" /> == GVirConfigNetworkDNSEntry <srv service='name' protocol='tcp' domain='test-domain-name' target='.' port='1024' priority='10' weight='10'/> == GVirConfigNetworkDNSEntry <host ip='192.168.122.2'> == GVirConfigNetworkDNSEntry <hostname>myhost</hostname> <hostname>myhostalias</hostname> </host> </dns> <ip address='10.24.75.1' netmask='255.255.255.0'> == GVirConfigNetworkAddress <dhcp> == GVirConfigNetworkDHCP <range start='10.24.75.128' end='10.24.75.254' /> <host mac='52:54:3e:77:e2:ed' name='X.example.com' ip='10.24.75.10' /> == GVirConfigNetworkDHCPHost <host mac='52:54:3e:77:e2:ef' name='Y.example.com' ip='10.24.75.11' /> <host mac='52:54:34:77:e2:f0' name='Z.example.com' ip='10.24.75.12' /> <host mac='52:54:3e:77:e2:f1' name='A.example.com' ip='10.24.75.13' /> </dhcp> </ip> <ip address='192.168.4.1' netmask='255.255.255.0'/> == GVirConfigNetworkAddress </network> So for example we get the config object using GVirNetwork *net = gvir_connection_get_network_by_name("default"); GVirConfigNetwork *conf = gvir_network_get_config(net); Now we want to remove all portgroups. This is easy enough - I'd have an API like GList *groups = gvir_config_network_get_portgroups(conf); while (groups) { GVirConfigNetworkPortgroup *group = groups->data; gvir_network_remove_portgroup(net, group); data = data->next; } As you say, the concept of parentIndex doesn't make sense for portgroups, so I'll just ignore it in the API I expose. Likewise adding/removing <interface> from <forward>, we just ignore the parentIndex. Modify doesn't make sense for this part of the schema since there are no attributes to change, beyond the interface name. DNS entries are reasonably easy to deal with add/remove, since again parentIndex is irrelevant, there only being one <dns> block. I'm a little fuzzy on whether a modify action is practical for DNS entries, since the thing you'd want to change is probably also the thing the API would want to use as the unique identifier. The only way around this I see is to pass in both the original and new XML for the DNS entry being modified. The original XML is used to lookup which entry is being mdified, and then replace with the new XML. Perhaps this doesn't matter, and add+remove is sufficient for DNS. The updating of DHCP entries is the interesting / hard one that causes the fun with parentIndex. It is possible to come up with a mapping to GObject, GList *addrs = gvir_config_network_get_addresses(conf); int idx = 0; while (addrs) { GVirConfigNetworkAddress *addr = addrs->data; GVirConfigNetworkDHCP *dhcp = gvir_config_network_address_get_dhcp(addr); GList *hosts = gvir_config_network_dhcp_get_hosts(dhcp); while (hosts) { GVirConfigNeworkDHCPHost *host = hosts->data; gvir_network_remove_dhcp_host(net, idx, host); } idx++; data = data->next; } What I don't like is that the user has to maintain this 'idx' counter value. It doesn't hurt in this example, but consider if you were just passed a single "GVirConfigNetworkAddress" object, and wanted to add a host entry to it. You have no idea what the parentIndex this corresponds to. This isn't fatal, but it is slightly unpleasant. I don't have any better idea though, so I guess we'll just go with what you designed. Daniel -- |: http://berrange.com -o- http://www.flickr.com/photos/dberrange/ :| |: http://libvirt.org -o- http://virt-manager.org :| |: http://autobuild.org -o- http://search.cpan.org/~danberr/ :| |: http://entangle-photo.org -o- http://live.gnome.org/gtk-vnc :|

On 09/17/2012 02:04 PM, Daniel P. Berrange wrote:
On Mon, Sep 17, 2012 at 05:48:45AM -0400, Laine Stump wrote:
This patchset implements a new API function called virNetworkUpdate which enables updating certain parts of a libvirt network's definition without the need to destroy/re-start the network. This is especially useful, for example, to add/remove hosts from the dhcp static hosts table, or change portgroup settings.
This was previously discussed in this thread:
https://www.redhat.com/archives/libvir-list/2012-August/msg01535.html
continuing here in September:
https://www.redhat.com/archives/libvir-list/2012-September/msg00328.html
with the final form here:
https://www.redhat.com/archives/libvir-list/2012-September/msg00465.html
In short, the single function has a "section" specifier which tells the part of the network definition to be updated, a "parentIndex" that gives the index of the *parent* element containing this section (when there are multiples - in particular in the case of the <ip> element), and a fully formed XML element which will be added as-is in the case of VIR_NETWORK_UPDATE_ADD_* (after checking for a duplicate), used to search for the specific element to delete in case of VIR_NETWORK_UPDATE_DELETE, and used both to find the existing element and replace its current contents in the case of VIR_UPDATE_EXISTING (this implies that you can't change the change the attribute used for indexing, e.g. the name of a portgroup, or mac address of a dhcp host entry).
An example of use: to add a dhcp host entry to network "net", you would do this:
virNetworkUpdate(net, VIR_NETWORK_SECTION_IP_DHCP_HOST, -1, "<host mac='00:11:22:33:44:55' ip='192.168.122.5'/>", VIR_NETWORK_UPDATE_AFFECT_LIVE | VIR_NETWORK_UPDATE_AFFECT_CONFIG | VIR_NETWORK_UPDATE_ADD_LAST);
To delete that same entry:
virNetworkUpdate(net, VIR_NETWORK_SECTION_IP_DHCP_HOST, -1, "<host mac='00:11:22:33:44:55'/>", VIR_NETWORK_UPDATE_AFFECT_LIVE | VIR_NETWORK_UPDATE_AFFECT_CONFIG | VIR_NETWORK_UPDATE_DELETE);
If you wanted to force any of these to affect the dhcp host list in the 3rd <ip> element of the network, you would replace "-1" with "2".
Another example: to modify the portgroup named "engineering" (e.g. to increase the inbound average bandwidth from 1000 to 2000):
virNetworkUpdate(net, VIR_NETWORK_SECTION_PORTGROUP, -1, "<portgroup name='engineering' default='yes'>" " <virtualport type='802.1Qbh'>" " <parameters profileid='test'/>" " </virtualport>" " <bandwidth>" " <inbound average='2000' peak='5000' burst='5120'/>" " <outbound average='1000' peak='5000' burst='5120'/>" " </bandwidth>" "</portgroup>", VIR_NETWORK_UPDATE_EXISTING | VIR_NETWORK_UPDATE_LIVE | VIR_NETWORK_UPDATE_CONFIG)
(note that parentIndex is irrelevant for PORTGROUP, since they are in the toplevel of <network>, so there aren't multiple instances of parents. In such cases, the caller *must* set parentIndex to -1 or 0 - any other value indicates that they don't understand the purpose/usage of parentIndex, so it must result in an error. Also note that the above function would fail if it couldn't find an existing portgroup with name='engineering' (i.e. it wouldn't automatically add a new one).) I've been trying to think about how this might all map into the LibvirtGObject/LibvirtGConfig APIs, and the thing I'm struggling with is the parentIndex parameter.
First of all, in the GConfig API I won't be exposing the virNetworkUpdate API as it is. To be typesafe, there will be separate APIs for each possible operation. eg
gvir_network_add_portgroup gvir_network_remove_portgroup gvir_network_update_portgroup
Consider how the <network> schema will eventually map into objects,
<network> == GVirConfigNetwork <name>test1</name> <uuid>2d39a0ba-ac4b-6097-114c-50f8bccc277c</uuid> <forward mode='bridge'/> <bridge name='virbr5' stp='on' delay='0' /> <mac address='52:54:00:38:81:4D'/> <domain name='example.com'/> <forward mode='private'/> <interface dev="eth20"/> == GVirConfigNetworkInterface <interface dev="eth21"/> == GVirConfigNetworkInterface <interface dev="eth22"/> == GVirConfigNetworkInterface <interface dev="eth23"/> == GVirConfigNetworkInterface <interface dev="eth24"/> == GVirConfigNetworkInterface </forward> <portgroup name='engineering' default='yes'> == GVirConfigNetworkPortGroup <virtualport type='802.1Qbh'> <parameters profileid='test'/> </virtualport> <bandwidth> <inbound average='1000' peak='5000' burst='5120'/> <outbound average='1000' peak='5000' burst='5120'/> </bandwidth> </portgroup> <portgroup name='sales'> == GVirConfigNetworkPortGroup <virtualport type='802.1Qbh'> <parameters profileid='salestest'/> </virtualport> <bandwidth> <inbound average='500' peak='2000' burst='2560'/> <outbound average='128' peak='256' burst='256'/> </bandwidth> </portgroup> <dns> == GVirConfigNetworkDNS <txt name="example" value="example value" /> == GVirConfigNetworkDNSEntry <srv service='name' protocol='tcp' domain='test-domain-name' target='.' port='1024' priority='10' weight='10'/> == GVirConfigNetworkDNSEntry <host ip='192.168.122.2'> == GVirConfigNetworkDNSEntry <hostname>myhost</hostname> <hostname>myhostalias</hostname> </host> </dns> <ip address='10.24.75.1' netmask='255.255.255.0'> == GVirConfigNetworkAddress <dhcp> == GVirConfigNetworkDHCP <range start='10.24.75.128' end='10.24.75.254' /> <host mac='52:54:3e:77:e2:ed' name='X.example.com' ip='10.24.75.10' /> == GVirConfigNetworkDHCPHost <host mac='52:54:3e:77:e2:ef' name='Y.example.com' ip='10.24.75.11' /> <host mac='52:54:34:77:e2:f0' name='Z.example.com' ip='10.24.75.12' /> <host mac='52:54:3e:77:e2:f1' name='A.example.com' ip='10.24.75.13' /> </dhcp> </ip> <ip address='192.168.4.1' netmask='255.255.255.0'/> == GVirConfigNetworkAddress </network>
So for example we get the config object using
GVirNetwork *net = gvir_connection_get_network_by_name("default"); GVirConfigNetwork *conf = gvir_network_get_config(net);
Now we want to remove all portgroups. This is easy enough - I'd have an API like
GList *groups = gvir_config_network_get_portgroups(conf);
while (groups) { GVirConfigNetworkPortgroup *group = groups->data;
gvir_network_remove_portgroup(net, group);
data = data->next; }
As you say, the concept of parentIndex doesn't make sense for portgroups, so I'll just ignore it in the API I expose.
Likewise adding/removing <interface> from <forward>, we just ignore the parentIndex. Modify doesn't make sense for this part of the schema since there are no attributes to change, beyond the interface name.
DNS entries are reasonably easy to deal with add/remove, since again parentIndex is irrelevant, there only being one <dns> block.
I'm a little fuzzy on whether a modify action is practical for DNS entries, since the thing you'd want to change is probably also the thing the API would want to use as the unique identifier. The only way around this I see is to pass in both the original and new XML for the DNS entry being modified. The original XML is used to lookup which entry is being mdified, and then replace with the new XML. Perhaps this doesn't matter, and add+remove is sufficient for DNS.
The updating of DHCP entries is the interesting / hard one that causes the fun with parentIndex.
It is possible to come up with a mapping to GObject,
GList *addrs = gvir_config_network_get_addresses(conf);
What if the private data for each address in this list had an index stored in it by gvir_config_network_get_addresses()...
int idx = 0;
while (addrs) { GVirConfigNetworkAddress *addr = addrs->data;
GVirConfigNetworkDHCP *dhcp = gvir_config_network_address_get_dhcp(addr);
...and the private data for dhcp had the same index set (by gvir_config_network_address_get_dhcp() grabbing it from the addr's private data)...
GList *hosts = gvir_config_network_dhcp_get_hosts(dhcp);
...and finally, the private data of each host would have an idx that is initialized from the dhcp's private data during gvir_config_network_dhcp_get_hosts().
while (hosts) { GVirConfigNeworkDHCPHost *host = hosts->data;
gvir_network_remove_dhcp_host(net, idx, host);
So now when you get to here, gvir_network_remove_dhcp_host only needs (net, host), because host->idx (which is private) is already set.
}
idx++; data = data->next; }
What I don't like is that the user has to maintain this 'idx' counter value. It doesn't hurt in this example, but consider if you were just passed a single "GVirConfigNetworkAddress" object, and wanted to add a host entry to it. You have no idea what the parentIndex this corresponds to.
I think storing the ip index in the private data and passing it down the hierarchy would work (until someone inserted a new <ip> element somewhere in the middle :-/)
This isn't fatal, but it is slightly unpleasant. I don't have any better idea though, so I guess we'll just go with what you designed.
Sigh. To paraphrase Hannibal Smith "I *hate* it when a plan doesn't come together!"
participants (3)
-
Daniel P. Berrange
-
Eric Blake
-
Laine Stump