Filtering libvirt XML in Nova
I saw a request from a customer float by the other day regarding the ability to filter the XML used to create Nova instances in libvirt. The customer effectively wanted to blacklist a variety of devices (and device types). The consensus seems to be “you can’t do this right now and upstream is unlikely to accept patches that implement this behavior”, but it sounded like an interesting problem, so…
This is a fork of Nova (Juno) that includes support for an extensible filtering mechanism that is applied to the generated XML before it gets passed to libvirt.
How it works⌗
The code uses the stevedore module to handle locating and loading
filters. A filter is a Python class that implements a filter
method with the following signature:
def filter(self, xml, instance=None, context=None)
The code in nova.virt.libvirt.domxmlfilters
collects all filters
registered in the nova.filters.domxml
namespace, and then runs them
in sequence, passing the output of one filter as the output to the
next:
filters = stevedore.extension.ExtensionManager(
'nova.filters.domxml',
invoke_on_load=True,
)
def filter_domain_xml(xml,
instance=None,
context=None):
'''Filter the XML content in 'xml' through any filters registered in
the nova.filters.domxml namespace.'''
revised = xml
for filter in filters:
LOG.debug('filtering xml with filter %s',
filter.name)
revised = filter.obj.filter(revised,
instance=instance,
context=context
)
return revised
The filters are called from the _get_guest_xml
method in
nova/virt/libvirt/driver.py
.
An example filter⌗
This filter will add an interface to the libvirt default
network to
any instance created by Nova:
from lxml import etree
class AddNetworkFilter (object):
def filter(self, xml,
instance=None,
context=None):
doc = etree.fromstring(xml)
network = etree.fromstring('''
<interface type="network">
<source network="default"/>
<model type="virtio"/>
</interface>
''')
devices = doc.xpath('/domain/devices')[0]
devices.append(network)
return etree.tostring(doc, pretty_print=True)
You can find this in my demo_nova_filters repository, along with a
few other trivial examples. The above filter is registered via the
entry_points
section of the setup.py
file:
#!/usr/bin/env python
import setuptools
setuptools.setup(
name="demo_nova_filters",
version=1,
packages=['demo_nova_filters'],
entry_points={
'nova.filters.domxml': [
'prettyprint=demo_nova_filters.prettyprint:PrettyPrintFilter',
'novideo=demo_nova_filters.novideo:NoVideoFilter',
'addnetwork=demo_nova_filters.addnetwork:AddNetworkFilter',
]
},
)
And that’s it. This is almost entirely untested. While it works in some cases it doesn’t work in all cases, and it’s unlikely that I’m going to update this to work with any future version of Nova. This was really just an exercise in curiosity. Enjoy!