diff --git a/src/libvirt_network.syms b/src/libvirt_network.syms index 6be5e45c94..e402b5f283 100644 --- a/src/libvirt_network.syms +++ b/src/libvirt_network.syms @@ -3,4 +3,7 @@ # # bridge_driver.h +networkAllocateActualDevice; networkBuildDhcpDaemonCommandLine; +networkNotifyActualDevice; +networkReleaseActualDevice; diff --git a/src/network/bridge_driver.c b/src/network/bridge_driver.c index 1a1e723e6d..99033a2e04 100644 --- a/src/network/bridge_driver.c +++ b/src/network/bridge_driver.c @@ -2718,3 +2718,375 @@ int networkRegister(void) { virRegisterStateDriver(&networkStateDriver); return 0; } + +/********************************************************/ + +/* Private API to deal with logical switch capabilities. + * These functions are exported so that other parts of libvirt can + * call them, but are not part of the public API and not in the + * driver's function table. If we ever have more than one network + * driver, we will need to present these functions via a second + * "backend" function table. + */ + +/* networkAllocateActualDevice: + * @iface: the original NetDef from the domain + * + * Looks up the network reference by iface, allocates a physical + * device from that network (if appropriate), and returns with the + * virDomainActualNetDef filled in accordingly. If there are no + * changes to be made in the netdef, then just leave the actualdef + * empty. + * + * Returns 0 on success, -1 on failure. + */ +int +networkAllocateActualDevice(virDomainNetDefPtr iface) +{ + struct network_driver *driver = driverState; + virNetworkObjPtr network; + virNetworkDefPtr netdef; + int ret = -1; + + if (iface->type != VIR_DOMAIN_NET_TYPE_NETWORK) + return 0; + + virDomainActualNetDefFree(iface->data.network.actual); + iface->data.network.actual = NULL; + + networkDriverLock(driver); + network = virNetworkFindByName(&driver->networks, iface->data.network.name); + networkDriverUnlock(driver); + if (!network) { + networkReportError(VIR_ERR_NO_NETWORK, + _("no network with matching name '%s'"), + iface->data.network.name); + goto cleanup; + } + + netdef = network->def; + if ((netdef->forwardType == VIR_NETWORK_FORWARD_BRIDGE) && + netdef->bridge) { + + /* + * is VIR_DOMAIN_NET_TYPE_BRIDGE + */ + + if (VIR_ALLOC(iface->data.network.actual) < 0) { + virReportOOMError(); + goto cleanup; + } + + iface->data.network.actual->type = VIR_DOMAIN_NET_TYPE_BRIDGE; + iface->data.network.actual->data.bridge.brname = strdup(netdef->bridge); + if (!iface->data.network.actual->data.bridge.brname) { + virReportOOMError(); + goto cleanup; + } + + } else if ((netdef->forwardType == VIR_NETWORK_FORWARD_BRIDGE) || + (netdef->forwardType == VIR_NETWORK_FORWARD_PRIVATE) || + (netdef->forwardType == VIR_NETWORK_FORWARD_VEPA) || + (netdef->forwardType == VIR_NETWORK_FORWARD_PASSTHROUGH)) { + virVirtualPortProfileParamsPtr virtport = NULL; + + /* are all + * VIR_DOMAIN_NET_TYPE_DIRECT. + */ + + if (VIR_ALLOC(iface->data.network.actual) < 0) { + virReportOOMError(); + goto cleanup; + } + + /* Set type=direct and appropriate */ + iface->data.network.actual->type = VIR_DOMAIN_NET_TYPE_DIRECT; + switch (netdef->forwardType) { + case VIR_NETWORK_FORWARD_BRIDGE: + iface->data.network.actual->data.direct.mode = VIR_MACVTAP_MODE_BRIDGE; + break; + case VIR_NETWORK_FORWARD_PRIVATE: + iface->data.network.actual->data.direct.mode = VIR_MACVTAP_MODE_PRIVATE; + break; + case VIR_NETWORK_FORWARD_VEPA: + iface->data.network.actual->data.direct.mode = VIR_MACVTAP_MODE_VEPA; + break; + case VIR_NETWORK_FORWARD_PASSTHROUGH: + iface->data.network.actual->data.direct.mode = VIR_MACVTAP_MODE_PASSTHRU; + break; + } + + /* Find the most specific virtportprofile and copy it */ + if (iface->data.network.virtPortProfile) { + virtport = iface->data.network.virtPortProfile; + } else { + virPortGroupDefPtr portgroup + = virPortGroupFindByName(netdef, iface->data.network.portgroup); + if (portgroup) + virtport = portgroup->virtPortProfile; + else + virtport = netdef->virtPortProfile; + } + if (virtport) { + if (VIR_ALLOC(iface->data.network.actual->data.direct.virtPortProfile) < 0) { + virReportOOMError(); + goto cleanup; + } + /* There are no pointers in a virtualPortProfile, so a shallow copy + * is sufficient + */ + *iface->data.network.actual->data.direct.virtPortProfile = *virtport; + } + /* If there is only a single device, just return it (caller will detect + * any error if exclusive use is required but could not be acquired). + */ + if (netdef->nForwardIfs == 0) { + networkReportError(VIR_ERR_INTERNAL_ERROR, + _("network '%s' uses a direct mode, but has no forward dev and no interface pool"), + netdef->name); + goto cleanup; + } else { + int ii; + virNetworkForwardIfDefPtr dev = NULL; + + /* pick an interface from the pool */ + + /* PASSTHROUGH mode, and PRIVATE Mode + 802.1Qbh both require + * exclusive access to a device, so current usageCount must be + * 0. Other modes can share, so just search for the one with + * the lowest usageCount. + */ + if ((netdef->forwardType == VIR_NETWORK_FORWARD_PASSTHROUGH) || + ((netdef->forwardType == VIR_NETWORK_FORWARD_PRIVATE) && + iface->data.network.actual->data.direct.virtPortProfile && + (iface->data.network.actual->data.direct.virtPortProfile->virtPortType + == VIR_VIRTUALPORT_8021QBH))) { + /* pick first dev with 0 usageCount */ + + for (ii = 0; ii < netdef->nForwardIfs; ii++) { + if (netdef->forwardIfs[ii].usageCount == 0) { + dev = &netdef->forwardIfs[ii]; + break; + } + } + } else { + /* pick least used dev */ + dev = &netdef->forwardIfs[0]; + for (ii = 1; ii < netdef->nForwardIfs; ii++) { + if (netdef->forwardIfs[ii].usageCount < dev->usageCount) + dev = &netdef->forwardIfs[ii]; + } + } + /* dev points at the physical device we want to use */ + if (!dev) { + networkReportError(VIR_ERR_INTERNAL_ERROR, + _("network '%s' requires exclusive access to interfaces, but none are available"), + netdef->name); + goto cleanup; + } + iface->data.network.actual->data.direct.linkdev = strdup(dev->dev); + if (!iface->data.network.actual->data.direct.linkdev) { + virReportOOMError(); + goto cleanup; + } + /* we are now assured of success, so mark the allocation */ + dev->usageCount++; + VIR_DEBUG("Using physical device %s, usageCount %d", + dev->dev, dev->usageCount); + } + } + + ret = 0; +cleanup: + if (network) + virNetworkObjUnlock(network); + if (ret < 0) { + virDomainActualNetDefFree(iface->data.network.actual); + iface->data.network.actual = NULL; + } + return ret; +} + +/* networkNotifyActualDevice: + * @iface: the domain's NetDef with an "actual" device already filled in. + * + * Called to notify the network driver when libvirtd is restarted and + * finds an already running domain. If appropriate it will force an + * allocation of the actual->direct.linkdev to get everything back in + * order. + * + * Returns 0 on success, -1 on failure. + */ +int +networkNotifyActualDevice(virDomainNetDefPtr iface) +{ + struct network_driver *driver = driverState; + virNetworkObjPtr network; + virNetworkDefPtr netdef; + char *actualDev; + int ret = -1; + + if (iface->type != VIR_DOMAIN_NET_TYPE_NETWORK) + return 0; + + if (!iface->data.network.actual || + (virDomainNetGetActualType(iface) != VIR_DOMAIN_NET_TYPE_DIRECT)) { + VIR_DEBUG("Nothing to claim from network %s", iface->data.network.name); + return 0; + } + + networkDriverLock(driver); + network = virNetworkFindByName(&driver->networks, iface->data.network.name); + networkDriverUnlock(driver); + if (!network) { + networkReportError(VIR_ERR_NO_NETWORK, + _("no network with matching name '%s'"), + iface->data.network.name); + goto cleanup; + } + + actualDev = virDomainNetGetActualDirectDev(iface); + if (!actualDev) { + networkReportError(VIR_ERR_INTERNAL_ERROR, + "%s", _("the interface uses a direct mode, but has no source dev")); + goto cleanup; + } + + netdef = network->def; + if (netdef->nForwardIfs == 0) { + networkReportError(VIR_ERR_INTERNAL_ERROR, + _("network '%s' uses a direct mode, but has no forward dev and no interface pool"), + netdef->name); + goto cleanup; + } else { + int ii; + virNetworkForwardIfDefPtr dev = NULL; + + /* find the matching interface in the pool and increment its usageCount */ + + for (ii = 0; ii < netdef->nForwardIfs; ii++) { + if (STREQ(actualDev, netdef->forwardIfs[ii].dev)) { + dev = &netdef->forwardIfs[ii]; + break; + } + } + /* dev points at the physical device we want to use */ + if (!dev) { + networkReportError(VIR_ERR_INTERNAL_ERROR, + _("network '%s' doesn't have dev='%s' in use by domain"), + netdef->name, actualDev); + goto cleanup; + } + + /* PASSTHROUGH mode, and PRIVATE Mode + 802.1Qbh both require + * exclusive access to a device, so current usageCount must be + * 0 in those cases. + */ + if ((dev->usageCount > 0) && + ((netdef->forwardType == VIR_NETWORK_FORWARD_PASSTHROUGH) || + ((netdef->forwardType == VIR_NETWORK_FORWARD_PRIVATE) && + iface->data.network.actual->data.direct.virtPortProfile && + (iface->data.network.actual->data.direct.virtPortProfile->virtPortType + == VIR_VIRTUALPORT_8021QBH)))) { + networkReportError(VIR_ERR_INTERNAL_ERROR, + _("network '%s' claims dev='%s' is already in use by a different domain"), + netdef->name, actualDev); + goto cleanup; + } + /* we are now assured of success, so mark the allocation */ + dev->usageCount++; + VIR_DEBUG("Using physical device %s, usageCount %d", + dev->dev, dev->usageCount); + } + + ret = 0; +cleanup: + if (network) + virNetworkObjUnlock(network); + return ret; +} + + +/* networkReleaseActualDevice: + * @iface: a domain's NetDef (interface definition) + * + * Given a domain element that previously had its + * element filled in (and possibly a physical device allocated to it), + * free up the physical device for use by someone else, and free the + * virDomainActualNetDef. + * + * Returns 0 on success, -1 on failure. + */ +int +networkReleaseActualDevice(virDomainNetDefPtr iface) +{ + struct network_driver *driver = driverState; + virNetworkObjPtr network = NULL; + virNetworkDefPtr netdef; + char *actualDev; + int ret = -1; + + if (iface->type != VIR_DOMAIN_NET_TYPE_NETWORK) + return 0; + + if (!iface->data.network.actual || + (virDomainNetGetActualType(iface) != VIR_DOMAIN_NET_TYPE_DIRECT)) { + VIR_DEBUG("Nothing to release to network %s", iface->data.network.name); + ret = 0; + goto cleanup; + } + + networkDriverLock(driver); + network = virNetworkFindByName(&driver->networks, iface->data.network.name); + networkDriverUnlock(driver); + if (!network) { + networkReportError(VIR_ERR_NO_NETWORK, + _("no network with matching name '%s'"), + iface->data.network.name); + goto cleanup; + } + + actualDev = virDomainNetGetActualDirectDev(iface); + if (!actualDev) { + networkReportError(VIR_ERR_INTERNAL_ERROR, + "%s", _("the interface uses a direct mode, but has no source dev")); + goto cleanup; + } + + netdef = network->def; + if (netdef->nForwardIfs == 0) { + networkReportError(VIR_ERR_INTERNAL_ERROR, + _("network '%s' uses a direct mode, but has no forward dev and no interface pool"), + netdef->name); + goto cleanup; + } else { + int ii; + virNetworkForwardIfDefPtr dev = NULL; + + for (ii = 0; ii < netdef->nForwardIfs; ii++) { + if (STREQ(actualDev, netdef->forwardIfs[ii].dev)) { + dev = &netdef->forwardIfs[ii]; + break; + } + } + /* dev points at the physical device we've been using */ + if (!dev) { + networkReportError(VIR_ERR_INTERNAL_ERROR, + _("network '%s' doesn't have dev='%s' in use by domain"), + netdef->name, actualDev); + goto cleanup; + } + + dev->usageCount--; + VIR_DEBUG("Releasing physical device %s, usageCount %d", + dev->dev, dev->usageCount); + } + + ret = 0; +cleanup: + if (network) + virNetworkObjUnlock(network); + virDomainActualNetDefFree(iface->data.network.actual); + iface->data.network.actual = NULL; + return ret; +} diff --git a/src/network/bridge_driver.h b/src/network/bridge_driver.h index 2896c84584..d409f76136 100644 --- a/src/network/bridge_driver.h +++ b/src/network/bridge_driver.h @@ -1,7 +1,7 @@ /* * network_driver.h: core driver methods for managing networks * - * Copyright (C) 2006, 2007 Red Hat, Inc. + * Copyright (C) 2006, 2007, 2011 Red Hat, Inc. * Copyright (C) 2006 Daniel P. Berrange * * This library is free software; you can redistribute it and/or @@ -29,13 +29,31 @@ # include "internal.h" # include "network_conf.h" +# include "domain_conf.h" # include "command.h" # include "dnsmasq.h" 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 networkBuildDhcpDaemonCommandLine(virNetworkObjPtr network, virCommandPtr *cmdout, char *pidfile, - dnsmasqContext *dctx); + dnsmasqContext *dctx) + ; +# else +/* Define no-op replacements that don't drag in any link dependencies. */ +# define networkAllocateActualDevice(iface) 0 +# define networkNotifyActualDevice(iface) 0 +# define networkReleaseActualDevice(iface) 0 +# define networkBuildDhcpDaemonCommandLine(network, cmdout, pidfile, dctx) 0 +# endif typedef char *(*networkDnsmasqLeaseFileNameFunc)(const char *netname); diff --git a/src/qemu/qemu_command.c b/src/qemu/qemu_command.c index ba8f08f89a..e785f00172 100644 --- a/src/qemu/qemu_command.c +++ b/src/qemu/qemu_command.c @@ -37,6 +37,7 @@ #include "domain_nwfilter.h" #include "domain_audit.h" #include "domain_conf.h" +#include "network/bridge_driver.h" #include #include @@ -3697,6 +3698,13 @@ qemuBuildCommandLine(virConnectPtr conn, else vlan = i; + /* If appropriate, grab a physical device from the configured + * network's pool of devices, or resolve bridge device name + * to the one defined in the network definition. + */ + if (networkAllocateActualDevice(net) < 0) + goto error; + actualType = virDomainNetGetActualType(net); if (actualType == VIR_DOMAIN_NET_TYPE_NETWORK || actualType == VIR_DOMAIN_NET_TYPE_BRIDGE) { @@ -4677,6 +4685,9 @@ qemuBuildCommandLine(virConnectPtr conn, no_memory: virReportOOMError(); error: + /* free up any resources in the network driver */ + for (i = 0 ; i < def->nnets ; i++) + networkReleaseActualDevice(def->nets[i]); for (i = 0; i <= last_good_net; i++) virDomainConfNWFilterTeardown(def->nets[i]); virBufferFreeAndReset(&rbd_hosts); diff --git a/src/qemu/qemu_hotplug.c b/src/qemu/qemu_hotplug.c index 882cee4f9b..9746cb3108 100644 --- a/src/qemu/qemu_hotplug.c +++ b/src/qemu/qemu_hotplug.c @@ -39,6 +39,7 @@ #include "virfile.h" #include "qemu_cgroup.h" #include "locking/domain_lock.h" +#include "network/bridge_driver.h" #define VIR_FROM_THIS VIR_FROM_QEMU @@ -603,7 +604,8 @@ int qemuDomainAttachNetDevice(virConnectPtr conn, virDomainDevicePCIAddress guestAddr; int vlan; bool releaseaddr = false; - int actualType = virDomainNetGetActualType(net); + bool iface_connected = false; + int actualType; if (!qemuCapsGet(priv->qemuCaps, QEMU_CAPS_HOST_NET_ADD)) { qemuReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s", @@ -611,18 +613,28 @@ int qemuDomainAttachNetDevice(virConnectPtr conn, return -1; } + /* If appropriate, grab a physical device from the configured + * network's pool of devices, or resolve bridge device name + * to the one defined in the network definition. + */ + if (networkAllocateActualDevice(net) < 0) + goto cleanup; + + actualType = virDomainNetGetActualType(net); if (actualType == VIR_DOMAIN_NET_TYPE_BRIDGE || actualType == VIR_DOMAIN_NET_TYPE_NETWORK) { if ((tapfd = qemuNetworkIfaceConnect(vm->def, conn, driver, net, priv->qemuCaps)) < 0) - return -1; + goto cleanup; + iface_connected = true; if (qemuOpenVhostNet(vm->def, net, priv->qemuCaps, &vhostfd) < 0) goto cleanup; } else if (actualType == VIR_DOMAIN_NET_TYPE_DIRECT) { if ((tapfd = qemuPhysIfaceConnect(vm->def, conn, driver, net, priv->qemuCaps, VIR_VM_OP_CREATE)) < 0) - return -1; + goto cleanup; + iface_connected = true; if (qemuOpenVhostNet(vm->def, net, priv->qemuCaps, &vhostfd) < 0) goto cleanup; } @@ -738,16 +750,19 @@ int qemuDomainAttachNetDevice(virConnectPtr conn, vm->def->nets[vm->def->nnets++] = net; cleanup: - if ((ret != 0) && - qemuCapsGet(priv->qemuCaps, QEMU_CAPS_DEVICE) && - (net->info.type == VIR_DOMAIN_DEVICE_ADDRESS_TYPE_PCI) && - releaseaddr && - qemuDomainPCIAddressReleaseSlot(priv->pciaddrs, - net->info.addr.pci.slot) < 0) - VIR_WARN("Unable to release PCI address on NIC"); + if (ret < 0) { + if (qemuCapsGet(priv->qemuCaps, QEMU_CAPS_DEVICE) && + (net->info.type == VIR_DOMAIN_DEVICE_ADDRESS_TYPE_PCI) && + releaseaddr && + qemuDomainPCIAddressReleaseSlot(priv->pciaddrs, + net->info.addr.pci.slot) < 0) + VIR_WARN("Unable to release PCI address on NIC"); - if (ret != 0) - virDomainConfNWFilterTeardown(net); + if (iface_connected) + virDomainConfNWFilterTeardown(net); + + networkReleaseActualDevice(net); + } VIR_FREE(nicstr); VIR_FREE(netstr); @@ -1634,6 +1649,7 @@ int qemuDomainDetachNetDevice(struct qemud_driver *driver, } } + networkReleaseActualDevice(detach); if (vm->def->nnets > 1) { memmove(vm->def->nets + i, vm->def->nets + i + 1, diff --git a/src/qemu/qemu_process.c b/src/qemu/qemu_process.c index 8d4c9de73c..a0ac4fa3f6 100644 --- a/src/qemu/qemu_process.c +++ b/src/qemu/qemu_process.c @@ -56,6 +56,7 @@ #include "domain_audit.h" #include "domain_nwfilter.h" #include "locking/domain_lock.h" +#include "network/bridge_driver.h" #include "uuid.h" #define VIR_FROM_THIS VIR_FROM_QEMU @@ -2177,6 +2178,19 @@ int qemuProcessStopCPUs(struct qemud_driver *driver, virDomainObjPtr vm, +static int +qemuProcessNotifyNets(virDomainDefPtr def) +{ + int ii; + + for (ii = 0 ; ii < def->nnets ; ii++) { + virDomainNetDefPtr net = def->nets[ii]; + if (networkNotifyActualDevice(net) < 0) + return -1; + } + return 0; +} + static int qemuProcessFiltersInstantiate(virConnectPtr conn, virDomainDefPtr def) @@ -2379,6 +2393,9 @@ qemuProcessReconnect(void *payload, const void *name ATTRIBUTE_UNUSED, void *opa if (virSecurityManagerReserveLabel(driver->securityManager, obj) < 0) goto error; + if (qemuProcessNotifyNets(obj->def) < 0) + goto error; + if (qemuProcessFiltersInstantiate(conn, obj->def)) goto error; @@ -3014,10 +3031,10 @@ void qemuProcessStop(struct qemud_driver *driver, qemuDomainReAttachHostDevices(driver, vm->def); -#if WITH_MACVTAP def = vm->def; for (i = 0; i < def->nnets; i++) { virDomainNetDefPtr net = def->nets[i]; +#if WITH_MACVTAP if (virDomainNetGetActualType(net) == VIR_DOMAIN_NET_TYPE_DIRECT) { delMacvtap(net->ifname, net->mac, virDomainNetGetActualDirectDev(net), @@ -3026,8 +3043,12 @@ void qemuProcessStop(struct qemud_driver *driver, driver->stateDir); VIR_FREE(net->ifname); } - } #endif + /* release the physical device (or any other resources used by + * this interface in the network driver + */ + networkReleaseActualDevice(net); + } retry: if ((ret = qemuRemoveCgroup(driver, vm, 0)) < 0) { diff --git a/tests/Makefile.am b/tests/Makefile.am index e9a445ed75..971ab706e1 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -314,23 +314,30 @@ EXTRA_DIST += xml2sexprtest.c sexpr2xmltest.c xmconfigtest.c \ endif if WITH_QEMU + +qemu_LDADDS = ../src/libvirt_driver_qemu.la + +if WITH_NETWORK +qemu_LDADDS += ../src/libvirt_driver_network.la +endif + qemuxml2argvtest_SOURCES = \ qemuxml2argvtest.c testutilsqemu.c testutilsqemu.h \ testutils.c testutils.h -qemuxml2argvtest_LDADD = ../src/libvirt_driver_qemu.la $(LDADDS) +qemuxml2argvtest_LDADD = $(qemu_LDADDS) $(LDADDS) qemuxml2xmltest_SOURCES = \ qemuxml2xmltest.c testutilsqemu.c testutilsqemu.h \ testutils.c testutils.h -qemuxml2xmltest_LDADD = ../src/libvirt_driver_qemu.la $(LDADDS) +qemuxml2xmltest_LDADD = $(qemu_LDADDS) $(LDADDS) qemuargv2xmltest_SOURCES = \ qemuargv2xmltest.c testutilsqemu.c testutilsqemu.h \ testutils.c testutils.h -qemuargv2xmltest_LDADD = ../src/libvirt_driver_qemu.la $(LDADDS) +qemuargv2xmltest_LDADD = $(qemu_LDADDS) $(LDADDS) qemuhelptest_SOURCES = qemuhelptest.c testutils.c testutils.h -qemuhelptest_LDADD = ../src/libvirt_driver_qemu.la $(LDADDS) +qemuhelptest_LDADD = $(qemu_LDADDS) $(LDADDS) else EXTRA_DIST += qemuxml2argvtest.c qemuxml2xmltest.c qemuargv2xmltest.c qemuhelptest.c testutilsqemu.c testutilsqemu.h endif