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 3f2edef626b..65d825105e4 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 @@ -14,6 +14,7 @@ # limitations under the License. +import collections import socket import sys import time @@ -22,6 +23,7 @@ from oslo_config import cfg from oslo_log import log as logging import oslo_messaging from oslo_service import loopingcall +import six from neutron._i18n import _, _LE, _LI, _LW from neutron.agent.l2.extensions import manager as ext_manager @@ -46,8 +48,13 @@ class SriovNicSwitchRpcCallbacks(sg_rpc.SecurityGroupAgentRpcCallbackMixin): # Set RPC API version to 1.0 by default. # history - # 1.1 Support Security Group RPC - target = oslo_messaging.Target(version='1.1') + # 1.1 Support Security Group RPC (works with NoopFirewallDriver) + # 1.2 Support DVR (Distributed Virtual Router) RPC (not supported) + # 1.3 Added param devices_to_update to security_groups_provider_updated + # (works with NoopFirewallDriver) + # 1.4 Added support for network_update + + target = oslo_messaging.Target(version='1.4') def __init__(self, context, agent, sg_agent): super(SriovNicSwitchRpcCallbacks, self).__init__() @@ -84,19 +91,28 @@ class SriovNicSwitchRpcCallbacks(sg_rpc.SecurityGroupAgentRpcCallbackMixin): "skipping", {'id': port['id'], 'mac': mac, 'pci_slot': pci_slot}) + def network_update(self, context, **kwargs): + network_id = kwargs['network']['id'] + LOG.debug("network_update message received for network " + "%(network_id)s, with ports: %(ports)s", + {'network_id': network_id, + 'ports': self.agent.network_ports[network_id]}) + for port_data in self.agent.network_ports[network_id]: + self.agent.updated_devices.add(port_data['device']) + class SriovNicSwitchAgent(object): def __init__(self, physical_devices_mappings, exclude_devices, polling_interval): self.polling_interval = polling_interval + self.network_ports = collections.defaultdict(list) self.conf = cfg.CONF self.setup_eswitch_mgr(physical_devices_mappings, exclude_devices) # Stores port update notifications for processing in the main loop self.updated_devices = set() - self.mac_to_port_id_mapping = {} self.context = context.get_admin_context_without_session() self.plugin_rpc = agent_rpc.PluginApi(topics.PLUGIN) @@ -135,6 +151,7 @@ class SriovNicSwitchAgent(object): # Define the listening consumers for the agent consumers = [[topics.PORT, topics.UPDATE], [topics.NETWORK, topics.DELETE], + [topics.NETWORK, topics.UPDATE], [topics.SECURITY_GROUP, topics.UPDATE]] self.connection = agent_rpc.create_consumers(self.endpoints, self.topic, @@ -173,10 +190,11 @@ class SriovNicSwitchAgent(object): device_info = {} device_info['current'] = curr_devices device_info['added'] = curr_devices - registered_devices - # we don't want to process updates for devices that don't exist - device_info['updated'] = updated_devices & curr_devices # we need to clean up after devices are removed device_info['removed'] = registered_devices - curr_devices + # we don't want to process updates for devices that don't exist + device_info['updated'] = (updated_devices & curr_devices - + device_info['removed']) return device_info def _device_info_has_changes(self, device_info): @@ -239,6 +257,21 @@ class SriovNicSwitchAgent(object): else: LOG.info(_LI("No device with MAC %s defined on agent."), device) + def _update_network_ports(self, network_id, port_id, mac_pci_slot): + self._clean_network_ports(mac_pci_slot) + self.network_ports[network_id].append({ + "port_id": port_id, + "device": mac_pci_slot}) + + def _clean_network_ports(self, mac_pci_slot): + for netid, ports_list in six.iteritems(self.network_ports): + for port_data in ports_list: + if mac_pci_slot == port_data['device']: + ports_list.remove(port_data) + if 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]) @@ -259,13 +292,15 @@ class SriovNicSwitchAgent(object): LOG.info(_LI("Port %(device)s updated. Details: %(details)s"), {'device': device, 'details': device_details}) port_id = device_details['port_id'] - self.mac_to_port_id_mapping[device] = port_id profile = device_details['profile'] spoofcheck = device_details.get('port_security_enabled', True) self.treat_device(device, profile.get('pci_slot'), device_details['admin_state_up'], spoofcheck) + self._update_network_ports(device_details['network_id'], + port_id, + (device, profile.get('pci_slot'))) self.ext_manager.handle_port(self.context, device_details) else: LOG.info(_LI("Device with MAC %s not defined on plugin"), @@ -280,14 +315,12 @@ class SriovNicSwitchAgent(object): "PCI slot %(pci_slot)s"), {'mac': mac, 'pci_slot': pci_slot}) try: - port_id = self.mac_to_port_id_mapping.get(mac) + port_id = self._clean_network_ports(device) if port_id: - profile = {'pci_slot': pci_slot} port = {'port_id': port_id, 'device': mac, - 'profile': profile} + 'profile': {'pci_slot': pci_slot}} self.ext_manager.delete_port(self.context, port) - del self.mac_to_port_id_mapping[mac] else: LOG.warning(_LW("port_id to device with MAC " "%s not found"), mac) 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 947a7aebc37..cdc9fe9af8c 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 @@ -150,6 +150,17 @@ class TestSriovAgent(base.BaseTestCase): 'removed': set(['1'])} 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']), + 'updated': set(), + 'added': set(['3']), + 'removed': set(['1'])} + self.mock_scan_devices(expected, mock_current, registered, updated) + def test_scan_devices_new_updates(self): registered = set(['1']) updated = set(['2']) @@ -191,6 +202,56 @@ class TestSriovAgent(base.BaseTestCase): 'mac4'])) agent.treat_devices_removed.assert_called_with(set(['mac1'])) + 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, + '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, + '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.network_ports) + agent.plugin_rpc.get_devices_details_list.return_value = ( + [mock_device2_details]) + # 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]) + # remove the second device and check the network_ports dict + self.assertEqual({'net123': [{'port_id': 'port123', + 'device': mac_pci_slot_device1}]}, + 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', @@ -268,6 +329,35 @@ class TestSriovAgent(base.BaseTestCase): self.assertFalse(resync_needed) self.assertFalse(agent.plugin_rpc.update_device_up.called) + def test_update_and_clean_network_ports(self): + network_id1 = 'network_id1' + network_id2 = 'network_id2' + + 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_id2, port_id1, mac_slot_1) + + 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) + + 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({}, self.agent.network_ports) + class FakeAgent(object): def __init__(self): @@ -310,6 +400,23 @@ class TestSriovNicSwitchRpcCallbacks(base.BaseTestCase): self.sriov_rpc_callback.port_update(**kwargs) self.assertEqual(set(), self.agent.updated_devices) + def test_network_update(self): + TEST_NETWORK_ID1 = "n1" + TEST_NETWORK_ID2 = "n2" + TEST_PORT_ID1 = 'p1' + TEST_PORT_ID2 = 'p2' + network1 = {'id': TEST_NETWORK_ID1} + port1 = {'id': TEST_PORT_ID1, 'network_id': TEST_NETWORK_ID1} + port2 = {'id': TEST_PORT_ID2, 'network_id': TEST_NETWORK_ID2} + self.agent.network_ports = { + TEST_NETWORK_ID1: [{'port_id': port1['id'], + 'device': ('mac1', 'slot1')}], + TEST_NETWORK_ID2: [{'port_id': port2['id'], + 'device': ('mac2', 'slot2')}]} + kwargs = {'context': self.context, 'network': network1} + self.sriov_rpc_callback.network_update(**kwargs) + self.assertEqual(set([('mac1', 'slot1')]), self.agent.updated_devices) + class TestSRIOVAgentExtensionConfig(base.BaseTestCase): def setUp(self):