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
This commit is contained in:
Rodolfo Alonso Hernandez 2019-04-22 08:11:09 +00:00
parent 5d607a13ba
commit 5d099f17eb
4 changed files with 131 additions and 24 deletions

View File

@ -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)

View File

@ -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<mac>([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2})) "
r"(dst (?P<ip>\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])

View File

@ -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')

View File

@ -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: