From 77ac42d2ee664263dd1dfd391dac4d8e062875e0 Mon Sep 17 00:00:00 2001 From: Rodolfo Alonso Hernandez Date: Tue, 9 Mar 2021 17:46:47 +0000 Subject: [PATCH] SR-IOV agent can handle ports with same MAC addresses SR-IOV agent can handle ports with same MAC address (located in different networks). The agent can retrieve, from the system, the MAC address and the PCI slot; because the PCI slot is unique per port in the same host, this parameter is used to match with the Neutron port ID stored in the database (published via RPC). RPC API bumped to version 1.9. Closes-Bug: #1791159 Change-Id: Id8c3e0485bebc55c778ecaadaabca1c28ec56205 --- neutron/agent/rpc.py | 21 +- neutron/objects/ports.py | 17 + neutron/plugins/ml2/db.py | 7 - .../mech_sriov/agent/eswitch_manager.py | 4 +- .../mech_sriov/agent/sriov_nic_agent.py | 105 +++-- neutron/plugins/ml2/plugin.py | 5 +- neutron/plugins/ml2/rpc.py | 44 +- neutron/tests/unit/objects/test_ports.py | 32 ++ .../mech_sriov/agent/test_eswitch_manager.py | 69 ++- .../mech_sriov/agent/test_sriov_nic_agent.py | 395 ++++++------------ neutron/tests/unit/plugins/ml2/test_db.py | 10 - neutron/tests/unit/plugins/ml2/test_rpc.py | 13 +- ...duplicated-mac-ports-a861b0ff800c3172.yaml | 20 + 13 files changed, 344 insertions(+), 398 deletions(-) create mode 100644 releasenotes/notes/sriov-agent-duplicated-mac-ports-a861b0ff800c3172.yaml diff --git a/neutron/agent/rpc.py b/neutron/agent/rpc.py index 990aef9d209..a5e75a69b4b 100644 --- a/neutron/agent/rpc.py +++ b/neutron/agent/rpc.py @@ -13,6 +13,7 @@ # License for the specific language governing permissions and limitations # under the License. +import collections from datetime import datetime import itertools @@ -38,6 +39,7 @@ from neutron import objects LOG = logging.getLogger(__name__) BINDING_DEACTIVATE = 'binding_deactivate' +DeviceInfo = collections.namedtuple('DeviceInfo', 'mac pci_slot') def create_consumers(endpoints, prefix, topic_details, start_listening=True): @@ -118,19 +120,26 @@ class PluginApi(object): 1.7 - Support get_ports_by_vnic_type_and_host 1.8 - Rename agent_restarted to refresh_tunnels in update_device_list to reflect its expanded purpose + 1.9 - Support for device definition as DeviceInfo(mac, pci_info) for: + - get_device_details + - get_devices_details_list (indirectly, calls get_device_details) + - update_device_down + - update_device_up + - update_device_list (indirectly, called from update_device_down + and update_device_up) ''' def __init__(self, topic): - target = oslo_messaging.Target(topic=topic, version='1.0') + target = oslo_messaging.Target(topic=topic, version='1.9') self.client = lib_rpc.get_client(target) def get_device_details(self, context, device, agent_id, host=None): - cctxt = self.client.prepare() + cctxt = self.client.prepare(version='1.9') return cctxt.call(context, 'get_device_details', device=device, agent_id=agent_id, host=host) def get_devices_details_list(self, context, devices, agent_id, host=None): - cctxt = self.client.prepare(version='1.3') + cctxt = self.client.prepare(version='1.9') return cctxt.call(context, 'get_devices_details_list', devices=devices, agent_id=agent_id, host=host) @@ -155,18 +164,18 @@ class PluginApi(object): agent_id=agent_id, host=host) def update_device_down(self, context, device, agent_id, host=None): - cctxt = self.client.prepare() + cctxt = self.client.prepare(version='1.9') return cctxt.call(context, 'update_device_down', device=device, agent_id=agent_id, host=host) def update_device_up(self, context, device, agent_id, host=None): - cctxt = self.client.prepare() + cctxt = self.client.prepare(version='1.9') return cctxt.call(context, 'update_device_up', device=device, agent_id=agent_id, host=host) def update_device_list(self, context, devices_up, devices_down, agent_id, host, refresh_tunnels=False): - cctxt = self.client.prepare(version='1.8') + cctxt = self.client.prepare(version='1.9') ret_devices_up = [] failed_devices_up = [] diff --git a/neutron/objects/ports.py b/neutron/objects/ports.py index b8885518200..2466f96ffb4 100644 --- a/neutron/objects/ports.py +++ b/neutron/objects/ports.py @@ -14,6 +14,7 @@ import netaddr from neutron_lib import constants +from neutron_lib.db import api as db_api from neutron_lib.objects import common_types from neutron_lib.utils import net as net_utils from oslo_log import log as logging @@ -688,3 +689,19 @@ class Port(base.NeutronDbObject): return context.session.query(models_v2.Port).filter( models_v2.IPAllocation.port_id == models_v2.Port.id).filter( models_v2.IPAllocation.subnet_id == subnet_id).all() + + @classmethod + def get_port_from_mac_and_pci_slot(cls, context, device_mac, + pci_slot=None): + with db_api.CONTEXT_READER.using(context): + ports = cls.get_objects(context, mac_address=device_mac) + + if not ports: + return + elif not pci_slot: + return ports.pop() + else: + for port in ports: + for _binding in port.bindings: + if _binding.get('profile', {}).get('pci_slot') == pci_slot: + return port diff --git a/neutron/plugins/ml2/db.py b/neutron/plugins/ml2/db.py index 0e4925b3b1c..2abe76ea8b4 100644 --- a/neutron/plugins/ml2/db.py +++ b/neutron/plugins/ml2/db.py @@ -143,13 +143,6 @@ def get_port(context, port_id): return -@db_api.CONTEXT_READER -def get_port_from_device_mac(context, device_mac): - LOG.debug("get_port_from_device_mac() called for mac %s", device_mac) - ports = port_obj.Port.get_objects(context, mac_address=device_mac) - return ports.pop() if ports else None - - def get_ports_and_sgs(context, port_ids): """Get ports from database with security group info.""" diff --git a/neutron/plugins/ml2/drivers/mech_sriov/agent/eswitch_manager.py b/neutron/plugins/ml2/drivers/mech_sriov/agent/eswitch_manager.py index 98f1fbc0e08..cb9e942503a 100644 --- a/neutron/plugins/ml2/drivers/mech_sriov/agent/eswitch_manager.py +++ b/neutron/plugins/ml2/drivers/mech_sriov/agent/eswitch_manager.py @@ -21,6 +21,7 @@ from neutron_lib.utils import helpers from oslo_log import log as logging from neutron._i18n import _ +from neutron.agent import rpc as agent_rpc from neutron.plugins.ml2.drivers.mech_sriov.agent.common \ import exceptions as exc from neutron.plugins.ml2.drivers.mech_sriov.agent import pci_lib @@ -185,7 +186,8 @@ class EmbSwitch(object): for pci_slot, vf_index in self.pci_slot_map.items(): mac = self.get_pci_device(pci_slot) if mac: - assigned_devices_info.append((mac, pci_slot)) + assigned_devices_info.append( + agent_rpc.DeviceInfo(mac, pci_slot)) return assigned_devices_info def get_device_state(self, pci_slot): diff --git a/neutron/plugins/ml2/drivers/mech_sriov/agent/sriov_nic_agent.py b/neutron/plugins/ml2/drivers/mech_sriov/agent/sriov_nic_agent.py index cd38b3c5d34..87979e8e98c 100644 --- a/neutron/plugins/ml2/drivers/mech_sriov/agent/sriov_nic_agent.py +++ b/neutron/plugins/ml2/drivers/mech_sriov/agent/sriov_nic_agent.py @@ -88,12 +88,9 @@ class SriovNicSwitchRpcCallbacks(sg_rpc.SecurityGroupAgentRpcCallbackMixin): # notifications there is no guarantee the notifications are # processed in the same order as the relevant API requests. mac = port['mac_address'] - pci_slot = None - if port.get(portbindings.PROFILE): - pci_slot = port[portbindings.PROFILE].get('pci_slot') - + pci_slot = port.get(portbindings.PROFILE, {}).get('pci_slot') if pci_slot: - self.agent.updated_devices.add((mac, pci_slot)) + self.agent.updated_devices.add(agent_rpc.DeviceInfo(mac, pci_slot)) LOG.debug("port_update RPC received for port: %(id)s with MAC " "%(mac)s and PCI slot %(pci_slot)s slot", {'id': port['id'], 'mac': mac, 'pci_slot': pci_slot}) @@ -277,97 +274,98 @@ class SriovNicSwitchAgent(object): # If one of the above operations fails => resync with plugin return (resync_a | resync_b) - def treat_device(self, device, pci_slot, admin_state_up, spoofcheck=True, + def treat_device(self, device_info, admin_state_up, spoofcheck=True, propagate_uplink_state=False): - if self.eswitch_mgr.device_exists(device, pci_slot): + if self.eswitch_mgr.device_exists(device_info.mac, + device_info.pci_slot): try: - self.eswitch_mgr.set_device_spoofcheck(device, pci_slot, - spoofcheck) + self.eswitch_mgr.set_device_spoofcheck( + device_info.mac, device_info.pci_slot, spoofcheck) except Exception: LOG.warning("Failed to set spoofcheck for device %s", - device) + device_info) LOG.info("Device %(device)s spoofcheck %(spoofcheck)s", - {"device": device, "spoofcheck": spoofcheck}) + {"device": device_info, "spoofcheck": spoofcheck}) try: - self.eswitch_mgr.set_device_state(device, pci_slot, - admin_state_up, - propagate_uplink_state) + self.eswitch_mgr.set_device_state( + device_info.mac, device_info.pci_slot, admin_state_up, + propagate_uplink_state) except priv_ip_lib.InterfaceOperationNotSupported: - LOG.warning("Device %s does not support state change", device) + LOG.warning("Device %s does not support state change", + device_info) except pyroute2.NetlinkError: - LOG.warning("Failed to set device %s state", device) + LOG.warning("Failed to set device %s state", device_info) return False else: - LOG.info("No device with MAC %s defined on agent.", device) + LOG.info("No device %s defined on agent.", device_info) return False return True - def _update_network_ports(self, network_id, port_id, mac_pci_slot): - self._clean_network_ports(mac_pci_slot) + def _update_network_ports(self, network_id, port_id, device): + self._clean_network_ports(device) self.network_ports[network_id].append({ "port_id": port_id, - "device": mac_pci_slot}) + "device": device}) - def _clean_network_ports(self, mac_pci_slot): - for netid, ports_list in self.network_ports.items(): - for port_data in ports_list: - if mac_pci_slot == port_data['device']: + def _clean_network_ports(self, device_to_clean): + for netid, ports_list in dict(self.network_ports).items(): + for port_data in list(ports_list): + if device_to_clean == port_data['device']: ports_list.remove(port_data) - if ports_list == []: + if not ports_list: self.network_ports.pop(netid) return port_data['port_id'] def treat_devices_added_updated(self, devices_info): try: - macs_list = set([device_info[0] for device_info in devices_info]) - devices_details_list = self.plugin_rpc.get_devices_details_list( - self.context, macs_list, self.agent_id, self.conf.host) + rpc_devices_details = self.plugin_rpc.get_devices_details_list( + self.context, devices_info, self.agent_id, self.conf.host) except Exception as e: LOG.debug("Unable to get port details for devices " "with MAC addresses %(devices)s: %(e)s", - {'devices': macs_list, 'e': e}) + {'devices': devices_info, 'e': e}) # resync is needed return True devices_up = set() devices_down = set() resync = False - for device_details in devices_details_list: - device = device_details['device'] - LOG.debug("Port with MAC address %s is added", device) + for device_details in rpc_devices_details: + mac_address = device_details['device'] + LOG.debug("Port with MAC address %s is added", mac_address) if 'port_id' in device_details: LOG.info("Port %(device)s updated. Details: %(details)s", - {'device': device, 'details': device_details}) + {'device': mac_address, 'details': device_details}) port_id = device_details['port_id'] profile = device_details['profile'] + device_info = agent_rpc.DeviceInfo(mac_address, + profile.get('pci_slot')) spoofcheck = device_details.get('port_security_enabled', True) if self.treat_device( - device, - profile.get('pci_slot'), + device_info, device_details['admin_state_up'], spoofcheck, device_details['propagate_uplink_status']): if device_details['admin_state_up']: - devices_up.add(device) + devices_up.add(device_info) else: - devices_down.add(device) + devices_down.add(device_info) else: resync = True self._update_network_ports(device_details['network_id'], - port_id, - (device, profile.get('pci_slot'))) + port_id, device_info) self.ext_manager.handle_port(self.context, device_details) elif n_constants.NO_ACTIVE_BINDING in device_details: # Port was added but its binding in this agent # hasn't been activated yet. It will be treated as # added when binding is activated LOG.info("Device with MAC %s has no active binding in host", - device) + mac_address) else: LOG.info("Device with MAC %s not defined on plugin", - device) + mac_address) self.plugin_rpc.update_device_list(self.context, devices_up, devices_down, @@ -378,39 +376,32 @@ class SriovNicSwitchAgent(object): def treat_devices_removed(self, devices): resync = False for device in devices: - mac, pci_slot = device LOG.info("Removing device with MAC address %(mac)s and " "PCI slot %(pci_slot)s", - {'mac': mac, 'pci_slot': pci_slot}) + {'mac': device.mac, 'pci_slot': device.pci_slot}) try: port_id = self._clean_network_ports(device) if port_id: port = {'port_id': port_id, - 'device': mac, - 'profile': {'pci_slot': pci_slot}} + 'device': device.mac, + 'profile': {'pci_slot': device.pci_slot}} self.ext_manager.delete_port(self.context, port) else: - LOG.warning("port_id to device with MAC " - "%s not found", mac) + LOG.warning("port_id to device %s not found", device) dev_details = self.plugin_rpc.update_device_down(self.context, - mac, + device, self.agent_id, cfg.CONF.host) except Exception as e: - LOG.debug("Removing port failed for device with MAC address " - "%(mac)s and PCI slot %(pci_slot)s due to %(exc)s", - {'mac': mac, 'pci_slot': pci_slot, 'exc': e}) + LOG.debug("Removing port failed for device %(device)s due to " + "%(exc)s", {'device': device, 'exc': e}) resync = True continue if dev_details['exists']: - LOG.info("Port with MAC %(mac)s and PCI slot " - "%(pci_slot)s updated.", - {'mac': mac, 'pci_slot': pci_slot}) + LOG.info("Port from device %s updated", device) else: - LOG.debug("Device with MAC %(mac)s and PCI slot " - "%(pci_slot)s not defined on plugin", - {'mac': mac, 'pci_slot': pci_slot}) + LOG.debug("Device %s not defined on plugin", device) return resync def process_activated_bindings(self, device_info, activated_bindings_copy): diff --git a/neutron/plugins/ml2/plugin.py b/neutron/plugins/ml2/plugin.py index 76e29072269..3a3feb60e94 100644 --- a/neutron/plugins/ml2/plugin.py +++ b/neutron/plugins/ml2/plugin.py @@ -2251,7 +2251,7 @@ class Ml2Plugin(db_base_plugin_v2.NeutronDbPluginV2, return ports @staticmethod - def _device_to_port_id(context, device): + def _device_to_port_id(context, device, pci_slot=None): # REVISIT(rkukura): Consider calling into MechanismDrivers to # process device names, or having MechanismDrivers supply list # of device prefixes to strip. @@ -2261,7 +2261,8 @@ class Ml2Plugin(db_base_plugin_v2.NeutronDbPluginV2, # REVISIT(irenab): Consider calling into bound MD to # handle the get_device_details RPC if not uuidutils.is_uuid_like(device): - port = db.get_port_from_device_mac(context, device) + port = ports_obj.Port.get_port_from_mac_and_pci_slot( + context, device, pci_slot=pci_slot) if port: return port.id return device diff --git a/neutron/plugins/ml2/rpc.py b/neutron/plugins/ml2/rpc.py index c0356366f8f..6d99b9245f6 100644 --- a/neutron/plugins/ml2/rpc.py +++ b/neutron/plugins/ml2/rpc.py @@ -57,8 +57,14 @@ class RpcCallbacks(type_tunnel.TunnelRpcCallbackMixin): # 1.7 Support get_ports_by_vnic_type_and_host # 1.8 Rename agent_restarted to refresh_tunnels in # update_device_list to reflect its expanded purpose - - target = oslo_messaging.Target(version='1.8') + # 1.9 Support for device definition as DeviceInfo(mac, pci_info) for: + # - get_device_details + # - get_devices_details_list (indirectly, calls get_device_details) + # - update_device_down + # - update_device_up + # - update_device_list (indirectly, called from update_device_down + # and update_device_up) + target = oslo_messaging.Target(version='1.9') def __init__(self, notifier, type_manager): self.setup_tunnel_callback_mixin(notifier, type_manager) @@ -78,6 +84,14 @@ class RpcCallbacks(type_tunnel.TunnelRpcCallbackMixin): kwargs.get('host'), kwargs.get('device') or kwargs.get('network')) + @staticmethod + def _device_to_mac_pci_slot(device): + """This method will keep backwards compatibility with agents < 1.9""" + # NOTE(ralonsoh): this method can be removed in Z release. + if isinstance(device, list): # RPC loads from agent_rpc.DeviceInfo + return device[0], device[1] + return device, None + def get_device_details(self, rpc_context, **kwargs): """Agent requests device details.""" agent_id, host, device = self._get_request_details(kwargs) @@ -90,7 +104,9 @@ class RpcCallbacks(type_tunnel.TunnelRpcCallbackMixin): {'device': device, 'agent_id': agent_id, 'host': host}) plugin = directory.get_plugin() - port_id = plugin._device_to_port_id(rpc_context, device) + mac_or_port_id, pci_slot = self._device_to_mac_pci_slot(device) + port_id = plugin._device_to_port_id(rpc_context, mac_or_port_id, + pci_slot=pci_slot) port_context = plugin.get_bound_port_context(rpc_context, port_id, host, @@ -98,8 +114,8 @@ class RpcCallbacks(type_tunnel.TunnelRpcCallbackMixin): if not port_context: LOG.debug("Device %(device)s requested by agent " "%(agent_id)s not found in database", - {'device': device, 'agent_id': agent_id}) - return {'device': device} + {'device': mac_or_port_id, 'agent_id': agent_id}) + return {'device': mac_or_port_id} port = port_context.current # caching information about networks for future use @@ -108,7 +124,7 @@ class RpcCallbacks(type_tunnel.TunnelRpcCallbackMixin): cached_networks[port['network_id']] = ( port_context.network.current) result = self._get_device_details(rpc_context, agent_id=agent_id, - host=host, device=device, + host=host, device=mac_or_port_id, port_context=port_context) if 'network_id' in result: # success so we update status @@ -244,13 +260,15 @@ class RpcCallbacks(type_tunnel.TunnelRpcCallbackMixin): "%(agent_id)s", {'device': device, 'agent_id': agent_id}) plugin = directory.get_plugin() - port_id = plugin._device_to_port_id(rpc_context, device) + mac_or_device, pci_slot = self._device_to_mac_pci_slot(device) + port_id = plugin._device_to_port_id(rpc_context, mac_or_device, + pci_slot=pci_slot) port_exists = True if (host and not plugin.port_bound_to_host(rpc_context, port_id, host)): LOG.debug("Device %(device)s not bound to the" " agent host %(host)s", - {'device': device, 'host': host}) + {'device': mac_or_device, 'host': host}) else: try: port_exists = bool(plugin.update_port_status( @@ -259,12 +277,12 @@ class RpcCallbacks(type_tunnel.TunnelRpcCallbackMixin): port_exists = False LOG.debug("delete_port and update_device_down are being " "executed concurrently. Ignoring StaleDataError.") - return {'device': device, + return {'device': mac_or_device, 'exists': port_exists} self.notify_l2pop_port_wiring(port_id, rpc_context, n_const.PORT_STATUS_DOWN, host) - return {'device': device, + return {'device': mac_or_device, 'exists': port_exists} @profiler.trace("rpc") @@ -278,12 +296,14 @@ class RpcCallbacks(type_tunnel.TunnelRpcCallbackMixin): LOG.debug("Device %(device)s up at agent %(agent_id)s", {'device': device, 'agent_id': agent_id}) plugin = directory.get_plugin() - port_id = plugin._device_to_port_id(rpc_context, device) + mac_or_device, pci_slot = self._device_to_mac_pci_slot(device) + port_id = plugin._device_to_port_id(rpc_context, mac_or_device, + pci_slot=pci_slot) port = plugin.port_bound_to_host(rpc_context, port_id, host) if host and not port: LOG.debug("Device %(device)s not bound to the" " agent host %(host)s", - {'device': device, 'host': host}) + {'device': mac_or_device, 'host': host}) # this might mean that a VM is in the process of live migration # and vif was plugged on the destination compute node; # need to notify nova explicitly diff --git a/neutron/tests/unit/objects/test_ports.py b/neutron/tests/unit/objects/test_ports.py index fa3c832ebb6..77cd1688a2b 100644 --- a/neutron/tests/unit/objects/test_ports.py +++ b/neutron/tests/unit/objects/test_ports.py @@ -13,6 +13,7 @@ from unittest import mock import netaddr +from neutron_lib.api.definitions import portbindings from neutron_lib import constants from neutron_lib.tests import tools from oslo_utils import uuidutils @@ -609,3 +610,34 @@ class PortDbObjectTestCase(obj_test_base.BaseDbObjectTestCase, (0, 1), (len(dhcp_ports), count), ) + + def test_get_port_from_mac_and_pci_slot_no_ports(self): + self.assertIsNone( + ports.Port.get_port_from_mac_and_pci_slot(self.context, + 'ca:fe:ca:fe:ca:fe')) + + def test_get_port_from_mac_and_pci_slot_no_pci_slot(self): + obj = self._make_object(self.obj_fields[0]) + obj.create() + mac_address = obj.mac_address + port = ports.Port.get_port_from_mac_and_pci_slot(self.context, + mac_address) + self.assertEqual(obj.id, port.id) + + def test_get_port_from_mac_and_pci_slot(self): + obj = self._make_object(self.obj_fields[0]) + obj.create() + mac_address = obj.mac_address + pci_slot = '0000:04:00.1' + port = ports.Port.get_port_from_mac_and_pci_slot( + self.context, mac_address, pci_slot=pci_slot) + self.assertIsNone(port) + + port_binding = ports.PortBinding( + self.context, port_id=obj.id, host='any_host', + vif_type=portbindings.VIF_TYPE_OTHER, + vnic_type=portbindings.VNIC_DIRECT, profile={'pci_slot': pci_slot}) + port_binding.create() + port = ports.Port.get_port_from_mac_and_pci_slot( + self.context, mac_address, pci_slot=pci_slot) + self.assertEqual(obj.id, port.id) diff --git a/neutron/tests/unit/plugins/ml2/drivers/mech_sriov/agent/test_eswitch_manager.py b/neutron/tests/unit/plugins/ml2/drivers/mech_sriov/agent/test_eswitch_manager.py index 3799c6c8e75..83d1ef7e02a 100644 --- a/neutron/tests/unit/plugins/ml2/drivers/mech_sriov/agent/test_eswitch_manager.py +++ b/neutron/tests/unit/plugins/ml2/drivers/mech_sriov/agent/test_eswitch_manager.py @@ -16,9 +16,11 @@ import os from unittest import mock +from neutron.agent import rpc as agent_rpc from neutron.plugins.ml2.drivers.mech_sriov.agent.common \ import exceptions as exc from neutron.plugins.ml2.drivers.mech_sriov.agent import eswitch_manager as esm +from neutron.plugins.ml2.drivers.mech_sriov.agent import pci_lib from neutron.tests import base @@ -429,51 +431,38 @@ class TestEmbSwitch(base.BaseTestCase): return_value=self.SCANNED_DEVICES): self.emb_switch = esm.EmbSwitch(self.DEV_NAME, exclude_devices) - @mock.patch("neutron.plugins.ml2.drivers.mech_sriov.agent." - "eswitch_manager.PciOsWrapper.scan_vf_devices", - return_value=[(PCI_SLOT, 0)]) - def test_get_assigned_devices_info(self, *args): - emb_switch = esm.EmbSwitch(self.DEV_NAME, ()) - with mock.patch("neutron.plugins.ml2.drivers.mech_sriov.agent.pci_lib." - "PciDeviceIPWrapper.get_assigned_macs", - return_value={0: self.ASSIGNED_MAC}),\ - mock.patch("neutron.plugins.ml2.drivers.mech_sriov.agent." - "eswitch_manager.PciOsWrapper.pf_device_exists", - return_value=True), \ - mock.patch("neutron.plugins.ml2.drivers.mech_sriov.agent." - "eswitch_manager.PciOsWrapper." - "is_assigned_vf_direct", return_value=True): - result = emb_switch.get_assigned_devices_info() - self.assertIn(self.ASSIGNED_MAC, list(result)[0]) - self.assertIn(self.PCI_SLOT, list(result)[0]) + def test_get_assigned_devices_info(self): + with mock.patch.object(pci_lib.PciDeviceIPWrapper, 'get_assigned_macs', + return_value={0: self.ASSIGNED_MAC}), \ + mock.patch.object(esm.PciOsWrapper, 'pf_device_exists', + return_value=True), \ + mock.patch.object(esm.PciOsWrapper, 'is_assigned_vf_direct', + return_value=True): + result = self.emb_switch.get_assigned_devices_info() + device_info = agent_rpc.DeviceInfo(self.ASSIGNED_MAC, + self.PCI_SLOT) + self.assertEqual(1, len(result)) + self.assertEqual(device_info, result[0]) - @mock.patch("neutron.plugins.ml2.drivers.mech_sriov.agent." - "eswitch_manager.PciOsWrapper.scan_vf_devices", - return_value=SCANNED_DEVICES) - def test_get_assigned_devices_info_multiple_slots(self, *args): - emb_switch = esm.EmbSwitch(self.DEV_NAME, ()) - with mock.patch("neutron.plugins.ml2.drivers.mech_sriov.agent.pci_lib." - "PciDeviceIPWrapper.get_assigned_macs", - return_value=self.VF_TO_MAC_MAPPING),\ - mock.patch("neutron.plugins.ml2.drivers.mech_sriov.agent." - "eswitch_manager.PciOsWrapper." - "is_assigned_vf_direct", return_value=True): - devices_info = emb_switch.get_assigned_devices_info() + def test_get_assigned_devices_info_multiple_slots(self): + with mock.patch.object(pci_lib.PciDeviceIPWrapper, 'get_assigned_macs', + return_value=self.VF_TO_MAC_MAPPING), \ + mock.patch.object(esm.PciOsWrapper, 'pf_device_exists', + return_value=True), \ + mock.patch.object(esm.PciOsWrapper, 'is_assigned_vf_direct', + return_value=True): + devices_info = self.emb_switch.get_assigned_devices_info() for device_info in devices_info: - mac = device_info[0] - pci_slot = device_info[1] - self.assertEqual( - self.EXPECTED_MAC_TO_PCI[mac], pci_slot) + self.assertEqual(self.EXPECTED_MAC_TO_PCI[device_info.mac], + device_info.pci_slot) def test_get_assigned_devices_empty(self): - with mock.patch("neutron.plugins.ml2.drivers.mech_sriov.agent." - "eswitch_manager.PciOsWrapper.is_assigned_vf_direct", - return_value=False), \ - mock.patch("neutron.plugins.ml2.drivers.mech_sriov.agent." - "eswitch_manager.PciOsWrapper." - "is_assigned_vf_macvtap", return_value=False): + with mock.patch.object(esm.PciOsWrapper, 'is_assigned_vf_direct', + return_value=False), \ + mock.patch.object(esm.PciOsWrapper, 'is_assigned_vf_macvtap', + return_value=False): result = self.emb_switch.get_assigned_devices_info() - self.assertFalse(result) + self.assertEqual([], result) def test_get_device_state_ok(self): with mock.patch("neutron.plugins.ml2.drivers.mech_sriov.agent.pci_lib." diff --git a/neutron/tests/unit/plugins/ml2/drivers/mech_sriov/agent/test_sriov_nic_agent.py b/neutron/tests/unit/plugins/ml2/drivers/mech_sriov/agent/test_sriov_nic_agent.py index 3934bba3c75..ede87aa439b 100644 --- a/neutron/tests/unit/plugins/ml2/drivers/mech_sriov/agent/test_sriov_nic_agent.py +++ b/neutron/tests/unit/plugins/ml2/drivers/mech_sriov/agent/test_sriov_nic_agent.py @@ -13,6 +13,7 @@ # See the License for the specific language governing permissions and # limitations under the License. +import copy from unittest import mock from neutron_lib.api.definitions import portbindings @@ -30,6 +31,30 @@ from neutron.tests import base DEVICE_MAC = '11:22:33:44:55:66' PCI_SLOT = "0000:06:00.1" +DEV1 = agent_rpc.DeviceInfo('mac1', 'pci_slot1') +DEV2 = agent_rpc.DeviceInfo('mac2', 'pci_slot2') +DEV3 = agent_rpc.DeviceInfo('mac3', 'pci_slot3') +DEV4 = agent_rpc.DeviceInfo('mac4', 'pci_slot4') +RPC_DEV1 = {'device': DEV1.mac, + 'port_id': 'port123', + 'network_id': 'net123', + 'admin_state_up': True, + 'propagate_uplink_status': False, + 'network_type': 'vlan', + 'segmentation_id': 100, + 'profile': {'pci_slot': DEV1.pci_slot}, + 'physical_network': 'physnet1', + 'port_security_enabled': False} +RPC_DEV2 = {'device': DEV2.mac, + 'port_id': 'port321', + 'network_id': 'net123', + 'admin_state_up': True, + 'propagate_uplink_status': False, + 'network_type': 'vlan', + 'segmentation_id': 100, + 'profile': {'pci_slot': DEV2.pci_slot}, + 'physical_network': 'physnet1', + 'port_security_enabled': False} class TestSriovAgent(base.BaseTestCase): @@ -78,7 +103,7 @@ class TestSriovAgent(base.BaseTestCase): def test_treat_devices_removed_with_existed_device(self, *args): agent = sriov_nic_agent.SriovNicSwitchAgent({}, {}, 0, {}, {}, {}) - devices = [(DEVICE_MAC, PCI_SLOT)] + devices = {agent_rpc.DeviceInfo(DEVICE_MAC, PCI_SLOT)} with mock.patch.object(agent.plugin_rpc, "update_device_down") as fn_udd: fn_udd.return_value = {'device': DEVICE_MAC, @@ -89,7 +114,7 @@ class TestSriovAgent(base.BaseTestCase): def test_treat_devices_removed_with_not_existed_device(self, *args): agent = sriov_nic_agent.SriovNicSwitchAgent({}, {}, 0, {}, {}, {}) - devices = [(DEVICE_MAC, PCI_SLOT)] + devices = {agent_rpc.DeviceInfo(DEVICE_MAC, PCI_SLOT)} with mock.patch.object(agent.plugin_rpc, "update_device_down") as fn_udd: fn_udd.return_value = {'device': DEVICE_MAC, @@ -103,7 +128,7 @@ class TestSriovAgent(base.BaseTestCase): def test_treat_devices_removed_failed(self, *args): agent = sriov_nic_agent.SriovNicSwitchAgent({}, {}, 0, {}, {}, {}) - devices = [(DEVICE_MAC, PCI_SLOT)] + devices = {agent_rpc.DeviceInfo(DEVICE_MAC, PCI_SLOT)} with mock.patch.object(agent.plugin_rpc, "update_device_down") as fn_udd: fn_udd.side_effect = Exception() @@ -134,51 +159,51 @@ class TestSriovAgent(base.BaseTestCase): self.mock_scan_devices(expected, mock_current, registered, updated) def test_scan_devices_no_changes(self): - registered = set(['1', '2']) + registered = {DEV1, DEV2} updated = set() - mock_current = set(['1', '2']) - expected = {'current': set(['1', '2']), + mock_current = {DEV1, DEV2} + expected = {'current': {DEV1, DEV2}, 'updated': set(), 'added': set(), 'removed': set()} self.mock_scan_devices(expected, mock_current, registered, updated) def test_scan_devices_new_and_removed(self): - registered = set(['1', '2']) + registered = {DEV1, DEV2} updated = set() - mock_current = set(['2', '3']) - expected = {'current': set(['2', '3']), + mock_current = {DEV2, DEV3} + expected = {'current': {DEV2, DEV3}, 'updated': set(), - 'added': set(['3']), - 'removed': set(['1'])} + 'added': {DEV3}, + 'removed': {DEV1}} self.mock_scan_devices(expected, mock_current, registered, updated) def test_scan_devices_updated_and_removed(self): - registered = set(['1', '2']) - # '1' is in removed and updated tuple - updated = set(['1']) - mock_current = set(['2', '3']) - expected = {'current': set(['2', '3']), + registered = {DEV1, DEV2} + # 'DEV1' is in removed and updated tuple + updated = {DEV1} + mock_current = {DEV2, DEV3} + expected = {'current': {DEV2, DEV3}, 'updated': set(), - 'added': set(['3']), - 'removed': set(['1'])} + 'added': {DEV3}, + 'removed': {DEV1}} self.mock_scan_devices(expected, mock_current, registered, updated) def test_scan_devices_new_updates(self): - registered = set(['1']) - updated = set(['2']) - mock_current = set(['1', '2']) - expected = {'current': set(['1', '2']), - 'updated': set(['2']), - 'added': set(['2']), + registered = {DEV1} + updated = {DEV2} + mock_current = {DEV1, DEV2} + expected = {'current': {DEV1, DEV2}, + 'updated': {DEV2}, + 'added': {DEV2}, 'removed': set()} self.mock_scan_devices(expected, mock_current, registered, updated) def test_scan_devices_updated_missing(self): - registered = set(['1']) - updated = set(['2']) - mock_current = set(['1']) - expected = {'current': set(['1']), + registered = {DEV1} + updated = {DEV2} + mock_current = {DEV1} + expected = {'current': {DEV1}, 'updated': set(), 'added': set(), 'removed': set()} @@ -187,9 +212,9 @@ class TestSriovAgent(base.BaseTestCase): def test_process_network_devices(self): agent = self.agent device_info = {'current': set(), - 'added': set(['mac3', 'mac4']), - 'updated': set(['mac2', 'mac3']), - 'removed': set(['mac1'])} + 'added': {DEV3, DEV4}, + 'updated': {DEV2, DEV3}, + 'removed': {DEV1}} agent.sg_agent.prepare_devices_filter = mock.Mock() agent.sg_agent.refresh_firewall = mock.Mock() agent.treat_devices_added_updated = mock.Mock(return_value=False) @@ -197,226 +222,115 @@ class TestSriovAgent(base.BaseTestCase): agent.process_network_devices(device_info) - agent.sg_agent.prepare_devices_filter.assert_called_with( - set(['mac3', 'mac4'])) + agent.sg_agent.prepare_devices_filter.assert_called_with({DEV3, DEV4}) self.assertTrue(agent.sg_agent.refresh_firewall.called) - agent.treat_devices_added_updated.assert_called_with(set(['mac2', - 'mac3', - 'mac4'])) - agent.treat_devices_removed.assert_called_with(set(['mac1'])) + agent.treat_devices_added_updated.assert_called_with( + {DEV2, DEV3, DEV4}) + agent.treat_devices_removed.assert_called_with({DEV1}) def test_treat_devices_added_updated_sends_host(self): agent = self.agent host = 'host1' cfg.CONF.set_override('host', host) agent.plugin_rpc = mock.Mock() - MAC = 'aa:bb:cc:dd:ee:ff' - device_details = {'device': MAC, - 'port_id': 'port123', - 'network_id': 'net123', - 'admin_state_up': True, - 'propagate_uplink_status': True, - 'network_type': 'vlan', - 'segmentation_id': 100, - 'profile': {'pci_slot': '1:2:3.0'}, - 'physical_network': 'physnet1', - 'port_security_enabled': False} - agent.plugin_rpc.get_devices_details_list.return_value = ( - [device_details]) - agent.treat_devices_added_updated([[MAC]]) + agent.plugin_rpc.get_devices_details_list.return_value = [RPC_DEV1] + devices = {agent_rpc.DeviceInfo(DEV1.mac, DEV1.pci_slot)} + agent.treat_devices_added_updated(devices) agent.plugin_rpc.get_devices_details_list.assert_called_once_with( - mock.ANY, set([MAC]), mock.ANY, host) + agent.context, devices, agent.agent_id, host) def test_treat_devices_added_updated_and_removed(self): agent = self.agent - MAC1 = 'aa:bb:cc:dd:ee:ff' - SLOT1 = '1:2:3.0' - MAC2 = 'aa:bb:cc:dd:ee:fe' - SLOT2 = '1:3:3.0' - mac_pci_slot_device1 = (MAC1, SLOT1) - mac_pci_slot_device2 = (MAC2, SLOT2) - mock_device1_details = {'device': MAC1, - 'port_id': 'port123', - 'network_id': 'net123', - 'admin_state_up': True, - 'propagate_uplink_status': False, - 'network_type': 'vlan', - 'segmentation_id': 100, - 'profile': {'pci_slot': SLOT1}, - 'physical_network': 'physnet1', - 'port_security_enabled': False} - mock_device2_details = {'device': MAC2, - 'port_id': 'port124', - 'network_id': 'net123', - 'admin_state_up': True, - 'propagate_uplink_status': False, - 'network_type': 'vlan', - 'segmentation_id': 100, - 'profile': {'pci_slot': SLOT2}, - 'physical_network': 'physnet1', - 'port_security_enabled': False} agent.plugin_rpc = mock.Mock() - agent.plugin_rpc.get_devices_details_list.return_value = ( - [mock_device1_details]) - agent.treat_devices_added_updated(set([MAC1])) - self.assertEqual({'net123': [{'port_id': 'port123', - 'device': mac_pci_slot_device1}]}, + agent.plugin_rpc.get_devices_details_list.return_value = [RPC_DEV1] + agent.treat_devices_added_updated({DEV1}) + self.assertEqual({'net123': [{'port_id': 'port123', 'device': DEV1}]}, agent.network_ports) - agent.plugin_rpc.get_devices_details_list.return_value = ( - [mock_device2_details]) + agent.plugin_rpc.get_devices_details_list.return_value = [RPC_DEV2] # add the second device and check the network_ports dict - agent.treat_devices_added_updated(set([MAC2])) - self.assertEqual( - {'net123': [{'port_id': 'port123', - 'device': mac_pci_slot_device1}, {'port_id': 'port124', - 'device': mac_pci_slot_device2}]}, - agent.network_ports) - with mock.patch.object(agent.plugin_rpc, - "update_device_down"): - agent.treat_devices_removed([mac_pci_slot_device2]) + agent.treat_devices_added_updated({DEV2}) + self.assertEqual({'net123': [{'port_id': 'port123', 'device': DEV1}, + {'port_id': 'port321', 'device': DEV2}]}, + agent.network_ports) + with mock.patch.object(agent.plugin_rpc, "update_device_down"): + agent.treat_devices_removed({DEV2}) # remove the second device and check the network_ports dict - self.assertEqual({'net123': [{'port_id': 'port123', - 'device': mac_pci_slot_device1}]}, + self.assertEqual({'net123': [{'port_id': 'port123', 'device': DEV1}]}, agent.network_ports) def test_treat_devices_added_updated_admin_state_up_true(self): agent = self.agent - mock_details = {'device': 'aa:bb:cc:dd:ee:ff', - 'port_id': 'port123', - 'network_id': 'net123', - 'admin_state_up': True, - 'propagate_uplink_status': False, - 'network_type': 'vlan', - 'segmentation_id': 100, - 'profile': {'pci_slot': '1:2:3.0'}, - 'physical_network': 'physnet1', - 'port_security_enabled': False} agent.plugin_rpc = mock.Mock() - agent.plugin_rpc.get_devices_details_list.return_value = [mock_details] + agent.plugin_rpc.get_devices_details_list.return_value = [RPC_DEV1] agent.eswitch_mgr = mock.Mock() agent.eswitch_mgr.device_exists.return_value = True agent.set_device_state = mock.Mock() agent.set_device_spoofcheck = mock.Mock() - resync_needed = agent.treat_devices_added_updated( - set(['aa:bb:cc:dd:ee:ff'])) - + devices = {DEV1} + resync_needed = agent.treat_devices_added_updated(devices) self.assertFalse(resync_needed) - agent.eswitch_mgr.device_exists.assert_called_with('aa:bb:cc:dd:ee:ff', - '1:2:3.0') + agent.eswitch_mgr.device_exists.assert_called_with(DEV1.mac, + DEV1.pci_slot) agent.eswitch_mgr.set_device_state.assert_called_with( - 'aa:bb:cc:dd:ee:ff', - '1:2:3.0', - True, False) + DEV1.mac, DEV1.pci_slot, True, False) agent.eswitch_mgr.set_device_spoofcheck.assert_called_with( - 'aa:bb:cc:dd:ee:ff', - '1:2:3.0', - False) + DEV1.mac, DEV1.pci_slot, False) agent.plugin_rpc.update_device_list.assert_called_once_with( - mock.ANY, - set(['aa:bb:cc:dd:ee:ff']), - set(), - mock.ANY, - mock.ANY) + agent.context, devices, set(), agent.agent_id, agent.conf.host) def test_treat_devices_added_updated_multiple_admin_state_up_true(self): agent = self.agent - mock_details = [{'device': 'aa:bb:cc:dd:ee:ff', - 'port_id': 'port123', - 'network_id': 'net123', - 'admin_state_up': True, - 'propagate_uplink_status': False, - 'network_type': 'vlan', - 'segmentation_id': 100, - 'profile': {'pci_slot': '1:2:3.0'}, - 'physical_network': 'physnet1', - 'port_security_enabled': False}, - {'device': '11:22:33:44:55:66', - 'port_id': 'port321', - 'network_id': 'net123', - 'admin_state_up': True, - 'propagate_uplink_status': False, - 'network_type': 'vlan', - 'segmentation_id': 100, - 'profile': {'pci_slot': '1:2:3.0'}, - 'physical_network': 'physnet1', - 'port_security_enabled': False}] + mock_details = [RPC_DEV1, RPC_DEV2] agent.plugin_rpc = mock.Mock() agent.plugin_rpc.get_devices_details_list.return_value = mock_details agent.eswitch_mgr = mock.Mock() agent.eswitch_mgr.device_exists.return_value = True agent.set_device_state = mock.Mock() agent.set_device_spoofcheck = mock.Mock() - resync_needed = agent.treat_devices_added_updated( - set(['aa:bb:cc:dd:ee:ff', - '11:22:33:44:55:66'])) + devices = {DEV1, DEV2} + resync_needed = agent.treat_devices_added_updated(devices) self.assertFalse(resync_needed) - calls = [mock.call('aa:bb:cc:dd:ee:ff', '1:2:3.0'), - mock.call('11:22:33:44:55:66', '1:2:3.0')] + calls = [mock.call(DEV1.mac, DEV1.pci_slot), + mock.call(DEV2.mac, DEV2.pci_slot)] agent.eswitch_mgr.device_exists.assert_has_calls(calls, any_order=True) - calls = [mock.call('aa:bb:cc:dd:ee:ff', '1:2:3.0', True, False), - mock.call('11:22:33:44:55:66', '1:2:3.0', True, False)] + calls = [mock.call(DEV1.mac, DEV1.pci_slot, True, False), + mock.call(DEV2.mac, DEV2.pci_slot, True, False)] agent.eswitch_mgr.set_device_state.assert_has_calls(calls, any_order=True) - calls = [mock.call('aa:bb:cc:dd:ee:ff', '1:2:3.0', False), - mock.call('11:22:33:44:55:66', '1:2:3.0', False)] - agent.eswitch_mgr.set_device_spoofcheck.assert_has_calls(calls, - any_order=True) + calls = [mock.call(DEV1.mac, DEV1.pci_slot, False), + mock.call(DEV2.mac, DEV2.pci_slot, False)] + agent.eswitch_mgr.set_device_spoofcheck.assert_has_calls( + calls, any_order=True) agent.plugin_rpc.update_device_list.assert_called_once_with( - mock.ANY, - set(['aa:bb:cc:dd:ee:ff', '11:22:33:44:55:66']), - set(), - mock.ANY, - mock.ANY) + agent.context, devices, set(), agent.agent_id, agent.conf.host) def test_treat_devices_added_updated_multiple_admin_states(self): agent = self.agent - mock_details = [{'device': 'aa:bb:cc:dd:ee:ff', - 'port_id': 'port123', - 'network_id': 'net123', - 'admin_state_up': True, - 'propagate_uplink_status': False, - 'network_type': 'vlan', - 'segmentation_id': 100, - 'profile': {'pci_slot': '1:2:3.0'}, - 'physical_network': 'physnet1', - 'port_security_enabled': False}, - {'device': '11:22:33:44:55:66', - 'port_id': 'port321', - 'network_id': 'net123', - 'admin_state_up': False, - 'propagate_uplink_status': False, - 'network_type': 'vlan', - 'segmentation_id': 100, - 'profile': {'pci_slot': '1:2:3.0'}, - 'physical_network': 'physnet1', - 'port_security_enabled': False}] + rpc_dev2 = copy.deepcopy(RPC_DEV2) + rpc_dev2['admin_state_up'] = False + mock_details = [RPC_DEV1, rpc_dev2] agent.plugin_rpc = mock.Mock() agent.plugin_rpc.get_devices_details_list.return_value = mock_details agent.eswitch_mgr = mock.Mock() agent.eswitch_mgr.device_exists.return_value = True agent.set_device_state = mock.Mock() agent.set_device_spoofcheck = mock.Mock() - resync_needed = agent.treat_devices_added_updated( - set(['aa:bb:cc:dd:ee:ff', - '11:22:33:44:55:66'])) + devices = {DEV1, DEV2} + resync_needed = agent.treat_devices_added_updated(devices) self.assertFalse(resync_needed) - calls = [mock.call('aa:bb:cc:dd:ee:ff', '1:2:3.0'), - mock.call('11:22:33:44:55:66', '1:2:3.0')] + calls = [mock.call(DEV1.mac, DEV1.pci_slot), + mock.call(DEV2.mac, DEV2.pci_slot)] agent.eswitch_mgr.device_exists.assert_has_calls(calls, any_order=True) - calls = [mock.call('aa:bb:cc:dd:ee:ff', '1:2:3.0', True, False), - mock.call('11:22:33:44:55:66', '1:2:3.0', False, False)] + calls = [mock.call(DEV1.mac, DEV1.pci_slot, True, False), + mock.call(DEV2.mac, DEV2.pci_slot, False, False)] agent.eswitch_mgr.set_device_state.assert_has_calls(calls, any_order=True) - calls = [mock.call('aa:bb:cc:dd:ee:ff', '1:2:3.0', False), - mock.call('11:22:33:44:55:66', '1:2:3.0', False)] - agent.eswitch_mgr.set_device_spoofcheck.assert_has_calls(calls, - any_order=True) + calls = [mock.call(DEV1.mac, DEV1.pci_slot, False), + mock.call(DEV2.mac, DEV2.pci_slot, False)] + agent.eswitch_mgr.set_device_spoofcheck.assert_has_calls( + calls, any_order=True) agent.plugin_rpc.update_device_list.assert_called_once_with( - mock.ANY, - set(['aa:bb:cc:dd:ee:ff']), - set(['11:22:33:44:55:66']), - mock.ANY, - mock.ANY) + agent.context, {DEV1}, {DEV2}, agent.agent_id, agent.conf.host) def test_treat_device_ip_link_state_not_supported(self): agent = self.agent @@ -426,8 +340,7 @@ class TestSriovAgent(base.BaseTestCase): agent.eswitch_mgr.set_device_state.side_effect = ( priv_ip_lib.InterfaceOperationNotSupported()) - self.assertTrue(agent.treat_device('aa:bb:cc:dd:ee:ff', '1:2:3:0', - admin_state_up=True)) + self.assertTrue(agent.treat_device(DEV1, admin_state_up=True)) def test_treat_device_set_device_state_exception(self): agent = self.agent @@ -437,8 +350,7 @@ class TestSriovAgent(base.BaseTestCase): agent.eswitch_mgr.set_device_state.side_effect = ( pyroute2.NetlinkError(22)) - self.assertFalse(agent.treat_device('aa:bb:cc:dd:ee:ff', '1:2:3:0', - admin_state_up=True)) + self.assertFalse(agent.treat_device(DEV1, admin_state_up=True)) def test_treat_device_no_device_found(self): agent = self.agent @@ -446,53 +358,32 @@ class TestSriovAgent(base.BaseTestCase): agent.eswitch_mgr = mock.Mock() agent.eswitch_mgr.device_exists.return_value = False - self.assertFalse(agent.treat_device('aa:bb:cc:dd:ee:ff', '1:2:3:0', - admin_state_up=True)) + self.assertFalse(agent.treat_device(DEV1, admin_state_up=True)) def test_treat_devices_added_updated_admin_state_up_false(self): agent = self.agent - mock_details = {'device': 'aa:bb:cc:dd:ee:ff', - 'port_id': 'port123', - 'network_id': 'net123', - 'admin_state_up': False, - 'propagate_uplink_status': False, - 'network_type': 'vlan', - 'segmentation_id': 100, - 'profile': {'pci_slot': '1:2:3.0'}, - 'physical_network': 'physnet1'} agent.plugin_rpc = mock.Mock() - agent.plugin_rpc.get_devices_details_list.return_value = [mock_details] + rpc_dev1 = copy.deepcopy(RPC_DEV1) + rpc_dev1['admin_state_up'] = False + agent.plugin_rpc.get_devices_details_list.return_value = [rpc_dev1] agent.remove_port_binding = mock.Mock() agent.eswitch_mgr = mock.Mock() agent.eswitch_mgr.device_exists.return_value = True - resync_needed = agent.treat_devices_added_updated( - set(['aa:bb:cc:dd:ee:ff'])) + devices = {DEV1} + resync_needed = agent.treat_devices_added_updated(devices) self.assertFalse(resync_needed) agent.plugin_rpc.update_device_list.assert_called_once_with( - mock.ANY, - set(), - set(['aa:bb:cc:dd:ee:ff']), - mock.ANY, - mock.ANY) + agent.context, set(), devices, agent.agent_id, agent.conf.host) def test_treat_devices_added_updated_no_device_found(self): agent = self.agent - mock_details = {'device': 'aa:bb:cc:dd:ee:ff', - 'port_id': 'port123', - 'network_id': 'net123', - 'admin_state_up': True, - 'network_type': 'vlan', - 'segmentation_id': 100, - 'profile': {'pci_slot': '1:2:3.0'}, - 'propagate_uplink_status': False, - 'physical_network': 'physnet1'} agent.plugin_rpc = mock.Mock() - agent.plugin_rpc.get_devices_details_list.return_value = [mock_details] + agent.plugin_rpc.get_devices_details_list.return_value = [RPC_DEV1] agent.remove_port_binding = mock.Mock() agent.eswitch_mgr = mock.Mock() agent.eswitch_mgr.device_exists.return_value = False - resync_needed = agent.treat_devices_added_updated( - set(['aa:bb:cc:dd:ee:ff'])) + devices = {DEV1} + resync_needed = agent.treat_devices_added_updated(devices) self.assertTrue(resync_needed) self.assertFalse(agent.plugin_rpc.update_device_up.called) @@ -502,27 +393,20 @@ class TestSriovAgent(base.BaseTestCase): port_id1 = 'port_id1' port_id2 = 'port_id2' - mac_slot_1 = ('mac1', 'slot1') - mac_slot_2 = ('mac2', 'slot2') - self.agent.network_ports[network_id1] = [{'port_id': port_id1, - 'device': mac_slot_1}, {'port_id': port_id2, 'device': mac_slot_2}] + self.agent._update_network_ports(network_id1, port_id1, DEV1) + self.agent._update_network_ports(network_id1, port_id2, DEV2) - self.agent._update_network_ports(network_id2, port_id1, mac_slot_1) + self.agent._update_network_ports(network_id2, port_id1, DEV1) + expected = {network_id1: [{'port_id': port_id2, 'device': DEV2}], + network_id2: [{'port_id': port_id1, 'device': DEV1}]} + self.assertEqual(expected, dict(self.agent.network_ports)) - self.assertEqual({network_id1: [{'port_id': port_id2, - 'device': mac_slot_2}], network_id2: [ - {'port_id': port_id1, 'device': mac_slot_1}]}, - self.agent.network_ports) + self.assertEqual(port_id1, self.agent._clean_network_ports(DEV1)) + expected = {network_id1: [{'port_id': port_id2, 'device': DEV2}]} + self.assertEqual(expected, self.agent.network_ports) - cleaned_port_id = self.agent._clean_network_ports(mac_slot_1) - self.assertEqual(cleaned_port_id, port_id1) - - self.assertEqual({network_id1: [{'port_id': port_id2, - 'device': mac_slot_2}]}, - self.agent.network_ports) - - cleaned_port_id = self.agent._clean_network_ports(mac_slot_2) + self.assertEqual(port_id2, self.agent._clean_network_ports(DEV2)) self.assertEqual({}, self.agent.network_ports) def test_configurations_has_rp_bandwidth(self): @@ -558,17 +442,12 @@ class TestSriovAgent(base.BaseTestCase): rp_inv_defaults[inv_key]) def test_process_activated_bindings(self): - # Create several devices which are pairs of (, ) - dev_a = ('fa:16:3e:f8:ae:af', "0000:01:00.0") - dev_b = ('fa:16:3e:f8:ae:b0', "0000:02:00.0") - dev_c = ('fa:16:3e:f8:ae:b1', "0000:03:00.0") - # Create device_info fake_device_info = { - 'current': set([dev_a, dev_b]), - 'added': set([dev_c]), + 'current': {DEV1, DEV2}, + 'added': {DEV3}, 'removed': set(), 'updated': set()} - fake_activated_bindings = set([dev_a]) + fake_activated_bindings = {DEV1} self.agent.process_activated_bindings(fake_device_info, fake_activated_bindings) self.assertLessEqual(fake_activated_bindings, @@ -592,6 +471,7 @@ class TestSriovNicSwitchRpcCallbacks(base.BaseTestCase): sg_agent = object() self.sriov_rpc_callback = sriov_nic_agent.SriovNicSwitchRpcCallbacks( self.context, self.agent, sg_agent) + self.device_info = agent_rpc.DeviceInfo(DEVICE_MAC, PCI_SLOT) def _create_fake_port(self): return {'id': uuidutils.generate_uuid(), @@ -606,8 +486,7 @@ class TestSriovNicSwitchRpcCallbacks(base.BaseTestCase): port = self._create_fake_port() kwargs = {'context': self.context, 'port': port} self.sriov_rpc_callback.port_update(**kwargs) - self.assertEqual(set([(DEVICE_MAC, PCI_SLOT)]), - self.agent.updated_devices) + self.assertEqual({self.device_info}, self.agent.updated_devices) def test_port_update_with_vnic_physical_direct(self): port = self._create_fake_port() @@ -618,7 +497,7 @@ class TestSriovNicSwitchRpcCallbacks(base.BaseTestCase): def test_port_update_without_pci_slot(self): port = self._create_fake_port() - port[portbindings.PROFILE] = None + port[portbindings.PROFILE] = {} kwargs = {'context': self.context, 'port': port} self.sriov_rpc_callback.port_update(**kwargs) self.assertEqual(set(), self.agent.updated_devices) diff --git a/neutron/tests/unit/plugins/ml2/test_db.py b/neutron/tests/unit/plugins/ml2/test_db.py index 07ada232e69..fbdd649d147 100644 --- a/neutron/tests/unit/plugins/ml2/test_db.py +++ b/neutron/tests/unit/plugins/ml2/test_db.py @@ -280,16 +280,6 @@ class Ml2DBTestCase(testlib_api.SqlTestCase): port = ml2_db.get_port(self.ctx, port_id) self.assertIsNone(port) - def test_get_port_from_device_mac(self): - network_id = uuidutils.generate_uuid() - port_id = uuidutils.generate_uuid() - self._setup_neutron_network(network_id) - port = self._setup_neutron_port(network_id, port_id) - - observed_port = ml2_db.get_port_from_device_mac(self.ctx, - port['mac_address']) - self.assertEqual(port_id, observed_port.id) - def test_generating_multiple_mac_addresses(self): mac_regex = "^([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2})$" diff --git a/neutron/tests/unit/plugins/ml2/test_rpc.py b/neutron/tests/unit/plugins/ml2/test_rpc.py index 4e0facca7b8..d70d072d0f4 100644 --- a/neutron/tests/unit/plugins/ml2/test_rpc.py +++ b/neutron/tests/unit/plugins/ml2/test_rpc.py @@ -436,7 +436,8 @@ class RpcApiTestCase(base.BaseTestCase): 'get_device_details', rpc_method='call', device='fake_device', agent_id='fake_agent_id', - host='fake_host') + host='fake_host', + version='1.9') def test_devices_details_list(self): rpcapi = agent_rpc.PluginApi(topics.PLUGIN) @@ -444,7 +445,7 @@ class RpcApiTestCase(base.BaseTestCase): 'get_devices_details_list', rpc_method='call', devices=['fake_device1', 'fake_device2'], agent_id='fake_agent_id', host='fake_host', - version='1.3') + version='1.9') def test_update_device_down(self): rpcapi = agent_rpc.PluginApi(topics.PLUGIN) @@ -452,7 +453,8 @@ class RpcApiTestCase(base.BaseTestCase): 'update_device_down', rpc_method='call', device='fake_device', agent_id='fake_agent_id', - host='fake_host') + host='fake_host', + version='1.9') def test_tunnel_sync(self): rpcapi = agent_rpc.PluginApi(topics.PLUGIN) @@ -469,7 +471,8 @@ class RpcApiTestCase(base.BaseTestCase): 'update_device_up', rpc_method='call', device='fake_device', agent_id='fake_agent_id', - host='fake_host') + host='fake_host', + version='1.9') def test_update_device_list(self): rpcapi = agent_rpc.PluginApi(topics.PLUGIN) @@ -480,7 +483,7 @@ class RpcApiTestCase(base.BaseTestCase): agent_id='fake_agent_id', host='fake_host', refresh_tunnels=False, - version='1.8') + version='1.9') def test_get_devices_details_list_and_failed_devices(self): rpcapi = agent_rpc.PluginApi(topics.PLUGIN) diff --git a/releasenotes/notes/sriov-agent-duplicated-mac-ports-a861b0ff800c3172.yaml b/releasenotes/notes/sriov-agent-duplicated-mac-ports-a861b0ff800c3172.yaml new file mode 100644 index 00000000000..3722f03a853 --- /dev/null +++ b/releasenotes/notes/sriov-agent-duplicated-mac-ports-a861b0ff800c3172.yaml @@ -0,0 +1,20 @@ +--- +features: + - | + SR-IOV agent now can handle ports from different networks with the same + MAC addresses. This feature implies an upgrade in the agent and the server + RPC version (see ``neutron.plugins.ml2.rpc.RpcCallbacks`` version 1.9). + Some agent RPC methods have been updated to pass not only the device MAC + address but the PCI slot too. In case of having more than one port with + the same MAC address, the PCI slot will discriminate the requested port. +upgrade: + - | + Both the server and the agent RPC versions have been bumped to 1.9; to + provide a smooth upgrade transition, the `Upgrade Procedure + `_ should + be followed, upgrading first the servers and then the agents. + The agent RPC methods returned values are not modified to keep + compatibility with other agents (Linux Bridge, Open vSwitch). The RPC + server side is capable of attending calls from agent API < 1.9, in order to + provide backwards compatibility. If the device PCI slot is not provided, + the behavior will be the previous one.