[libvirt] [PATCH v4 0/3] Network hooks

Tier four. Michal Privoznik (3): network_conf: Expose virNetworkDefFormatInternal network: Introduce network hooks network: Taint networks that are using hook script docs/hooks.html.in | 98 ++++++++++++++++++++++++++++++++------ src/conf/network_conf.c | 64 ++++++++++++++++++++++--- src/conf/network_conf.h | 20 ++++++++ src/libvirt_private.syms | 4 ++ src/lxc/lxc_driver.c | 4 +- src/lxc/lxc_process.c | 6 +-- src/network/bridge_driver.c | 111 ++++++++++++++++++++++++++++++++++++++++++-- src/network/bridge_driver.h | 19 ++++---- src/qemu/qemu_command.c | 2 +- src/qemu/qemu_hotplug.c | 14 +++--- src/qemu/qemu_process.c | 4 +- src/util/virhook.c | 13 +++++- src/util/virhook.h | 11 +++++ 13 files changed, 321 insertions(+), 49 deletions(-) -- 1.8.5.3

In the next patch I'm going to need the network format function that takes virBuffer as argument. However, slightly change of name is more appropriate then: virNetworkDefFormatBuf to match the rest of functions that format an object to buffer. Signed-off-by: Michal Privoznik <mprivozn@redhat.com> --- src/conf/network_conf.c | 12 ++++++------ src/conf/network_conf.h | 3 +++ src/libvirt_private.syms | 1 + 3 files changed, 10 insertions(+), 6 deletions(-) diff --git a/src/conf/network_conf.c b/src/conf/network_conf.c index dd3fa19..8b6236d 100644 --- a/src/conf/network_conf.c +++ b/src/conf/network_conf.c @@ -2593,10 +2593,10 @@ cleanup: return ret; } -static int -virNetworkDefFormatInternal(virBufferPtr buf, - const virNetworkDef *def, - unsigned int flags) +int +virNetworkDefFormatBuf(virBufferPtr buf, + const virNetworkDef *def, + unsigned int flags) { const unsigned char *uuid; char uuidstr[VIR_UUID_STRING_BUFLEN]; @@ -2763,7 +2763,7 @@ virNetworkDefFormat(const virNetworkDef *def, { virBuffer buf = VIR_BUFFER_INITIALIZER; - if (virNetworkDefFormatInternal(&buf, def, flags) < 0) + if (virNetworkDefFormatBuf(&buf, def, flags) < 0) goto error; if (virBufferError(&buf)) @@ -2794,7 +2794,7 @@ virNetworkObjFormat(virNetworkObjPtr net, VIR_FREE(class_id); virBufferAdjustIndent(&buf, 2); - if (virNetworkDefFormatInternal(&buf, net->def, flags) < 0) + if (virNetworkDefFormatBuf(&buf, net->def, flags) < 0) goto error; virBufferAdjustIndent(&buf, -2); diff --git a/src/conf/network_conf.h b/src/conf/network_conf.h index b84762a..47124ce 100644 --- a/src/conf/network_conf.h +++ b/src/conf/network_conf.h @@ -338,6 +338,9 @@ virNetworkDefPtr virNetworkDefParseFile(const char *filename); virNetworkDefPtr virNetworkDefParseNode(xmlDocPtr xml, xmlNodePtr root); char *virNetworkDefFormat(const virNetworkDef *def, unsigned int flags); +int virNetworkDefFormatBuf(virBufferPtr buf, + const virNetworkDef *def, + unsigned int flags); static inline const char * virNetworkDefForwardIf(const virNetworkDef *def, size_t n) diff --git a/src/libvirt_private.syms b/src/libvirt_private.syms index 2c9536a..b554f11 100644 --- a/src/libvirt_private.syms +++ b/src/libvirt_private.syms @@ -504,6 +504,7 @@ virNetworkConfigChangeSetup; virNetworkConfigFile; virNetworkDefCopy; virNetworkDefFormat; +virNetworkDefFormatBuf; virNetworkDefFree; virNetworkDefGetIpByIndex; virNetworkDefParseFile; -- 1.8.5.3

