[libvirt] PATCH: Generic internal API for network XML parser/formatter

We currently have two drivers which handle the networking XML containing duplicated parsers and formatters for the XML, and very similar structs. This patch introduces a new general purpose internal API for parsing and formatting network XML, and representing it as a series of structs. This code is derived from the current equivalent code in the QEMU driver for networks. The naming conventions I'm adopting in this patch follow those in the storage driver: - virNetworkPtr - the public opaque object in libvirt.h - virNetworkObjPtr - the primary internal object for network state - virNetworkDefPtr - the configuration data for a network A virNetworkObjPtr contains a reference to one or two virNetworkDefPtr objects - the current live config, and potentially a secondary inactive config which will become live at the next restart. The structs are defined in network_conf.h, along with a bunch of APIs for dealing with them. These APIs are the same as similarly named ones from the current qemu driver, but I'll go over them again for a reminder: virNetworkObjPtr virNetworkFindByUUID(const virNetworkObjPtr nets, const unsigned char *uuid); virNetworkObjPtr virNetworkFindByName(const virNetworkObjPtr nets, const char *name); Allow lookup of a virNetworkObjPtr object based on its name or UUID, as typically obtained from the public virNetworkPtr object. The 'nets' parameter to both of thse is a linked list of networks which are currently known to the driver using this API. void virNetworkDefFree(virNetworkDefPtr def); void virNetworkObjFree(virNetworkObjPtr net); Convenience APIs to totally free the memory associated with these objects. virNetworkObjPtr virNetworkAssignDef(virConnectPtr conn, virNetworkObjPtr *nets, const virNetworkDefPtr def); Given a virNetworkDefPtr object, it'll search for a pre-existing virNetworkObjPtr object with matching config. If one is found, its config will be updated, otherwise a new object will be allocated. void virNetworkRemoveInactive(virNetworkObjPtr *nets, const virNetworkObjPtr net); Convenience for removing and free'ing a virNetworkObjPtr object in the current list of active networks. virNetworkDefPtr virNetworkDefParse(virConnectPtr conn, const char *xmlStr, const char *displayName); Given an XML document describing a network, parses the doc and generates a virNetworkDefPtr to represent it in memory. char *virNetworkDefFormat(virConnectPtr conn, const virNetworkDefPtr def); Given a virNetworkDefPtr object, generate a XML document describing the network. As a mentioned earlier, the impl of these APIs is just copied from the QEMU driver, but instead of using pre-declared char[PATH_MAX] fields, we allocate memory for strings as required. Regards, Daniel diff -r a6e5acdd23df src/Makefile.am --- a/src/Makefile.am Tue Jun 24 15:07:21 2008 +0100 +++ b/src/Makefile.am Tue Jun 24 15:07:23 2008 +0100 @@ -52,6 +52,7 @@ driver.h \ proxy_internal.c proxy_internal.h \ conf.c conf.h \ + network_conf.c network_conf.h \ xm_internal.c xm_internal.h \ remote_internal.c remote_internal.h \ bridge.c bridge.h \ diff -r a6e5acdd23df src/network_conf.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/network_conf.c Tue Jun 24 15:07:23 2008 +0100 @@ -0,0 +1,467 @@ +/* + * network_conf.h: network XML handling + * + * Copyright (C) 2006-2008 Red Hat, Inc. + * Copyright (C) 2006-2008 Daniel P. Berrange + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * Author: Daniel P. Berrange <berrange@redhat.com> + */ + + + +#include <config.h> + +#include <arpa/inet.h> + +#include "internal.h" + +#include "network_conf.h" +#include "memory.h" +#include "xml.h" +#include "uuid.h" +#include "util.h" +#include "buf.h" + +VIR_ENUM_DECL(virNetworkForward) + +VIR_ENUM_IMPL(virNetworkForward, + VIR_NETWORK_FORWARD_LAST, + "none", "nat", "route" ) + +static void virNetworkReportError(virConnectPtr conn, + int code, const char *fmt, ...) +{ + va_list args; + char errorMessage[1024]; + const char *virerr; + + if (fmt) { + va_start(args, fmt); + vsnprintf(errorMessage, sizeof(errorMessage)-1, fmt, args); + va_end(args); + } else { + errorMessage[0] = '\0'; + } + + virerr = __virErrorMsg(code, (errorMessage[0] ? errorMessage : NULL)); + __virRaiseError(conn, NULL, NULL, VIR_FROM_QEMU, code, VIR_ERR_ERROR, + virerr, errorMessage, NULL, -1, -1, virerr, errorMessage); +} + + +virNetworkObjPtr virNetworkFindByUUID(const virNetworkObjPtr nets, + const unsigned char *uuid) +{ + virNetworkObjPtr net = nets; + while (net) { + if (!memcmp(net->def->uuid, uuid, VIR_UUID_BUFLEN)) + return net; + net = net->next; + } + + return NULL; +} + +virNetworkObjPtr virNetworkFindByName(const virNetworkObjPtr nets, + const char *name) +{ + virNetworkObjPtr net = nets; + while (net) { + if (STREQ(net->def->name, name)) + return net; + net = net->next; + } + + return NULL; +} + + +void virNetworkDefFree(virNetworkDefPtr def) +{ + int i; + + if (!def) + return; + + VIR_FREE(def->name); + VIR_FREE(def->bridge); + VIR_FREE(def->forwardDev); + VIR_FREE(def->ipAddress); + VIR_FREE(def->network); + VIR_FREE(def->netmask); + + for (i = 0 ; i < def->nranges && def->ranges ; i++) { + VIR_FREE(def->ranges[i].start); + VIR_FREE(def->ranges[i].end); + } + VIR_FREE(def->ranges); + + VIR_FREE(def); +} + +void virNetworkObjFree(virNetworkObjPtr net) +{ + if (!net) + return; + + virNetworkDefFree(net->def); + virNetworkDefFree(net->newDef); + + VIR_FREE(net->configFile); + VIR_FREE(net->autostartLink); + + VIR_FREE(net); +} + +virNetworkObjPtr virNetworkAssignDef(virConnectPtr conn, + virNetworkObjPtr *nets, + const virNetworkDefPtr def) +{ + virNetworkObjPtr network; + + if ((network = virNetworkFindByName(*nets, def->name))) { + if (!virNetworkIsActive(network)) { + virNetworkDefFree(network->def); + network->def = def; + } else { + if (network->newDef) + virNetworkDefFree(network->newDef); + network->newDef = def; + } + + return network; + } + + if (VIR_ALLOC(network) < 0) { + virNetworkReportError(conn, VIR_ERR_NO_MEMORY, NULL); + return NULL; + } + + network->def = def; + network->next = *nets; + + *nets = network; + + return network; + +} + +void virNetworkRemoveInactive(virNetworkObjPtr *nets, + const virNetworkObjPtr net) +{ + virNetworkObjPtr prev = NULL; + virNetworkObjPtr curr = *nets; + + while (curr && + curr != net) { + prev = curr; + curr = curr->next; + } + + if (curr) { + if (prev) + prev->next = curr->next; + else + *nets = curr->next; + } + + virNetworkObjFree(net); +} + + +static int +virNetworkDHCPRangeDefParseXML(virConnectPtr conn, + virNetworkDefPtr def, + xmlNodePtr node) { + + xmlNodePtr cur; + + cur = node->children; + while (cur != NULL) { + xmlChar *start, *end; + + if (cur->type != XML_ELEMENT_NODE || + !xmlStrEqual(cur->name, BAD_CAST "range")) { + cur = cur->next; + continue; + } + + if (!(start = xmlGetProp(cur, BAD_CAST "start"))) { + cur = cur->next; + continue; + } + if (!(end = xmlGetProp(cur, BAD_CAST "end"))) { + cur = cur->next; + xmlFree(start); + continue; + } + + if (VIR_REALLOC_N(def->ranges, def->nranges + 1) < 0) { + xmlFree(start); + xmlFree(end); + virNetworkReportError(conn, VIR_ERR_NO_MEMORY, NULL); + return -1; + } + def->ranges[def->nranges].start = (char *)start; + def->ranges[def->nranges].end = (char *)end; + def->nranges++; + + cur = cur->next; + } + + return 0; +} + +static virNetworkDefPtr +virNetworkDefParseXML(virConnectPtr conn, + xmlDocPtr xml) +{ + xmlNodePtr root = NULL; + xmlXPathContextPtr ctxt = NULL; + virNetworkDefPtr def; + char *tmp; + + if (VIR_ALLOC(def) < 0) { + virNetworkReportError(conn, VIR_ERR_NO_MEMORY, NULL); + return NULL; + } + + /* Prepare parser / xpath context */ + root = xmlDocGetRootElement(xml); + if ((root == NULL) || (!xmlStrEqual(root->name, BAD_CAST "network"))) { + virNetworkReportError(conn, VIR_ERR_INTERNAL_ERROR, + "%s", _("incorrect root element")); + goto error; + } + + ctxt = xmlXPathNewContext(xml); + if (ctxt == NULL) { + virNetworkReportError(conn, VIR_ERR_NO_MEMORY, + "%s", _("failed to allocate space for xmlXPathContext string")); + goto error; + } + + + /* Extract network name */ + def->name = virXPathString("string(/network/name[1])", ctxt); + if (!def->name) { + virNetworkReportError(conn, VIR_ERR_NO_NAME, NULL); + goto error; + } + + /* Extract network uuid */ + tmp = virXPathString("string(/network/uuid[1])", ctxt); + if (!tmp) { + int err; + if ((err = virUUIDGenerate(def->uuid))) { + virNetworkReportError(conn, VIR_ERR_INTERNAL_ERROR, + _("Failed to generate UUID: %s"), strerror(err)); + goto error; + } + } else { + if (virUUIDParse(tmp, def->uuid) < 0) { + VIR_FREE(tmp); + virNetworkReportError(conn, VIR_ERR_INTERNAL_ERROR, + "%s", _("malformed uuid element")); + goto error; + } + VIR_FREE(tmp); + } + + /* Parse bridge information */ + def->bridge = virXPathString("string(/network/bridge[1]/@name)", ctxt); + tmp = virXPathString("string(/network/bridge[1]/@stp)", ctxt); + if (tmp && STREQ(tmp, "off")) + def->stp = 0; + else + def->stp = 1; + VIR_FREE(tmp); + + if (virXPathLong("string(/network/bridge[1]/@delay)", ctxt, &def->delay) < 0) + def->delay = 0; + + def->ipAddress = virXPathString("string(/network/ip[1]/@address)", ctxt); + def->netmask = virXPathString("string(/network/ip[1]/@netmask)", ctxt); + if (def->ipAddress && + def->netmask) { + /* XXX someday we want IPv6 too, so inet_aton won't work there */ + struct in_addr inaddress, innetmask; + char *netaddr; + int netlen; + xmlNodePtr dhcp; + + if (!inet_aton(def->ipAddress, &inaddress)) { + virNetworkReportError(conn, VIR_ERR_INTERNAL_ERROR, + _("cannot parse IP address '%s'"), + def->ipAddress); + goto error; + } + if (!inet_aton(def->netmask, &innetmask)) { + virNetworkReportError(conn, VIR_ERR_INTERNAL_ERROR, + _("cannot parse netmask '%s'"), + def->netmask); + goto error; + } + + inaddress.s_addr &= innetmask.s_addr; + netaddr = inet_ntoa(inaddress); + + netlen = strlen(netaddr) + 1 + strlen(def->netmask) + 1; + if (VIR_ALLOC_N(def->network, netlen) < 0) { + virNetworkReportError(conn, VIR_ERR_NO_MEMORY, NULL); + goto error; + } + strcpy(def->network, netaddr); + strcat(def->network, "/"); + strcat(def->network, def->netmask); + + + if ((dhcp = virXPathNode("/network/ip[1]/dhcp[1]", ctxt)) && + virNetworkDHCPRangeDefParseXML(conn, def, dhcp) < 0) + goto error; + } + + + /* IPv4 forwarding setup */ + if (virXPathBoolean("count(/network/forward) > 0", ctxt)) { + if (!def->ipAddress || + !def->netmask) { + virNetworkReportError(conn, VIR_ERR_INTERNAL_ERROR, + "%s", _("Forwarding requested, but no IPv4 address/netmask provided")); + goto error; + } + + tmp = virXPathString("string(/network/forward[1]/@mode)", ctxt); + if (tmp) { + if ((def->forwardType = virNetworkForwardTypeFromString(tmp)) < 0) { + virNetworkReportError(conn, VIR_ERR_INTERNAL_ERROR, + _("unknown forwarding type '%s'"), tmp); + VIR_FREE(tmp); + } + VIR_FREE(tmp); + } else { + def->forwardType = VIR_NETWORK_FORWARD_NAT; + } + + + def->forwardDev = virXPathString("string(/network/forward[1]/@dev)", ctxt); + } else { + def->forwardType = VIR_NETWORK_FORWARD_NONE; + } + + xmlXPathFreeContext(ctxt); + + return def; + + error: + xmlXPathFreeContext(ctxt); + virNetworkDefFree(def); + return NULL; +} + +virNetworkDefPtr virNetworkDefParse(virConnectPtr conn, + const char *xmlStr, + const char *displayName) +{ + xmlDocPtr xml; + virNetworkDefPtr def; + + if (!(xml = xmlReadDoc(BAD_CAST xmlStr, displayName ? displayName : "network.xml", NULL, + XML_PARSE_NOENT | XML_PARSE_NONET | + XML_PARSE_NOERROR | XML_PARSE_NOWARNING))) { + virNetworkReportError(conn, VIR_ERR_XML_ERROR, NULL); + return NULL; + } + + def = virNetworkDefParseXML(conn, xml); + + xmlFreeDoc(xml); + + return def; +} + +char *virNetworkDefFormat(virConnectPtr conn, + const virNetworkDefPtr def) +{ + virBuffer buf = VIR_BUFFER_INITIALIZER; + unsigned char *uuid; + char *tmp; + char uuidstr[VIR_UUID_STRING_BUFLEN]; + + virBufferAddLit(&buf, "<network>\n"); + virBufferVSprintf(&buf, " <name>%s</name>\n", def->name); + + uuid = def->uuid; + virUUIDFormat(uuid, uuidstr); + virBufferVSprintf(&buf, " <uuid>%s</uuid>\n", uuidstr); + + if (def->forwardType != VIR_NETWORK_FORWARD_NONE) { + const char *mode = virNetworkForwardTypeToString(def->forwardType); + if (mode) { + if (def->forwardDev) { + virBufferVSprintf(&buf, " <forward dev='%s' mode='%s'/>\n", + def->forwardDev, mode); + } else { + virBufferVSprintf(&buf, " <forward mode='%s'/>\n", mode); + } + } + } + + virBufferAddLit(&buf, " <bridge"); + if (def->bridge) + virBufferVSprintf(&buf, " name='%s'", def->bridge); + virBufferVSprintf(&buf, " stp='%s' forwardDelay='%ld' />\n", + def->stp ? "on" : "off", + def->delay); + + if (def->ipAddress || def->netmask) { + virBufferAddLit(&buf, " <ip"); + + if (def->ipAddress) + virBufferVSprintf(&buf, " address='%s'", def->ipAddress); + + if (def->netmask) + virBufferVSprintf(&buf, " netmask='%s'", def->netmask); + + virBufferAddLit(&buf, ">\n"); + + if (def->nranges) { + int i; + virBufferAddLit(&buf, " <dhcp>\n"); + for (i = 0 ; i < def->nranges ; i++) + virBufferVSprintf(&buf, " <range start='%s' end='%s' />\n", + def->ranges[i].start, def->ranges[i].end); + virBufferAddLit(&buf, " </dhcp>\n"); + } + + virBufferAddLit(&buf, " </ip>\n"); + } + + virBufferAddLit(&buf, "</network>\n"); + + if (virBufferError(&buf)) + goto no_memory; + + return virBufferContentAndReset(&buf); + + no_memory: + virNetworkReportError(conn, VIR_ERR_NO_MEMORY, NULL); + tmp = virBufferContentAndReset(&buf); + VIR_FREE(tmp); + return NULL; +} + diff -r a6e5acdd23df src/network_conf.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/network_conf.h Tue Jun 24 15:07:23 2008 +0100 @@ -0,0 +1,114 @@ +/* + * network_conf.h: network XML handling + * + * Copyright (C) 2006-2008 Red Hat, Inc. + * Copyright (C) 2006-2008 Daniel P. Berrange + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * Author: Daniel P. Berrange <berrange@redhat.com> + */ + +#ifndef __NETWORK_CONF_H__ +#define __NETWORK_CONF_H__ + +#include "internal.h" + +/* 2 possible types of forwarding */ +enum virNetworkForwardType { + VIR_NETWORK_FORWARD_NONE = 0, + VIR_NETWORK_FORWARD_NAT, + VIR_NETWORK_FORWARD_ROUTE, + + VIR_NETWORK_FORWARD_LAST, +}; + +typedef struct _virNetworkDHCPRangeDef virNetworkDHCPRangeDef; +typedef virNetworkDHCPRangeDef *virNetworkDHCPRangeDefPtr; +struct _virNetworkDHCPRangeDef { + char *start; + char *end; +}; + +typedef struct _virNetworkDef virNetworkDef; +typedef virNetworkDef *virNetworkDefPtr; +struct _virNetworkDef { + unsigned char uuid[VIR_UUID_BUFLEN]; + char *name; + + char *bridge; /* Name of bridge device */ + int stp : 1; /* Spanning tree protocol */ + long delay; /* Bridge forward delay (ms) */ + + int forwardType; /* One of virNetworkForwardType constants */ + char *forwardDev; /* Destination device for forwarding */ + + char *ipAddress; /* Bridge IP address */ + char *netmask; + char *network; + + int nranges; /* Zero or more dhcp ranges */ + virNetworkDHCPRangeDefPtr ranges; +}; + +typedef struct _virNetworkObj virNetworkObj; +typedef virNetworkObj *virNetworkObjPtr; +struct _virNetworkObj { + int dnsmasqPid; + unsigned int active : 1; + unsigned int autostart : 1; + unsigned int persistent : 1; + + char *configFile; /* Persistent config file path */ + char *autostartLink; /* Symlink path for autostart */ + + virNetworkDefPtr def; /* The current definition */ + virNetworkDefPtr newDef; /* New definition to activate at shutdown */ + + virNetworkObjPtr next; +}; + +static inline int +virNetworkIsActive(const virNetworkObjPtr net) +{ + return net->active; +} + + +virNetworkObjPtr virNetworkFindByUUID(const virNetworkObjPtr nets, + const unsigned char *uuid); +virNetworkObjPtr virNetworkFindByName(const virNetworkObjPtr nets, + const char *name); + + +void virNetworkDefFree(virNetworkDefPtr def); +void virNetworkObjFree(virNetworkObjPtr net); + +virNetworkObjPtr virNetworkAssignDef(virConnectPtr conn, + virNetworkObjPtr *nets, + const virNetworkDefPtr def); +void virNetworkRemoveInactive(virNetworkObjPtr *nets, + const virNetworkObjPtr net); + +virNetworkDefPtr virNetworkDefParse(virConnectPtr conn, + const char *xmlStr, + const char *displayName); + +char *virNetworkDefFormat(virConnectPtr conn, + const virNetworkDefPtr def); + + +#endif /* __NETWORK_CONF_H__ */ + -- |: Red Hat, Engineering, London -o- http://people.redhat.com/berrange/ :| |: http://libvirt.org -o- http://virt-manager.org -o- http://ovirt.org :| |: http://autobuild.org -o- http://search.cpan.org/~danberr/ :| |: GnuPG: 7D3B9505 -o- F3C9 553F A1DA 4AC2 5648 23C1 B3DF F742 7D3B 9505 :|

