diff --git a/nova/tests/test_libvirt_vif.py b/nova/tests/test_libvirt_vif.py new file mode 100644 index 000000000000..3f9b8e1686a0 --- /dev/null +++ b/nova/tests/test_libvirt_vif.py @@ -0,0 +1,159 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 +# +# Copyright 2012 Nicira, Inc +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +from xml.etree import ElementTree + +from nova import flags +from nova import test +from nova import utils +from nova.virt import firewall +from nova.virt import vif +from nova.virt.libvirt import connection +from nova.virt.libvirt.vif import LibvirtBridgeDriver, \ + LibvirtOpenVswitchDriver, LibvirtOpenVswitchVirtualPortDriver + +FLAGS = flags.FLAGS + + +class LibvirtVifTestCase(test.TestCase): + + net = { + 'cidr': '101.168.1.0/24', + 'cidr_v6': '101:1db9::/64', + 'gateway_v6': '101:1db9::1', + 'netmask_v6': '64', + 'netmask': '255.255.255.0', + 'bridge': 'br0', + 'bridge_interface': 'eth0', + 'vlan': 99, + 'gateway': '101.168.1.1', + 'broadcast': '101.168.1.255', + 'dns1': '8.8.8.8' + } + + mapping = { + 'mac': 'ca:fe:de:ad:be:ef', + 'gateway_v6': net['gateway_v6'], + 'ips': [{'ip': '101.168.1.9'}], + 'dhcp_server': '191.168.1.1', + 'vif_uuid': 'vif-xxx-yyy-zzz' + } + + instance = { + 'uuid': 'instance-uuid' + } + + def setUp(self): + super(LibvirtVifTestCase, self).setUp() + self.flags(allow_same_net_traffic=True) + self.executes = [] + + def fake_execute(*cmd, **kwargs): + self.executes.append(cmd) + return None, None + + self.stubs.Set(utils, 'execute', fake_execute) + t = __import__('Cheetah.Template', globals(), locals(), + ['Template'], -1) + self.Template = t.Template + xml_file = open(FLAGS.libvirt_xml_template) + self.xml_template = xml_file.read() + + def _create_xml_info(self, vif_type, nics): + return { + 'type': 'qemu', + 'name': 'fake-name', + 'uuid': 'fake-uuid', + 'memory_kb': 100 * 1024, + 'basepath': 'foobar', + 'vcpus': 4, + 'rescue': False, + 'disk_prefix': '/dev/sda', + 'driver_type': 'raw', + 'root_device_type': 'disk', + 'vif_type': vif_type, + 'nics': nics, + 'ebs_root': True, + 'ephemeral_device': False, + 'volumes': [], + 'use_virtio_for_bridges': False, + 'ephemerals': []} + + def _get_instance_xml(self, driver, vif_type): + nic_dict = driver.plug(self.instance, self.net, self.mapping) + xml_info = self._create_xml_info(vif_type, [nic_dict]) + xml = str(self.Template(self.xml_template, searchList=[xml_info])) + return xml + + def test_bridge_driver(self): + d = LibvirtBridgeDriver() + xml = self._get_instance_xml(d, 'bridge') + + doc = ElementTree.fromstring(xml) + ret = doc.findall('./devices/interface') + self.assertEqual(len(ret), 1) + node = ret[0] + self.assertEqual(node.get("type"), "bridge") + br_name = node.find("source").get("bridge") + self.assertEqual(br_name, self.net['bridge']) + mac = node.find("mac").get("address") + self.assertEqual(mac, self.mapping['mac']) + + d.unplug(None, self.net, self.mapping) + + def test_ovs_ethernet_driver(self): + d = LibvirtOpenVswitchDriver() + xml = self._get_instance_xml(d, 'ethernet') + + doc = ElementTree.fromstring(xml) + ret = doc.findall('./devices/interface') + self.assertEqual(len(ret), 1) + node = ret[0] + self.assertEqual(node.get("type"), "ethernet") + dev_name = node.find("target").get("dev") + self.assertTrue(dev_name.startswith("tap")) + mac = node.find("mac").get("address") + self.assertEqual(mac, self.mapping['mac']) + script = node.find("script").get("path") + self.assertEquals(script, "") + + d.unplug(None, self.net, self.mapping) + + def test_ovs_virtualport_driver(self): + d = LibvirtOpenVswitchVirtualPortDriver() + xml = self._get_instance_xml(d, 'ovs_virtualport') + + doc = ElementTree.fromstring(xml) + ret = doc.findall('./devices/interface') + self.assertEqual(len(ret), 1) + node = ret[0] + self.assertEqual(node.get("type"), "bridge") + + br_name = node.find("source").get("bridge") + self.assertEqual(br_name, FLAGS.libvirt_ovs_bridge) + mac = node.find("mac").get("address") + self.assertEqual(mac, self.mapping['mac']) + vp = node.find("virtualport") + self.assertEqual(vp.get("type"), "openvswitch") + iface_id_found = False + for p_elem in vp.findall("parameters"): + iface_id = p_elem.get("interfaceid", None) + if iface_id: + self.assertEqual(iface_id, self.mapping['vif_uuid']) + iface_id_found = True + + self.assertTrue(iface_id_found) + d.unplug(None, self.net, self.mapping) diff --git a/nova/virt/libvirt.xml.template b/nova/virt/libvirt.xml.template index d41b09f7be38..9a216e14b856 100644 --- a/nova/virt/libvirt.xml.template +++ b/nova/virt/libvirt.xml.template @@ -130,6 +130,14 @@ + #else if $vif_type == 'ovs_virtualport' + + + + + + + #else diff --git a/nova/virt/libvirt/vif.py b/nova/virt/libvirt/vif.py index b60a9b930853..886eea77f9e6 100644 --- a/nova/virt/libvirt/vif.py +++ b/nova/virt/libvirt/vif.py @@ -103,7 +103,9 @@ class LibvirtBridgeDriver(VIFDriver): class LibvirtOpenVswitchDriver(VIFDriver): - """VIF driver for Open vSwitch.""" + """VIF driver for Open vSwitch that uses type='ethernet' + libvirt XML. Used for libvirt versions that do not support + OVS virtual port XML (0.9.10 or earlier).""" def get_dev_name(_self, iface_id): return "tap" + iface_id[0:11] @@ -132,6 +134,8 @@ class LibvirtOpenVswitchDriver(VIFDriver): "external-ids:iface-status=active", '--', 'set', 'Interface', dev, "external-ids:attached-mac=%s" % mapping['mac'], + '--', 'set', 'Interface', dev, + "external-ids:vm-uuid=%s" % instance['uuid'], run_as_root=True) result = { @@ -152,3 +156,19 @@ class LibvirtOpenVswitchDriver(VIFDriver): LOG.warning(_("Failed while unplugging vif of instance '%s'"), instance['name']) raise + + +class LibvirtOpenVswitchVirtualPortDriver(VIFDriver): + """VIF driver for Open vSwitch that uses integrated libvirt + OVS virtual port XML (introduced in libvirt 0.9.11).""" + + def plug(self, instance, network, mapping): + """ Pass data required to create OVS virtual port element""" + return { + 'bridge_name': FLAGS.libvirt_ovs_bridge, + 'ovs_interfaceid': mapping['vif_uuid'], + 'mac_address': mapping['mac']} + + def unplug(self, instance, network, mapping): + """No action needed. Libvirt takes care of cleanup""" + pass