On 02/12/2014 07:07 PM, Michal Privoznik wrote:
In the next patch I'm going to need the network format function that takes virBuffer as argument. However, slightly change of name is more appropriate then: virNetworkDefFormatBuf to match the rest of functions that format an object to buffer.
Signed-off-by: Michal Privoznik <mprivozn@redhat.com> --- src/conf/network_conf.c | 12 ++++++------ src/conf/network_conf.h | 3 +++ src/libvirt_private.syms | 1 + 3 files changed, 10 insertions(+), 6 deletions(-)
diff --git a/src/conf/network_conf.c b/src/conf/network_conf.c index dd3fa19..8b6236d 100644 --- a/src/conf/network_conf.c +++ b/src/conf/network_conf.c @@ -2593,10 +2593,10 @@ cleanup: return ret; }
-static int -virNetworkDefFormatInternal(virBufferPtr buf, - const virNetworkDef *def, - unsigned int flags) +int +virNetworkDefFormatBuf(virBufferPtr buf, + const virNetworkDef *def, + unsigned int flags) { const unsigned char *uuid; char uuidstr[VIR_UUID_STRING_BUFLEN]; @@ -2763,7 +2763,7 @@ virNetworkDefFormat(const virNetworkDef *def, { virBuffer buf = VIR_BUFFER_INITIALIZER;
- if (virNetworkDefFormatInternal(&buf, def, flags) < 0) + if (virNetworkDefFormatBuf(&buf, def, flags) < 0) goto error;
if (virBufferError(&buf)) @@ -2794,7 +2794,7 @@ virNetworkObjFormat(virNetworkObjPtr net, VIR_FREE(class_id);
virBufferAdjustIndent(&buf, 2); - if (virNetworkDefFormatInternal(&buf, net->def, flags) < 0) + if (virNetworkDefFormatBuf(&buf, net->def, flags) < 0) goto error;
virBufferAdjustIndent(&buf, -2); diff --git a/src/conf/network_conf.h b/src/conf/network_conf.h index b84762a..47124ce 100644 --- a/src/conf/network_conf.h +++ b/src/conf/network_conf.h @@ -338,6 +338,9 @@ virNetworkDefPtr virNetworkDefParseFile(const char *filename); virNetworkDefPtr virNetworkDefParseNode(xmlDocPtr xml, xmlNodePtr root); char *virNetworkDefFormat(const virNetworkDef *def, unsigned int flags); +int virNetworkDefFormatBuf(virBufferPtr buf, + const virNetworkDef *def, + unsigned int flags);
static inline const char * virNetworkDefForwardIf(const virNetworkDef *def, size_t n) diff --git a/src/libvirt_private.syms b/src/libvirt_private.syms index 2c9536a..b554f11 100644 --- a/src/libvirt_private.syms +++ b/src/libvirt_private.syms @@ -504,6 +504,7 @@ virNetworkConfigChangeSetup; virNetworkConfigFile; virNetworkDefCopy; virNetworkDefFormat; +virNetworkDefFormatBuf; virNetworkDefFree; virNetworkDefGetIpByIndex; virNetworkDefParseFile;
ACK.

There might be some use cases, where user wants to prepare the host or its environment prior to starting a network and do some cleanup after the network has been shut down. Consider all the functionality that libvirt doesn't currently have as an example what a hook script can possibly do. Signed-off-by: Michal Privoznik <mprivozn@redhat.com> --- docs/hooks.html.in | 98 ++++++++++++++++++++++++++++++++++++++------- src/lxc/lxc_driver.c | 4 +- src/lxc/lxc_process.c | 6 +-- src/network/bridge_driver.c | 91 +++++++++++++++++++++++++++++++++++++++-- src/network/bridge_driver.h | 19 +++++---- src/qemu/qemu_command.c | 2 +- src/qemu/qemu_hotplug.c | 14 +++---- src/qemu/qemu_process.c | 4 +- src/util/virhook.c | 13 +++++- src/util/virhook.h | 11 +++++ 10 files changed, 220 insertions(+), 42 deletions(-) diff --git a/docs/hooks.html.in b/docs/hooks.html.in index f0f692b..29995f7 100644 --- a/docs/hooks.html.in +++ b/docs/hooks.html.in @@ -13,9 +13,15 @@ actions occur:</p> <ul> <li>The libvirt daemon starts, stops, or reloads its - configuration<br/><br/></li> - <li>A QEMU guest is started or stopped<br/><br/></li> - <li>An LXC guest is started or stopped<br/><br/></li> + configuration + (<span class="since">since 0.8.0</span>)<br/><br/></li> + <li>A QEMU guest is started or stopped + (<span class="since">since 0.8.0</span>)<br/><br/></li> + <li>An LXC guest is started or stopped + (<span class="since">since 0.8.0</span>)<br/><br/></li> + <li>A network is started or stopped or an interface is + un-/plugged from/to the network + (<span class="since">since 1.2.2</span>)<br/><br/></li> </ul> <h2><a name="location">Script location</a></h2> @@ -44,6 +50,9 @@ Executed when a QEMU guest is started, stopped, or migrated<br/><br/></li> <li><code>/etc/libvirt/hooks/lxc</code><br /><br/> Executed when an LXC guest is started or stopped</li> + <li><code>/etc/libvirt/hooks/network</code><br/><br/> + Executed when a network is started or stopped or an + interface is un-/plugged from/to the network</li> </ul> <br/> @@ -66,6 +75,39 @@ XML description for the domain on their stdin. This includes items such the UUID of the domain and its storage information, and is intended to provide all the libvirt information the script needs.</p> + <p>For all cases, stdin of the network hook script is provided with the + full XML description of the network status in the following form:</p> + +<pre><hookData> + <network> + <name>$network_name</name> + <uuid>XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX</uuid> + ... + </network> +</hookData></pre> + + <p>In the case of an interface + being plugged/unplugged to the network, the network XML will be followed + with the full XML description of the domain containing the interface + that is being plugged/unplugged:</p> + +<pre><hookData> + <network> + <name>$network_name</name> + <uuid>XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX</uuid> + ... + </network> + <domain type='$domain_type' id='$domain_id'> + <name>$domain_name</name> + <uuid>XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX</uuid> + ... + </domain> +</hookData></pre> + + <p>Please note that this approach is different to other cases such as + <code>daemon</code>, <code>qemu</code> or <code>lxc</code> hook scripts, + because two XMLs may be passed here, while in the other cases only a single + XML is passed.</p> <p>The command line arguments take this approach:</p> <ol> @@ -181,25 +223,51 @@ <pre>/etc/libvirt/hooks/lxc guest_name reconnect begin -</pre> </li> </ul> + + <h5><a name="network">/etc/libvirt/hooks/network</a></h5> + <ul> + <li><span class="since">Since 1.2.2</span>, before a network is started, + this script is called as:<br/> + <pre>/etc/libvirt/hooks/network network_name start begin -</pre></li> + <li>After the network is started, up ∧ running, the script is + called as:<br/> + <pre>/etc/libvirt/hooks/network network_name started begin -</pre></li> + <li>When a network is shut down, this script is called as:<br/> + <pre>/etc/libvirt/hooks/network network_name stopped end -</pre></li> + <li>Later, when network is started and there's an interface from a + domain to be plugged into the network (plugged may not be the correct + expression when it comes to bridgeless networks, perhaps allocated is + better one then), the hook script is called as:<br/> + <pre>/etc/libvirt/hooks/network network_name plugged begin -</pre> + Please note, that in this case, the script is passed both network and + domain XMLs on its stdin.</li> + <li>When the domain from previous case is shutting down, the interface + is unplugged. This leads to another script invocation:<br/> + <pre>/etc/libvirt/hooks/network network_name unplugged begin -</pre> + And again, as in previous case, both network and domain XMLs are passed + onto script's stdin.</li> + </ul> + <br/> <h2><a name="execution">Script execution</a></h2> <ul> - <li>The "start" operation for the guest hook scripts, qemu and lxc, - executes <b>prior</b> to the guest being created. This allows the - guest start operation to be aborted if the script returns indicating - failure.<br/><br/></li> - <li>The "shutdown" operation for the guest hook scripts, qemu and lxc, - executes <b>after</b> the guest has stopped. If the hook script - indicates failure in its return, the shut down of the guest cannot - be aborted because it has already been performed.<br/><br/></li> + <li>The "start" operation for the guest and network hook scripts, + executes <b>prior</b> to the object (guest or network) being created. + This allows the object start operation to be aborted if the script + returns indicating failure.<br/><br/></li> + <li>The "shutdown" operation for the guest and network hook scripts, + executes <b>after</b> the object (guest or network) has stopped. If + the hook script indicates failure in its return, the shut down of the + object cannot be aborted because it has already been performed. + <br/><br/></li> <li>Hook scripts execute in a synchronous fashion. Libvirt waits for them to return before continuing the given operation.<br/><br/> - This is most noticeable with the guest start operation, as a lengthy - operation in the hook script can mean an extended wait for the guest - to be available to end users.<br/><br/></li> + This is most noticeable with the guest or network start operation, + as a lengthy operation in the hook script can mean an extended wait + for the guest or network to be available to end users.<br/><br/></li> <li>For a hook script to be utilised, it must have its execute bit set - (ie. chmod o+rx <i>qemu</i>), and must be present when the libvirt + (e.g. chmod o+rx <i>qemu</i>), and must be present when the libvirt daemon is started.<br/><br/></li> <li>If a hook script is added to a host after the libvirt daemon is already running, it won't be used until the libvirt daemon diff --git a/src/lxc/lxc_driver.c b/src/lxc/lxc_driver.c index 687046e..c48177c 100644 --- a/src/lxc/lxc_driver.c +++ b/src/lxc/lxc_driver.c @@ -3772,7 +3772,7 @@ lxcDomainAttachDeviceNetLive(virConnectPtr conn, * network's pool of devices, or resolve bridge device name * to the one defined in the network definition. */ - if (networkAllocateActualDevice(net) < 0) + if (networkAllocateActualDevice(vm->def, net) < 0) return -1; actualType = virDomainNetGetActualType(net); @@ -4427,7 +4427,7 @@ lxcDomainDetachDeviceNetLive(virDomainObjPtr vm, ret = 0; cleanup: if (!ret) { - networkReleaseActualDevice(detach); + networkReleaseActualDevice(vm->def, detach); virDomainNetRemove(vm->def, detachidx); virDomainNetDefFree(detach); } diff --git a/src/lxc/lxc_process.c b/src/lxc/lxc_process.c index ed729f6..8989245 100644 --- a/src/lxc/lxc_process.c +++ b/src/lxc/lxc_process.c @@ -198,7 +198,7 @@ static void virLXCProcessCleanup(virLXCDriverPtr driver, iface->ifname)); ignore_value(virNetDevVethDelete(iface->ifname)); } - networkReleaseActualDevice(iface); + networkReleaseActualDevice(vm->def, iface); } virDomainConfVMNWFilterTeardown(vm); @@ -374,7 +374,7 @@ static int virLXCProcessSetupInterfaces(virConnectPtr conn, * network's pool of devices, or resolve bridge device name * to the one defined in the network definition. */ - if (networkAllocateActualDevice(def->nets[i]) < 0) + if (networkAllocateActualDevice(def, def->nets[i]) < 0) goto cleanup; if (VIR_EXPAND_N(*veths, *nveths, 1) < 0) @@ -476,7 +476,7 @@ cleanup: ignore_value(virNetDevOpenvswitchRemovePort( virDomainNetGetActualBridgeName(iface), iface->ifname)); - networkReleaseActualDevice(iface); + networkReleaseActualDevice(def, iface); } } return ret; diff --git a/src/network/bridge_driver.c b/src/network/bridge_driver.c index 0964350..6a2d56a 100644 --- a/src/network/bridge_driver.c +++ b/src/network/bridge_driver.c @@ -71,6 +71,7 @@ #include "virstring.h" #include "viraccessapicheck.h" #include "network_event.h" +#include "virhook.h" #define VIR_FROM_THIS VIR_FROM_NETWORK @@ -134,6 +135,51 @@ networkObjFromNetwork(virNetworkPtr net) return network; } +static int +networkRunHook(virNetworkObjPtr network, + virDomainDefPtr dom, + int op, + int sub_op) +{ + virBuffer buf = VIR_BUFFER_INITIALIZER; + char *xml = NULL, *net_xml = NULL, *dom_xml = NULL; + int hookret; + int ret = -1; + + if (virHookPresent(VIR_HOOK_DRIVER_NETWORK)) { + virBufferAddLit(&buf, "<hookData>\n"); + virBufferAdjustIndent(&buf, 2); + if (virNetworkDefFormatBuf(&buf, network->def, 0) < 0) + goto cleanup; + if (dom && virDomainDefFormatInternal(dom, 0, &buf) < 0) + goto cleanup; + + virBufferAdjustIndent(&buf, -2); + virBufferAddLit(&buf, "</hookData>"); + + if (virBufferError(&buf) || + !(xml = virBufferContentAndReset(&buf))) + goto cleanup; + + hookret = virHookCall(VIR_HOOK_DRIVER_NETWORK, network->def->name, + op, sub_op, NULL, xml, NULL); + + /* + * If the script raised an error, pass it to the callee. + */ + if (hookret < 0) + goto cleanup; + } + + ret = 0; +cleanup: + virBufferFreeAndReset(&buf); + VIR_FREE(xml); + VIR_FREE(net_xml); + VIR_FREE(dom_xml); + return ret; +} + static char * networkDnsmasqLeaseFileNameDefault(const char *netname) { @@ -2008,6 +2054,13 @@ networkStartNetwork(virNetworkDriverStatePtr driver, if (virNetworkObjSetDefTransient(network, true) < 0) goto cleanup; + /* Run an early hook to set-up missing devices. + * If the script raised an error abort the launch. */ + if (networkRunHook(network, NULL, + VIR_HOOK_NETWORK_OP_START, + VIR_HOOK_SUBOP_BEGIN) < 0) + goto cleanup; + switch (network->def->forward.type) { case VIR_NETWORK_FORWARD_NONE: @@ -2027,6 +2080,12 @@ networkStartNetwork(virNetworkDriverStatePtr driver, break; } + /* finally we can call the 'started' hook script if any */ + if (networkRunHook(network, NULL, + VIR_HOOK_NETWORK_OP_STARTED, + VIR_HOOK_SUBOP_BEGIN) < 0) + goto cleanup; + /* Persist the live configuration now that anything autogenerated * is setup. */ @@ -2087,6 +2146,10 @@ static int networkShutdownNetwork(virNetworkDriverStatePtr driver, break; } + /* now that we know it's stopped call the hook if present */ + networkRunHook(network, NULL, VIR_HOOK_NETWORK_OP_STOPPED, + VIR_HOOK_SUBOP_END); + network->active = 0; virNetworkObjUnsetDefTransient(network); return ret; @@ -3239,6 +3302,7 @@ finish: } /* networkAllocateActualDevice: + * @dom: domain definition that @iface belongs to * @iface: the original NetDef from the domain * * Looks up the network reference by iface, allocates a physical @@ -3250,7 +3314,8 @@ finish: * Returns 0 on success, -1 on failure. */ int -networkAllocateActualDevice(virDomainNetDefPtr iface) +networkAllocateActualDevice(virDomainDefPtr dom, + virDomainNetDefPtr iface) { virNetworkDriverStatePtr driver = driverState; enum virDomainNetType actualType = iface->type; @@ -3583,6 +3648,12 @@ validate: } } + /* finally we can call the 'plugged' hook script if any */ + if (networkRunHook(network, dom, + VIR_HOOK_NETWORK_OP_IFACE_PLUGGED, + VIR_HOOK_SUBOP_BEGIN) < 0) + goto error; + if (dev) { /* we are now assured of success, so mark the allocation */ dev->connections++; @@ -3618,6 +3689,7 @@ error: } /* networkNotifyActualDevice: + * @dom: domain definition that @iface belongs to * @iface: the domain's NetDef with an "actual" device already filled in. * * Called to notify the network driver when libvirtd is restarted and @@ -3628,7 +3700,8 @@ error: * Returns 0 on success, -1 on failure. */ int -networkNotifyActualDevice(virDomainNetDefPtr iface) +networkNotifyActualDevice(virDomainDefPtr dom, + virDomainNetDefPtr iface) { virNetworkDriverStatePtr driver = driverState; enum virDomainNetType actualType = virDomainNetGetActualType(iface); @@ -3781,6 +3854,11 @@ networkNotifyActualDevice(virDomainNetDefPtr iface) } success: + /* finally we can call the 'plugged' hook script if any */ + if (networkRunHook(network, dom, VIR_HOOK_NETWORK_OP_IFACE_PLUGGED, + VIR_HOOK_SUBOP_BEGIN) < 0) + goto error; + netdef->connections++; VIR_DEBUG("Using network %s, %d connections", netdef->name, netdef->connections); @@ -3796,6 +3874,7 @@ error: /* networkReleaseActualDevice: + * @dom: domain definition that @iface belongs to * @iface: a domain's NetDef (interface definition) * * Given a domain <interface> element that previously had its <actual> @@ -3806,7 +3885,8 @@ error: * Returns 0 on success, -1 on failure. */ int -networkReleaseActualDevice(virDomainNetDefPtr iface) +networkReleaseActualDevice(virDomainDefPtr dom, + virDomainNetDefPtr iface) { virNetworkDriverStatePtr driver = driverState; enum virDomainNetType actualType = virDomainNetGetActualType(iface); @@ -3925,6 +4005,11 @@ networkReleaseActualDevice(virDomainNetDefPtr iface) success: if (iface->data.network.actual) netdef->connections--; + + /* finally we can call the 'unplugged' hook script if any */ + networkRunHook(network, dom, VIR_HOOK_NETWORK_OP_IFACE_UNPLUGGED, + VIR_HOOK_SUBOP_BEGIN); + VIR_DEBUG("Releasing network %s, %d connections", netdef->name, netdef->connections); ret = 0; diff --git a/src/network/bridge_driver.h b/src/network/bridge_driver.h index 50258b5..0f80556 100644 --- a/src/network/bridge_driver.h +++ b/src/network/bridge_driver.h @@ -34,12 +34,15 @@ int networkRegister(void); # if WITH_NETWORK -int networkAllocateActualDevice(virDomainNetDefPtr iface) - ATTRIBUTE_NONNULL(1); -int networkNotifyActualDevice(virDomainNetDefPtr iface) - ATTRIBUTE_NONNULL(1); -int networkReleaseActualDevice(virDomainNetDefPtr iface) - ATTRIBUTE_NONNULL(1); +int networkAllocateActualDevice(virDomainDefPtr dom, + virDomainNetDefPtr iface) + ATTRIBUTE_NONNULL(1) ATTRIBUTE_NONNULL(2); +int networkNotifyActualDevice(virDomainDefPtr dom, + virDomainNetDefPtr iface) + ATTRIBUTE_NONNULL(1) ATTRIBUTE_NONNULL(2); +int networkReleaseActualDevice(virDomainDefPtr dom, + virDomainNetDefPtr iface) + ATTRIBUTE_NONNULL(1) ATTRIBUTE_NONNULL(2); int networkGetNetworkAddress(const char *netname, char **netaddr) ATTRIBUTE_NONNULL(1) ATTRIBUTE_NONNULL(2); @@ -51,9 +54,9 @@ int networkDnsmasqConfContents(virNetworkObjPtr network, dnsmasqCapsPtr caps); # else /* Define no-op replacements that don't drag in any link dependencies. */ -# define networkAllocateActualDevice(iface) 0 +# define networkAllocateActualDevice(dom, iface) 0 # define networkNotifyActualDevice(iface) (iface=iface, 0) -# define networkReleaseActualDevice(iface) (iface=iface, 0) +# define networkReleaseActualDevice(dom, iface) (dom=dom, iface=iface, 0) # define networkGetNetworkAddress(netname, netaddr) (-2) # define networkDnsmasqConfContents(network, pidfile, configstr, \ dctx, caps) 0 diff --git a/src/qemu/qemu_command.c b/src/qemu/qemu_command.c index e499d54..bce32b8 100644 --- a/src/qemu/qemu_command.c +++ b/src/qemu/qemu_command.c @@ -548,7 +548,7 @@ qemuNetworkPrepareDevices(virDomainDefPtr def) * network's pool of devices, or resolve bridge device name * to the one defined in the network definition. */ - if (networkAllocateActualDevice(net) < 0) + if (networkAllocateActualDevice(def, net) < 0) goto cleanup; actualType = virDomainNetGetActualType(net); diff --git a/src/qemu/qemu_hotplug.c b/src/qemu/qemu_hotplug.c index 7066be6..6703c92 100644 --- a/src/qemu/qemu_hotplug.c +++ b/src/qemu/qemu_hotplug.c @@ -837,7 +837,7 @@ int qemuDomainAttachNetDevice(virConnectPtr conn, * network's pool of devices, or resolve bridge device name * to the one defined in the network definition. */ - if (networkAllocateActualDevice(net) < 0) + if (networkAllocateActualDevice(vm->def, net) < 0) goto cleanup; actualType = virDomainNetGetActualType(net); @@ -1082,7 +1082,7 @@ cleanup: virDomainNetRemoveHostdev(vm->def, net); - networkReleaseActualDevice(net); + networkReleaseActualDevice(vm->def, net); } VIR_FREE(nicstr); @@ -2017,7 +2017,7 @@ qemuDomainChangeNet(virQEMUDriverPtr driver, * free it if we fail for any reason */ if (newdev->type == VIR_DOMAIN_NET_TYPE_NETWORK && - networkAllocateActualDevice(newdev) < 0) { + networkAllocateActualDevice(vm->def, newdev) < 0) { goto cleanup; } @@ -2204,7 +2204,7 @@ qemuDomainChangeNet(virQEMUDriverPtr driver, /* this function doesn't work with HOSTDEV networks yet, thus * no need to change the pointer in the hostdev structure */ - networkReleaseActualDevice(olddev); + networkReleaseActualDevice(vm->def, olddev); virDomainNetDefFree(olddev); /* move newdev into the nets list, and NULL it out from the * virDomainDeviceDef that we were given so that the caller @@ -2236,7 +2236,7 @@ cleanup: * replace the entire device object. */ if (newdev) - networkReleaseActualDevice(newdev); + networkReleaseActualDevice(vm->def, newdev); return ret; } @@ -2649,7 +2649,7 @@ qemuDomainRemoveHostDevice(virQEMUDriverPtr driver, virDomainHostdevDefFree(hostdev); if (net) { - networkReleaseActualDevice(net); + networkReleaseActualDevice(vm->def, net); virDomainNetDefFree(net); } virObjectUnref(cfg); @@ -2717,7 +2717,7 @@ qemuDomainRemoveNetDevice(virQEMUDriverPtr driver, virDomainNetGetActualBridgeName(net), net->ifname)); - networkReleaseActualDevice(net); + networkReleaseActualDevice(vm->def, net); virDomainNetDefFree(net); virObjectUnref(cfg); } diff --git a/src/qemu/qemu_process.c b/src/qemu/qemu_process.c index 33d2a77..ffa939a 100644 --- a/src/qemu/qemu_process.c +++ b/src/qemu/qemu_process.c @@ -2773,7 +2773,7 @@ qemuProcessNotifyNets(virDomainDefPtr def) for (i = 0; i < def->nnets; i++) { virDomainNetDefPtr net = def->nets[i]; - if (networkNotifyActualDevice(net) < 0) + if (networkNotifyActualDevice(def, net) < 0) return -1; } return 0; @@ -4393,7 +4393,7 @@ void qemuProcessStop(virQEMUDriverPtr driver, /* kick the device out of the hostdev list too */ virDomainNetRemoveHostdev(def, net); - networkReleaseActualDevice(net); + networkReleaseActualDevice(vm->def, net); } retry: diff --git a/src/util/virhook.c b/src/util/virhook.c index 159efdb..0f5d0c5 100644 --- a/src/util/virhook.c +++ b/src/util/virhook.c @@ -48,12 +48,14 @@ VIR_ENUM_DECL(virHookDaemonOp) VIR_ENUM_DECL(virHookSubop) VIR_ENUM_DECL(virHookQemuOp) VIR_ENUM_DECL(virHookLxcOp) +VIR_ENUM_DECL(virHookNetworkOp) VIR_ENUM_IMPL(virHookDriver, VIR_HOOK_DRIVER_LAST, "daemon", "qemu", - "lxc") + "lxc", + "network") VIR_ENUM_IMPL(virHookDaemonOp, VIR_HOOK_DAEMON_OP_LAST, "start", @@ -83,6 +85,13 @@ VIR_ENUM_IMPL(virHookLxcOp, VIR_HOOK_LXC_OP_LAST, "started", "reconnect") +VIR_ENUM_IMPL(virHookNetworkOp, VIR_HOOK_NETWORK_OP_LAST, + "start", + "started", + "stopped", + "plugged", + "unplugged") + static int virHooksFound = -1; /** @@ -246,6 +255,8 @@ virHookCall(int driver, case VIR_HOOK_DRIVER_LXC: opstr = virHookLxcOpTypeToString(op); break; + case VIR_HOOK_DRIVER_NETWORK: + opstr = virHookNetworkOpTypeToString(op); } if (opstr == NULL) { virReportError(VIR_ERR_INTERNAL_ERROR, diff --git a/src/util/virhook.h b/src/util/virhook.h index 96bf4cf..7b09ac5 100644 --- a/src/util/virhook.h +++ b/src/util/virhook.h @@ -30,6 +30,7 @@ enum virHookDriverType { VIR_HOOK_DRIVER_DAEMON = 0, /* Daemon related events */ VIR_HOOK_DRIVER_QEMU, /* QEmu domains related events */ VIR_HOOK_DRIVER_LXC, /* LXC domains related events */ + VIR_HOOK_DRIVER_NETWORK, /* network related events */ VIR_HOOK_DRIVER_LAST, }; @@ -74,6 +75,16 @@ enum virHookLxcOpType { VIR_HOOK_LXC_OP_LAST, }; +enum virHookNetworkOpType { + VIR_HOOK_NETWORK_OP_START, /* network is about to start */ + VIR_HOOK_NETWORK_OP_STARTED, /* network has start */ + VIR_HOOK_NETWORK_OP_STOPPED, /* network has stopped */ + VIR_HOOK_NETWORK_OP_IFACE_PLUGGED, /* an interface has been plugged into the network */ + VIR_HOOK_NETWORK_OP_IFACE_UNPLUGGED, /* an interface was unplugged from the network */ + + VIR_HOOK_NETWORK_OP_LAST, +}; + int virHookInitialize(void); int virHookPresent(int driver); -- 1.8.5.3

On 02/12/2014 07:07 PM, Michal Privoznik wrote:
There might be some use cases, where user wants to prepare the host or its environment prior to starting a network and do some cleanup after the network has been shut down. Consider all the functionality that libvirt doesn't currently have as an example what a hook script can possibly do.
Signed-off-by: Michal Privoznik <mprivozn@redhat.com> --- docs/hooks.html.in | 98 ++++++++++++++++++++++++++++++++++++++------- src/lxc/lxc_driver.c | 4 +- src/lxc/lxc_process.c | 6 +-- src/network/bridge_driver.c | 91 +++++++++++++++++++++++++++++++++++++++-- src/network/bridge_driver.h | 19 +++++---- src/qemu/qemu_command.c | 2 +- src/qemu/qemu_hotplug.c | 14 +++---- src/qemu/qemu_process.c | 4 +- src/util/virhook.c | 13 +++++- src/util/virhook.h | 11 +++++ 10 files changed, 220 insertions(+), 42 deletions(-)
diff --git a/docs/hooks.html.in b/docs/hooks.html.in index f0f692b..29995f7 100644 --- a/docs/hooks.html.in +++ b/docs/hooks.html.in @@ -13,9 +13,15 @@ actions occur:</p> <ul> <li>The libvirt daemon starts, stops, or reloads its - configuration<br/><br/></li> - <li>A QEMU guest is started or stopped<br/><br/></li> - <li>An LXC guest is started or stopped<br/><br/></li> + configuration + (<span class="since">since 0.8.0</span>)<br/><br/></li> + <li>A QEMU guest is started or stopped + (<span class="since">since 0.8.0</span>)<br/><br/></li> + <li>An LXC guest is started or stopped + (<span class="since">since 0.8.0</span>)<br/><br/></li> + <li>A network is started or stopped or an interface is + un-/plugged from/to the network + (<span class="since">since 1.2.2</span>)<br/><br/></li> </ul>
<h2><a name="location">Script location</a></h2> @@ -44,6 +50,9 @@ Executed when a QEMU guest is started, stopped, or migrated<br/><br/></li> <li><code>/etc/libvirt/hooks/lxc</code><br /><br/> Executed when an LXC guest is started or stopped</li> + <li><code>/etc/libvirt/hooks/network</code><br/><br/> + Executed when a network is started or stopped or an + interface is un-/plugged from/to the network</li>
further down you use "plugged/unplugged", which I think looks better (you'll then want to change "from/to" to "to/from" as well, or just use "to" for simplicity)
</ul> <br/>
@@ -66,6 +75,39 @@ XML description for the domain on their stdin. This includes items such the UUID of the domain and its storage information, and is intended to provide all the libvirt information the script needs.</p> + <p>For all cases, stdin of the network hook script is provided with the + full XML description of the network status in the following form:</p> + +<pre><hookData> + <network> + <name>$network_name</name> + <uuid>XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX</uuid> + ... + </network> +</hookData></pre> + + <p>In the case of an interface + being plugged/unplugged to the network, the network XML will be followed + with the full XML description of the domain containing the interface + that is being plugged/unplugged:</p> + +<pre><hookData> + <network> + <name>$network_name</name> + <uuid>XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX</uuid>
Do we use "XXXX..." for uuid examples anywhere else? The few examples I found in the existing documentation were actual valid uuids.
+ ... + </network> + <domain type='$domain_type' id='$domain_id'> + <name>$domain_name</name> + <uuid>XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX</uuid> + ... + </domain> +</hookData></pre> + + <p>Please note that this approach is different to other cases such as
"different from other cases"
+ <code>daemon</code>, <code>qemu</code> or <code>lxc</code> hook scripts, + because two XMLs may be passed here, while in the other cases only a single + XML is passed.</p>
<p>The command line arguments take this approach:</p> <ol> @@ -181,25 +223,51 @@ <pre>/etc/libvirt/hooks/lxc guest_name reconnect begin -</pre> </li> </ul> + + <h5><a name="network">/etc/libvirt/hooks/network</a></h5> + <ul> + <li><span class="since">Since 1.2.2</span>, before a network is started, + this script is called as:<br/> + <pre>/etc/libvirt/hooks/network network_name start begin -</pre></li> + <li>After the network is started, up ∧ running, the script is + called as:<br/> + <pre>/etc/libvirt/hooks/network network_name started begin -</pre></li> + <li>When a network is shut down, this script is called as:<br/> + <pre>/etc/libvirt/hooks/network network_name stopped end -</pre></li> + <li>Later, when network is started and there's an interface from a + domain to be plugged into the network (plugged may not be the correct + expression when it comes to bridgeless networks, perhaps allocated is + better one then), the hook script is called as:<br/>
you can just remove that comment in ( )
+ <pre>/etc/libvirt/hooks/network network_name plugged begin -</pre> + Please note, that in this case, the script is passed both network and + domain XMLs on its stdin.</li> + <li>When the domain from previous case is shutting down, the interface + is unplugged. This leads to another script invocation:<br/> + <pre>/etc/libvirt/hooks/network network_name unplugged begin -</pre> + And again, as in previous case, both network and domain XMLs are passed + onto script's stdin.</li> + </ul> + <br/>
<h2><a name="execution">Script execution</a></h2> <ul> - <li>The "start" operation for the guest hook scripts, qemu and lxc, - executes <b>prior</b> to the guest being created. This allows the - guest start operation to be aborted if the script returns indicating - failure.<br/><br/></li> - <li>The "shutdown" operation for the guest hook scripts, qemu and lxc, - executes <b>after</b> the guest has stopped. If the hook script - indicates failure in its return, the shut down of the guest cannot - be aborted because it has already been performed.<br/><br/></li> + <li>The "start" operation for the guest and network hook scripts, + executes <b>prior</b> to the object (guest or network) being created. + This allows the object start operation to be aborted if the script + returns indicating failure.<br/><br/></li> + <li>The "shutdown" operation for the guest and network hook scripts, + executes <b>after</b> the object (guest or network) has stopped. If + the hook script indicates failure in its return, the shut down of the + object cannot be aborted because it has already been performed. + <br/><br/></li> <li>Hook scripts execute in a synchronous fashion. Libvirt waits for them to return before continuing the given operation.<br/><br/> - This is most noticeable with the guest start operation, as a lengthy - operation in the hook script can mean an extended wait for the guest - to be available to end users.<br/><br/></li> + This is most noticeable with the guest or network start operation, + as a lengthy operation in the hook script can mean an extended wait + for the guest or network to be available to end users.<br/><br/></li> <li>For a hook script to be utilised, it must have its execute bit set - (ie. chmod o+rx <i>qemu</i>), and must be present when the libvirt + (e.g. chmod o+rx <i>qemu</i>), and must be present when the libvirt daemon is started.<br/><br/></li> <li>If a hook script is added to a host after the libvirt daemon is already running, it won't be used until the libvirt daemon diff --git a/src/lxc/lxc_driver.c b/src/lxc/lxc_driver.c index 687046e..c48177c 100644 --- a/src/lxc/lxc_driver.c +++ b/src/lxc/lxc_driver.c @@ -3772,7 +3772,7 @@ lxcDomainAttachDeviceNetLive(virConnectPtr conn, * network's pool of devices, or resolve bridge device name * to the one defined in the network definition. */ - if (networkAllocateActualDevice(net) < 0) + if (networkAllocateActualDevice(vm->def, net) < 0) return -1;
actualType = virDomainNetGetActualType(net); @@ -4427,7 +4427,7 @@ lxcDomainDetachDeviceNetLive(virDomainObjPtr vm, ret = 0; cleanup: if (!ret) { - networkReleaseActualDevice(detach); + networkReleaseActualDevice(vm->def, detach); virDomainNetRemove(vm->def, detachidx); virDomainNetDefFree(detach); } diff --git a/src/lxc/lxc_process.c b/src/lxc/lxc_process.c index ed729f6..8989245 100644 --- a/src/lxc/lxc_process.c +++ b/src/lxc/lxc_process.c @@ -198,7 +198,7 @@ static void virLXCProcessCleanup(virLXCDriverPtr driver, iface->ifname)); ignore_value(virNetDevVethDelete(iface->ifname)); } - networkReleaseActualDevice(iface); + networkReleaseActualDevice(vm->def, iface); }
virDomainConfVMNWFilterTeardown(vm); @@ -374,7 +374,7 @@ static int virLXCProcessSetupInterfaces(virConnectPtr conn, * network's pool of devices, or resolve bridge device name * to the one defined in the network definition. */ - if (networkAllocateActualDevice(def->nets[i]) < 0) + if (networkAllocateActualDevice(def, def->nets[i]) < 0) goto cleanup;
if (VIR_EXPAND_N(*veths, *nveths, 1) < 0) @@ -476,7 +476,7 @@ cleanup: ignore_value(virNetDevOpenvswitchRemovePort( virDomainNetGetActualBridgeName(iface), iface->ifname)); - networkReleaseActualDevice(iface); + networkReleaseActualDevice(def, iface); } } return ret; diff --git a/src/network/bridge_driver.c b/src/network/bridge_driver.c index 0964350..6a2d56a 100644 --- a/src/network/bridge_driver.c +++ b/src/network/bridge_driver.c @@ -71,6 +71,7 @@ #include "virstring.h" #include "viraccessapicheck.h" #include "network_event.h" +#include "virhook.h"
#define VIR_FROM_THIS VIR_FROM_NETWORK
@@ -134,6 +135,51 @@ networkObjFromNetwork(virNetworkPtr net) return network; }
+static int +networkRunHook(virNetworkObjPtr network, + virDomainDefPtr dom, + int op, + int sub_op) +{ + virBuffer buf = VIR_BUFFER_INITIALIZER; + char *xml = NULL, *net_xml = NULL, *dom_xml = NULL; + int hookret; + int ret = -1; + + if (virHookPresent(VIR_HOOK_DRIVER_NETWORK)) { + virBufferAddLit(&buf, "<hookData>\n"); + virBufferAdjustIndent(&buf, 2); + if (virNetworkDefFormatBuf(&buf, network->def, 0) < 0) + goto cleanup; + if (dom && virDomainDefFormatInternal(dom, 0, &buf) < 0) + goto cleanup; + + virBufferAdjustIndent(&buf, -2); + virBufferAddLit(&buf, "</hookData>"); + + if (virBufferError(&buf) || + !(xml = virBufferContentAndReset(&buf))) + goto cleanup; + + hookret = virHookCall(VIR_HOOK_DRIVER_NETWORK, network->def->name, + op, sub_op, NULL, xml, NULL); + + /* + * If the script raised an error, pass it to the callee. + */ + if (hookret < 0) + goto cleanup; + } + + ret = 0; +cleanup: + virBufferFreeAndReset(&buf); + VIR_FREE(xml); + VIR_FREE(net_xml); + VIR_FREE(dom_xml); + return ret; +} + static char * networkDnsmasqLeaseFileNameDefault(const char *netname) { @@ -2008,6 +2054,13 @@ networkStartNetwork(virNetworkDriverStatePtr driver, if (virNetworkObjSetDefTransient(network, true) < 0) goto cleanup;
+ /* Run an early hook to set-up missing devices. + * If the script raised an error abort the launch. */ + if (networkRunHook(network, NULL, + VIR_HOOK_NETWORK_OP_START, + VIR_HOOK_SUBOP_BEGIN) < 0) + goto cleanup; + switch (network->def->forward.type) {
case VIR_NETWORK_FORWARD_NONE: @@ -2027,6 +2080,12 @@ networkStartNetwork(virNetworkDriverStatePtr driver, break; }
+ /* finally we can call the 'started' hook script if any */ + if (networkRunHook(network, NULL, + VIR_HOOK_NETWORK_OP_STARTED, + VIR_HOOK_SUBOP_BEGIN) < 0) + goto cleanup; + /* Persist the live configuration now that anything autogenerated * is setup. */ @@ -2087,6 +2146,10 @@ static int networkShutdownNetwork(virNetworkDriverStatePtr driver, break; }
+ /* now that we know it's stopped call the hook if present */ + networkRunHook(network, NULL, VIR_HOOK_NETWORK_OP_STOPPED, + VIR_HOOK_SUBOP_END); + network->active = 0; virNetworkObjUnsetDefTransient(network); return ret; @@ -3239,6 +3302,7 @@ finish: }
/* networkAllocateActualDevice: + * @dom: domain definition that @iface belongs to * @iface: the original NetDef from the domain * * Looks up the network reference by iface, allocates a physical @@ -3250,7 +3314,8 @@ finish: * Returns 0 on success, -1 on failure. */ int -networkAllocateActualDevice(virDomainNetDefPtr iface) +networkAllocateActualDevice(virDomainDefPtr dom, + virDomainNetDefPtr iface) { virNetworkDriverStatePtr driver = driverState; enum virDomainNetType actualType = iface->type; @@ -3583,6 +3648,12 @@ validate: } }
+ /* finally we can call the 'plugged' hook script if any */ + if (networkRunHook(network, dom, + VIR_HOOK_NETWORK_OP_IFACE_PLUGGED, + VIR_HOOK_SUBOP_BEGIN) < 0) + goto error; +
So did we decide that, unlike the network start needing a "start" and "started" hook, the plug needs only "plugged", and doesn't need "plug"? (and btw, what's the deal with the sub-ops anyway - you're following the pattern established by the domain hooks, but I looked through the domain hook documentation and they seem to be more or less a waste of an argument. I would have figured that the hooks during a domain start would be "start begin" then "start done", but instead they are "start begin" followed by "started begin". ???)
if (dev) { /* we are now assured of success, so mark the allocation */ dev->connections++; @@ -3618,6 +3689,7 @@ error: }
/* networkNotifyActualDevice: + * @dom: domain definition that @iface belongs to * @iface: the domain's NetDef with an "actual" device already filled in. * * Called to notify the network driver when libvirtd is restarted and @@ -3628,7 +3700,8 @@ error: * Returns 0 on success, -1 on failure. */ int -networkNotifyActualDevice(virDomainNetDefPtr iface) +networkNotifyActualDevice(virDomainDefPtr dom, + virDomainNetDefPtr iface) { virNetworkDriverStatePtr driver = driverState; enum virDomainNetType actualType = virDomainNetGetActualType(iface); @@ -3781,6 +3854,11 @@ networkNotifyActualDevice(virDomainNetDefPtr iface) }
success: + /* finally we can call the 'plugged' hook script if any */ + if (networkRunHook(network, dom, VIR_HOOK_NETWORK_OP_IFACE_PLUGGED, + VIR_HOOK_SUBOP_BEGIN) < 0) + goto error; + netdef->connections++; VIR_DEBUG("Using network %s, %d connections", netdef->name, netdef->connections); @@ -3796,6 +3874,7 @@ error:
/* networkReleaseActualDevice: + * @dom: domain definition that @iface belongs to * @iface: a domain's NetDef (interface definition) * * Given a domain <interface> element that previously had its <actual> @@ -3806,7 +3885,8 @@ error: * Returns 0 on success, -1 on failure. */ int -networkReleaseActualDevice(virDomainNetDefPtr iface) +networkReleaseActualDevice(virDomainDefPtr dom, + virDomainNetDefPtr iface) { virNetworkDriverStatePtr driver = driverState; enum virDomainNetType actualType = virDomainNetGetActualType(iface); @@ -3925,6 +4005,11 @@ networkReleaseActualDevice(virDomainNetDefPtr iface) success: if (iface->data.network.actual) netdef->connections--; + + /* finally we can call the 'unplugged' hook script if any */ + networkRunHook(network, dom, VIR_HOOK_NETWORK_OP_IFACE_UNPLUGGED, + VIR_HOOK_SUBOP_BEGIN); + VIR_DEBUG("Releasing network %s, %d connections", netdef->name, netdef->connections); ret = 0; diff --git a/src/network/bridge_driver.h b/src/network/bridge_driver.h index 50258b5..0f80556 100644 --- a/src/network/bridge_driver.h +++ b/src/network/bridge_driver.h @@ -34,12 +34,15 @@ int networkRegister(void);
# if WITH_NETWORK -int networkAllocateActualDevice(virDomainNetDefPtr iface) - ATTRIBUTE_NONNULL(1); -int networkNotifyActualDevice(virDomainNetDefPtr iface) - ATTRIBUTE_NONNULL(1); -int networkReleaseActualDevice(virDomainNetDefPtr iface) - ATTRIBUTE_NONNULL(1); +int networkAllocateActualDevice(virDomainDefPtr dom, + virDomainNetDefPtr iface) + ATTRIBUTE_NONNULL(1) ATTRIBUTE_NONNULL(2); +int networkNotifyActualDevice(virDomainDefPtr dom, + virDomainNetDefPtr iface) + ATTRIBUTE_NONNULL(1) ATTRIBUTE_NONNULL(2); +int networkReleaseActualDevice(virDomainDefPtr dom, + virDomainNetDefPtr iface) + ATTRIBUTE_NONNULL(1) ATTRIBUTE_NONNULL(2);
int networkGetNetworkAddress(const char *netname, char **netaddr) ATTRIBUTE_NONNULL(1) ATTRIBUTE_NONNULL(2); @@ -51,9 +54,9 @@ int networkDnsmasqConfContents(virNetworkObjPtr network, dnsmasqCapsPtr caps); # else /* Define no-op replacements that don't drag in any link dependencies. */ -# define networkAllocateActualDevice(iface) 0 +# define networkAllocateActualDevice(dom, iface) 0 # define networkNotifyActualDevice(iface) (iface=iface, 0) -# define networkReleaseActualDevice(iface) (iface=iface, 0) +# define networkReleaseActualDevice(dom, iface) (dom=dom, iface=iface, 0) # define networkGetNetworkAddress(netname, netaddr) (-2) # define networkDnsmasqConfContents(network, pidfile, configstr, \ dctx, caps) 0 diff --git a/src/qemu/qemu_command.c b/src/qemu/qemu_command.c index e499d54..bce32b8 100644 --- a/src/qemu/qemu_command.c +++ b/src/qemu/qemu_command.c @@ -548,7 +548,7 @@ qemuNetworkPrepareDevices(virDomainDefPtr def) * network's pool of devices, or resolve bridge device name * to the one defined in the network definition. */ - if (networkAllocateActualDevice(net) < 0) + if (networkAllocateActualDevice(def, net) < 0) goto cleanup;
actualType = virDomainNetGetActualType(net); diff --git a/src/qemu/qemu_hotplug.c b/src/qemu/qemu_hotplug.c index 7066be6..6703c92 100644 --- a/src/qemu/qemu_hotplug.c +++ b/src/qemu/qemu_hotplug.c @@ -837,7 +837,7 @@ int qemuDomainAttachNetDevice(virConnectPtr conn, * network's pool of devices, or resolve bridge device name * to the one defined in the network definition. */ - if (networkAllocateActualDevice(net) < 0) + if (networkAllocateActualDevice(vm->def, net) < 0) goto cleanup;
actualType = virDomainNetGetActualType(net); @@ -1082,7 +1082,7 @@ cleanup:
virDomainNetRemoveHostdev(vm->def, net);
- networkReleaseActualDevice(net); + networkReleaseActualDevice(vm->def, net); }
VIR_FREE(nicstr); @@ -2017,7 +2017,7 @@ qemuDomainChangeNet(virQEMUDriverPtr driver, * free it if we fail for any reason */ if (newdev->type == VIR_DOMAIN_NET_TYPE_NETWORK && - networkAllocateActualDevice(newdev) < 0) { + networkAllocateActualDevice(vm->def, newdev) < 0) { goto cleanup; }
@@ -2204,7 +2204,7 @@ qemuDomainChangeNet(virQEMUDriverPtr driver,
/* this function doesn't work with HOSTDEV networks yet, thus * no need to change the pointer in the hostdev structure */ - networkReleaseActualDevice(olddev); + networkReleaseActualDevice(vm->def, olddev); virDomainNetDefFree(olddev); /* move newdev into the nets list, and NULL it out from the * virDomainDeviceDef that we were given so that the caller @@ -2236,7 +2236,7 @@ cleanup: * replace the entire device object. */ if (newdev) - networkReleaseActualDevice(newdev); + networkReleaseActualDevice(vm->def, newdev);
return ret; } @@ -2649,7 +2649,7 @@ qemuDomainRemoveHostDevice(virQEMUDriverPtr driver, virDomainHostdevDefFree(hostdev);
if (net) { - networkReleaseActualDevice(net); + networkReleaseActualDevice(vm->def, net); virDomainNetDefFree(net); } virObjectUnref(cfg); @@ -2717,7 +2717,7 @@ qemuDomainRemoveNetDevice(virQEMUDriverPtr driver, virDomainNetGetActualBridgeName(net), net->ifname));
- networkReleaseActualDevice(net); + networkReleaseActualDevice(vm->def, net); virDomainNetDefFree(net); virObjectUnref(cfg); } diff --git a/src/qemu/qemu_process.c b/src/qemu/qemu_process.c index 33d2a77..ffa939a 100644 --- a/src/qemu/qemu_process.c +++ b/src/qemu/qemu_process.c @@ -2773,7 +2773,7 @@ qemuProcessNotifyNets(virDomainDefPtr def)
for (i = 0; i < def->nnets; i++) { virDomainNetDefPtr net = def->nets[i]; - if (networkNotifyActualDevice(net) < 0) + if (networkNotifyActualDevice(def, net) < 0) return -1; } return 0; @@ -4393,7 +4393,7 @@ void qemuProcessStop(virQEMUDriverPtr driver,
/* kick the device out of the hostdev list too */ virDomainNetRemoveHostdev(def, net); - networkReleaseActualDevice(net); + networkReleaseActualDevice(vm->def, net); }
retry: diff --git a/src/util/virhook.c b/src/util/virhook.c index 159efdb..0f5d0c5 100644 --- a/src/util/virhook.c +++ b/src/util/virhook.c @@ -48,12 +48,14 @@ VIR_ENUM_DECL(virHookDaemonOp) VIR_ENUM_DECL(virHookSubop) VIR_ENUM_DECL(virHookQemuOp) VIR_ENUM_DECL(virHookLxcOp) +VIR_ENUM_DECL(virHookNetworkOp)
VIR_ENUM_IMPL(virHookDriver, VIR_HOOK_DRIVER_LAST, "daemon", "qemu", - "lxc") + "lxc", + "network")
VIR_ENUM_IMPL(virHookDaemonOp, VIR_HOOK_DAEMON_OP_LAST, "start", @@ -83,6 +85,13 @@ VIR_ENUM_IMPL(virHookLxcOp, VIR_HOOK_LXC_OP_LAST, "started", "reconnect")
+VIR_ENUM_IMPL(virHookNetworkOp, VIR_HOOK_NETWORK_OP_LAST, + "start", + "started", + "stopped", + "plugged", + "unplugged") + static int virHooksFound = -1;
/** @@ -246,6 +255,8 @@ virHookCall(int driver, case VIR_HOOK_DRIVER_LXC: opstr = virHookLxcOpTypeToString(op); break; + case VIR_HOOK_DRIVER_NETWORK: + opstr = virHookNetworkOpTypeToString(op); } if (opstr == NULL) { virReportError(VIR_ERR_INTERNAL_ERROR, diff --git a/src/util/virhook.h b/src/util/virhook.h index 96bf4cf..7b09ac5 100644 --- a/src/util/virhook.h +++ b/src/util/virhook.h @@ -30,6 +30,7 @@ enum virHookDriverType { VIR_HOOK_DRIVER_DAEMON = 0, /* Daemon related events */ VIR_HOOK_DRIVER_QEMU, /* QEmu domains related events */ VIR_HOOK_DRIVER_LXC, /* LXC domains related events */ + VIR_HOOK_DRIVER_NETWORK, /* network related events */
VIR_HOOK_DRIVER_LAST, }; @@ -74,6 +75,16 @@ enum virHookLxcOpType { VIR_HOOK_LXC_OP_LAST, };
+enum virHookNetworkOpType { + VIR_HOOK_NETWORK_OP_START, /* network is about to start */ + VIR_HOOK_NETWORK_OP_STARTED, /* network has start */ + VIR_HOOK_NETWORK_OP_STOPPED, /* network has stopped */ + VIR_HOOK_NETWORK_OP_IFACE_PLUGGED, /* an interface has been plugged into the network */ + VIR_HOOK_NETWORK_OP_IFACE_UNPLUGGED, /* an interface was unplugged from the network */ + + VIR_HOOK_NETWORK_OP_LAST, +}; + int virHookInitialize(void);
int virHookPresent(int driver);
It all looks fine (aside from the few small grammar corrections in the docs. I just want one last confirmation that we don't want both "plug" and "plugged" events, and in that case ACK.

On 02/12/2014 08:20 PM, Laine Stump wrote:
It all looks fine (aside from the few small grammar corrections in the docs. I just want one last confirmation that we don't want both "plug" and "plugged" events, and in that case ACK.
A few marginally related things came to my mind since I sent that reply, leading me to rethink my conditional ACK: 1) ==================================================== I looked back through the review comments of previous versions, and see that I mis-remembered about anyone objecting to both a "plug" and a "plugged" hook. I wasn't really thinking in terms of the "plug" hook being able to refuse plugging in an interface, but more that there my be some operations that must be performed prior to allocating the tap/macvtap device, and also that it makes it more consistent with the other hook groups (domains and networks both have "start" "started" "stopped", so to me it seems consistent for a network interface to have "plug" "plugged" "unplugged"). Daniel - care to reel in my over-ambitious spirit here? :-) 2) ==================================================== Beyond that, I was thinking about this last night as I fell asleep and realized that in these plug hooks, there is no simple way to determine which interface of the domain is being plugged/unplugged - we are sending the full domain XML and full network XML, but if the domain has multiple interfaces we can't easily figure out which is the one we're plugging (and as a matter of fact, due to the way qemuDomainAttachNetDevice() is organized, the new device being plugged in will not yet be in the domain XML *at all* at the time the "plugged" hook is called![*]) So I think we need to add the specific <interface> to the XML sent to the hook, i.e.: <hookData> <interface> ... </interface> <network> ... </network> <domain> ... </domain> <hookData> (Putting the lone <interface> first will make it simpler for luddites like me to just grep for the first occurence of "<mac address" on stdin rather than firing up a full-fledged XML parser.) ([*] I'm not certain yet if we should also be modifying qemuDomainAttachNetDevice() to add the new <interface> to the domain prior to calling networkAllocateActualDevice(), and then remove it afterwards if there is a failure, or if it's okay to post-add it after everything is successful.) 3) ==================================================== ANNNNDDDD.... I've also just realized that it may finally be time to make public what has up to now been something only stored in the domain's *status* kept on disk for internal use (and never returned in virDomainGetXMLDesc(), thus not in the public API or documentation): the "<actual>" element of an interface, which contains information such as what type of interface it *really* is, which physdev a macvtap interface is connected to, and what bandwidth was actually selected based on merging the <interface> config + any <portgroup> that the interface may belong to. Alternately, perhaps when we format status for public consumption, the <actual> element should just be merged up to its parent <interface>. As an example of what I'm talking about, let's say that you have the following network: [A] <network> <name>testnet</network> <forward mode='bridge' dev='eth0'> <interface dev='eth0'/> <interface dev='eth1'/> <interface dev='eth2'/> </forward> <portgroup name='Bob'> <bandwidth> <inbound average='1000' peak='5000' floor='200' burst='1024'/> <outbound average='128' peak='256' burst='256'/> </bandwidth> </portgroup> </network> and then you have the following interface in a domain: [B] <interface type='network'> <source network='testnet' portgroup='Bob'/> <model type='virtio'/> <mac address='52:54:00:11:22:33'/> </interface> Once you've allocated the actual device, this is what's stored in the status file: [C] <interface type='network'> <source network='testnet' portgroup='Bob'/> <model type='virtio'/> <mac address='52:54:00:11:22:33'/> <actual type='direct'> <source dev='eth1' mode='bridge'/> <bandwidth> <inbound average='1000' peak='5000' floor='200' burst='1024'/> <outbound average='128' peak='256' burst='256'/> </bandwidth> </actual> </interface> (note that the <actual> element is, as previously stated, *never* returned from any public API, it is only stored internally so that we can correctly reconstruct state when libvirtd is restarted) Now, you may ask why the actual data is stored in a subelement rather than just storing it merged into <interface> directly, and that would be a good question! The reason is that if we did that, we would lose information. Here's what happens when you merge: [D] <interface type='direct'> <source dev='eth1' mode='bridge'/> <bandwidth> <inbound average='1000' peak='5000' floor='200' burst='1024'/> <outbound average='128' peak='256' burst='256'/> </bandwidth> <model type='virtio'/> <mac address='52:54:00:11:22:33'/> </network> If we now restarted libvirtd, we would be missing the fact that this interface was allocated from a network, and that it thus must be returned to the network when the guest terminates. You might think that would be okay, since the persistent config contains that information, BUT if it's a transient domain, then there is no persistent config. HOWEVER (I know, this is getting much too long!), I didn't want to publish <actual> in the API, since once that is done, it's done forever, and I've always hoped to figure out a way around it before someone really needed the information. So, where was I? Oh - so now somebody *does* need this "actual" information, meaning it's time to figure out the correct fix. How about this (I would do it as a separate patch from your hooks patch series): 1) internally and in the status file, we continue storing the "actual" data just as we have been. 2) externally (both in the virDomainGetXMLDesc() API and in the network and domain hooks) we "merge" the <actual> element up to <interface> level, as I did in [D] above. This would mean that those looking externally at status (vir virDomainGetXMLDesc() or a hook script) wouldn't see that an interface was really allocated from a network or that the bandwidth came from a portgroup, but it would have the advantage of giving them exact info about what resources the guest is using (and more importantly, that information would always be in the same place, regardless of whether the interface was setup directly with <interface type='direct|bridge|hostdev'> or indirectly with <interface type='network'> I suppose we could go beyond that and add some tags to indicate the origins of the items in question, but I don't know if that's necessary (and, again, once we put it in, we would have to keep it, even if we later decided it was a bad idea).

On 13.02.2014 12:10, Laine Stump wrote:
On 02/12/2014 08:20 PM, Laine Stump wrote:
It all looks fine (aside from the few small grammar corrections in the docs. I just want one last confirmation that we don't want both "plug" and "plugged" events, and in that case ACK.
A few marginally related things came to my mind since I sent that reply, leading me to rethink my conditional ACK:
1) ====================================================
I looked back through the review comments of previous versions, and see that I mis-remembered about anyone objecting to both a "plug" and a "plugged" hook. I wasn't really thinking in terms of the "plug" hook being able to refuse plugging in an interface, but more that there my be some operations that must be performed prior to allocating the tap/macvtap device, and also that it makes it more consistent with the other hook groups (domains and networks both have "start" "started" "stopped", so to me it seems consistent for a network interface to have "plug" "plugged" "unplugged"). Daniel - care to reel in my over-ambitious spirit here? :-)
Okay, we can ignore the hook return value in (un-)plug case. I don't really care.
2) ====================================================
Beyond that, I was thinking about this last night as I fell asleep and realized that in these plug hooks, there is no simple way to determine which interface of the domain is being plugged/unplugged - we are sending the full domain XML and full network XML, but if the domain has multiple interfaces we can't easily figure out which is the one we're plugging (and as a matter of fact, due to the way qemuDomainAttachNetDevice() is organized, the new device being plugged in will not yet be in the domain XML *at all* at the time the "plugged" hook is called![*])
So I think we need to add the specific <interface> to the XML sent to the hook, i.e.:
<hookData> <interface> ... </interface> <network> ... </network> <domain> ... </domain> <hookData>
(Putting the lone <interface> first will make it simpler for luddites like me to just grep for the first occurence of "<mac address" on stdin rather than firing up a full-fledged XML parser.)
Well, the hook script is now fed with XML - we can do whatever we want - even after this is pushed :)
([*] I'm not certain yet if we should also be modifying qemuDomainAttachNetDevice() to add the new <interface> to the domain prior to calling networkAllocateActualDevice(), and then remove it afterwards if there is a failure, or if it's okay to post-add it after everything is successful.)
3) ====================================================
ANNNNDDDD.... I've also just realized that it may finally be time to make public what has up to now been something only stored in the domain's *status* kept on disk for internal use (and never returned in virDomainGetXMLDesc(), thus not in the public API or documentation): the "<actual>" element of an interface, which contains information such as what type of interface it *really* is, which physdev a macvtap interface is connected to, and what bandwidth was actually selected based on merging the <interface> config + any <portgroup> that the interface may belong to. Alternately, perhaps when we format status for public consumption, the <actual> element should just be merged up to its parent <interface>. As an example of what I'm talking about, let's say that you have the following network:
[A] <network> <name>testnet</network> <forward mode='bridge' dev='eth0'> <interface dev='eth0'/> <interface dev='eth1'/> <interface dev='eth2'/> </forward> <portgroup name='Bob'> <bandwidth> <inbound average='1000' peak='5000' floor='200' burst='1024'/> <outbound average='128' peak='256' burst='256'/> </bandwidth> </portgroup> </network>
and then you have the following interface in a domain:
[B] <interface type='network'> <source network='testnet' portgroup='Bob'/> <model type='virtio'/> <mac address='52:54:00:11:22:33'/> </interface>
Once you've allocated the actual device, this is what's stored in the status file:
[C] <interface type='network'> <source network='testnet' portgroup='Bob'/> <model type='virtio'/> <mac address='52:54:00:11:22:33'/> <actual type='direct'> <source dev='eth1' mode='bridge'/> <bandwidth> <inbound average='1000' peak='5000' floor='200' burst='1024'/> <outbound average='128' peak='256' burst='256'/> </bandwidth> </actual> </interface>
(note that the <actual> element is, as previously stated, *never* returned from any public API, it is only stored internally so that we can correctly reconstruct state when libvirtd is restarted)
Now, you may ask why the actual data is stored in a subelement rather than just storing it merged into <interface> directly, and that would be a good question! The reason is that if we did that, we would lose information. Here's what happens when you merge:
[D] <interface type='direct'> <source dev='eth1' mode='bridge'/> <bandwidth> <inbound average='1000' peak='5000' floor='200' burst='1024'/> <outbound average='128' peak='256' burst='256'/> </bandwidth> <model type='virtio'/> <mac address='52:54:00:11:22:33'/> </network>
If we now restarted libvirtd, we would be missing the fact that this interface was allocated from a network, and that it thus must be returned to the network when the guest terminates. You might think that would be okay, since the persistent config contains that information, BUT if it's a transient domain, then there is no persistent config.
HOWEVER (I know, this is getting much too long!), I didn't want to publish <actual> in the API, since once that is done, it's done forever, and I've always hoped to figure out a way around it before someone really needed the information.
So, where was I? Oh - so now somebody *does* need this "actual" information, meaning it's time to figure out the correct fix. How about this (I would do it as a separate patch from your hooks patch series):
1) internally and in the status file, we continue storing the "actual" data just as we have been.
2) externally (both in the virDomainGetXMLDesc() API and in the network and domain hooks) we "merge" the <actual> element up to <interface> level, as I did in [D] above.
This would mean that those looking externally at status (vir virDomainGetXMLDesc() or a hook script) wouldn't see that an interface was really allocated from a network or that the bandwidth came from a portgroup, but it would have the advantage of giving them exact info about what resources the guest is using (and more importantly, that information would always be in the same place, regardless of whether the interface was setup directly with <interface type='direct|bridge|hostdev'> or indirectly with <interface type='network'>
I suppose we could go beyond that and add some tags to indicate the origins of the items in question, but I don't know if that's necessary (and, again, once we put it in, we would have to keep it, even if we later decided it was a bad idea).
Your approach seems reasonable. But then again, it's XML what we are passing to the hook script. <domain/>, <network/> even <hookData/> can be extended anytime. We can do that later if we please. Or if somebody turns out requesting such behavior. Frankly, with every round of the patches it's getting more and more complicated. I'm surely in for doing the right thing, but we can do the necessary now and enhance later if somebody with real use case shows up. Michal

