From 18a23b747b410c81ff222cc552943326aacbb179 Mon Sep 17 00:00:00 2001 From: Vladik Romanovsky Date: Fri, 2 Dec 2016 00:34:32 -0500 Subject: [PATCH] libvirt: expose virtual interfaces with vlans to metadata Enhance the _build_device_metadata method to expose virtual interface with vlan to the devices metadata. Previously, only interfaces with tags could be exposed. Implements: blueprint sriov-pf-passthrough-neutron-port-vlan Change-Id: I7abf16b996bd68aa7fa1e3a121eae3147a244284 --- nova/tests/unit/virt/libvirt/test_driver.py | 20 ++++++++-- nova/tests/unit/virt/test_netutils.py | 44 +++++++++++++++++++++ nova/virt/libvirt/driver.py | 18 ++++++--- nova/virt/netutils.py | 10 +++++ 4 files changed, 82 insertions(+), 10 deletions(-) create mode 100644 nova/tests/unit/virt/test_netutils.py diff --git a/nova/tests/unit/virt/libvirt/test_driver.py b/nova/tests/unit/virt/libvirt/test_driver.py index 169bf438bb66..e355aa59d1f4 100644 --- a/nova/tests/unit/virt/libvirt/test_driver.py +++ b/nova/tests/unit/virt/libvirt/test_driver.py @@ -1568,7 +1568,6 @@ class LibvirtConnTestCase(test.NoDBTestCase): vif1.network_id = 123 vif1.instance_uuid = '32dfcb37-5af1-552b-357c-be8c3aa38310' vif1.uuid = 'abec4b21-ef22-6c21-534b-ba3e3ab3a312' - vif1.tag = None vif2 = obj_vif.VirtualInterface(context=self.context) vif2.address = 'fa:16:3e:d1:28:e4' vif2.network_id = 123 @@ -1583,6 +1582,13 @@ class LibvirtConnTestCase(test.NoDBTestCase): vif3.tag = 'mytag3' vifs = [vif, vif1, vif2, vif3] + network_info = _fake_network_info(self, 4) + network_info[0]['vnic_type'] = network_model.VNIC_TYPE_DIRECT_PHYSICAL + network_info[0]['address'] = "51:5a:2c:a4:5e:1b" + network_info[0]['details'] = dict(vlan=2145) + instance_ref.info_cache = objects.InstanceInfoCache( + network_info=network_info) + with test.nested( mock.patch('nova.objects.VirtualInterfaceList' '.get_by_instance_uuid', return_value=vifs), @@ -1595,7 +1601,7 @@ class LibvirtConnTestCase(test.NoDBTestCase): metadata_obj = drvr._build_device_metadata(self.context, instance_ref) metadata = metadata_obj.devices - self.assertEqual(9, len(metadata)) + self.assertEqual(10, len(metadata)) self.assertIsInstance(metadata[0], objects.DiskMetadata) self.assertIsInstance(metadata[0].bus, @@ -1633,12 +1639,18 @@ class LibvirtConnTestCase(test.NoDBTestCase): objects.PCIDeviceBus) self.assertEqual(['mytag1'], metadata[6].tags) self.assertEqual('0000:00:03.0', metadata[6].bus.address) + + # Make sure that interface with vlan is exposed to the metadata self.assertIsInstance(metadata[7], objects.NetworkInterfaceMetadata) - self.assertEqual(['mytag2'], metadata[7].tags) + self.assertEqual('51:5a:2c:a4:5e:1b', metadata[7].mac) + self.assertEqual(2145, metadata[7].vlan) self.assertIsInstance(metadata[8], objects.NetworkInterfaceMetadata) - self.assertEqual(['mytag3'], metadata[8].tags) + self.assertEqual(['mytag2'], metadata[8].tags) + self.assertIsInstance(metadata[9], + objects.NetworkInterfaceMetadata) + self.assertEqual(['mytag3'], metadata[9].tags) @mock.patch.object(host.Host, 'get_connection') @mock.patch.object(nova.virt.libvirt.guest.Guest, 'get_xml_desc') diff --git a/nova/tests/unit/virt/test_netutils.py b/nova/tests/unit/virt/test_netutils.py new file mode 100644 index 000000000000..7ef3dd2418c2 --- /dev/null +++ b/nova/tests/unit/virt/test_netutils.py @@ -0,0 +1,44 @@ +# 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 nova.network import model as network_model +from nova import test +from nova.tests.unit import fake_network +from nova.virt import netutils + + +class TestNetUtilsTestCase(test.NoDBTestCase): + def setUp(self): + super(TestNetUtilsTestCase, self).setUp() + + def test_get_cached_vifs_with_vlan_no_nw_info(self): + # Make sure that an empty dictionary will be returned when + # nw_info is None + self.assertEqual({}, netutils.get_cached_vifs_with_vlan(None)) + + def test_get_cached_vifs_with_vlan_with_no_vlan_details(self): + network_info = fake_network.fake_get_instance_nw_info(self, 1) + network_info[0]['vnic_type'] = network_model.VNIC_TYPE_DIRECT_PHYSICAL + network_info[0]['address'] = "51:5a:2c:a4:5e:1b" + self.assertEqual({}, netutils.get_cached_vifs_with_vlan(network_info)) + + def test_get_cached_vifs_with_vlan(self): + network_info = fake_network.fake_get_instance_nw_info(self, 2) + network_info[0]['vnic_type'] = network_model.VNIC_TYPE_DIRECT_PHYSICAL + network_info[0]['address'] = "51:5a:2c:a4:5e:1b" + + network_info[1]['vnic_type'] = network_model.VNIC_TYPE_DIRECT_PHYSICAL + network_info[1]['address'] = "fa:16:3e:d1:28:e4" + network_info[1]['details'] = dict(vlan=2145) + expected = {'fa:16:3e:d1:28:e4': 2145} + self.assertEqual(expected, + netutils.get_cached_vifs_with_vlan(network_info)) diff --git a/nova/virt/libvirt/driver.py b/nova/virt/libvirt/driver.py index ffcef901ebc0..e2245d4d714e 100644 --- a/nova/virt/libvirt/driver.py +++ b/nova/virt/libvirt/driver.py @@ -7674,9 +7674,13 @@ class LibvirtDriver(driver.ComputeDriver): def _get_device_name(bdm): return block_device.strip_dev(bdm.device_name) + network_info = instance.info_cache.network_info + vlans_by_mac = netutils.get_cached_vifs_with_vlan(network_info) vifs = objects.VirtualInterfaceList.get_by_instance_uuid(context, instance.uuid) - tagged_vifs = {vif.address: vif for vif in vifs if vif.tag} + vifs_to_expose = {vif.address: vif for vif in vifs + if ('tag' in vif and vif.tag) or + vlans_by_mac.get(vif.address)} # TODO(mriedem): We should be able to avoid the DB query here by using # block_device_info['block_device_mapping'] which is passed into most # methods that call this function. @@ -7694,16 +7698,18 @@ class LibvirtDriver(driver.ComputeDriver): for dev in guest_config.devices: # Build network interfaces related metadata if isinstance(dev, vconfig.LibvirtConfigGuestInterface): - vif = tagged_vifs.get(dev.mac_addr) + vif = vifs_to_expose.get(dev.mac_addr) if not vif: continue bus = self._prepare_device_bus(dev) - device = objects.NetworkInterfaceMetadata( - mac=vif.address, - tags=[vif.tag] - ) + device = objects.NetworkInterfaceMetadata(mac=vif.address) + if 'tag' in vif and vif.tag: + device.tags = [vif.tag] if bus: device.bus = bus + vlan = vlans_by_mac.get(vif.address) + if vlan: + device.vlan = int(vlan) devices.append(device) # Build disks related metadata diff --git a/nova/virt/netutils.py b/nova/virt/netutils.py index 8dbff85a8e0a..89f2db6c564f 100644 --- a/nova/virt/netutils.py +++ b/nova/virt/netutils.py @@ -344,3 +344,13 @@ def _get_dns_services(subnet): return services return [{'type': 'dns', 'address': ip.get('address')} for ip in subnet['dns']] + + +def get_cached_vifs_with_vlan(network_info): + """Generates a dict from a list of VIFs that has a vlan tag, with + MAC, VLAN as a key, value. + """ + if network_info is None: + return {} + return {vif['address']: vif['details']['vlan'] for vif in network_info + if vif.get('details', {}).get('vlan')}