On Tue, Jun 24, 2008 at 04:17:18PM +0100, Daniel P. Berrange wrote:
We currently have two drivers which handle the networking XML containing duplicated parsers and formatters for the XML, and very similar structs. This patch introduces a new general purpose internal API for parsing and formatting network XML, and representing it as a series of structs.
This code is derived from the current equivalent code in the QEMU driver for networks.
The naming conventions I'm adopting in this patch follow those in the storage driver:
- virNetworkPtr - the public opaque object in libvirt.h - virNetworkObjPtr - the primary internal object for network state - virNetworkDefPtr - the configuration data for a network [...] As a mentioned earlier, the impl of these APIs is just copied from the QEMU driver, but instead of using pre-declared char[PATH_MAX] fields, we allocate memory for strings as required.
yeah, this all makes perfect sense, thanks for cleaning this up [...]
+static virNetworkDefPtr +virNetworkDefParseXML(virConnectPtr conn, + xmlDocPtr xml) +{ + xmlNodePtr root = NULL; + xmlXPathContextPtr ctxt = NULL; + virNetworkDefPtr def; + char *tmp; + + if (VIR_ALLOC(def) < 0) { + virNetworkReportError(conn, VIR_ERR_NO_MEMORY, NULL); + return NULL; + } + + /* Prepare parser / xpath context */ + root = xmlDocGetRootElement(xml); + if ((root == NULL) || (!xmlStrEqual(root->name, BAD_CAST "network"))) { + virNetworkReportError(conn, VIR_ERR_INTERNAL_ERROR, + "%s", _("incorrect root element")); + goto error; + }
One thing i'm wondering is if it would not make a bit more sense to cut the API at a different level, getting the root element in the caller and have the ParseXML() routines operates from an xmlNodePtr. That would allow easy refactoring if we want to pack multiple XML definitions in a single instance and just call the ParseXML() on each network (or other) subtree. [...]
+virNetworkDefPtr virNetworkDefParse(virConnectPtr conn, + const char *xmlStr, + const char *displayName) +{ + xmlDocPtr xml; + virNetworkDefPtr def; + + if (!(xml = xmlReadDoc(BAD_CAST xmlStr, displayName ? displayName : "network.xml", NULL, + XML_PARSE_NOENT | XML_PARSE_NONET | + XML_PARSE_NOERROR | XML_PARSE_NOWARNING))) { + virNetworkReportError(conn, VIR_ERR_XML_ERROR, NULL); + return NULL; + } + + def = virNetworkDefParseXML(conn, xml);
Do the Root lookup here and pass it down
+ xmlFreeDoc(xml); + + return def; +} + +char *virNetworkDefFormat(virConnectPtr conn, + const virNetworkDefPtr def) +{ + virBuffer buf = VIR_BUFFER_INITIALIZER; + unsigned char *uuid; + char *tmp; + char uuidstr[VIR_UUID_STRING_BUFLEN];
A bit of the same thing but in reverse, basically allocate the buffer in a top routine and call with (conn, buf, def) that way it gets trivial to serialize all network definitions in a single XML file for example. It's just code refactoring around boundaries for function work, but while we are at code refactoring maybe it's easier to do this now. The only drawback one can see is that if you don't get indentation in the serialization routine but honnestly I don't think it's a big deal.
diff -r a6e5acdd23df src/network_conf.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/network_conf.h Tue Jun 24 15:07:23 2008 +0100 @@ -0,0 +1,114 @@ +/* + * network_conf.h: network XML handling + * + * Copyright (C) 2006-2008 Red Hat, Inc. + * Copyright (C) 2006-2008 Daniel P. Berrange
Hum, what about just pointing to the COPYING.LIB file instead of including the text below ?
+ * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
....
+#ifndef __NETWORK_CONF_H__ +#define __NETWORK_CONF_H__
many type definitions looks good. Ultimately we may export some of them to provide non-XML interfaces once we get ready for for an 1.0 release. Looks good to me, +1, thanks a lot ! Daniel -- Red Hat Virtualization group http://redhat.com/virtualization/ Daniel Veillard | virtualization library http://libvirt.org/ veillard@redhat.com | libxml GNOME XML XSLT toolkit http://xmlsoft.org/ http://veillard.com/ | Rpmfind RPM search engine http://rpmfind.net/

On Thu, Jun 26, 2008 at 08:36:46AM -0400, Daniel Veillard wrote:
[...]
+static virNetworkDefPtr +virNetworkDefParseXML(virConnectPtr conn, + xmlDocPtr xml) +{ + xmlNodePtr root = NULL; + xmlXPathContextPtr ctxt = NULL; + virNetworkDefPtr def; + char *tmp; + + if (VIR_ALLOC(def) < 0) { + virNetworkReportError(conn, VIR_ERR_NO_MEMORY, NULL); + return NULL; + } + + /* Prepare parser / xpath context */ + root = xmlDocGetRootElement(xml); + if ((root == NULL) || (!xmlStrEqual(root->name, BAD_CAST "network"))) { + virNetworkReportError(conn, VIR_ERR_INTERNAL_ERROR, + "%s", _("incorrect root element")); + goto error; + }
One thing i'm wondering is if it would not make a bit more sense to cut the API at a different level, getting the root element in the caller and have the ParseXML() routines operates from an xmlNodePtr. That would allow easy refactoring if we want to pack multiple XML definitions in a single instance and just call the ParseXML() on each network (or other) subtree.
Hmm, i think I'd probably go a ittle further still - this line you quote is the only place which uses the 'root' element directly - the rest is all just XPath queries. So if we changed that to just query '/network' or similar, we can just pass an xmlXPathContextPtr object into this method instead.
+virNetworkDefPtr virNetworkDefParse(virConnectPtr conn, + const char *xmlStr, + const char *displayName) +{ + xmlDocPtr xml; + virNetworkDefPtr def; + + if (!(xml = xmlReadDoc(BAD_CAST xmlStr, displayName ? displayName : "network.xml", NULL, + XML_PARSE_NOENT | XML_PARSE_NONET | + XML_PARSE_NOERROR | XML_PARSE_NOWARNING))) { + virNetworkReportError(conn, VIR_ERR_XML_ERROR, NULL); + return NULL; + } + + def = virNetworkDefParseXML(conn, xml);
Do the Root lookup here and pass it down
Or ctxt = xmlXPathNewContext(xml); And just pass 'ctxt' in.
+ xmlFreeDoc(xml); + + return def; +} + +char *virNetworkDefFormat(virConnectPtr conn, + const virNetworkDefPtr def) +{ + virBuffer buf = VIR_BUFFER_INITIALIZER; + unsigned char *uuid; + char *tmp; + char uuidstr[VIR_UUID_STRING_BUFLEN];
A bit of the same thing but in reverse, basically allocate the buffer in a top routine and call with (conn, buf, def) that way it gets trivial to serialize all network definitions in a single XML file for example. It's just code refactoring around boundaries for function work, but while we are at code refactoring maybe it's easier to do this now.
Sure that sounds reasonable to me.
The only drawback one can see is that if you don't get indentation in the serialization routine but honnestly I don't think it's a big deal.
Yeah, no big deal - anyone who cares can easily run it through xmllint
@@ -0,0 +1,114 @@ +/* + * network_conf.h: network XML handling + * + * Copyright (C) 2006-2008 Red Hat, Inc. + * Copyright (C) 2006-2008 Daniel P. Berrange
Hum, what about just pointing to the COPYING.LIB file instead of including the text below ?
I prefer to keep the full boiler-plate text since that's what's recommended by the COPYING.LIB file itself. It also means that if someone takes libvirt code and merges it into another library the copying terms are clearly shown.
+ * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
Regards, Daniel -- |: Red Hat, Engineering, London -o- http://people.redhat.com/berrange/ :| |: http://libvirt.org -o- http://virt-manager.org -o- http://ovirt.org :| |: http://autobuild.org -o- http://search.cpan.org/~danberr/ :| |: GnuPG: 7D3B9505 -o- F3C9 553F A1DA 4AC2 5648 23C1 B3DF F742 7D3B 9505 :|

On Thu, Jun 26, 2008 at 02:48:58PM +0100, Daniel P. Berrange wrote:
On Thu, Jun 26, 2008 at 08:36:46AM -0400, Daniel Veillard wrote:
[...]
+static virNetworkDefPtr +virNetworkDefParseXML(virConnectPtr conn, + xmlDocPtr xml) +{ + xmlNodePtr root = NULL; + xmlXPathContextPtr ctxt = NULL; + virNetworkDefPtr def; + char *tmp; + + if (VIR_ALLOC(def) < 0) { + virNetworkReportError(conn, VIR_ERR_NO_MEMORY, NULL); + return NULL; + } + + /* Prepare parser / xpath context */ + root = xmlDocGetRootElement(xml); + if ((root == NULL) || (!xmlStrEqual(root->name, BAD_CAST "network"))) { + virNetworkReportError(conn, VIR_ERR_INTERNAL_ERROR, + "%s", _("incorrect root element")); + goto error; + }
One thing i'm wondering is if it would not make a bit more sense to cut the API at a different level, getting the root element in the caller and have the ParseXML() routines operates from an xmlNodePtr. That would allow easy refactoring if we want to pack multiple XML definitions in a single instance and just call the ParseXML() on each network (or other) subtree.
Hmm, i think I'd probably go a ittle further still - this line you quote is the only place which uses the 'root' element directly - the rest is all just XPath queries. So if we changed that to just query '/network' or similar, we can just pass an xmlXPathContextPtr object into this method instead.
hum, not that simple one actually need to make the XPath queries relative for this to work on the subtree independantly of the location of the top node in the document tree. And it's better to set back the current node for the query after each XPath lookup (i.e. reset ctxt->node back to the subtree top node before each XPath lookup).
Do the Root lookup here and pass it down
Or
ctxt = xmlXPathNewContext(xml);
And just pass 'ctxt' in.
I think we still need to pass the node in practice to be safe.
Hum, what about just pointing to the COPYING.LIB file instead of including the text below ?
I prefer to keep the full boiler-plate text since that's what's recommended by the COPYING.LIB file itself. It also means that if someone takes libvirt code and merges it into another library the copying terms are clearly shown.
Hum, I suggested Dan Smith to do it the other way a couple days ago :-\ ... Daniel -- Red Hat Virtualization group http://redhat.com/virtualization/ Daniel Veillard | virtualization library http://libvirt.org/ veillard@redhat.com | libxml GNOME XML XSLT toolkit http://xmlsoft.org/ http://veillard.com/ | Rpmfind RPM search engine http://rpmfind.net/

"Daniel P. Berrange" <berrange@redhat.com> wrote:
We currently have two drivers which handle the networking XML containing duplicated parsers and formatters for the XML, and very similar structs. This patch introduces a new general purpose internal API for parsing and formatting network XML, and representing it as a series of structs.
This code is derived from the current equivalent code in the QEMU driver for networks.
The naming conventions I'm adopting in this patch follow those in the storage driver:
- virNetworkPtr - the public opaque object in libvirt.h - virNetworkObjPtr - the primary internal object for network state - virNetworkDefPtr - the configuration data for a network
A virNetworkObjPtr contains a reference to one or two virNetworkDefPtr objects - the current live config, and potentially a secondary inactive config which will become live at the next restart.
The structs are defined in network_conf.h, along with a bunch of APIs for dealing with them. These APIs are the same as similarly named ones from the current qemu driver, but I'll go over them again for a reminder:
virNetworkObjPtr virNetworkFindByUUID(const virNetworkObjPtr nets, const unsigned char *uuid); virNetworkObjPtr virNetworkFindByName(const virNetworkObjPtr nets, const char *name);
Allow lookup of a virNetworkObjPtr object based on its name or UUID, as typically obtained from the public virNetworkPtr object. The 'nets' parameter to both of thse is a linked list of networks which are currently known to the driver using this API. ...
Thanks for writing those descriptions. When you write so much code, I'm reluctant to say anything about comment density, but I was surprised to see the same functions defined below with no comment saying what each does. It'd be nice to copy each of the above into a function-describing comment.
diff -r a6e5acdd23df src/Makefile.am --- a/src/Makefile.am Tue Jun 24 15:07:21 2008 +0100 +++ b/src/Makefile.am Tue Jun 24 15:07:23 2008 +0100 @@ -52,6 +52,7 @@ driver.h \ proxy_internal.c proxy_internal.h \ conf.c conf.h \ + network_conf.c network_conf.h \
leading spaces in Makefile.am is technically ok in this context, but it's inconsistent with the neighboring lines.
xm_internal.c xm_internal.h \ remote_internal.c remote_internal.h \ bridge.c bridge.h \ diff -r a6e5acdd23df src/network_conf.c
...
+++ b/src/network_conf.c Tue Jun 24 15:07:23 2008 +0100 @@ -0,0 +1,467 @@ +/* + * network_conf.h: network XML handling
s/\.h/.c/ This is a good reason not to put the name of the file in a comment inside the file itself. ...
+static int +virNetworkDHCPRangeDefParseXML(virConnectPtr conn, + virNetworkDefPtr def, + xmlNodePtr node) { + + xmlNodePtr cur; + + cur = node->children; + while (cur != NULL) { + xmlChar *start, *end; + + if (cur->type != XML_ELEMENT_NODE || + !xmlStrEqual(cur->name, BAD_CAST "range")) { + cur = cur->next; + continue; + } + + if (!(start = xmlGetProp(cur, BAD_CAST "start"))) { + cur = cur->next; + continue; + } + if (!(end = xmlGetProp(cur, BAD_CAST "end"))) { + cur = cur->next; + xmlFree(start); + continue; + } + + if (VIR_REALLOC_N(def->ranges, def->nranges + 1) < 0) { + xmlFree(start); + xmlFree(end); + virNetworkReportError(conn, VIR_ERR_NO_MEMORY, NULL); + return -1; + } + def->ranges[def->nranges].start = (char *)start; + def->ranges[def->nranges].end = (char *)end; + def->nranges++; + + cur = cur->next; + } + + return 0; +}
With four "cur = cur->next;" statements, I prefer a for-loop. And rather than all the if-negated-condition-then-continue, I find the positive-cond tests to be more readable. Here's an untested alternative that should be equivalent: static int virNetworkDHCPRangeDefParseXML(virConnectPtr conn, virNetworkDefPtr def, xmlNodePtr node) { xmlNodePtr cur; for (cur = node->children; cur != NULL; cur = cur->next) { xmlChar *start, *end = NULL; if (!(cur->type == XML_ELEMENT_NODE && xmlStrEqual(cur->name, BAD_CAST "range")) continue; if ((start = xmlGetProp(cur, BAD_CAST "start")) && (end = xmlGetProp(cur, BAD_CAST "end"))) { if (VIR_REALLOC_N(def->ranges, def->nranges + 1) < 0) { xmlFree(start); xmlFree(end); virNetworkReportError(conn, VIR_ERR_NO_MEMORY, NULL); return -1; } def->ranges[def->nranges].start = (char *)start; def->ranges[def->nranges].end = (char *)end; def->nranges++; } else { xmlFree(start); xmlFree(end); } } return 0; }
+static virNetworkDefPtr +virNetworkDefParseXML(virConnectPtr conn, + xmlDocPtr xml) +{ + xmlNodePtr root = NULL;
Initialization not required.
+ xmlXPathContextPtr ctxt = NULL; + virNetworkDefPtr def; + char *tmp; + + if (VIR_ALLOC(def) < 0) { + virNetworkReportError(conn, VIR_ERR_NO_MEMORY, NULL); + return NULL; + } + + /* Prepare parser / xpath context */ + root = xmlDocGetRootElement(xml); + if ((root == NULL) || (!xmlStrEqual(root->name, BAD_CAST "network"))) { + virNetworkReportError(conn, VIR_ERR_INTERNAL_ERROR, + "%s", _("incorrect root element")); + goto error; + } + + ctxt = xmlXPathNewContext(xml); + if (ctxt == NULL) { + virNetworkReportError(conn, VIR_ERR_NO_MEMORY, + "%s", _("failed to allocate space for xmlXPathContext string")); + goto error; + } + + + /* Extract network name */ + def->name = virXPathString("string(/network/name[1])", ctxt); + if (!def->name) { + virNetworkReportError(conn, VIR_ERR_NO_NAME, NULL); + goto error; + } + + /* Extract network uuid */ + tmp = virXPathString("string(/network/uuid[1])", ctxt); + if (!tmp) { + int err; + if ((err = virUUIDGenerate(def->uuid))) { + virNetworkReportError(conn, VIR_ERR_INTERNAL_ERROR, + _("Failed to generate UUID: %s"), strerror(err)); + goto error; + } + } else { + if (virUUIDParse(tmp, def->uuid) < 0) { + VIR_FREE(tmp); + virNetworkReportError(conn, VIR_ERR_INTERNAL_ERROR, + "%s", _("malformed uuid element")); + goto error; + } + VIR_FREE(tmp); + } + + /* Parse bridge information */ + def->bridge = virXPathString("string(/network/bridge[1]/@name)", ctxt); + tmp = virXPathString("string(/network/bridge[1]/@stp)", ctxt); + if (tmp && STREQ(tmp, "off")) + def->stp = 0; + else + def->stp = 1;
I have a slight preference for the one-liner, but if you prefer to avoid C's ternary operator, ... /* Default is "on". */ def->stp = (tmp && STREQ(tmp, "off") ? 0 : 1);
+ VIR_FREE(tmp); + + if (virXPathLong("string(/network/bridge[1]/@delay)", ctxt, &def->delay) < 0) + def->delay = 0; + + def->ipAddress = virXPathString("string(/network/ip[1]/@address)", ctxt); + def->netmask = virXPathString("string(/network/ip[1]/@netmask)", ctxt); + if (def->ipAddress && + def->netmask) { + /* XXX someday we want IPv6 too, so inet_aton won't work there */ + struct in_addr inaddress, innetmask; + char *netaddr; + int netlen; + xmlNodePtr dhcp; + + if (!inet_aton(def->ipAddress, &inaddress)) { + virNetworkReportError(conn, VIR_ERR_INTERNAL_ERROR, + _("cannot parse IP address '%s'"), + def->ipAddress); + goto error; + } + if (!inet_aton(def->netmask, &innetmask)) { + virNetworkReportError(conn, VIR_ERR_INTERNAL_ERROR, + _("cannot parse netmask '%s'"), + def->netmask); + goto error; + } + + inaddress.s_addr &= innetmask.s_addr; + netaddr = inet_ntoa(inaddress); + + netlen = strlen(netaddr) + 1 + strlen(def->netmask) + 1; + if (VIR_ALLOC_N(def->network, netlen) < 0) { + virNetworkReportError(conn, VIR_ERR_NO_MEMORY, NULL); + goto error; + } + strcpy(def->network, netaddr); + strcat(def->network, "/"); + strcat(def->network, def->netmask);
I prefer to use asprintf, and then remove 4 of the 8 lines above, as well as the netlen declaration.
+ if ((dhcp = virXPathNode("/network/ip[1]/dhcp[1]", ctxt)) && + virNetworkDHCPRangeDefParseXML(conn, def, dhcp) < 0) + goto error; + } + + + /* IPv4 forwarding setup */ + if (virXPathBoolean("count(/network/forward) > 0", ctxt)) { + if (!def->ipAddress || + !def->netmask) { + virNetworkReportError(conn, VIR_ERR_INTERNAL_ERROR, + "%s", _("Forwarding requested, but no IPv4 address/netmask provided")); + goto error; + } + + tmp = virXPathString("string(/network/forward[1]/@mode)", ctxt); + if (tmp) { + if ((def->forwardType = virNetworkForwardTypeFromString(tmp)) < 0) { + virNetworkReportError(conn, VIR_ERR_INTERNAL_ERROR, + _("unknown forwarding type '%s'"), tmp); + VIR_FREE(tmp);
missing goto
+ } + VIR_FREE(tmp);
Recently, I've begun using a slightly different paradigm, in order to remove the need for a separate free call in the error path, since it's so easy to omit. i.e, here, you can do this: if (tmp) { def->forwardType = virNetworkForwardTypeFromString(tmp); VIR_FREE(tmp); if (def->forwardType < 0) { virNetworkReportError(conn, VIR_ERR_INTERNAL_ERROR, _("unknown forwarding type '%s'"), tmp); goto error; } } ... Here, it's not ideal, since this separation duplicates the relatively long LHS, "def->forwardType", but if you don't like that, use a temporary: if (tmp) { int fail = ((def->forwardType = virNetworkForwardTypeFromString(tmp)) < 0); VIR_FREE(tmp); if (fail) { virNetworkReportError(conn, VIR_ERR_INTERNAL_ERROR, _("unknown forwarding type '%s'"), tmp); goto error; } } ...
+ } else { + def->forwardType = VIR_NETWORK_FORWARD_NAT; + } + + + def->forwardDev = virXPathString("string(/network/forward[1]/@dev)", ctxt); + } else { + def->forwardType = VIR_NETWORK_FORWARD_NONE; + } + + xmlXPathFreeContext(ctxt); + + return def; + + error: + xmlXPathFreeContext(ctxt); + virNetworkDefFree(def); + return NULL; +}
Here's a patch with the suggested changes to that single function: --- parse.c 2008-07-01 11:27:58.000000000 +0200 +++ parse.c 2008-07-01 13:41:18.000000000 +0200 @@ -45,22 +45,20 @@ goto error; } } else { - if (virUUIDParse(tmp, def->uuid) < 0) { - VIR_FREE(tmp); + int fail = (virUUIDParse(tmp, def->uuid) < 0); + VIR_FREE(tmp); + if (fail) { virNetworkReportError(conn, VIR_ERR_INTERNAL_ERROR, "%s", _("malformed uuid element")); goto error; } - VIR_FREE(tmp); } /* Parse bridge information */ def->bridge = virXPathString("string(/network/bridge[1]/@name)", ctxt); tmp = virXPathString("string(/network/bridge[1]/@stp)", ctxt); - if (tmp && STREQ(tmp, "off")) - def->stp = 0; - else - def->stp = 1; + /* Default is "on". */ + def->stp = (tmp && STREQ(tmp, "off") ? 0 : 1); VIR_FREE(tmp); if (virXPathLong("string(/network/bridge[1]/@delay)", ctxt, &def->delay) < 0) @@ -73,7 +71,6 @@ /* XXX someday we want IPv6 too, so inet_aton won't work there */ struct in_addr inaddress, innetmask; char *netaddr; - int netlen; xmlNodePtr dhcp; if (!inet_aton(def->ipAddress, &inaddress)) { @@ -92,15 +89,11 @@ inaddress.s_addr &= innetmask.s_addr; netaddr = inet_ntoa(inaddress); - netlen = strlen(netaddr) + 1 + strlen(def->netmask) + 1; - if (VIR_ALLOC_N(def->network, netlen) < 0) { + if (asprintf(&def->network, "%s/%s", netaddr, def->netmask) < 0) { + def->network = NULL; virNetworkReportError(conn, VIR_ERR_NO_MEMORY, NULL); goto error; } - strcpy(def->network, netaddr); - strcat(def->network, "/"); - strcat(def->network, def->netmask); - if ((dhcp = virXPathNode("/network/ip[1]/dhcp[1]", ctxt)) && virNetworkDHCPRangeDefParseXML(conn, def, dhcp) < 0) @@ -119,12 +112,13 @@ tmp = virXPathString("string(/network/forward[1]/@mode)", ctxt); if (tmp) { - if ((def->forwardType = virNetworkForwardTypeFromString(tmp)) < 0) { + def->forwardType = virNetworkForwardTypeFromString(tmp); + VIR_FREE(tmp); + if (def->forwardType < 0) { virNetworkReportError(conn, VIR_ERR_INTERNAL_ERROR, _("unknown forwarding type '%s'"), tmp); - VIR_FREE(tmp); + goto error; } - VIR_FREE(tmp); } else { def->forwardType = VIR_NETWORK_FORWARD_NAT; }
+virNetworkDefPtr virNetworkDefParse(virConnectPtr conn,
I'll look at the rest later.

"Daniel P. Berrange" <berrange@redhat.com> wrote: ...
diff -r a6e5acdd23df src/network_conf.h ... +/* 2 possible types of forwarding */ +enum virNetworkForwardType { + VIR_NETWORK_FORWARD_NONE = 0, + VIR_NETWORK_FORWARD_NAT, + VIR_NETWORK_FORWARD_ROUTE, + + VIR_NETWORK_FORWARD_LAST, +}; + +typedef struct _virNetworkDHCPRangeDef virNetworkDHCPRangeDef; +typedef virNetworkDHCPRangeDef *virNetworkDHCPRangeDefPtr; +struct _virNetworkDHCPRangeDef { + char *start; + char *end; +}; + +typedef struct _virNetworkDef virNetworkDef; +typedef virNetworkDef *virNetworkDefPtr; +struct _virNetworkDef { + unsigned char uuid[VIR_UUID_BUFLEN]; + char *name; + + char *bridge; /* Name of bridge device */ + int stp : 1; /* Spanning tree protocol */ + long delay; /* Bridge forward delay (ms) */
It looks like delay can only be non-negative, so "unsigned long" would work. The only change that'd have to accompany it would be s/%ld/%lu/ in a printf format string.
+ int forwardType; /* One of virNetworkForwardType constants */
How about using enum virNetworkForwardType? Nicer for debugging.
+ char *forwardDev; /* Destination device for forwarding */ + + char *ipAddress; /* Bridge IP address */ + char *netmask; + char *network; + + int nranges; /* Zero or more dhcp ranges */
This is never negative, so could be "unsigned int". ...
+struct _virNetworkObj { + int dnsmasqPid;
How about pid_t?
+ unsigned int active : 1; + unsigned int autostart : 1; + unsigned int persistent : 1; + + char *configFile; /* Persistent config file path */ + char *autostartLink; /* Symlink path for autostart */ + + virNetworkDefPtr def; /* The current definition */ + virNetworkDefPtr newDef; /* New definition to activate at shutdown */ + + virNetworkObjPtr next; +};
participants (3)
-
Daniel P. Berrange
-
Daniel Veillard
-
Jim Meyering