On 02/13/2014 03:17 PM, Michal Privoznik wrote:
On 13.02.2014 12:10, Laine Stump wrote:
On 02/12/2014 08:20 PM, Laine Stump wrote:
It all looks fine (aside from the few small grammar corrections in the docs. I just want one last confirmation that we don't want both "plug" and "plugged" events, and in that case ACK.
A few marginally related things came to my mind since I sent that reply, leading me to rethink my conditional ACK:
1) ====================================================
I looked back through the review comments of previous versions, and see that I mis-remembered about anyone objecting to both a "plug" and a "plugged" hook. I wasn't really thinking in terms of the "plug" hook being able to refuse plugging in an interface, but more that there my be some operations that must be performed prior to allocating the tap/macvtap device, and also that it makes it more consistent with the other hook groups (domains and networks both have "start" "started" "stopped", so to me it seems consistent for a network interface to have "plug" "plugged" "unplugged"). Daniel - care to reel in my over-ambitious spirit here? :-)
Okay, we can ignore the hook return value in (un-)plug case. I don't really care.
(Actually, I hadn't said that I thought it was a bad idea to check the return value, only that I hadn't thought of it :-)
2) ====================================================
Beyond that, I was thinking about this last night as I fell asleep and realized that in these plug hooks, there is no simple way to determine which interface of the domain is being plugged/unplugged - we are sending the full domain XML and full network XML, but if the domain has multiple interfaces we can't easily figure out which is the one we're plugging (and as a matter of fact, due to the way qemuDomainAttachNetDevice() is organized, the new device being plugged in will not yet be in the domain XML *at all* at the time the "plugged" hook is called![*])
So I think we need to add the specific <interface> to the XML sent to the hook, i.e.:
<hookData> <interface> ... </interface> <network> ... </network> <domain> ... </domain> <hookData>
(Putting the lone <interface> first will make it simpler for luddites like me to just grep for the first occurence of "<mac address" on stdin rather than firing up a full-fledged XML parser.)
Well, the hook script is now fed with XML - we can do whatever we want - even after this is pushed :)
Okay, since my main objection is the lack of XML for the specific interface in the "plugged" event, and since the interface XML anyway wouldn't be useful until I fix it to contain the actual allocated device info (as described in the remainder of my earlier message), I say ACK to this patch (with the couple of grammar fixes I'd mentioned earlier), so the entire series is now ACKed. After you've pushed these patches, I will fix the live <interface> XML (I'm working on that already, and when it is fixed I'll add it to the XML that is sent for the plugged and unplugged hooks.

