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:
parent
5d607a13ba
commit
5d099f17eb
|
@ -21,7 +21,6 @@ import os
|
||||||
from oslo_utils import excutils
|
from oslo_utils import excutils
|
||||||
|
|
||||||
from neutron.agent.linux import ip_lib
|
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
|
# NOTE(toabctl): Don't use /sys/devices/virtual/net here because not all tap
|
||||||
# devices are listed here (i.e. when using Xen)
|
# devices are listed here (i.e. when using Xen)
|
||||||
|
@ -109,32 +108,37 @@ class BridgeDevice(ip_lib.IPDevice):
|
||||||
class FdbInterface(object):
|
class FdbInterface(object):
|
||||||
"""provide basic functionality to edit the FDB table"""
|
"""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
|
@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]
|
cmd = ['bridge', 'fdb', op, mac, 'dev', dev]
|
||||||
if ip_dst is not None:
|
if ip_dst is not None:
|
||||||
cmd += ['dst', ip_dst]
|
cmd += ['dst', ip_dst]
|
||||||
return utils.execute(cmd, run_as_root=True, **kwargs)
|
cls._execute_bridge(cmd, namespace, **kwargs)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def add(cls, mac, dev, ip_dst=None, **kwargs):
|
def add(cls, mac, dev, ip_dst=None, namespace=None, **kwargs):
|
||||||
return cls._execute('add', mac, dev, ip_dst, **kwargs)
|
return cls._cmd('add', mac, dev, ip_dst, namespace, **kwargs)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def append(cls, mac, dev, ip_dst=None, **kwargs):
|
def append(cls, mac, dev, ip_dst=None, namespace=None, **kwargs):
|
||||||
return cls._execute('append', mac, dev, ip_dst, **kwargs)
|
return cls._cmd('append', mac, dev, ip_dst, namespace, **kwargs)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def replace(cls, mac, dev, ip_dst=None, **kwargs):
|
def replace(cls, mac, dev, ip_dst=None, namespace=None, **kwargs):
|
||||||
return cls._execute('replace', mac, dev, ip_dst, **kwargs)
|
return cls._cmd('replace', mac, dev, ip_dst, namespace, **kwargs)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def delete(cls, mac, dev, ip_dst=None, **kwargs):
|
def delete(cls, mac, dev, ip_dst=None, namespace=None, **kwargs):
|
||||||
return cls._execute('delete', mac, dev, ip_dst, **kwargs)
|
return cls._cmd('delete', mac, dev, ip_dst, namespace, **kwargs)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def show(cls, dev=None, **kwargs):
|
def show(cls, dev=None, namespace=None, **kwargs):
|
||||||
cmd = ['bridge', 'fdb', 'show']
|
cmd = ['bridge', 'fdb', 'show']
|
||||||
if dev:
|
if dev:
|
||||||
cmd += ['dev', dev]
|
cmd += ['dev', dev]
|
||||||
return utils.execute(cmd, run_as_root=True, **kwargs)
|
return cls._execute_bridge(cmd, namespace, **kwargs)
|
||||||
|
|
|
@ -12,9 +12,15 @@
|
||||||
# License for the specific language governing permissions and limitations
|
# License for the specific language governing permissions and limitations
|
||||||
# under the License.
|
# under the License.
|
||||||
|
|
||||||
|
import re
|
||||||
|
|
||||||
|
import netaddr
|
||||||
from oslo_utils import uuidutils
|
from oslo_utils import uuidutils
|
||||||
|
import testscenarios
|
||||||
|
|
||||||
from neutron.agent.linux import bridge_lib
|
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.common import net_helpers
|
||||||
from neutron.tests.functional import base
|
from neutron.tests.functional import base
|
||||||
|
|
||||||
|
@ -85,3 +91,99 @@ class BridgeLibTestCase(base.BaseSudoTestCase):
|
||||||
with open(sysfs_path, 'r') as sysfs_disable_ipv6_file:
|
with open(sysfs_path, 'r') as sysfs_disable_ipv6_file:
|
||||||
sysfs_disable_ipv6 = sysfs_disable_ipv6_file.read()
|
sysfs_disable_ipv6 = sysfs_disable_ipv6_file.read()
|
||||||
self.assertEqual("1\n", sysfs_disable_ipv6)
|
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])
|
||||||
|
|
|
@ -23,6 +23,7 @@ import six
|
||||||
|
|
||||||
from neutron.agent.l2.extensions.fdb_population import (
|
from neutron.agent.l2.extensions.fdb_population import (
|
||||||
FdbPopulationAgentExtension)
|
FdbPopulationAgentExtension)
|
||||||
|
from neutron.agent.linux import ip_lib
|
||||||
from neutron.plugins.ml2.drivers.linuxbridge.agent.common import (
|
from neutron.plugins.ml2.drivers.linuxbridge.agent.common import (
|
||||||
constants as linux_bridge_constants)
|
constants as linux_bridge_constants)
|
||||||
from neutron.plugins.ml2.drivers.openvswitch.agent.common import (
|
from neutron.plugins.ml2.drivers.openvswitch.agent.common import (
|
||||||
|
@ -70,13 +71,13 @@ class FdbPopulationExtensionTestCase(base.BaseTestCase):
|
||||||
fdb_extension = FdbPopulationAgentExtension()
|
fdb_extension = FdbPopulationAgentExtension()
|
||||||
self.assertRaises(SystemExit, fdb_extension.initialize, None, 'sriov')
|
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):
|
def test_construct_empty_fdb_table(self, mock_execute):
|
||||||
self._get_fdb_extension(mock_execute, fdb_table='')
|
self._get_fdb_extension(mock_execute, fdb_table='')
|
||||||
cmd = ['bridge', 'fdb', 'show', 'dev', self.DEVICE]
|
cmd = ['bridge', 'fdb', 'show', 'dev', self.DEVICE]
|
||||||
mock_execute.assert_called_once_with(cmd, run_as_root=True)
|
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):
|
def test_construct_existing_fdb_table(self, mock_execute):
|
||||||
fdb_extension = self._get_fdb_extension(mock_execute,
|
fdb_extension = self._get_fdb_extension(mock_execute,
|
||||||
fdb_table=self.FDB_TABLE)
|
fdb_table=self.FDB_TABLE)
|
||||||
|
@ -88,7 +89,7 @@ class FdbPopulationExtensionTestCase(base.BaseTestCase):
|
||||||
for mac in macs:
|
for mac in macs:
|
||||||
self.assertIn(mac, updated_macs_for_device)
|
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):
|
def test_update_port_add_rule(self, mock_execute):
|
||||||
fdb_extension = self._get_fdb_extension(mock_execute, self.FDB_TABLE)
|
fdb_extension = self._get_fdb_extension(mock_execute, self.FDB_TABLE)
|
||||||
mock_execute.reset_mock()
|
mock_execute.reset_mock()
|
||||||
|
@ -101,7 +102,7 @@ class FdbPopulationExtensionTestCase(base.BaseTestCase):
|
||||||
mac = self.UPDATE_MSG['mac_address']
|
mac = self.UPDATE_MSG['mac_address']
|
||||||
self.assertIn(mac, updated_macs_for_device)
|
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):
|
def test_update_port_changed_mac(self, mock_execute):
|
||||||
fdb_extension = self._get_fdb_extension(mock_execute, self.FDB_TABLE)
|
fdb_extension = self._get_fdb_extension(mock_execute, self.FDB_TABLE)
|
||||||
mock_execute.reset_mock()
|
mock_execute.reset_mock()
|
||||||
|
@ -145,7 +146,7 @@ class FdbPopulationExtensionTestCase(base.BaseTestCase):
|
||||||
fdb_extension.fdb_tracker.device_to_macs.get(self.DEVICE))
|
fdb_extension.fdb_tracker.device_to_macs.get(self.DEVICE))
|
||||||
self.assertIsNone(updated_macs_for_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):
|
def test_catch_update_port_exception(self, mock_execute):
|
||||||
fdb_extension = self._get_fdb_extension(mock_execute, '')
|
fdb_extension = self._get_fdb_extension(mock_execute, '')
|
||||||
mock_execute.side_effect = RuntimeError
|
mock_execute.side_effect = RuntimeError
|
||||||
|
@ -155,7 +156,7 @@ class FdbPopulationExtensionTestCase(base.BaseTestCase):
|
||||||
mac = self.UPDATE_MSG['mac_address']
|
mac = self.UPDATE_MSG['mac_address']
|
||||||
self.assertNotIn(mac, updated_macs_for_device)
|
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):
|
def test_catch_delete_port_exception(self, mock_execute):
|
||||||
fdb_extension = self._get_fdb_extension(mock_execute, '')
|
fdb_extension = self._get_fdb_extension(mock_execute, '')
|
||||||
fdb_extension.handle_port(context=None, details=self.UPDATE_MSG)
|
fdb_extension.handle_port(context=None, details=self.UPDATE_MSG)
|
||||||
|
@ -166,7 +167,7 @@ class FdbPopulationExtensionTestCase(base.BaseTestCase):
|
||||||
mac = self.UPDATE_MSG['mac_address']
|
mac = self.UPDATE_MSG['mac_address']
|
||||||
self.assertIn(mac, updated_macs_for_device)
|
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):
|
def test_delete_port(self, mock_execute):
|
||||||
fdb_extension = self._get_fdb_extension(mock_execute, '')
|
fdb_extension = self._get_fdb_extension(mock_execute, '')
|
||||||
fdb_extension.handle_port(context=None, details=self.UPDATE_MSG)
|
fdb_extension.handle_port(context=None, details=self.UPDATE_MSG)
|
||||||
|
@ -176,7 +177,7 @@ class FdbPopulationExtensionTestCase(base.BaseTestCase):
|
||||||
'dev', self.DEVICE]
|
'dev', self.DEVICE]
|
||||||
mock_execute.assert_called_once_with(cmd, run_as_root=True)
|
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):
|
def test_multiple_devices(self, mock_execute):
|
||||||
cfg.CONF.set_override('shared_physical_device_mappings',
|
cfg.CONF.set_override('shared_physical_device_mappings',
|
||||||
['physnet1:p1p1', 'physnet1:p2p2'], 'FDB')
|
['physnet1:p1p1', 'physnet1:p2p2'], 'FDB')
|
||||||
|
|
|
@ -859,7 +859,7 @@ class TestLinuxBridgeManager(base.BaseTestCase):
|
||||||
'ensure_vxlan',
|
'ensure_vxlan',
|
||||||
return_value=None),\
|
return_value=None),\
|
||||||
mock.patch.object(
|
mock.patch.object(
|
||||||
utils,
|
ip_lib.IpNetnsCommand,
|
||||||
'execute',
|
'execute',
|
||||||
side_effect=None if fdb_append else RuntimeError()),\
|
side_effect=None if fdb_append else RuntimeError()),\
|
||||||
mock.patch.object(ip_lib,
|
mock.patch.object(ip_lib,
|
||||||
|
@ -1071,7 +1071,7 @@ class TestLinuxBridgeRpcCallbacks(base.BaseTestCase):
|
||||||
'network_type': 'vxlan',
|
'network_type': 'vxlan',
|
||||||
'segment_id': 1}}
|
'segment_id': 1}}
|
||||||
|
|
||||||
with mock.patch.object(utils, 'execute',
|
with mock.patch.object(ip_lib.IpNetnsCommand, 'execute',
|
||||||
return_value='') as execute_fn, \
|
return_value='') as execute_fn, \
|
||||||
mock.patch.object(ip_lib, 'add_neigh_entry',
|
mock.patch.object(ip_lib, 'add_neigh_entry',
|
||||||
return_value='') as add_fn:
|
return_value='') as add_fn:
|
||||||
|
@ -1140,7 +1140,7 @@ class TestLinuxBridgeRpcCallbacks(base.BaseTestCase):
|
||||||
'network_type': 'vxlan',
|
'network_type': 'vxlan',
|
||||||
'segment_id': 1}}
|
'segment_id': 1}}
|
||||||
|
|
||||||
with mock.patch.object(utils, 'execute',
|
with mock.patch.object(ip_lib.IpNetnsCommand, 'execute',
|
||||||
return_value='') as execute_fn, \
|
return_value='') as execute_fn, \
|
||||||
mock.patch.object(ip_lib, 'delete_neigh_entry',
|
mock.patch.object(ip_lib, 'delete_neigh_entry',
|
||||||
return_value='') as del_fn:
|
return_value='') as del_fn:
|
||||||
|
|
Loading…
Reference in New Issue