[libvirt] [PATCH] docs: Document hypervisor drivers that support certain timer models
by Peter Krempa
Not every timer model is supported with each hypervisor. Explicitly
mention the driver supporting each timer model.
---
docs/formatdomain.html.in | 6 ++++--
1 file changed, 4 insertions(+), 2 deletions(-)
diff --git a/docs/formatdomain.html.in b/docs/formatdomain.html.in
index 77126a5..d77bf3f 100644
--- a/docs/formatdomain.html.in
+++ b/docs/formatdomain.html.in
@@ -1310,8 +1310,10 @@
<dt><code>name</code></dt>
<dd>
The <code>name</code> attribute selects which timer is
- being modified, and can be one of "platform", "hpet",
- "kvmclock", "pit", "rtc", or "tsc".
+ being modified, and can be one of
+ "platform" (currently unsupported),
+ "hpet" (libxl, qemu), "kvmclock" (xen, libxl, qemu),
+ "pit" (qemu), "rtc" (qemu), or "tsc" (libxl).
</dd>
<dt><code>track</code></dt>
<dd>
--
1.8.2.1
11 years, 5 months
[libvirt] [PATCHv3] pci: new iommu_group functions
by Laine Stump
Any device which belongs to an "IOMMU group" (used by vfio) will
have links to all devices of its group listed in
/sys/bus/pci/$device/iommu_group/devices;
/sys/bus/pci/$device/iommu_group is actually a link to
/sys/kernel/iommu_groups/$n, where $n is the group number (there
will be a corresponding device node at /dev/vfio/$n once the
devices are bound to the vfio-pci driver)
The following functions are added:
virPCIDeviceGetIOMMUGroupList
Gets a virPCIDeviceList with one virPCIDeviceList for each device
in the same IOMMU group as the provided virPCIDevice (a copy of the
original device object is included in the list.
virPCIDeviceAddressIOMMUGroupIterate
Calls the function @actor once for each device in the group that
contains the given virPCIDeviceAddress.
virPCIDeviceAddressGetIOMMUGroupAddresses
Fills in a virPCIDeviceAddressPtr * with an array of
virPCIDeviceAddress, one for each device in the iommu group of the
provided virPCIDeviceAddress (including a copy of the original).
virPCIDeviceAddressGetIOMMUGroupNum
Returns the group number as an int (a valid group number will always
be 0 or greater). If there is no iommu_group link in the device's
directory (usually indicating that vfio isn't loaded), -2 will be
returned. On any real error, -1 will be returned.
---
changes from v2:
1) rename functions for consistency
2) set errno=0 prior to each call to readdir, and check for non-zero
errno at the end.
3) remove some braces.
Everything else in the series of 12 was ACKed, and all but this and
the patch that adds the <iommuGroup> element to the nodedev-dumpxml
output have been pushed.
src/libvirt_private.syms | 4 +
src/util/virpci.c | 223 +++++++++++++++++++++++++++++++++++++++++++++++
src/util/virpci.h | 15 +++-
3 files changed, 239 insertions(+), 3 deletions(-)
diff --git a/src/libvirt_private.syms b/src/libvirt_private.syms
index 795e011..f08ac64 100644
--- a/src/libvirt_private.syms
+++ b/src/libvirt_private.syms
@@ -1693,13 +1693,17 @@ virObjectUnref;
# util/virpci.h
+virPCIDeviceAddressGetIOMMUGroupAddresses;
+virPCIDeviceAddressGetIOMMUGroupNum;
virPCIDeviceAddressGetSysfsFile;
+virPCIDeviceAddressIOMMUGroupIterate;
virPCIDeviceAddressParse;
virPCIDeviceCopy;
virPCIDeviceDetach;
virPCIDeviceFileIterate;
virPCIDeviceFree;
virPCIDeviceGetIOMMUGroupDev;
+virPCIDeviceGetIOMMUGroupList;
virPCIDeviceGetManaged;
virPCIDeviceGetName;
virPCIDeviceGetRemoveSlot;
diff --git a/src/util/virpci.c b/src/util/virpci.c
index 32a5493..520dbb8 100644
--- a/src/util/virpci.c
+++ b/src/util/virpci.c
@@ -1876,6 +1876,229 @@ cleanup:
return ret;
}
+
+/* virPCIDeviceAddressIOMMUGroupIterate:
+ * Call @actor for all devices in the same iommu_group as orig
+ * (including orig itself) Even if there is no iommu_group for the
+ * device, call @actor once for orig.
+ */
+int
+virPCIDeviceAddressIOMMUGroupIterate(virPCIDeviceAddressPtr orig,
+ virPCIDeviceAddressActor actor,
+ void *opaque)
+{
+ char *groupPath = NULL;
+ DIR *groupDir = NULL;
+ int ret = -1;
+ struct dirent *ent;
+
+ if (virAsprintf(&groupPath,
+ PCI_SYSFS "devices/%04x:%02x:%02x.%x/iommu_group/devices",
+ orig->domain, orig->bus, orig->slot, orig->function) < 0) {
+ virReportOOMError();
+ goto cleanup;
+ }
+
+ if (!(groupDir = opendir(groupPath))) {
+ /* just process the original device, nothing more */
+ ret = (actor)(orig, opaque);
+ goto cleanup;
+ }
+
+ while ((errno = 0, ent = readdir(groupDir)) != NULL) {
+ virPCIDeviceAddress newDev;
+
+ if (ent->d_name[0] == '.')
+ continue;
+
+ if (virPCIDeviceAddressParse(ent->d_name, &newDev) < 0) {
+ virReportError(VIR_ERR_INTERNAL_ERROR,
+ _("Found invalid device link '%s' in '%s'"),
+ ent->d_name, groupPath);
+ goto cleanup;
+ }
+
+ if ((actor)(&newDev, opaque) < 0)
+ goto cleanup;
+ }
+ if (errno != 0) {
+ virReportSystemError(errno,
+ _("Failed to read directory entry for %s"),
+ groupPath);
+ goto cleanup;
+ }
+
+ ret = 0;
+
+cleanup:
+ VIR_FREE(groupPath);
+ if (groupDir)
+ closedir(groupDir);
+ return ret;
+}
+
+
+static int
+virPCIDeviceGetIOMMUGroupAddOne(virPCIDeviceAddressPtr newDevAddr, void *opaque)
+{
+ int ret = -1;
+ virPCIDeviceListPtr groupList = opaque;
+ virPCIDevicePtr newDev;
+
+ if (!(newDev = virPCIDeviceNew(newDevAddr->domain, newDevAddr->bus,
+ newDevAddr->slot, newDevAddr->function)))
+ goto cleanup;
+
+ if (virPCIDeviceListAdd(groupList, newDev) < 0)
+ goto cleanup;
+
+ newDev = NULL; /* it's now on the list */
+ ret = 0;
+cleanup:
+ virPCIDeviceFree(newDev);
+ return ret;
+}
+
+
+/*
+ * virPCIDeviceGetIOMMUGroupList - return a virPCIDeviceList containing
+ * all of the devices in the same iommu_group as @dev.
+ *
+ * Return the new list, or NULL on failure
+ */
+virPCIDeviceListPtr
+virPCIDeviceGetIOMMUGroupList(virPCIDevicePtr dev)
+{
+ virPCIDeviceListPtr groupList = virPCIDeviceListNew();
+ virPCIDeviceAddress devAddr = { dev->domain, dev->bus,
+ dev->slot, dev->function };
+
+ if (!groupList)
+ goto error;
+
+ if (virPCIDeviceAddressIOMMUGroupIterate(&devAddr,
+ virPCIDeviceGetIOMMUGroupAddOne,
+ groupList) < 0)
+ goto error;
+
+ return groupList;
+
+error:
+ virObjectUnref(groupList);
+ return NULL;
+}
+
+
+typedef struct {
+ virPCIDeviceAddressPtr **iommuGroupDevices;
+ size_t *nIommuGroupDevices;
+} virPCIDeviceAddressList;
+typedef virPCIDeviceAddressList *virPCIDeviceAddressListPtr;
+
+static int
+virPCIGetIOMMUGroupAddressesAddOne(virPCIDeviceAddressPtr newDevAddr, void *opaque)
+{
+ int ret = -1;
+ virPCIDeviceAddressListPtr addrList = opaque;
+ virPCIDeviceAddressPtr copyAddr;
+
+ /* make a copy to insert onto the list */
+ if (VIR_ALLOC(copyAddr) < 0)
+ goto cleanup;
+
+ *copyAddr = *newDevAddr;
+
+ if (VIR_APPEND_ELEMENT(*addrList->iommuGroupDevices,
+ *addrList->nIommuGroupDevices, copyAddr) < 0) {
+ virReportOOMError();
+ goto cleanup;
+ }
+
+ ret = 0;
+cleanup:
+ VIR_FREE(copyAddr);
+ return ret;
+}
+
+
+/*
+ * virPCIGetIOMMUGroupAddresses - return a virPCIDeviceList containing
+ * all of the devices in the same iommu_group as @dev.
+ *
+ * Return the new list, or NULL on failure
+ */
+int
+virPCIDeviceAddressGetIOMMUGroupAddresses(virPCIDeviceAddressPtr devAddr,
+ virPCIDeviceAddressPtr **iommuGroupDevices,
+ size_t *nIommuGroupDevices)
+{
+ int ret = -1;
+ virPCIDeviceAddressList addrList = { iommuGroupDevices,
+ nIommuGroupDevices };
+
+ if (virPCIDeviceAddressIOMMUGroupIterate(devAddr,
+ virPCIGetIOMMUGroupAddressesAddOne,
+ &addrList) < 0)
+ goto cleanup;
+
+ ret = 0;
+cleanup:
+ return ret;
+}
+
+
+/* virPCIGetIOMMUGroupNum - return the group number of this PCI
+ * device's iommu_group, or -2 if there is no iommu_group for the
+ * device (or -1 if there was any other error)
+ */
+int
+virPCIDeviceAddressGetIOMMUGroupNum(virPCIDeviceAddressPtr addr)
+{
+ char *devName = NULL;
+ char *devPath = NULL;
+ char *groupPath = NULL;
+ const char *groupNumStr;
+ unsigned int groupNum;
+ int ret = -1;
+
+ if (virAsprintf(&devName, "%.4x:%.2x:%.2x.%.1x", addr->domain,
+ addr->bus, addr->slot, addr->function) < 0) {
+ virReportOOMError();
+ goto cleanup;
+ }
+
+ if (virPCIFile(&devPath, devName, "iommu_group") < 0)
+ goto cleanup;
+ if (virFileIsLink(devPath) != 1) {
+ ret = -2;
+ goto cleanup;
+ }
+ if (virFileResolveLink(devPath, &groupPath) < 0) {
+ virReportError(VIR_ERR_INTERNAL_ERROR,
+ _("Unable to resolve device %s iommu_group symlink %s"),
+ devName, devPath);
+ goto cleanup;
+ }
+
+ groupNumStr = last_component(groupPath);
+ if (virStrToLong_ui(groupNumStr, NULL, 10, &groupNum) < 0) {
+ virReportError(VIR_ERR_INTERNAL_ERROR,
+ _("device %s iommu_group symlink %s has "
+ "invalid group number %s"),
+ devName, groupPath, groupNumStr);
+ ret = -1;
+ goto cleanup;
+ }
+
+ ret = groupNum;
+cleanup:
+ VIR_FREE(devName);
+ VIR_FREE(devPath);
+ VIR_FREE(groupPath);
+ return ret;
+}
+
+
/* virPCIDeviceGetIOMMUGroupDev - return the name of the device used
* to control this PCI device's group (e.g. "/dev/vfio/15")
*/
diff --git a/src/util/virpci.h b/src/util/virpci.h
index b56b89b..0aa6fee 100644
--- a/src/util/virpci.h
+++ b/src/util/virpci.h
@@ -113,12 +113,21 @@ int virPCIDeviceListFindIndex(virPCIDeviceListPtr list,
*/
typedef int (*virPCIDeviceFileActor)(virPCIDevicePtr dev,
const char *path, void *opaque);
-
int virPCIDeviceFileIterate(virPCIDevicePtr dev,
virPCIDeviceFileActor actor,
void *opaque);
-char *
-virPCIDeviceGetIOMMUGroupDev(virPCIDevicePtr dev);
+
+typedef int (*virPCIDeviceAddressActor)(virPCIDeviceAddress *addr,
+ void *opaque);
+int virPCIDeviceAddressIOMMUGroupIterate(virPCIDeviceAddressPtr orig,
+ virPCIDeviceAddressActor actor,
+ void *opaque);
+virPCIDeviceListPtr virPCIDeviceGetIOMMUGroupList(virPCIDevicePtr dev);
+int virPCIDeviceAddressGetIOMMUGroupAddresses(virPCIDeviceAddressPtr devAddr,
+ virPCIDeviceAddressPtr **iommuGroupDevices,
+ size_t *nIommuGroupDevices);
+int virPCIDeviceAddressGetIOMMUGroupNum(virPCIDeviceAddressPtr dev);
+char *virPCIDeviceGetIOMMUGroupDev(virPCIDevicePtr dev);
int virPCIDeviceIsAssignable(virPCIDevicePtr dev,
int strict_acs_check);
--
1.7.11.7
11 years, 5 months
[libvirt] RFC: Improving unit test coverage
by Daniel P. Berrange
Over the many years that libvirt has been in existance, we have
developed a huge number of features, and rate of change in the
codebase shows no sign of slowing down.
Unfortunately the rate of regressions and/or new bugs also shows
no sign of decreasing. As libvirt is used ever more widely, poor
quality releases are having an increasingly serious negative
impact on the impressions of libvirt by downstream consumers.
In addition the introduction of the new access control facilities
will have the unfortunate side-effect of increasing the severity
of many bugs. Historically a read-write connection to libvirtd
could be considered to be equivalent to root, so a bug affecting
an authenticated user would not be considered a privilege
escalation, merely denial-of-service. With the introduction of
access control, a read-write connection to libvirtd may have
substantially fewer privileges than root. The result is that
many (arguably all) crasher bugs in libvirtd which were previously
considered mere denial of service issues, and not worthy of
security alerts, will now be classed as potential privilege
escalation flaws.
It is clear that the current rate of defects in libvirt code
is no longer tenable / acceptable. There are a number of test
suites which are targetting libvirt, but this document will focus
on the unit testing framework, rather than the integration test
harnesses. The rationale for this is
- Unit tests are run by all developers daily via 'make check'
- Unit tests gate the release process
- Unit tests are run every time any RPM is built
- Unit tests do not have an dependancy on external system
infrastructure / setup
- Unit tests are directly synchronized with the code commits
since they're in the same GIT repo
Unit testing techniques
=======================
One of the challenges of unit testing is how to isolate the
test suite from the broader environment of the machine being
used for development. For example, the test suite must not
rely on information located in the filesystem outside the
source treee (eg it must not touch /proc or /sys). In addition
it must be careful not to negatively impact anything running
on the developers' machine (eg it must not spawn libvirtd in
a way which would clash with existing running libvirtd)
There is no silver bullet to achieve isolation of the test
suite from the host OS environment. Instead a variety of
different techniques must be applied depending on the scenario
to be addressed.
Code structure
--------------
First, and foremost, code structure has an enourmous impact
on the ability to unit test code. Ensuring there are clear
boundaries & interfaces between different areas of cdoe is
key to allowing pieces to be unit tested in isolation from
each other.
For example, testing of the QEMU command line generator, can
only be achieved because this code for converting from XML
to ARGV has been isolated from the code which actually starts
up the QEMU process.
While some areas of libvirt code are very well modularized,
others are not. When writing new unit tests it is not at all
unexpected to have to re-factor existing code to further
modularize it. Such refactoring is usually good for overall
readability of the code, as well as facilitating testing.
Intermediate data forms
-----------------------
As hinted at above, a key idea when refactoring code to make
it more amenable to testing is to introduce an intermediate
data form. For example, when generating the QEMU command
line arguments from an XML document, the virCommandPtr object
serves as the intermediate data form. Instead of having the
API which builds the command line, directly execute the QEMU
binary, it spits out a virCommandPtr object as an intermediate
data form. The contents of this object can then be validated
against pre-defined expected command lines.
As a counter-example, the network filter code is very hard to
unit test as it is currently designed because it does not have
any intermediate data form. The methods which process the
network filter configuration build up command lines and then
directly execute them as they go. There is no way to extract
the command line data, without them being executed. To enable
unit testing it is neccessary to refactor the network filter
code to introduce a clear intermediate data form which can
represent the commands that will be executed.
Static analysis
---------------
While the elephant in the room is Coverity, there is a large
amount of static analysis that can be done directly in the
libvirt source tree. Indeed all of the 'syntax-check' rules
are considered to be static analysis. While these rules all
focus on style issues, there is no reason why this should be
the case.
What makes static analysis tools like Coverity so hard to
write is the problem of understanding the semantics of C
code. Fortunately, when doing static analysis of a specific
project (like libvirt), there is often no need to solve the
general case. If there are certain style patterns used in
a project, these can make it possible to write project
specific analysis rules with little more than a few regular
expressions or other simple scripting statements.
For example, the two style checks 'src/check-drivername.pl'
'src/check-driverimpls.pl' enforce a consistent naming
convention on each internal driver method. With this naming
pattern enforced, it is now possible to write a script which
validates that every driver method includes a call to the
correct access control hook. This is project-specific static
analysis that could not be done with Coverity.
For more advanced problems it is possible to make use of a
framework such as CIL to perform static analysis. CIL is an
OCaml project which can parse most C code providing an
in-memory representation of its structure. Then it has a
number of modules to perform data flow analysis on the code.
Combined with knowledge of project coding patterns this tool
allows the creation of scripts to validate mutex lock
acquisition ordering rules, or correct error reporting on
all error exit paths.
Monkey patching / Duck Punching
-------------------------------
One of the pain-points with a project such as libvirt is finding
a way to test parts of the code which rely on external system
state, such as /proc, or /sys filesystem data. It is not always
possible to refactor the code to test it in isolation from the
system services. In such cases, the technique of monkey patching
or duck punching (better known from the world of scripting
languages) can in fact be used from C.
The idea behind the technique is to replace the APIs / objects
that are used by the test suite, with set of "mock" objects or
API implementations which simulate their operation, without
relying on external state. The trick to applying this technique
in the C world with native code, is to rely on the use of the
dynamic linkers' library preload facility, via the LD_PRELOAD.
Any symbol exported by a library listed in the LD_PRELOAD env
variable, will override symbols with the same name in the library
that the test suite is actually linked to. So, for example, if
the preloaded library exports an 'open' symbol, this will
replace the 'open' symbol exported by the standard C library.
To use this technique it is neccessary to first understand what
interfaces need to be replaced. If attempting to test some code
which uses files in some location under /sys/, one might change
the implementation of 'open', so that when passed a filename
starting with '/sys', it will re-write the path to point to a
location under the test suite directory.
This technique is used in the vircgrouptest.c / vircgroupmock.c
files to allow comprehensive testing of libvirt's cgroup
management code, without actually ever touching the cgroup
filesystem. It is also used by the securityselinuxtest.c /
securityselinuxhelper.c files to stub out the sys calls which
write SELinux attributes.
Fuzz testing
------------
This refers to the technique of taking an API or interface and
giving it some data which has some intentionale inaccuracies
or edge cases. For example, a API accepting a 'const char *str'
might be given a NULL string, or a zero length string, or a
string containing non-printable control characters. An API
accepting an 'int' might be tested with various boundary
conditions such as -1, 0, -MAX_INT, +MAX_INT.
In the case of an XML document, one technique would be to
incrementally remove attributes and/or elements to see if the
parser will ever crash on a NULL pointer (it should either
succeed or return a error, never crash). Or alternatively
change the values in various attributes as one would with
parameters passed to an API. If an attribute appears to be
a number, then replace it with a string. If an attribute
appears to be an enum, replace it with some bogus value.
Try zero length strings for various attributes, or again
try non-printable control characters.
In the case of an RPC protocol, the idea would be to make
(intelligent) changes to packets in an attempt to trick
the server into accepting bad data, or even crashing.
It is not practical to test all possible wierd data sets,
so the trick with fuzz testing is to try to intelligently
tailor the data to the interface being tested. eg if a
string parameter represents a file path, there's no point
just randomly changing characters in the path, since that'll
just generate loads of other valid file paths. Instead it is
neccessary to make some more planned changes like removing
the leading '/' to produce a relative path, inserting a
number of '../../../' dirs to trick the application into
writing to the incorrect location, and things of that nature.
For any moderately complex parser, intelligently designed
fuzz testing is likely to identify a wide range of bugs.
Error testing
-------------
Validating correct operation is great, but there is plenty
of code which lives in error handling code paths, which will
never be exercised this way. Since they are seldom run, and
by their nature deal with unexpected data or events, the
error paths are often a good source of bugs. The fuzz testing
technique is one very effective way of exercising error
handling paths, but should not be considered the only possible
one. The monkey patching techniques can also be applied to
the task of error checking. Alternatively the code structure
may facilitate the injection of error conditions in a well
defined manner. For example, all libvirt memory allocation
goes via a wrapper API, instead of to malloc() directly. It
is possible to tell the wrapper API to fail specific allocation
attempts. By running the test suite repeatedly, failing a
different memory allocation each time, it is possible to
exhaustively test error handling of the tested code.
Areas requiring testing
=======================
The best way to identify areas lacking code coverage is to
simply pick a random libvirt source code file. The odds are
that it has insufficient testing. The more optimistic way
is to look at the code coverage reports.
Run 'autogen.sh' with the --enable-test-coverage arg. Then
build it and run 'make check'. Once the tests have completed,
then run 'make cov' to generate an HTML code coverage report.
Some known areas needing work are:
Cgroups setup for QEMU
----------------------
For a given QEMU guest XML configuration there is a well
defined set of cgroups parameters that are expected to be
set.
The vircgroupmock.c file demonstrates how to monkey patch
the cgroups filesystem, allowing unit tests to be written.
The vircgroupmock.c helper could be reused to create a test
suite for validating the operation of the qemu_cgroups.c
APIs.
Cgroups setup for LXC
----------------------
The same as the QEMU test, but for the lxc_cgroups.c file.
SELinux security driver labelling
---------------------------------
For a given XML configuration, there is a well defined set
of file labels that the SELinux security driver must apply
to the filesystem on VM startup.
The securityselinuxhelper.c file demonstrates how to monkey
patch the SELinux library file labelling APIs from unit
tests.
Currently the securityselinuxlabeltest.c test suite does
checks for labels set on disks and serial devices. There
are many other aspects of domain XML that imply labelling.
In particular PCI/USB host devices & kernel/initrd boot
images. It is also neccessary to deal with backing files
for qcow2 format disks.
Audit messages
--------------
Upon QEMU or LXC guest startup a number of audit records must
be logged detailing various pieces of metadata about the guest.
By monkey patching the audit_log_user_message() and audit_open()
library APIs with an LD_PRELOAD library, it would be possible
to perform unit testsing of the domain_audit.c APIs to ensure
that correct audit records are logged for various different
guest configurations.
Network filter
--------------
As described earlier, the network filter code is really badly
designed from the point of view of unit testing. It needs to
be refactored to have an intermediate data form that represents
the list of iptables/ebtables rules that are expected for a
given XML configuration.
Currently it generates shell scripts containing iptables
commands, optionally re-directing via the 'firewall-cmd' program
for integration with firewalld. Refactoring to unit test this
offers a prime opportunity to remove the use of shell entirely
The core observation of the nwfilter code is that it consists
of lists of commands to be run to create ebtables/iptables rules.
Periodically there are commands which are able to rollback the
state changes made by earlier commands when an error occurs.
The idea for the intermediate data form would thus be to introduce
the concept of a "script" object, as a higher level interface than
that offered by virCommandPtr.
A script would comprise one or more sets of command lines for
making changes. At various points it would be possible to add a
checkpoint marker, together with one or more sets of command
lines that would be execute when errors occur which require
rollback.
The network filter code would thus output "script" objects
whose command sets could be validated by a unit test suite.
These script objects can then be translated into virCommandPtr
objects for actual execution by separate code. Or even translated
into DBus API calls for direct communication with firewalld,
bypassing the inefficient firewall-cmd program.
The unit test would consist of XML data files with network
filter configuration rules, alongside expected script command
lines, similar to how the qemu xml -> argv test works.
virsh testing
-------------
The virsh command has rarely had any serious direct testing
of its own. Its code is mostly exercised indirectly by test
suites doing testing of QEMU or other drivers.
Libvirt has a hypervisor driver that is explicit targetted
to testing tools like virsh - the "test" driver. This is a
100% in-memory driver that stubs out all the libvirt APIs
with semi-serious semantics. It is used to do automated
testing of large parts of virt-install and virt-manager.
It can be used to get near 100% testing coverage of the virsh
command from within unit tests. Since it is all in-memory
there is no scope for it to be affected the host system
state.
libvirtd testing
----------------
Testing libvirtd is a tricky proposition, because merely
starting the daemon will have an effect on host OS state,
particularly if any resources are set to autostart. It
can also clash with any libvirtd process the developer is
already running.
The test driver is able to be run inside libvirtd. If it
were possible to disable all the other hypervisor drivers
in libvirtd, the test driver could be used to execise the
RPC dispatch code in libvirtd in a safe contained manner.
This could be particularly good for doing scalability
testing of libvirtd when large numbers of objects. eg since
the test driver doesn't actually run anything, it is possible
to start many 1000's of "test" guests with the only memory
overhead being that of the parsed XML configuration.
This would allow the test suite to identify & validate
limits in the RPC protocol or inefficiencies in libvirtd.
lock driver client
------------------
The virtlockd client code recently had a bug where it
generated invalid RPC packets in response to certain
operations. It is not desirable to have the unit tests
actually run the full virtlockd daemon, just to validate
the client code.
It would, however, be possible to have a unit test which
provided a fake virtlockd UNIX domain socket for the
client to connect to. It would blindly read any data off
the socket & validate it against pre-defined RPC packets.
This approach is already used with some success in the
QEMU JSON monitor test suite.
QEMU disk snapshots
-------------------
The QEMU snapshot code is some of the most complex code in
the QEMU driver. As such it would be desirable to get test
coverage of it. The easiest way todo this would be via the
libvirt TCK integration test harness. There is, however,
value in refactoring some of the code to allow its testing
in a unit test environment.
One approach would be for the test suite to directly invoke
methods in the qemu driver dispatch table. This would require
refactoring the qemuStateInitialize code though, to make it
possible to setup the basic QEMU driver global state. More
desirable would be to refactor the snapshot code so that it
was more detached from the public driver APIs. For example
if some of it could be isolated in a qemu_snapshot.c file,
this may make it practical to unit test some of it.
Techniques from the qemumonitorjsontest.c file could be used
to stub out the monitor APIs that the snapshot code requires
in order to operate.
QEMU driver startup
-------------------
An important aspect of the QEMU driver is that it is able
to reconnect to existing domains when it starts up. It would
be desirable to unit test this functionality to validate the
sequence of operations done when reconnecting to a guest.
Again this would require some refactoring of the
qemuStateInitialize method to allow its execution in a test
suite. It would also likely require the techniques for stubing
the QEMU monitor APIs.
XML parser/formatter reliability
--------------------------------
While we have good testing of valid XML documents, there have
been a number of cases where invalid XML documents have caused
crashes or other bad behaviour. It is not reasonable to add
test cases for every possible invalid XML document. Instead
we need to adopt a fuzzing approach, where we take valid XML
documents and then make incremental changes to them & then
ensure that the code does not crash.
Regards,
Daniel
--
|: http://berrange.com -o- http://www.flickr.com/photos/dberrange/ :|
|: http://libvirt.org -o- http://virt-manager.org :|
|: http://autobuild.org -o- http://search.cpan.org/~danberr/ :|
|: http://entangle-photo.org -o- http://live.gnome.org/gtk-vnc :|
11 years, 5 months
[libvirt] [PATCH v4] qemu: Implement CPUs check against machine type's cpu-max
by Michal Novotny
Implement check whether (maximum) vCPUs doesn't exceed machine
type's cpu-max settings.
Differences between v3 and v4 (this one):
- Rebased to latest libvirt version
- Capability XML output extended by maxCpus field
- Extended caps-qemu-kvm.xml test by maxCpus for one of test emulators
On older versions of QEMU the check is disabled.
Signed-off-by: Michal Novotny <minovotn(a)redhat.com>
---
docs/schemas/capability.rng | 5 ++++
src/conf/capabilities.c | 4 +++
src/conf/capabilities.h | 1 +
src/qemu/qemu_capabilities.c | 41 +++++++++++++++++++++++++++-
src/qemu/qemu_capabilities.h | 3 +-
src/qemu/qemu_monitor.h | 1 +
src/qemu/qemu_monitor_json.c | 6 ++++
src/qemu/qemu_process.c | 27 ++++++++++++++++++
tests/capabilityschemadata/caps-qemu-kvm.xml | 16 +++++------
9 files changed, 94 insertions(+), 10 deletions(-)
diff --git a/docs/schemas/capability.rng b/docs/schemas/capability.rng
index 106ca73..65c7c72 100644
--- a/docs/schemas/capability.rng
+++ b/docs/schemas/capability.rng
@@ -290,6 +290,11 @@
<text/>
</attribute>
</optional>
+ <optional>
+ <attribute name='maxCpus'>
+ <ref name='unsignedInt'/>
+ </attribute>
+ </optional>
<text/>
</element>
</define>
diff --git a/src/conf/capabilities.c b/src/conf/capabilities.c
index da92c78..5aeb2ab 100644
--- a/src/conf/capabilities.c
+++ b/src/conf/capabilities.c
@@ -853,6 +853,8 @@ virCapabilitiesFormatXML(virCapsPtr caps)
virBufferAddLit(&xml, " <machine");
if (machine->canonical)
virBufferAsprintf(&xml, " canonical='%s'", machine->canonical);
+ if (machine->maxCpus > 0)
+ virBufferAsprintf(&xml, " maxCpus='%d'", machine->maxCpus);
virBufferAsprintf(&xml, ">%s</machine>\n", machine->name);
}
@@ -871,6 +873,8 @@ virCapabilitiesFormatXML(virCapsPtr caps)
virBufferAddLit(&xml, " <machine");
if (machine->canonical)
virBufferAsprintf(&xml, " canonical='%s'", machine->canonical);
+ if (machine->maxCpus > 0)
+ virBufferAsprintf(&xml, " maxCpus='%d'", machine->maxCpus);
virBufferAsprintf(&xml, ">%s</machine>\n", machine->name);
}
virBufferAddLit(&xml, " </domain>\n");
diff --git a/src/conf/capabilities.h b/src/conf/capabilities.h
index abcf6de..22b6fb6 100644
--- a/src/conf/capabilities.h
+++ b/src/conf/capabilities.h
@@ -46,6 +46,7 @@ typedef virCapsGuestMachine *virCapsGuestMachinePtr;
struct _virCapsGuestMachine {
char *name;
char *canonical;
+ int maxCpus;
};
typedef struct _virCapsGuestDomainInfo virCapsGuestDomainInfo;
diff --git a/src/qemu/qemu_capabilities.c b/src/qemu/qemu_capabilities.c
index c4e076a..89f41b8 100644
--- a/src/qemu/qemu_capabilities.c
+++ b/src/qemu/qemu_capabilities.c
@@ -256,6 +256,7 @@ struct _virQEMUCaps {
size_t nmachineTypes;
char **machineTypes;
char **machineAliases;
+ int *machineMaxCpus;
};
struct _virQEMUCapsCache {
@@ -335,6 +336,7 @@ virQEMUCapsSetDefaultMachine(virQEMUCapsPtr qemuCaps,
{
char *name = qemuCaps->machineTypes[defIdx];
char *alias = qemuCaps->machineAliases[defIdx];
+ int cpu_max = qemuCaps->machineMaxCpus[defIdx];
memmove(qemuCaps->machineTypes + 1,
qemuCaps->machineTypes,
@@ -342,8 +344,12 @@ virQEMUCapsSetDefaultMachine(virQEMUCapsPtr qemuCaps,
memmove(qemuCaps->machineAliases + 1,
qemuCaps->machineAliases,
sizeof(qemuCaps->machineAliases[0]) * defIdx);
+ memmove(qemuCaps->machineMaxCpus + 1,
+ qemuCaps->machineMaxCpus,
+ sizeof(qemuCaps->machineMaxCpus[0]) * defIdx);
qemuCaps->machineTypes[0] = name;
qemuCaps->machineAliases[0] = alias;
+ qemuCaps->machineMaxCpus[0] = cpu_max;
}
/* Format is:
@@ -390,7 +396,8 @@ virQEMUCapsParseMachineTypesStr(const char *output,
}
if (VIR_REALLOC_N(qemuCaps->machineTypes, qemuCaps->nmachineTypes + 1) < 0 ||
- VIR_REALLOC_N(qemuCaps->machineAliases, qemuCaps->nmachineTypes + 1) < 0) {
+ VIR_REALLOC_N(qemuCaps->machineAliases, qemuCaps->nmachineTypes + 1) < 0 ||
+ VIR_REALLOC_N(qemuCaps->machineMaxCpus, qemuCaps->nmachineTypes + 1) < 0) {
VIR_FREE(name);
VIR_FREE(canonical);
virReportOOMError();
@@ -404,6 +411,8 @@ virQEMUCapsParseMachineTypesStr(const char *output,
qemuCaps->machineTypes[qemuCaps->nmachineTypes-1] = name;
qemuCaps->machineAliases[qemuCaps->nmachineTypes-1] = NULL;
}
+ /* Value 0 means "unknown" as it's not exposed by QEMU binary */
+ qemuCaps->machineMaxCpus[qemuCaps->nmachineTypes-1] = 0;
} while ((p = next));
@@ -1764,11 +1773,14 @@ virQEMUCapsPtr virQEMUCapsNewCopy(virQEMUCapsPtr qemuCaps)
goto no_memory;
if (VIR_ALLOC_N(ret->machineAliases, qemuCaps->nmachineTypes) < 0)
goto no_memory;
+ if (VIR_ALLOC_N(ret->machineMaxCpus, qemuCaps->nmachineTypes) < 0)
+ goto no_memory;
ret->nmachineTypes = qemuCaps->nmachineTypes;
for (i = 0; i < qemuCaps->nmachineTypes; i++) {
if (VIR_STRDUP(ret->machineTypes[i], qemuCaps->machineTypes[i]) < 0 ||
VIR_STRDUP(ret->machineAliases[i], qemuCaps->machineAliases[i]) < 0)
goto error;
+ ret->machineMaxCpus[i] = qemuCaps->machineMaxCpus[i];
}
return ret;
@@ -1789,9 +1801,11 @@ void virQEMUCapsDispose(void *obj)
for (i = 0; i < qemuCaps->nmachineTypes; i++) {
VIR_FREE(qemuCaps->machineTypes[i]);
VIR_FREE(qemuCaps->machineAliases[i]);
+ qemuCaps->machineMaxCpus[i] = -1;
}
VIR_FREE(qemuCaps->machineTypes);
VIR_FREE(qemuCaps->machineAliases);
+ VIR_FREE(qemuCaps->machineMaxCpus);
for (i = 0; i < qemuCaps->ncpuDefinitions; i++) {
VIR_FREE(qemuCaps->cpuDefinitions[i]);
@@ -1932,6 +1946,7 @@ int virQEMUCapsGetMachineTypesCaps(virQEMUCapsPtr qemuCaps,
if (VIR_STRDUP(mach->name, qemuCaps->machineTypes[i]) < 0)
goto error;
}
+ mach->maxCpus = qemuCaps->machineMaxCpus[i];
(*machines)[i] = mach;
}
@@ -1966,6 +1981,25 @@ const char *virQEMUCapsGetCanonicalMachine(virQEMUCapsPtr qemuCaps,
}
+int virQEMUCapsGetMachineMaxCpus(virQEMUCapsPtr qemuCaps,
+ const char *name)
+{
+ size_t i;
+
+ if (!name)
+ return 0;
+
+ for (i = 0; i < qemuCaps->nmachineTypes; i++) {
+ if (!qemuCaps->machineMaxCpus[i])
+ continue;
+ if (STREQ(qemuCaps->machineTypes[i], name))
+ return qemuCaps->machineMaxCpus[i];
+ }
+
+ return 0;
+}
+
+
static int
virQEMUCapsProbeQMPCommands(virQEMUCapsPtr qemuCaps,
qemuMonitorPtr mon)
@@ -2083,6 +2117,10 @@ virQEMUCapsProbeQMPMachineTypes(virQEMUCapsPtr qemuCaps,
virReportOOMError();
goto cleanup;
}
+ if (VIR_ALLOC_N(qemuCaps->machineMaxCpus, nmachines) < 0) {
+ virReportOOMError();
+ goto cleanup;
+ }
for (i = 0; i < nmachines; i++) {
if (VIR_STRDUP(qemuCaps->machineAliases[i], machines[i]->alias) < 0 ||
@@ -2090,6 +2128,7 @@ virQEMUCapsProbeQMPMachineTypes(virQEMUCapsPtr qemuCaps,
goto cleanup;
if (machines[i]->isDefault)
defIdx = i;
+ qemuCaps->machineMaxCpus[i] = machines[i]->cpu_max;
}
qemuCaps->nmachineTypes = nmachines;
diff --git a/src/qemu/qemu_capabilities.h b/src/qemu/qemu_capabilities.h
index 64a4b1d..7088747 100644
--- a/src/qemu/qemu_capabilities.h
+++ b/src/qemu/qemu_capabilities.h
@@ -234,7 +234,8 @@ size_t virQEMUCapsGetMachineTypes(virQEMUCapsPtr qemuCaps,
char ***names);
const char *virQEMUCapsGetCanonicalMachine(virQEMUCapsPtr qemuCaps,
const char *name);
-
+int virQEMUCapsGetMachineMaxCpus(virQEMUCapsPtr qemuCaps,
+ const char *name);
int virQEMUCapsGetMachineTypesCaps(virQEMUCapsPtr qemuCaps,
size_t *nmachines,
virCapsGuestMachinePtr **machines);
diff --git a/src/qemu/qemu_monitor.h b/src/qemu/qemu_monitor.h
index 3d9afa3..06ae4c5 100644
--- a/src/qemu/qemu_monitor.h
+++ b/src/qemu/qemu_monitor.h
@@ -654,6 +654,7 @@ struct _qemuMonitorMachineInfo {
char *name;
bool isDefault;
char *alias;
+ int cpu_max;
};
int qemuMonitorGetMachines(qemuMonitorPtr mon,
diff --git a/src/qemu/qemu_monitor_json.c b/src/qemu/qemu_monitor_json.c
index 88a0dc9..1362acf 100644
--- a/src/qemu/qemu_monitor_json.c
+++ b/src/qemu/qemu_monitor_json.c
@@ -4042,6 +4042,12 @@ int qemuMonitorJSONGetMachines(qemuMonitorPtr mon,
if (VIR_STRDUP(info->alias, tmp) < 0)
goto cleanup;
}
+ if (virJSONValueObjectHasKey(child, "cpu-max") &&
+ virJSONValueObjectGetNumberInt(child, "cpu-max", &info->cpu_max) < 0) {
+ virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
+ _("query-machines reply has malformed 'cpu-max' data"));
+ goto cleanup;
+ }
}
ret = n;
diff --git a/src/qemu/qemu_process.c b/src/qemu/qemu_process.c
index 5a0f18b..3146ce2 100644
--- a/src/qemu/qemu_process.c
+++ b/src/qemu/qemu_process.c
@@ -3330,6 +3330,30 @@ error:
}
+static bool
+qemuValidateCpuMax(virDomainDefPtr def, virQEMUCapsPtr qemuCaps)
+{
+ int cpu_max;
+
+ cpu_max = virQEMUCapsGetMachineMaxCpus(qemuCaps, def->os.machine);
+ if (!cpu_max)
+ return true;
+
+ if (def->vcpus > cpu_max) {
+ virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
+ "%s", _("CPUs greater than specified machine type limit"));
+ return false;
+ }
+
+ if (def->maxvcpus > cpu_max) {
+ virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
+ "%s", _("Maximum CPUs greater than specified machine type limit"));
+ return false;
+ }
+
+ return true;
+}
+
int qemuProcessStart(virConnectPtr conn,
virQEMUDriverPtr driver,
virDomainObjPtr vm,
@@ -3519,6 +3543,9 @@ int qemuProcessStart(virConnectPtr conn,
vm->def->emulator)))
goto cleanup;
+ if (!qemuValidateCpuMax(vm->def, priv->qemuCaps))
+ goto cleanup;
+
if (qemuAssignDeviceAliases(vm->def, priv->qemuCaps) < 0)
goto cleanup;
diff --git a/tests/capabilityschemadata/caps-qemu-kvm.xml b/tests/capabilityschemadata/caps-qemu-kvm.xml
index 36c4b49..1fbc22b 100644
--- a/tests/capabilityschemadata/caps-qemu-kvm.xml
+++ b/tests/capabilityschemadata/caps-qemu-kvm.xml
@@ -33,18 +33,18 @@
<arch name='i686'>
<wordsize>32</wordsize>
<emulator>/usr/bin/qemu</emulator>
- <machine>pc-0.11</machine>
- <machine canonical='pc-0.11'>pc</machine>
- <machine>pc-0.10</machine>
- <machine>isapc</machine>
+ <machine maxCpus='255'>pc-0.11</machine>
+ <machine canonical='pc-0.11' maxCpus='255'>pc</machine>
+ <machine maxCpus='255'>pc-0.10</machine>
+ <machine maxCpus='1'>isapc</machine>
<domain type='qemu'>
</domain>
<domain type='kvm'>
<emulator>/usr/bin/qemu-kvm</emulator>
- <machine>pc-0.11</machine>
- <machine canonical='pc-0.11'>pc</machine>
- <machine>pc-0.10</machine>
- <machine>isapc</machine>
+ <machine maxCpus='255'>pc-0.11</machine>
+ <machine canonical='pc-0.11' maxCpus='255'>pc</machine>
+ <machine maxCpus='255'>pc-0.10</machine>
+ <machine maxCpus='1'>isapc</machine>
</domain>
</arch>
<features>
--
1.7.11.7
11 years, 5 months
[libvirt] [PATCH] Plug leak in virCgroupMoveTask
by Ján Tomko
We only break out of the while loop if *content is an empty string.
However the buffer has been allocated to BUFSIZ + 1 (8193 in my case),
but it gets overwritten in the next for iteration.
Move VIR_FREE right before we overwrite it to avoid the leak.
==5777== 16,386 bytes in 2 blocks are definitely lost in loss record 1,022 of 1,027
==5777== by 0x5296E28: virReallocN (viralloc.c:184)
==5777== by 0x52B0C66: virFileReadLimFD (virfile.c:1137)
==5777== by 0x52B0E1A: virFileReadAll (virfile.c:1199)
==5777== by 0x529B092: virCgroupGetValueStr (vircgroup.c:534)
==5777== by 0x529AF64: virCgroupMoveTask (vircgroup.c:1079)
Introduced by 83e4c77.
https://bugzilla.redhat.com/show_bug.cgi?id=978352
---
src/util/vircgroup.c | 3 +--
1 file changed, 1 insertion(+), 2 deletions(-)
diff --git a/src/util/vircgroup.c b/src/util/vircgroup.c
index 0484c71..5e75355 100644
--- a/src/util/vircgroup.c
+++ b/src/util/vircgroup.c
@@ -1076,6 +1076,7 @@ int virCgroupMoveTask(virCgroupPtr src_group, virCgroupPtr dest_group)
* aware that it needs to move. Therefore, we must iterate
* until content is empty. */
while (1) {
+ VIR_FREE(content);
rc = virCgroupGetValueStr(src_group, i, "tasks", &content);
if (rc != 0)
return rc;
@@ -1085,8 +1086,6 @@ int virCgroupMoveTask(virCgroupPtr src_group, virCgroupPtr dest_group)
rc = virCgroupAddTaskStrController(dest_group, content, i);
if (rc != 0)
goto cleanup;
-
- VIR_FREE(content);
}
}
--
1.8.1.5
11 years, 5 months
[libvirt] [PATCH] Fix invalid read in virCgroupGetValueStr
by Ján Tomko
Don't check for '\n' at the end of file if zero bytes were read.
Found by valgrind:
==404== Invalid read of size 1
==404== at 0x529B09F: virCgroupGetValueStr (vircgroup.c:540)
==404== by 0x529AF64: virCgroupMoveTask (vircgroup.c:1079)
==404== by 0x1EB475: qemuSetupCgroupForEmulator (qemu_cgroup.c:1061)
==404== by 0x1D9489: qemuProcessStart (qemu_process.c:3801)
==404== by 0x18557E: qemuDomainObjStart (qemu_driver.c:5787)
==404== by 0x190FA4: qemuDomainCreateWithFlags (qemu_driver.c:5839)
Introduced by 0d0b409.
https://bugzilla.redhat.com/show_bug.cgi?id=978356
---
src/util/vircgroup.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/util/vircgroup.c b/src/util/vircgroup.c
index 0484c71..a0ee3f7 100644
--- a/src/util/vircgroup.c
+++ b/src/util/vircgroup.c
@@ -537,7 +537,7 @@ static int virCgroupGetValueStr(virCgroupPtr group,
VIR_DEBUG("Failed to read %s: %m\n", keypath);
} else {
/* Terminated with '\n' has sometimes harmful effects to the caller */
- if ((*value)[rc - 1] == '\n')
+ if (rc > 0 && (*value)[rc - 1] == '\n')
(*value)[rc - 1] = '\0';
rc = 0;
--
1.8.1.5
11 years, 5 months
[libvirt] [PATCH] [docs] Fix sample TPM XML
by Stefan Berger
Fix an error in the sample TPM XML.
Signed-off-by: Stefan Berger <stefanb(a)linux.vnet.ibm.com>
---
docs/formatdomain.html.in | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
Index: libvirt-acl/docs/formatdomain.html.in
===================================================================
--- libvirt-acl.orig/docs/formatdomain.html.in
+++ libvirt-acl/docs/formatdomain.html.in
@@ -4735,7 +4735,7 @@ qemu-kvm -net nic,model=? /dev/null
<devices>
<tpm model='tpm-tis'>
<backend type='passthrough'>
- <backend path='/dev/tpm0'/>
+ <device path='/dev/tpm0'/>
</backend>
</tpm>
</devices>
11 years, 5 months
[libvirt] [PATCHv2 00/12] remains of 'support VFIO groups'
by Laine Stump
This is what remains of yesterday's VFIO groups patchset that is
considered still useful, but wasn't yet ACKed (I've pushed the 5 that
were ACKed). In addition, I found a few more bugs in the
virPCIDeviceList handling and have included patches for those. There
are a couple of these that would be really nice to get into 1.0.7, one
in particular which is new functionality, but has 4 prerequisites:
The most important patch here from a functionality point of view is
PATCH 05/12, which adds the <iommuGroup> element to the output of
"virsh nodedev-dumpxml". This is essential for users/management
applications to have adequate information to know which devices need
to be detached from host drivers. (Unfortunately, all of the patches
prior to 05/12 are required for 05/12 to cleanly apply.)
The other new patch that I've been told is important is 08/12, which
supresses reset of PCI devices that were/will be assigned using VFIO -
apparently the vfio driver already resets the devices when
appropriate, so having libvirt do it is just redundant. (In order for
08/12 to operate reliably, 07/12 is required, and in order for 07/12
to apply, 06/12 must also be applied).
If someone ACKs any of these patches prior to DV cutting the first rc
and I don't respond on IRC (not sure what my schedule is in the next
12 hours :-), please feel free to push any of them you like, as long
as you push them in order with none missing from the middle.
Laine Stump (12):
pci: eliminate unused driver arg from virPCIDeviceDetach
pci: rename virPCIDeviceGetVFIOGroupDev to
virPCIDeviceGetIOMMUGroupDev
pci: make virPCIParseDeviceAddress public
pci: new iommu_group functions
nodedev: add iommuGroup to node device object
pci: eliminate repetitive path constructions in
virPCIDeviceBindToStub
pci: update stubDriver name in virPCIDeviceBindToStub
qemu: don't reset PCI devices being assigned with VFIO
pci: virPCIDeviceListAddCopy API
pci: eliminate leak in OOM condition
pci: fix dangling pointer in qemuDomainReAttachHostdevDevices
qemu: fix infinite loop in OOM error path
docs/formatnode.html.in | 63 +++-
docs/schemas/nodedev.rng | 11 +
src/conf/node_device_conf.c | 86 +++++-
src/conf/node_device_conf.h | 5 +-
src/libvirt_private.syms | 8 +-
src/node_device/node_device_udev.c | 21 +-
src/qemu/qemu_cgroup.c | 4 +-
src/qemu/qemu_driver.c | 2 +-
src/qemu/qemu_hostdev.c | 52 ++--
src/qemu/qemu_hotplug.c | 5 +-
src/security/security_apparmor.c | 2 +-
src/security/security_dac.c | 4 +-
src/security/security_selinux.c | 4 +-
src/util/virpci.c | 344 +++++++++++++++++----
src/util/virpci.h | 21 +-
src/xen/xen_driver.c | 2 +-
tests/nodedevschemadata/pci_8086_10c9_sriov_pf.xml | 16 +
tests/nodedevxml2xmltest.c | 1 +
18 files changed, 553 insertions(+), 98 deletions(-)
create mode 100644 tests/nodedevschemadata/pci_8086_10c9_sriov_pf.xml
--
1.7.11.7
11 years, 5 months
[libvirt] [PATCH 0/2] a couple of vlan-related fixes
by Laine Stump
One in the code, one in the documentation (the latter was pushed as trivial)
Laine Stump (2):
network: allow <vlan> in type='hostdev' networks
docs: correct and update network vlan example
docs/formatnetwork.html.in | 39 ++++++++++++++++++---------------------
src/network/bridge_driver.c | 6 ++++--
2 files changed, 22 insertions(+), 23 deletions(-)
--
1.7.11.7
11 years, 5 months