On 18.02.2014 12:20, Laine Stump wrote:
On 02/13/2014 03:17 PM, Michal Privoznik wrote:
On 13.02.2014 12:10, Laine Stump wrote:
On 02/12/2014 08:20 PM, Laine Stump wrote:
It all looks fine (aside from the few small grammar corrections in the docs. I just want one last confirmation that we don't want both "plug" and "plugged" events, and in that case ACK.
A few marginally related things came to my mind since I sent that reply, leading me to rethink my conditional ACK:
1) ====================================================
I looked back through the review comments of previous versions, and see that I mis-remembered about anyone objecting to both a "plug" and a "plugged" hook. I wasn't really thinking in terms of the "plug" hook being able to refuse plugging in an interface, but more that there my be some operations that must be performed prior to allocating the tap/macvtap device, and also that it makes it more consistent with the other hook groups (domains and networks both have "start" "started" "stopped", so to me it seems consistent for a network interface to have "plug" "plugged" "unplugged"). Daniel - care to reel in my over-ambitious spirit here? :-)
Okay, we can ignore the hook return value in (un-)plug case. I don't really care.
(Actually, I hadn't said that I thought it was a bad idea to check the return value, only that I hadn't thought of it :-)
2) ====================================================
Beyond that, I was thinking about this last night as I fell asleep and realized that in these plug hooks, there is no simple way to determine which interface of the domain is being plugged/unplugged - we are sending the full domain XML and full network XML, but if the domain has multiple interfaces we can't easily figure out which is the one we're plugging (and as a matter of fact, due to the way qemuDomainAttachNetDevice() is organized, the new device being plugged in will not yet be in the domain XML *at all* at the time the "plugged" hook is called![*])
So I think we need to add the specific <interface> to the XML sent to the hook, i.e.:
<hookData> <interface> ... </interface> <network> ... </network> <domain> ... </domain> <hookData>
(Putting the lone <interface> first will make it simpler for luddites like me to just grep for the first occurence of "<mac address" on stdin rather than firing up a full-fledged XML parser.)
Well, the hook script is now fed with XML - we can do whatever we want - even after this is pushed :)
Okay, since my main objection is the lack of XML for the specific interface in the "plugged" event, and since the interface XML anyway wouldn't be useful until I fix it to contain the actual allocated device info (as described in the remainder of my earlier message), I say ACK to this patch (with the couple of grammar fixes I'd mentioned earlier), so the entire series is now ACKed.
After you've pushed these patches, I will fix the live <interface> XML (I'm working on that already, and when it is fixed I'll add it to the XML that is sent for the plugged and unplugged hooks.
Okay, I've pushed this. Thanks. Michal

