From 5d099f17ebb95c53c69ed1777d8077f590bd5aeb Mon Sep 17 00:00:00 2001 From: Rodolfo Alonso Hernandez Date: Mon, 22 Apr 2019 08:11:09 +0000 Subject: [PATCH] Add namespace support for "bridge" commands "bridge" commands executed inside a namespace will be needed initially to test the TC filter for VXLAN traffic. Those tests will create two namespaces with VXLAN interfaces in order to check the functionality of this new TC filter. Related-Bug: #1560963 Change-Id: I3553b89fc0436c9cf83c66ab447ba4b4a6268ee1 --- neutron/agent/linux/bridge_lib.py | 30 +++--- .../functional/agent/linux/test_bridge_lib.py | 102 ++++++++++++++++++ .../l2/extensions/test_fdb_population.py | 17 +-- .../agent/test_linuxbridge_neutron_agent.py | 6 +- 4 files changed, 131 insertions(+), 24 deletions(-) diff --git a/neutron/agent/linux/bridge_lib.py b/neutron/agent/linux/bridge_lib.py index edf89fcbcfb..2482e6d7478 100644 --- a/neutron/agent/linux/bridge_lib.py +++ b/neutron/agent/linux/bridge_lib.py @@ -21,7 +21,6 @@ import os from oslo_utils import excutils from neutron.agent.linux import ip_lib -from neutron.agent.linux import utils # NOTE(toabctl): Don't use /sys/devices/virtual/net here because not all tap # devices are listed here (i.e. when using Xen) @@ -109,32 +108,37 @@ class BridgeDevice(ip_lib.IPDevice): class FdbInterface(object): """provide basic functionality to edit the FDB table""" + @staticmethod + def _execute_bridge(cmd, namespace, **kwargs): + ip_wrapper = ip_lib.IPWrapper(namespace) + return ip_wrapper.netns.execute(cmd, run_as_root=True, **kwargs) + @classmethod - def _execute(cls, op, mac, dev, ip_dst, **kwargs): + def _cmd(cls, op, mac, dev, ip_dst, namespace, **kwargs): cmd = ['bridge', 'fdb', op, mac, 'dev', dev] if ip_dst is not None: cmd += ['dst', ip_dst] - return utils.execute(cmd, run_as_root=True, **kwargs) + cls._execute_bridge(cmd, namespace, **kwargs) @classmethod - def add(cls, mac, dev, ip_dst=None, **kwargs): - return cls._execute('add', mac, dev, ip_dst, **kwargs) + def add(cls, mac, dev, ip_dst=None, namespace=None, **kwargs): + return cls._cmd('add', mac, dev, ip_dst, namespace, **kwargs) @classmethod - def append(cls, mac, dev, ip_dst=None, **kwargs): - return cls._execute('append', mac, dev, ip_dst, **kwargs) + def append(cls, mac, dev, ip_dst=None, namespace=None, **kwargs): + return cls._cmd('append', mac, dev, ip_dst, namespace, **kwargs) @classmethod - def replace(cls, mac, dev, ip_dst=None, **kwargs): - return cls._execute('replace', mac, dev, ip_dst, **kwargs) + def replace(cls, mac, dev, ip_dst=None, namespace=None, **kwargs): + return cls._cmd('replace', mac, dev, ip_dst, namespace, **kwargs) @classmethod - def delete(cls, mac, dev, ip_dst=None, **kwargs): - return cls._execute('delete', mac, dev, ip_dst, **kwargs) + def delete(cls, mac, dev, ip_dst=None, namespace=None, **kwargs): + return cls._cmd('delete', mac, dev, ip_dst, namespace, **kwargs) @classmethod - def show(cls, dev=None, **kwargs): + def show(cls, dev=None, namespace=None, **kwargs): cmd = ['bridge', 'fdb', 'show'] if dev: cmd += ['dev', dev] - return utils.execute(cmd, run_as_root=True, **kwargs) + return cls._execute_bridge(cmd, namespace, **kwargs) diff --git a/neutron/tests/functional/agent/linux/test_bridge_lib.py b/neutron/tests/functional/agent/linux/test_bridge_lib.py index 88f0e4981d8..577ac577cda 100644 --- a/neutron/tests/functional/agent/linux/test_bridge_lib.py +++ b/neutron/tests/functional/agent/linux/test_bridge_lib.py @@ -12,9 +12,15 @@ # License for the specific language governing permissions and limitations # under the License. +import re + +import netaddr from oslo_utils import uuidutils +import testscenarios from neutron.agent.linux import bridge_lib +from neutron.agent.linux import ip_lib +from neutron.privileged.agent.linux import ip_lib as priv_ip_lib from neutron.tests.common import net_helpers from neutron.tests.functional import base @@ -85,3 +91,99 @@ class BridgeLibTestCase(base.BaseSudoTestCase): with open(sysfs_path, 'r') as sysfs_disable_ipv6_file: sysfs_disable_ipv6 = sysfs_disable_ipv6_file.read() self.assertEqual("1\n", sysfs_disable_ipv6) + + +class FdbInterfaceTestCase(testscenarios.WithScenarios, base.BaseSudoTestCase): + + MAC1 = 'ca:fe:ca:fe:ca:fe' + MAC2 = 'ca:fe:ca:fe:ca:01' + RULE_PATTERN = (r"^(?P([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2})) " + r"(dst (?P\b\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\b))*") + + scenarios = [ + ('namespace', {'namespace': 'ns_' + uuidutils.generate_uuid()}), + ('no_namespace', {'namespace': None}) + ] + + def setUp(self): + super(FdbInterfaceTestCase, self).setUp() + self.device = 'interface' + self.device_vxlan = 'int_vxlan' + self.ip = '10.220.0.1/24' + self.ip_vxlan = '10.221.0.1/24' + if self.namespace: + priv_ip_lib.create_netns(self.namespace) + self.addCleanup(self._cleanup) + ip_wrapper = ip_lib.IPWrapper(self.namespace) + ip_wrapper.add_dummy(self.device) + ip_wrapper.add_vxlan(self.device_vxlan, 100, dev=self.device) + ip_device = ip_lib.IPDevice(self.device, self.namespace) + ip_device.link.set_up() + ip_device.addr.add(self.ip) + ip_device_vxlan = ip_lib.IPDevice(self.device_vxlan, self.namespace) + ip_device_vxlan.link.set_up() + ip_device_vxlan.addr.add(self.ip_vxlan) + + def _cleanup(self): + if self.namespace: + priv_ip_lib.remove_netns(self.namespace) + else: + priv_ip_lib.delete_interface(self.device_vxlan, None) + priv_ip_lib.delete_interface(self.device, None) + + def _list_fdb_rules(self, device): + output = bridge_lib.FdbInterface.show(dev=device, + namespace=self.namespace) + rules = re.finditer(self.RULE_PATTERN, output, flags=re.MULTILINE) + ret = {} + for rule in rules: + ret[rule.groupdict()['mac']] = rule.groupdict()['ip'] + return ret + + def test_add_delete(self): + self.assertNotIn(self.MAC1, self._list_fdb_rules(self.device)) + bridge_lib.FdbInterface.add(self.MAC1, self.device, + namespace=self.namespace) + self.assertIn(self.MAC1, self._list_fdb_rules(self.device)) + bridge_lib.FdbInterface.delete(self.MAC1, self.device, + namespace=self.namespace) + self.assertNotIn(self.MAC1, self._list_fdb_rules(self.device)) + + def test_add_delete_dst(self): + self.assertNotIn(self.MAC1, self._list_fdb_rules(self.device_vxlan)) + bridge_lib.FdbInterface.add( + self.MAC1, self.device_vxlan, namespace=self.namespace, + ip_dst=str(netaddr.IPNetwork(self.ip).ip)) + rules = self._list_fdb_rules(self.device_vxlan) + self.assertEqual(str(netaddr.IPNetwork(self.ip).ip), rules[self.MAC1]) + bridge_lib.FdbInterface.delete( + self.MAC1, self.device_vxlan, namespace=self.namespace, + ip_dst=str(netaddr.IPNetwork(self.ip).ip)) + self.assertNotIn(self.MAC1, self._list_fdb_rules(self.device_vxlan)) + + def test_append(self): + self.assertNotIn(self.MAC1, self._list_fdb_rules(self.device)) + bridge_lib.FdbInterface.append(self.MAC1, self.device, + namespace=self.namespace) + self.assertIn(self.MAC1, self._list_fdb_rules(self.device)) + + def test_append_dst(self): + self.assertNotIn(self.MAC1, self._list_fdb_rules(self.device_vxlan)) + bridge_lib.FdbInterface.append( + self.MAC1, self.device_vxlan, namespace=self.namespace, + ip_dst=str(netaddr.IPNetwork(self.ip).ip)) + rules = self._list_fdb_rules(self.device_vxlan) + self.assertEqual(str(netaddr.IPNetwork(self.ip).ip), rules[self.MAC1]) + + def test_replace(self): + self.assertNotIn(self.MAC1, self._list_fdb_rules(self.device)) + bridge_lib.FdbInterface.add( + self.MAC1, self.device_vxlan, namespace=self.namespace, + ip_dst=str(netaddr.IPNetwork(self.ip).ip)) + rules = self._list_fdb_rules(self.device_vxlan) + self.assertEqual(str(netaddr.IPNetwork(self.ip).ip), rules[self.MAC1]) + bridge_lib.FdbInterface.replace( + self.MAC1, self.device_vxlan, namespace=self.namespace, + ip_dst='1.1.1.1') + rules = self._list_fdb_rules(self.device_vxlan) + self.assertEqual('1.1.1.1', rules[self.MAC1]) diff --git a/neutron/tests/unit/agent/l2/extensions/test_fdb_population.py b/neutron/tests/unit/agent/l2/extensions/test_fdb_population.py index 9f7a68c3fda..2cc142187e4 100644 --- a/neutron/tests/unit/agent/l2/extensions/test_fdb_population.py +++ b/neutron/tests/unit/agent/l2/extensions/test_fdb_population.py @@ -23,6 +23,7 @@ import six from neutron.agent.l2.extensions.fdb_population import ( FdbPopulationAgentExtension) +from neutron.agent.linux import ip_lib from neutron.plugins.ml2.drivers.linuxbridge.agent.common import ( constants as linux_bridge_constants) from neutron.plugins.ml2.drivers.openvswitch.agent.common import ( @@ -70,13 +71,13 @@ class FdbPopulationExtensionTestCase(base.BaseTestCase): fdb_extension = FdbPopulationAgentExtension() self.assertRaises(SystemExit, fdb_extension.initialize, None, 'sriov') - @mock.patch('neutron.agent.linux.utils.execute') + @mock.patch.object(ip_lib.IpNetnsCommand, 'execute') def test_construct_empty_fdb_table(self, mock_execute): self._get_fdb_extension(mock_execute, fdb_table='') cmd = ['bridge', 'fdb', 'show', 'dev', self.DEVICE] mock_execute.assert_called_once_with(cmd, run_as_root=True) - @mock.patch('neutron.agent.linux.utils.execute') + @mock.patch.object(ip_lib.IpNetnsCommand, 'execute') def test_construct_existing_fdb_table(self, mock_execute): fdb_extension = self._get_fdb_extension(mock_execute, fdb_table=self.FDB_TABLE) @@ -88,7 +89,7 @@ class FdbPopulationExtensionTestCase(base.BaseTestCase): for mac in macs: self.assertIn(mac, updated_macs_for_device) - @mock.patch('neutron.agent.linux.utils.execute') + @mock.patch.object(ip_lib.IpNetnsCommand, 'execute') def test_update_port_add_rule(self, mock_execute): fdb_extension = self._get_fdb_extension(mock_execute, self.FDB_TABLE) mock_execute.reset_mock() @@ -101,7 +102,7 @@ class FdbPopulationExtensionTestCase(base.BaseTestCase): mac = self.UPDATE_MSG['mac_address'] self.assertIn(mac, updated_macs_for_device) - @mock.patch('neutron.agent.linux.utils.execute') + @mock.patch.object(ip_lib.IpNetnsCommand, 'execute') def test_update_port_changed_mac(self, mock_execute): fdb_extension = self._get_fdb_extension(mock_execute, self.FDB_TABLE) mock_execute.reset_mock() @@ -145,7 +146,7 @@ class FdbPopulationExtensionTestCase(base.BaseTestCase): fdb_extension.fdb_tracker.device_to_macs.get(self.DEVICE)) self.assertIsNone(updated_macs_for_device) - @mock.patch('neutron.agent.linux.utils.execute') + @mock.patch.object(ip_lib.IpNetnsCommand, 'execute') def test_catch_update_port_exception(self, mock_execute): fdb_extension = self._get_fdb_extension(mock_execute, '') mock_execute.side_effect = RuntimeError @@ -155,7 +156,7 @@ class FdbPopulationExtensionTestCase(base.BaseTestCase): mac = self.UPDATE_MSG['mac_address'] self.assertNotIn(mac, updated_macs_for_device) - @mock.patch('neutron.agent.linux.utils.execute') + @mock.patch.object(ip_lib.IpNetnsCommand, 'execute') def test_catch_delete_port_exception(self, mock_execute): fdb_extension = self._get_fdb_extension(mock_execute, '') fdb_extension.handle_port(context=None, details=self.UPDATE_MSG) @@ -166,7 +167,7 @@ class FdbPopulationExtensionTestCase(base.BaseTestCase): mac = self.UPDATE_MSG['mac_address'] self.assertIn(mac, updated_macs_for_device) - @mock.patch('neutron.agent.linux.utils.execute') + @mock.patch.object(ip_lib.IpNetnsCommand, 'execute') def test_delete_port(self, mock_execute): fdb_extension = self._get_fdb_extension(mock_execute, '') fdb_extension.handle_port(context=None, details=self.UPDATE_MSG) @@ -176,7 +177,7 @@ class FdbPopulationExtensionTestCase(base.BaseTestCase): 'dev', self.DEVICE] mock_execute.assert_called_once_with(cmd, run_as_root=True) - @mock.patch('neutron.agent.linux.utils.execute') + @mock.patch.object(ip_lib.IpNetnsCommand, 'execute') def test_multiple_devices(self, mock_execute): cfg.CONF.set_override('shared_physical_device_mappings', ['physnet1:p1p1', 'physnet1:p2p2'], 'FDB') diff --git a/neutron/tests/unit/plugins/ml2/drivers/linuxbridge/agent/test_linuxbridge_neutron_agent.py b/neutron/tests/unit/plugins/ml2/drivers/linuxbridge/agent/test_linuxbridge_neutron_agent.py index 0cd16617020..065864c6b49 100644 --- a/neutron/tests/unit/plugins/ml2/drivers/linuxbridge/agent/test_linuxbridge_neutron_agent.py +++ b/neutron/tests/unit/plugins/ml2/drivers/linuxbridge/agent/test_linuxbridge_neutron_agent.py @@ -859,7 +859,7 @@ class TestLinuxBridgeManager(base.BaseTestCase): 'ensure_vxlan', return_value=None),\ mock.patch.object( - utils, + ip_lib.IpNetnsCommand, 'execute', side_effect=None if fdb_append else RuntimeError()),\ mock.patch.object(ip_lib, @@ -1071,7 +1071,7 @@ class TestLinuxBridgeRpcCallbacks(base.BaseTestCase): 'network_type': 'vxlan', 'segment_id': 1}} - with mock.patch.object(utils, 'execute', + with mock.patch.object(ip_lib.IpNetnsCommand, 'execute', return_value='') as execute_fn, \ mock.patch.object(ip_lib, 'add_neigh_entry', return_value='') as add_fn: @@ -1140,7 +1140,7 @@ class TestLinuxBridgeRpcCallbacks(base.BaseTestCase): 'network_type': 'vxlan', 'segment_id': 1}} - with mock.patch.object(utils, 'execute', + with mock.patch.object(ip_lib.IpNetnsCommand, 'execute', return_value='') as execute_fn, \ mock.patch.object(ip_lib, 'delete_neigh_entry', return_value='') as del_fn: