network: internal API functions to manage assignment of physdev to guest

The network driver needs to assign physical devices for use by modes
that use macvtap, keeping track of which physical devices are in use
(and how many instances, when the devices can be shared). Three calls
are added:

networkAllocateActualDevice - finds a physical device for use by the
domain, and sets up the virDomainActualNetDef accordingly.

networkNotifyActualDevice - assumes that the domain was already
running, but libvirtd was restarted, and needs to be notified by each
already-running domain about what interfaces they are using.

networkReleaseActualDevice - decrements the usage count of the
allocated physical device, and frees the virDomainActualNetDef to
avoid later accidentally using the device.

bridge_driver.[hc] - the new APIs. When WITH_NETWORK is false, these
functions are all #defined to be "0" in the .h file (effectively
becoming a NOP) to prevent link errors.

qemu_(command|driver|hotplug|process).c - add calls to the above APIs
    in the appropriate places.

tests/Makefile.am - we need to include libvirt_driver_network.la
    whenever libvirt_driver_qemu.la is linked, to avoid unreferenced
    symbols (in functions that are never called by the test
    programs...)
This commit is contained in:
Laine Stump 2011-07-04 02:27:12 -04:00
parent e9949a586a
commit 04711a0f32
7 changed files with 468 additions and 20 deletions

View File

@ -3,4 +3,7 @@
#
# bridge_driver.h
networkAllocateActualDevice;
networkBuildDhcpDaemonCommandLine;
networkNotifyActualDevice;
networkReleaseActualDevice;

View File

@ -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) {
/* <forward type='bridge'/> <bridge name='xxx'/>
* 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;
/* <forward type='bridge|private|vepa|passthrough'> are all
* VIR_DOMAIN_NET_TYPE_DIRECT.
*/
if (VIR_ALLOC(iface->data.network.actual) < 0) {
virReportOOMError();
goto cleanup;
}
/* Set type=direct and appropriate <source mode='xxx'/> */
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 <interface> element that previously had its <actual>
* 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;
}

View File

@ -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);

View File

@ -37,6 +37,7 @@
#include "domain_nwfilter.h"
#include "domain_audit.h"
#include "domain_conf.h"
#include "network/bridge_driver.h"
#include <sys/utsname.h>
#include <sys/stat.h>
@ -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);

View File

@ -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,

View File

@ -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) {

View File

@ -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