Basically, the idea is copied from domain code, where tainting exists for a while. Currently, only one taint reason exists - VIR_NETWORK_TAINT_HOOK to mark those networks which caused invoking of hook script. Signed-off-by: Michal Privoznik <mprivozn@redhat.com> --- src/conf/network_conf.c | 52 ++++++++++++++++++++++++++++++++++++++++++++- src/conf/network_conf.h | 17 +++++++++++++++ src/libvirt_private.syms | 3 +++ src/network/bridge_driver.c | 20 +++++++++++++++++ 4 files changed, 91 insertions(+), 1 deletion(-) diff --git a/src/conf/network_conf.c b/src/conf/network_conf.c index 8b6236d..bac0465 100644 --- a/src/conf/network_conf.c +++ b/src/conf/network_conf.c @@ -72,6 +72,22 @@ VIR_ENUM_IMPL(virNetworkDNSForwardPlainNames, "yes", "no") +VIR_ENUM_IMPL(virNetworkTaint, VIR_NETWORK_TAINT_LAST, + "hook-script"); + +bool +virNetworkObjTaint(virNetworkObjPtr obj, + enum virNetworkTaintFlags taint) +{ + unsigned int flag = (1 << taint); + + if (obj->taint & flag) + return false; + + obj->taint |= flag; + return true; +} + virNetworkObjPtr virNetworkFindByUUID(virNetworkObjListPtr nets, const unsigned char *uuid) { @@ -2784,6 +2800,7 @@ virNetworkObjFormat(virNetworkObjPtr net, { virBuffer buf = VIR_BUFFER_INITIALIZER; char *class_id = virBitmapFormat(net->class_id); + size_t i; if (!class_id) goto no_memory; @@ -2793,6 +2810,12 @@ virNetworkObjFormat(virNetworkObjPtr net, virBufferAsprintf(&buf, " <floor sum='%llu'/>\n", net->floor_sum); VIR_FREE(class_id); + for (i = 0; i < VIR_NETWORK_TAINT_LAST; i++) { + if (net->taint & (1 << i)) + virBufferAsprintf(&buf, " <taint flag='%s'/>\n", + virNetworkTaintTypeToString(i)); + } + virBufferAdjustIndent(&buf, 2); if (virNetworkDefFormatBuf(&buf, net->def, flags) < 0) goto error; @@ -2903,10 +2926,13 @@ virNetworkLoadState(virNetworkObjListPtr nets, virNetworkDefPtr def = NULL; virNetworkObjPtr net = NULL; xmlDocPtr xml = NULL; - xmlNodePtr node = NULL; + xmlNodePtr node = NULL, *nodes = NULL; xmlXPathContextPtr ctxt = NULL; virBitmapPtr class_id_map = NULL; unsigned long long floor_sum_val = 0; + unsigned int taint = 0; + int n; + size_t i; if ((configFile = virNetworkConfigFile(stateDir, name)) == NULL) @@ -2962,6 +2988,27 @@ virNetworkLoadState(virNetworkObjListPtr nets, goto error; } VIR_FREE(floor_sum); + + if ((n = virXPathNodeSet("./taint", ctxt, &nodes)) < 0) + goto error; + + for (i = 0; i < n; i++) { + char *str = virXMLPropString(nodes[i], "flag"); + if (str) { + int flag = virNetworkTaintTypeFromString(str); + if (flag < 0) { + virReportError(VIR_ERR_CONFIG_UNSUPPORTED, + _("Unknown taint flag %s"), str); + VIR_FREE(str); + goto error; + } + VIR_FREE(str); + /* Compute taint mask here. The network object does not + * exist yet, so we can't use virNetworkObjtTaint. */ + taint |= (1 << flag); + } + } + VIR_FREE(nodes); } /* create the object */ @@ -2978,6 +3025,8 @@ virNetworkLoadState(virNetworkObjListPtr nets, if (floor_sum_val > 0) net->floor_sum = floor_sum_val; + net->taint = taint; + cleanup: VIR_FREE(configFile); xmlFreeDoc(xml); @@ -2985,6 +3034,7 @@ cleanup: return net; error: + VIR_FREE(nodes); virBitmapFree(class_id_map); virNetworkDefFree(def); goto cleanup; diff --git a/src/conf/network_conf.h b/src/conf/network_conf.h index 47124ce..3abe180 100644 --- a/src/conf/network_conf.h +++ b/src/conf/network_conf.h @@ -287,6 +287,8 @@ struct _virNetworkObj { virBitmapPtr class_id; /* bitmap of class IDs for QoS */ unsigned long long floor_sum; /* sum of all 'floor'-s of attached NICs */ + + unsigned int taint; }; typedef struct _virNetworkObjList virNetworkObjList; @@ -296,12 +298,26 @@ struct _virNetworkObjList { virNetworkObjPtr *objs; }; +enum virNetworkTaintFlags { + VIR_NETWORK_TAINT_HOOK, /* Hook script was executed over + network. We can't guarantee + connectivity or other settings + as the script may have played + with iptables, tc, you name it. + */ + + VIR_NETWORK_TAINT_LAST +}; + static inline int virNetworkObjIsActive(const virNetworkObj *net) { return net->active; } +bool virNetworkObjTaint(virNetworkObjPtr obj, + enum virNetworkTaintFlags taint); + virNetworkObjPtr virNetworkFindByUUID(virNetworkObjListPtr nets, const unsigned char *uuid); virNetworkObjPtr virNetworkFindByName(virNetworkObjListPtr nets, @@ -455,4 +471,5 @@ virNetworkDefUpdateSection(virNetworkDefPtr def, const char *xml, unsigned int flags); /* virNetworkUpdateFlags */ +VIR_ENUM_DECL(virNetworkTaint) #endif /* __NETWORK_CONF_H__ */ diff --git a/src/libvirt_private.syms b/src/libvirt_private.syms index b554f11..ab4be02 100644 --- a/src/libvirt_private.syms +++ b/src/libvirt_private.syms @@ -528,6 +528,7 @@ virNetworkObjListFree; virNetworkObjLock; virNetworkObjReplacePersistentDef; virNetworkObjSetDefTransient; +virNetworkObjTaint; virNetworkObjUnlock; virNetworkObjUnsetDefTransient; virNetworkObjUpdate; @@ -536,6 +537,8 @@ virNetworkSaveConfig; virNetworkSaveStatus; virNetworkSetBridgeMacAddr; virNetworkSetBridgeName; +virNetworkTaintTypeFromString; +virNetworkTaintTypeToString; virPortGroupFindByName; diff --git a/src/network/bridge_driver.c b/src/network/bridge_driver.c index 6a2d56a..ee264b9 100644 --- a/src/network/bridge_driver.c +++ b/src/network/bridge_driver.c @@ -112,6 +112,9 @@ static int networkPlugBandwidth(virNetworkObjPtr net, static int networkUnplugBandwidth(virNetworkObjPtr net, virDomainNetDefPtr iface); +static void networkNetworkObjTaint(virNetworkObjPtr net, + enum virNetworkTaintFlags taint); + static virNetworkDriverStatePtr driverState = NULL; static virNetworkObjPtr @@ -169,6 +172,8 @@ networkRunHook(virNetworkObjPtr network, */ if (hookret < 0) goto cleanup; + + networkNetworkObjTaint(network, VIR_NETWORK_TAINT_HOOK); } ret = 0; @@ -4344,3 +4349,18 @@ networkUnplugBandwidth(virNetworkObjPtr net, cleanup: return ret; } + +static void +networkNetworkObjTaint(virNetworkObjPtr net, + enum virNetworkTaintFlags taint) +{ + if (virNetworkObjTaint(net, taint)) { + char uuidstr[VIR_UUID_STRING_BUFLEN]; + virUUIDFormat(net->def->uuid, uuidstr); + + VIR_WARN("Network name='%s' uuid=%s is tainted: %s", + net->def->name, + uuidstr, + virNetworkTaintTypeToString(taint)); + } +} -- 1.8.5.3

On 02/12/2014 07:07 PM, Michal Privoznik wrote:
Basically, the idea is copied from domain code, where tainting exists for a while. Currently, only one taint reason exists - VIR_NETWORK_TAINT_HOOK to mark those networks which caused invoking of hook script.
Signed-off-by: Michal Privoznik <mprivozn@redhat.com> --- src/conf/network_conf.c | 52 ++++++++++++++++++++++++++++++++++++++++++++- src/conf/network_conf.h | 17 +++++++++++++++ src/libvirt_private.syms | 3 +++ src/network/bridge_driver.c | 20 +++++++++++++++++ 4 files changed, 91 insertions(+), 1 deletion(-)
diff --git a/src/conf/network_conf.c b/src/conf/network_conf.c index 8b6236d..bac0465 100644 --- a/src/conf/network_conf.c +++ b/src/conf/network_conf.c @@ -72,6 +72,22 @@ VIR_ENUM_IMPL(virNetworkDNSForwardPlainNames, "yes", "no")
+VIR_ENUM_IMPL(virNetworkTaint, VIR_NETWORK_TAINT_LAST, + "hook-script"); + +bool +virNetworkObjTaint(virNetworkObjPtr obj, + enum virNetworkTaintFlags taint) +{ + unsigned int flag = (1 << taint); + + if (obj->taint & flag) + return false; + + obj->taint |= flag; + return true; +} + virNetworkObjPtr virNetworkFindByUUID(virNetworkObjListPtr nets, const unsigned char *uuid) { @@ -2784,6 +2800,7 @@ virNetworkObjFormat(virNetworkObjPtr net, { virBuffer buf = VIR_BUFFER_INITIALIZER; char *class_id = virBitmapFormat(net->class_id); + size_t i;
if (!class_id) goto no_memory; @@ -2793,6 +2810,12 @@ virNetworkObjFormat(virNetworkObjPtr net, virBufferAsprintf(&buf, " <floor sum='%llu'/>\n", net->floor_sum); VIR_FREE(class_id);
+ for (i = 0; i < VIR_NETWORK_TAINT_LAST; i++) { + if (net->taint & (1 << i)) + virBufferAsprintf(&buf, " <taint flag='%s'/>\n", + virNetworkTaintTypeToString(i)); + } + virBufferAdjustIndent(&buf, 2); if (virNetworkDefFormatBuf(&buf, net->def, flags) < 0) goto error; @@ -2903,10 +2926,13 @@ virNetworkLoadState(virNetworkObjListPtr nets, virNetworkDefPtr def = NULL; virNetworkObjPtr net = NULL; xmlDocPtr xml = NULL; - xmlNodePtr node = NULL; + xmlNodePtr node = NULL, *nodes = NULL; xmlXPathContextPtr ctxt = NULL; virBitmapPtr class_id_map = NULL; unsigned long long floor_sum_val = 0; + unsigned int taint = 0; + int n; + size_t i;
if ((configFile = virNetworkConfigFile(stateDir, name)) == NULL) @@ -2962,6 +2988,27 @@ virNetworkLoadState(virNetworkObjListPtr nets, goto error; } VIR_FREE(floor_sum); + + if ((n = virXPathNodeSet("./taint", ctxt, &nodes)) < 0) + goto error; + + for (i = 0; i < n; i++) { + char *str = virXMLPropString(nodes[i], "flag"); + if (str) { + int flag = virNetworkTaintTypeFromString(str); + if (flag < 0) { + virReportError(VIR_ERR_CONFIG_UNSUPPORTED, + _("Unknown taint flag %s"), str); + VIR_FREE(str); + goto error; + } + VIR_FREE(str); + /* Compute taint mask here. The network object does not + * exist yet, so we can't use virNetworkObjtTaint. */ + taint |= (1 << flag); + } + } + VIR_FREE(nodes); }
/* create the object */ @@ -2978,6 +3025,8 @@ virNetworkLoadState(virNetworkObjListPtr nets, if (floor_sum_val > 0) net->floor_sum = floor_sum_val;
+ net->taint = taint; + cleanup: VIR_FREE(configFile); xmlFreeDoc(xml); @@ -2985,6 +3034,7 @@ cleanup: return net;
error: + VIR_FREE(nodes); virBitmapFree(class_id_map); virNetworkDefFree(def); goto cleanup; diff --git a/src/conf/network_conf.h b/src/conf/network_conf.h index 47124ce..3abe180 100644 --- a/src/conf/network_conf.h +++ b/src/conf/network_conf.h @@ -287,6 +287,8 @@ struct _virNetworkObj {
virBitmapPtr class_id; /* bitmap of class IDs for QoS */ unsigned long long floor_sum; /* sum of all 'floor'-s of attached NICs */ + + unsigned int taint; };
typedef struct _virNetworkObjList virNetworkObjList; @@ -296,12 +298,26 @@ struct _virNetworkObjList { virNetworkObjPtr *objs; };
+enum virNetworkTaintFlags { + VIR_NETWORK_TAINT_HOOK, /* Hook script was executed over + network. We can't guarantee + connectivity or other settings + as the script may have played + with iptables, tc, you name it. + */ + + VIR_NETWORK_TAINT_LAST +}; + static inline int virNetworkObjIsActive(const virNetworkObj *net) { return net->active; }
+bool virNetworkObjTaint(virNetworkObjPtr obj, + enum virNetworkTaintFlags taint); + virNetworkObjPtr virNetworkFindByUUID(virNetworkObjListPtr nets, const unsigned char *uuid); virNetworkObjPtr virNetworkFindByName(virNetworkObjListPtr nets, @@ -455,4 +471,5 @@ virNetworkDefUpdateSection(virNetworkDefPtr def, const char *xml, unsigned int flags); /* virNetworkUpdateFlags */
+VIR_ENUM_DECL(virNetworkTaint) #endif /* __NETWORK_CONF_H__ */ diff --git a/src/libvirt_private.syms b/src/libvirt_private.syms index b554f11..ab4be02 100644 --- a/src/libvirt_private.syms +++ b/src/libvirt_private.syms @@ -528,6 +528,7 @@ virNetworkObjListFree; virNetworkObjLock; virNetworkObjReplacePersistentDef; virNetworkObjSetDefTransient; +virNetworkObjTaint; virNetworkObjUnlock; virNetworkObjUnsetDefTransient; virNetworkObjUpdate; @@ -536,6 +537,8 @@ virNetworkSaveConfig; virNetworkSaveStatus; virNetworkSetBridgeMacAddr; virNetworkSetBridgeName; +virNetworkTaintTypeFromString; +virNetworkTaintTypeToString; virPortGroupFindByName;
diff --git a/src/network/bridge_driver.c b/src/network/bridge_driver.c index 6a2d56a..ee264b9 100644 --- a/src/network/bridge_driver.c +++ b/src/network/bridge_driver.c @@ -112,6 +112,9 @@ static int networkPlugBandwidth(virNetworkObjPtr net, static int networkUnplugBandwidth(virNetworkObjPtr net, virDomainNetDefPtr iface);
+static void networkNetworkObjTaint(virNetworkObjPtr net, + enum virNetworkTaintFlags taint); + static virNetworkDriverStatePtr driverState = NULL;
static virNetworkObjPtr @@ -169,6 +172,8 @@ networkRunHook(virNetworkObjPtr network, */ if (hookret < 0) goto cleanup; + + networkNetworkObjTaint(network, VIR_NETWORK_TAINT_HOOK); }
ret = 0; @@ -4344,3 +4349,18 @@ networkUnplugBandwidth(virNetworkObjPtr net, cleanup: return ret; } + +static void +networkNetworkObjTaint(virNetworkObjPtr net, + enum virNetworkTaintFlags taint) +{ + if (virNetworkObjTaint(net, taint)) { + char uuidstr[VIR_UUID_STRING_BUFLEN]; + virUUIDFormat(net->def->uuid, uuidstr); + + VIR_WARN("Network name='%s' uuid=%s is tainted: %s", + net->def->name, + uuidstr, + virNetworkTaintTypeToString(taint)); + } +}
It's occurred to me that if a hook script exists, then all networks on the host will be tainted. That made me wonder how useful this really is... (I guess if nothing else it makes it simpler when debugging someone else's problem - just asking for the output of "virsh net-dumpxml $netname" will tell us whether or not they're running a hook script, rather than relying on them to tell us...). So, okay ACK.
participants (2)
-
Laine Stump
-
Michal Privoznik