diff --git a/docs/hooks.html.in b/docs/hooks.html.in
index f0f692bc0a9f77a519b57228a0bc07f273333bc3..07b9d499862afcbc7942e58c51d20352b3c5bb34 100644
--- a/docs/hooks.html.in
+++ b/docs/hooks.html.in
@@ -13,9 +13,15 @@
actions occur:
- The libvirt daemon starts, stops, or reloads its
- configuration
- - A QEMU guest is started or stopped
- - An LXC guest is started or stopped
+ configuration
+ (since 0.8.0)
+ - A QEMU guest is started or stopped
+ (since 0.8.0)
+ - An LXC guest is started or stopped
+ (since 0.8.0)
+ - A network is started or stopped or an interface is
+ plugged/unplugged to/from the network
+ (since 1.2.2)
@@ -44,6 +50,9 @@
Executed when a QEMU guest is started, stopped, or migrated
/etc/libvirt/hooks/lxc
Executed when an LXC guest is started or stopped
+ /etc/libvirt/hooks/network
+ Executed when a network is started or stopped or an
+ interface is plugged/unplugged to/from the network
@@ -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.
+ For all cases, stdin of the network hook script is provided with the
+ full XML description of the network status in the following form:
+
+<hookData>
+ <network>
+ <name>$network_name</name>
+ <uuid>afca425a-2c3a-420c-b2fb-dd7b4950d722</uuid>
+ ...
+ </network>
+</hookData>
+
+ In the case of an interface
+ being plugged/unplugged to/from the network, the network XML will be
+ followed with the full XML description of the domain containing the
+ interface that is being plugged/unplugged:
+
+<hookData>
+ <network>
+ <name>$network_name</name>
+ <uuid>afca425a-2c3a-420c-b2fb-dd7b4950d722</uuid>
+ ...
+ </network>
+ <domain type='$domain_type' id='$domain_id'>
+ <name>$domain_name</name>
+ <uuid>afca425a-2c3a-420c-b2fb-dd7b4950d722</uuid>
+ ...
+ </domain>
+</hookData>
+
+ Please note that this approach is different from other cases such as
+ daemon
, qemu
or lxc
hook scripts,
+ because two XMLs may be passed here, while in the other cases only a single
+ XML is passed.
The command line arguments take this approach:
@@ -181,25 +223,49 @@
/etc/libvirt/hooks/lxc guest_name reconnect begin -
+
+
+
+ - Since 1.2.2, before a network is started,
+ this script is called as:
+ /etc/libvirt/hooks/network network_name start begin -
+ - After the network is started, up ∧ running, the script is
+ called as:
+ /etc/libvirt/hooks/network network_name started begin -
+ - When a network is shut down, this script is called as:
+ /etc/libvirt/hooks/network network_name stopped end -
+ - Later, when network is started and there's an interface from a
+ domain to be plugged into the network, the hook script is called as:
+ /etc/libvirt/hooks/network network_name plugged begin -
+ Please note, that in this case, the script is passed both network and
+ domain XMLs on its stdin.
+ - When the domain from previous case is shutting down, the interface
+ is unplugged. This leads to another script invocation:
+ /etc/libvirt/hooks/network network_name unplugged begin -
+ And again, as in previous case, both network and domain XMLs are passed
+ onto script's stdin.
+
+
- - The "start" operation for the guest hook scripts, qemu and lxc,
- executes prior to the guest being created. This allows the
- guest start operation to be aborted if the script returns indicating
- failure.
- - The "shutdown" operation for the guest hook scripts, qemu and lxc,
- executes after 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.
+ - The "start" operation for the guest and network hook scripts,
+ executes prior to the object (guest or network) being created.
+ This allows the object start operation to be aborted if the script
+ returns indicating failure.
+ - The "shutdown" operation for the guest and network hook scripts,
+ executes after 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.
+
- Hook scripts execute in a synchronous fashion. Libvirt waits
for them to return before continuing the given operation.
- 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.
+ 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.
- For a hook script to be utilised, it must have its execute bit set
- (ie. chmod o+rx qemu), and must be present when the libvirt
+ (e.g. chmod o+rx qemu), and must be present when the libvirt
daemon is started.
- 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 53db957edf56e314ba2a8bfaf8e950f97dc8aa40..41b86c540a2de5227bf5aa2e8c2e4850e8aa7b63 100644
--- a/src/lxc/lxc_driver.c
+++ b/src/lxc/lxc_driver.c
@@ -3937,7 +3937,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);
@@ -4485,7 +4485,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 ed729f65d4a7614d4044e7e5502c0d4a8cb08681..8989245f64945b7fd92f759a0e8d21ddda4d60bc 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 0964350d3c00d2c84b440e1081ae1ffcef7784bf..6a2d56ae4f249f7fe9972d0ebc87dd27a1487627 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, "\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, "");
+
+ 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 element that previously had its
@@ -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 50258b571f9acd5a35e5f45ce9f1f316b8e07db0..0f8055650e0a66086d4a83baa85e98f3431d6d22 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 bb8756fd4631c30f459857c48d24ea02482b4763..008a64c430cc7d7a52fee4aa2cb651b1c4b551d4 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 7066be69047aadc0e92192a6a2113a70a23b2ce9..6703c92edddfacfb08ee12d46a6bb7ad003c8c7c 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 33d2a776f4ac2667f88268128a35d0a1e1adab3b..ffa939a9962c1a94ea8e1b4e4b5c624924f4a7fc 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 159efdbf4b89cee37a921fb49a19e446c1092c0d..0f5d0c56fbaeb43115598c356e1510868ba45b6f 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 96bf4cf126c672308909d8545c74ba0de4712b92..7b09ac5502fbc94ec9a867afdd15ca8afa3d2b14 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);