diff --git a/nova/network/neutronv2/api.py b/nova/network/neutronv2/api.py index 8d01b71be7d4..06bbde0b512e 100644 --- a/nova/network/neutronv2/api.py +++ b/nova/network/neutronv2/api.py @@ -37,8 +37,10 @@ from nova.network import base_api from nova.network import model as network_model from nova.network.neutronv2 import constants from nova import objects +from nova.objects import fields as obj_fields from nova.pci import manager as pci_manager from nova.pci import request as pci_request +from nova.pci import utils as pci_utils from nova.pci import whitelist as pci_whitelist neutron_opts = [ @@ -643,6 +645,8 @@ class API(base_api.NetworkAPI): context, instance, request.pci_request_id, port_req_body, network=network, neutron=neutron, bind_host_id=bind_host_id) + self._populate_mac_address(instance, request.pci_request_id, + port_req_body) if request.port_id: port = ports[request.port_id] port_client.update_port(port['id'], port_req_body) @@ -716,6 +720,38 @@ class API(base_api.NetworkAPI): } port_req_body['port']['binding:profile'] = profile + @staticmethod + def _populate_mac_address(instance, pci_request_id, port_req_body): + """Add the updated MAC address value to the update_port request body. + + Currently this is done only for PF passthrough. + """ + if pci_request_id is not None: + pci_devs = pci_manager.get_instance_pci_devs( + instance, pci_request_id) + if len(pci_devs) != 1: + # NOTE(ndipanov): We shouldn't ever get here since + # InstancePCIRequest instances built from network requests + # only ever index a single device, which needs to be + # successfully claimed for this to be called as part of + # allocate_networks method + LOG.error(_LE("PCI request %s does not have a " + "unique device associated with it. Unable to " + "determine MAC address"), + pci_request, instance=instance) + return + pci_dev = pci_devs[0] + if pci_dev.dev_type == obj_fields.PciDeviceType.SRIOV_PF: + try: + mac = pci_utils.get_mac_by_pci_address(pci_dev.address) + except exception.PciDeviceNotFoundById as e: + LOG.error( + _LE("Could not determine MAC address for %(addr)s, " + "error: %(e)s"), + {"addr": pci_dev.address, "e": e}, instance=instance) + else: + port_req_body['port']['mac_address'] = mac + def _populate_neutron_extension_values(self, context, instance, pci_request_id, port_req_body, network=None, neutron=None, diff --git a/nova/tests/unit/network/test_neutronv2.py b/nova/tests/unit/network/test_neutronv2.py index 4699cb336397..e16287396423 100644 --- a/nova/tests/unit/network/test_neutronv2.py +++ b/nova/tests/unit/network/test_neutronv2.py @@ -41,6 +41,7 @@ from nova.network.neutronv2 import api as neutronapi from nova.network.neutronv2 import constants from nova import objects from nova.pci import manager as pci_manager +from nova.pci import utils as pci_utils from nova.pci import whitelist as pci_whitelist from nova import policy from nova import test @@ -3907,6 +3908,74 @@ class TestNeutronv2Portbinding(TestNeutronv2Base): self.assertEqual(profile, port_req_body['port']['binding:profile']) + def _populate_mac_address_fakes(self): + instance = fake_instance.fake_instance_obj(self.context) + pci_dev = {'vendor_id': '1377', + 'product_id': '0047', + 'address': '0000:0a:00.1', + 'dev_type': 'type-PF'} + pf = objects.PciDevice() + vf = objects.PciDevice() + pf.update_device(pci_dev) + + pci_dev['dev_type'] = 'type-VF' + vf.update_device(pci_dev) + return instance, pf, vf + + @mock.patch.object(pci_manager, 'get_instance_pci_devs') + @mock.patch.object(pci_utils, 'get_mac_by_pci_address') + def test_populate_mac_address_pf(self, mock_get_mac_by_pci_address, + mock_get_instance_pci_devs): + api = neutronapi.API() + instance, pf, vf = self._populate_mac_address_fakes() + + port_req_body = {'port': {}} + mock_get_instance_pci_devs.return_value = [pf] + mock_get_mac_by_pci_address.return_value = 'fake-mac-address' + expected_port_req_body = {'port': {'mac_address': 'fake-mac-address'}} + req = port_req_body.copy() + api._populate_mac_address(instance, 0, req) + self.assertEqual(expected_port_req_body, req) + + @mock.patch.object(pci_manager, 'get_instance_pci_devs') + @mock.patch.object(pci_utils, 'get_mac_by_pci_address') + def test_populate_mac_address_vf(self, mock_get_mac_by_pci_address, + mock_get_instance_pci_devs): + api = neutronapi.API() + instance, pf, vf = self._populate_mac_address_fakes() + + port_req_body = {'port': {}} + mock_get_instance_pci_devs.return_value = [vf] + req = port_req_body.copy() + api._populate_mac_address(instance, 42, port_req_body) + self.assertEqual(port_req_body, req) + + @mock.patch.object(pci_manager, 'get_instance_pci_devs') + @mock.patch.object(pci_utils, 'get_mac_by_pci_address') + def test_populate_mac_address_vf_fail(self, mock_get_mac_by_pci_address, + mock_get_instance_pci_devs): + api = neutronapi.API() + instance, pf, vf = self._populate_mac_address_fakes() + + port_req_body = {'port': {}} + mock_get_instance_pci_devs.return_value = [vf] + mock_get_mac_by_pci_address.side_effect = ( + exception.PciDeviceNotFoundById) + req = port_req_body.copy() + api._populate_mac_address(instance, 42, port_req_body) + self.assertEqual(port_req_body, req) + + @mock.patch.object(pci_manager, 'get_instance_pci_devs') + def test_populate_mac_address_no_device(self, mock_get_instance_pci_devs): + api = neutronapi.API() + instance, pf, vf = self._populate_mac_address_fakes() + + port_req_body = {'port': {}} + mock_get_instance_pci_devs.return_value = [] + req = port_req_body.copy() + api._populate_mac_address(instance, 42, port_req_body) + self.assertEqual(port_req_body, req) + def _test_update_port_binding_false(self, func_name, *args): api = neutronapi.API() func = getattr(api, func_name)