Implement "FdbInterface" with Pyroute2

Change-Id: I4b03151ed10b1c5af36fd803e96e9d2f95e88247
Story: #2007686
Task: #41494
This commit is contained in:
Rodolfo Alonso Hernandez 2020-06-04 17:58:45 +00:00
parent b9e725ed72
commit 3df21ee472
8 changed files with 327 additions and 269 deletions

View File

@ -20,6 +20,7 @@ from neutron_lib import constants
from neutron_lib.utils import helpers
from oslo_config import cfg
from oslo_log import log as logging
from pyroute2.netlink import exceptions as netlink_exceptions
from neutron.agent.linux import bridge_lib
from neutron.conf.agent import l2_ext_fdb_population
@ -70,13 +71,14 @@ class FdbPopulationAgentExtension(
# update macs already in the physical interface's FDB table
for device in devices:
try:
_stdout = bridge_lib.FdbInterface.show(device)
except RuntimeError as e:
rules = bridge_lib.FdbInterface.show(dev=device)
except (OSError, netlink_exceptions.NetlinkError) as e:
LOG.warning(
'Unable to find FDB Interface %(device)s. '
'Exception: %(e)s', {'device': device, 'e': e})
continue
self.device_to_macs[device] = _stdout.split()[::3]
self.device_to_macs[device] = [rule['mac'] for rule in
rules[device]]
def update_port(self, device, port_id, mac):
# check if device is updated
@ -90,14 +92,9 @@ class FdbPopulationAgentExtension(
# check if rule for mac already exists
if mac in self.device_to_macs[device]:
return
try:
bridge_lib.FdbInterface.add(mac, device)
except RuntimeError as e:
LOG.warning(
'Unable to add mac %(mac)s '
'to FDB Interface %(device)s. '
'Exception: %(e)s',
{'mac': mac, 'device': device, 'e': e})
if not bridge_lib.FdbInterface.add(mac, device):
LOG.warning('Unable to add mac %(mac)s to FDB Interface '
'%(device)s.', {'mac': mac, 'device': device})
return
self.device_to_macs[device].append(mac)
@ -110,14 +107,10 @@ class FdbPopulationAgentExtension(
return
for device in devices:
if mac in self.device_to_macs[device]:
try:
bridge_lib.FdbInterface.delete(mac, device)
except RuntimeError as e:
LOG.warning(
'Unable to delete mac %(mac)s '
'from FDB Interface %(device)s. '
'Exception: %(e)s',
{'mac': mac, 'device': device, 'e': e})
if not bridge_lib.FdbInterface.delete(mac, device):
LOG.warning('Unable to delete mac %(mac)s from FDB '
'Interface %(device)s.',
{'mac': mac, 'device': device})
return
self.device_to_macs[device].remove(mac)
del self.portid_to_mac[port_id]

View File

@ -16,6 +16,7 @@
# License for the specific language governing permissions and limitations
# under the License.
import collections
import functools
import os
@ -128,39 +129,66 @@ class BridgeDevice(ip_lib.IPDevice):
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)
@catch_exceptions
def add(mac, dev, dst_ip=None, namespace=None, **kwargs):
priv_ip_lib.add_bridge_fdb(mac, dev, dst_ip=dst_ip,
namespace=namespace, **kwargs)
@classmethod
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]
cls._execute_bridge(cmd, namespace, **kwargs)
@staticmethod
@catch_exceptions
def append(mac, dev, dst_ip=None, namespace=None, **kwargs):
priv_ip_lib.append_bridge_fdb(mac, dev, dst_ip=dst_ip,
namespace=namespace, **kwargs)
@classmethod
def add(cls, mac, dev, ip_dst=None, namespace=None, **kwargs):
return cls._cmd('add', mac, dev, ip_dst, namespace, **kwargs)
@staticmethod
@catch_exceptions
def replace(mac, dev, dst_ip=None, namespace=None, **kwargs):
try:
priv_ip_lib.delete_bridge_fdb(mac, dev, namespace=namespace,
**kwargs)
except (RuntimeError, OSError, netlink_exceptions.NetlinkError):
pass
priv_ip_lib.add_bridge_fdb(mac, dev, dst_ip=dst_ip,
namespace=namespace, **kwargs)
@classmethod
def append(cls, mac, dev, ip_dst=None, namespace=None, **kwargs):
return cls._cmd('append', mac, dev, ip_dst, namespace, **kwargs)
@staticmethod
@catch_exceptions
def delete(mac, dev, dst_ip=None, namespace=None, **kwargs):
priv_ip_lib.delete_bridge_fdb(mac, dev, dst_ip=dst_ip,
namespace=namespace, **kwargs)
@classmethod
def replace(cls, mac, dev, ip_dst=None, namespace=None, **kwargs):
return cls._cmd('replace', mac, dev, ip_dst, namespace, **kwargs)
@staticmethod
def show(dev=None, namespace=None, **kwargs):
"""List the FDB entries in a namespace
@classmethod
def delete(cls, mac, dev, ip_dst=None, namespace=None, **kwargs):
return cls._cmd('delete', mac, dev, ip_dst, namespace, **kwargs)
:parameter dev: device name to filter the query
:parameter namespace: namespace name
:returns: a dictionary with the device names and the list of entries
per device.
"""
@classmethod
def show(cls, dev=None, namespace=None, **kwargs):
cmd = ['bridge', 'fdb', 'show']
if dev:
cmd += ['dev', dev]
return cls._execute_bridge(cmd, namespace, **kwargs)
def find_device_name(ifindex, devices):
for device in (device for device in devices if
device['index'] == ifindex):
return device['name']
ret = collections.defaultdict(list)
fdbs = priv_ip_lib.list_bridge_fdb(namespace=namespace, **kwargs)
devices = ip_lib.get_devices_info(namespace)
for fdb in fdbs:
name = find_device_name(fdb['ifindex'], devices)
if dev and dev != name:
continue
master = find_device_name(ip_lib.get_attr(fdb, 'NDA_MASTER'),
devices)
fdb_info = {'mac': ip_lib.get_attr(fdb, 'NDA_LLADDR'),
'master': master,
'vlan': ip_lib.get_attr(fdb, 'NDA_VLAN'),
'dst_ip': ip_lib.get_attr(fdb, 'NDA_DST')}
ret[name].append(fdb_info)
return ret

View File

@ -686,16 +686,7 @@ class LinuxBridgeManager(amb.CommonAgentManagerBase):
def vxlan_ucast_supported(self):
if not cfg.CONF.VXLAN.l2_population:
return False
if not ip_lib.iproute_arg_supported(
['bridge', 'fdb'], 'append'):
LOG.warning('Option "%(option)s" must be supported by command '
'"%(command)s" to enable %(mode)s mode',
{'option': 'append',
'command': 'bridge fdb',
'mode': 'VXLAN UCAST'})
return False
test_iface = None
for seg_id in range(1, constants.MAX_VXLAN_VNI + 1):
if (ip_lib.device_exists(self.get_vxlan_device_name(seg_id)) or
ip_lib.vxlan_in_use(seg_id)):
@ -706,15 +697,10 @@ class LinuxBridgeManager(amb.CommonAgentManagerBase):
LOG.error('No valid Segmentation ID to perform UCAST test.')
return False
try:
bridge_lib.FdbInterface.append(constants.FLOODING_ENTRY[0],
test_iface, '1.1.1.1',
log_fail_as_error=False)
return True
except RuntimeError:
return False
finally:
self.delete_interface(test_iface)
ret = bridge_lib.FdbInterface.append(constants.FLOODING_ENTRY[0],
test_iface, '1.1.1.1')
self.delete_interface(test_iface)
return ret
@staticmethod
def vxlan_mcast_supported():
@ -754,11 +740,13 @@ class LinuxBridgeManager(amb.CommonAgentManagerBase):
@staticmethod
def fdb_bridge_entry_exists(mac, interface, agent_ip=None):
entries = bridge_lib.FdbInterface.show(interface)
entries = bridge_lib.FdbInterface.show(dev=interface)
macs = [entry['mac'] for entry in entries[interface]]
ips = [entry['dst_ip'] for entry in entries[interface]]
if not agent_ip:
return mac in entries
return mac in macs
return (agent_ip in entries and mac in entries)
return agent_ip in ips and mac in macs
@staticmethod
def add_fdb_ip_entry(mac, ip, interface):
@ -774,25 +762,23 @@ class LinuxBridgeManager(amb.CommonAgentManagerBase):
for mac, ip in ports:
if mac != constants.FLOODING_ENTRY[0]:
self.add_fdb_ip_entry(mac, ip, interface)
bridge_lib.FdbInterface.replace(mac, interface, agent_ip,
check_exit_code=False)
bridge_lib.FdbInterface.replace(mac, interface,
dst_ip=agent_ip)
elif self.vxlan_mode == lconst.VXLAN_UCAST:
if self.fdb_bridge_entry_exists(mac, interface):
bridge_lib.FdbInterface.append(mac, interface, agent_ip,
check_exit_code=False)
bridge_lib.FdbInterface.append(mac, interface,
dst_ip=agent_ip)
else:
bridge_lib.FdbInterface.add(mac, interface, agent_ip,
check_exit_code=False)
bridge_lib.FdbInterface.add(mac, interface,
dst_ip=agent_ip)
def remove_fdb_entries(self, agent_ip, ports, interface):
for mac, ip in ports:
if mac != constants.FLOODING_ENTRY[0]:
self.remove_fdb_ip_entry(mac, ip, interface)
bridge_lib.FdbInterface.delete(mac, interface, agent_ip,
check_exit_code=False)
bridge_lib.FdbInterface.delete(mac, interface, dst_ip=agent_ip)
elif self.vxlan_mode == lconst.VXLAN_UCAST:
bridge_lib.FdbInterface.delete(mac, interface, agent_ip,
check_exit_code=False)
bridge_lib.FdbInterface.delete(mac, interface, dst_ip=agent_ip)
def get_agent_id(self):
if self.bridge_mappings:

View File

@ -761,3 +761,59 @@ def delete_ip_route(namespace, cidr, ip_version, device=None, via=None,
if e.errno == errno.ENOENT:
raise NetworkNamespaceNotFound(netns_name=namespace)
raise
@privileged.default.entrypoint
def list_bridge_fdb(namespace=None, **kwargs):
"""List bridge fdb table"""
# NOTE(ralonsoh): fbd does not support ifindex filtering in pyroute2 0.5.14
try:
with get_iproute(namespace) as ip:
return make_serializable(ip.fdb('dump', **kwargs))
except OSError as e:
if e.errno == errno.ENOENT:
raise NetworkNamespaceNotFound(netns_name=namespace)
raise
def _command_bridge_fdb(command, mac, device, dst_ip=None, namespace=None,
**kwargs):
try:
kwargs['lladdr'] = mac
kwargs['ifindex'] = get_link_id(device, namespace)
if dst_ip:
kwargs['dst'] = dst_ip
with get_iproute(namespace) as ip:
return make_serializable(ip.fdb(command, **kwargs))
except OSError as e:
if e.errno == errno.ENOENT:
raise NetworkNamespaceNotFound(netns_name=namespace)
raise
@privileged.default.entrypoint
def add_bridge_fdb(mac, device, dst_ip=None, namespace=None, **kwargs):
"""Add a FDB entry"""
return _command_bridge_fdb('add', mac, device, dst_ip=dst_ip,
namespace=namespace, **kwargs)
@privileged.default.entrypoint
def append_bridge_fdb(mac, device, dst_ip=None, namespace=None, **kwargs):
"""Add a FDB entry"""
return _command_bridge_fdb('append', mac, device, dst_ip=dst_ip,
namespace=namespace, **kwargs)
@privileged.default.entrypoint
def replace_bridge_fdb(mac, device, dst_ip=None, namespace=None, **kwargs):
"""Add a FDB entry"""
return _command_bridge_fdb('replace', mac, device, dst_ip=dst_ip,
namespace=namespace, **kwargs)
@privileged.default.entrypoint
def delete_bridge_fdb(mac, device, dst_ip=None, namespace=None, **kwargs):
"""Add a FDB entry"""
return _command_bridge_fdb('del', mac, device, dst_ip=dst_ip,
namespace=namespace, **kwargs)

View File

@ -13,7 +13,6 @@
# under the License.
import random
import re
import netaddr
from neutron_lib import constants
@ -27,6 +26,9 @@ from neutron.tests.common import net_helpers
from neutron.tests.functional import base
MAC_ALL_NODES_ADDRESS = '33:33:00:00:00:01'
class BridgeLibTestCase(base.BaseSudoTestCase):
def setUp(self):
@ -131,8 +133,6 @@ 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()}),
@ -172,59 +172,109 @@ class FdbInterfaceTestCase(testscenarios.WithScenarios, base.BaseSudoTestCase):
except priv_ip_lib.NetworkInterfaceNotFound:
pass
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 _assert_mac(self, mac_address, device, present=True):
msg = ('MAC address %(mac_address)s %(present)s in the FDB table for '
'the device %(device)s in namespace %(namespace)s' %
{'mac_address': mac_address, 'device': device,
'namespace': self.namespace,
'present': 'not present' if present else 'present'})
for _device, fdbs in bridge_lib.FdbInterface.show(
dev=device, namespace=self.namespace).items():
self.assertEqual(device, _device)
macs = [fdb['mac'] for fdb in fdbs]
if ((mac_address in macs and not present) or
(mac_address not in macs and present)):
self.fail(msg)
def _assert_ip(self, mac_address, ip_address, device):
msg = ('Destination IP address %(ip_address)s not present in the FDB '
'table for the MAC address %(mac_address)s and device '
'%(device)s in namespace %(namespace)s' %
{'mac_address': mac_address, 'device': device,
'namespace': self.namespace, 'ip_address': ip_address})
for _device, fdbs in bridge_lib.FdbInterface.show(
dev=device, namespace=self.namespace).items():
self.assertEqual(device, _device)
for _ in (fdb for fdb in fdbs if fdb['mac'] == mac_address and
fdb['dst_ip'] == ip_address):
return
self.fail(msg)
def test_add_delete(self):
self.assertNotIn(self.MAC1, self._list_fdb_rules(self.device))
self._assert_mac(self.MAC1, self.device, present=False)
bridge_lib.FdbInterface.add(self.MAC1, self.device,
namespace=self.namespace)
self.assertIn(self.MAC1, self._list_fdb_rules(self.device))
self._assert_mac(self.MAC1, self.device)
bridge_lib.FdbInterface.delete(self.MAC1, self.device,
namespace=self.namespace)
self.assertNotIn(self.MAC1, self._list_fdb_rules(self.device))
self._assert_mac(self.MAC1, self.device, present=False)
def test_add_delete_dst(self):
self.assertNotIn(self.MAC1, self._list_fdb_rules(self.device_vxlan))
self._assert_mac(self.MAC1, self.device_vxlan, present=False)
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])
dst_ip=str(netaddr.IPNetwork(self.ip).ip))
self._assert_ip(self.MAC1, str(netaddr.IPNetwork(self.ip).ip),
self.device_vxlan)
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))
dst_ip=str(netaddr.IPNetwork(self.ip).ip))
self._assert_mac(self.MAC1, self.device_vxlan, present=False)
def test_append(self):
self.assertNotIn(self.MAC1, self._list_fdb_rules(self.device))
self._assert_mac(self.MAC1, self.device, present=False)
bridge_lib.FdbInterface.append(self.MAC1, self.device,
namespace=self.namespace)
self.assertIn(self.MAC1, self._list_fdb_rules(self.device))
self._assert_mac(self.MAC1, self.device)
def test_append_dst(self):
self.assertNotIn(self.MAC1, self._list_fdb_rules(self.device_vxlan))
self._assert_mac(self.MAC1, 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])
dst_ip=str(netaddr.IPNetwork(self.ip).ip))
self._assert_ip(self.MAC1, str(netaddr.IPNetwork(self.ip).ip),
self.device_vxlan)
def test_replace(self):
self.assertNotIn(self.MAC1, self._list_fdb_rules(self.device))
self._assert_mac(self.MAC1, self.device, present=False)
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])
dst_ip=str(netaddr.IPNetwork(self.ip).ip))
self._assert_ip(self.MAC1, str(netaddr.IPNetwork(self.ip).ip),
self.device_vxlan)
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])
dst_ip='1.1.1.1')
self._assert_ip(self.MAC1, '1.1.1.1', self.device_vxlan)
def test_show(self):
ip_str = str(netaddr.IPNetwork(self.ip).ip)
bridge_lib.FdbInterface.add(
self.MAC1, self.device_vxlan, namespace=self.namespace,
dst_ip=ip_str)
rules = bridge_lib.FdbInterface.show(dev=self.device_vxlan,
namespace=self.namespace)
self.assertEqual(1, len(rules))
self.assertEqual(1, len(rules[self.device_vxlan]))
self.assertEqual(self.MAC1, rules[self.device_vxlan][0]['mac'])
self.assertEqual(ip_str, rules[self.device_vxlan][0]['dst_ip'])
_uuid = uuidutils.generate_uuid()
bridge_name = ('br_' + _uuid)[:constants.DEVICE_NAME_MAX_LEN]
priv_ip_lib.create_interface(bridge_name, self.namespace, 'bridge')
bridge = bridge_lib.BridgeDevice(bridge_name, namespace=self.namespace)
bridge.addif(self.device)
rules = bridge_lib.FdbInterface.show(dev=bridge_name,
namespace=self.namespace)
self.assertEqual(1, len(rules))
self._assert_mac(MAC_ALL_NODES_ADDRESS, bridge_name)
rules = bridge_lib.FdbInterface.show(dev=self.device,
namespace=self.namespace)
mac_address = ip_lib.IPDevice(self.device, self.namespace).link.address
for rule in (rule for rule in rules[self.device] if
rule['mac'] == mac_address):
self.assertEqual(bridge_name, rule['master'])
self.assertIn(rule['vlan'], (1, None))

View File

@ -220,10 +220,10 @@ class TcFiltersTestCase(functional_base.BaseSudoTestCase):
bridge_lib.FdbInterface.append(
'00:00:00:00:00:00', self.device_vxlan[0], namespace=self.ns[0],
ip_dst=str(netaddr.IPNetwork(self.ip[1]).ip))
dst_ip=str(netaddr.IPNetwork(self.ip[1]).ip))
bridge_lib.FdbInterface.append(
'00:00:00:00:00:00', self.device_vxlan[1], namespace=self.ns[1],
ip_dst=str(netaddr.IPNetwork(self.ip[0]).ip))
dst_ip=str(netaddr.IPNetwork(self.ip[0]).ip))
def test_add_tc_filter_vxlan(self):
# The traffic control is applied on the veth pair device of the first

View File

@ -19,10 +19,11 @@ from unittest import mock
from neutron_lib import constants
from neutron_lib.utils import helpers
from oslo_config import cfg
from pyroute2.netlink import exceptions as netlink_exceptions
from neutron.agent.l2.extensions.fdb_population import (
FdbPopulationAgentExtension)
from neutron.agent.linux import ip_lib
from neutron.agent.linux import bridge_lib
from neutron.plugins.ml2.drivers.linuxbridge.agent.common import (
constants as linux_bridge_constants)
from neutron.plugins.ml2.drivers.openvswitch.agent.common import (
@ -37,14 +38,22 @@ class FdbPopulationExtensionTestCase(base.BaseTestCase):
u'mac_address': u'fa:16:3e:ba:bc:21',
u'port_id': u'17ceda02-43e1-48d8-beb6-35885b20cae6'}
DELETE_MSG = {u'port_id': u'17ceda02-43e1-48d8-beb6-35885b20cae6'}
FDB_TABLE = ("aa:aa:aa:aa:aa:aa self permanent\n"
"bb:bb:bb:bb:bb:bb self permanent")
def setUp(self):
super(FdbPopulationExtensionTestCase, self).setUp()
cfg.CONF.set_override('shared_physical_device_mappings',
['physnet1:p1p1'], 'FDB')
self.DEVICE = self._get_existing_device()
self.mock_add = mock.patch.object(
bridge_lib.FdbInterface, 'add', return_value=0).start()
self.mock_append = mock.patch.object(
bridge_lib.FdbInterface, 'append', return_value=0).start()
self.mock_replace = mock.patch.object(
bridge_lib.FdbInterface, 'replace', return_value=0).start()
self.mock_delete = mock.patch.object(
bridge_lib.FdbInterface, 'delete', return_value=0).start()
self.mock_show = mock.patch.object(
bridge_lib.FdbInterface, 'show').start()
def _get_existing_device(self):
device_mappings = helpers.parse_mappings(
@ -52,14 +61,12 @@ class FdbPopulationExtensionTestCase(base.BaseTestCase):
DEVICES = next(iter(device_mappings.values()))
return DEVICES[0]
def _get_fdb_extension(self, mock_execute, fdb_table):
mock_execute.return_value = fdb_table
def _get_fdb_extension(self):
fdb_pop = FdbPopulationAgentExtension()
fdb_pop.initialize(None, ovs_constants.EXTENSION_DRIVER_TYPE)
return fdb_pop
@mock.patch('neutron.agent.common.utils.execute')
def test_initialize(self, mock_execute):
def test_initialize(self):
fdb_extension = FdbPopulationAgentExtension()
fdb_extension.initialize(None, ovs_constants.EXTENSION_DRIVER_TYPE)
fdb_extension.initialize(None,
@ -70,123 +77,101 @@ class FdbPopulationExtensionTestCase(base.BaseTestCase):
fdb_extension = FdbPopulationAgentExtension()
self.assertRaises(SystemExit, fdb_extension.initialize, None, 'sriov')
@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)
def test_construct_empty_fdb_table(self):
self._get_fdb_extension()
self.mock_show.assert_called_once_with(dev=self.DEVICE)
@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)
cmd = ['bridge', 'fdb', 'show', 'dev', self.DEVICE]
mock_execute.assert_called_once_with(cmd, run_as_root=True)
def test_construct_existing_fdb_table(self):
self.mock_show.return_value = {
self.DEVICE: [{'mac': 'aa:aa:aa:aa:aa:aa'},
{'mac': 'bb:bb:bb:bb:bb:bb'}]
}
fdb_extension = self._get_fdb_extension()
self.mock_show.assert_called_once_with(dev=self.DEVICE)
updated_macs_for_device = (
fdb_extension.fdb_tracker.device_to_macs.get(self.DEVICE))
macs = [line.split()[0] for line in self.FDB_TABLE.split('\n')]
for mac in macs:
self.assertIn(mac, updated_macs_for_device)
macs = ['aa:aa:aa:aa:aa:aa', 'bb:bb:bb:bb:bb:bb']
self.assertEqual(sorted(macs), sorted(updated_macs_for_device))
@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()
def test_update_port_add_rule(self):
fdb_extension = self._get_fdb_extension()
self.mock_add.return_value = True
fdb_extension.handle_port(context=None, details=self.UPDATE_MSG)
cmd = ['bridge', 'fdb', 'add', self.UPDATE_MSG['mac_address'],
'dev', self.DEVICE]
mock_execute.assert_called_once_with(cmd, run_as_root=True)
self.mock_add.assert_called_once_with(self.UPDATE_MSG['mac_address'],
self.DEVICE)
updated_macs_for_device = (
fdb_extension.fdb_tracker.device_to_macs.get(self.DEVICE))
mac = self.UPDATE_MSG['mac_address']
self.assertIn(mac, updated_macs_for_device)
@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()
def test_update_port_changed_mac(self):
fdb_extension = self._get_fdb_extension()
mac = self.UPDATE_MSG['mac_address']
updated_mac = 'fa:16:3e:ba:bc:33'
commands = []
self.mock_add.return_value = True
fdb_extension.handle_port(context=None, details=self.UPDATE_MSG)
commands.append(['bridge', 'fdb', 'add', mac, 'dev', self.DEVICE])
self.UPDATE_MSG['mac_address'] = updated_mac
self.mock_delete.return_value = True
fdb_extension.handle_port(context=None, details=self.UPDATE_MSG)
commands.append(['bridge', 'fdb', 'delete', mac, 'dev', self.DEVICE])
commands.append(['bridge', 'fdb', 'add', updated_mac,
'dev', self.DEVICE])
calls = []
for cmd in commands:
calls.append(mock.call(cmd, run_as_root=True))
mock_execute.assert_has_calls(calls)
calls_add = [mock.call(mac, self.DEVICE),
mock.call(updated_mac, self.DEVICE)]
self.mock_add.assert_has_calls(calls_add)
self.mock_delete.assert_called_once_with(mac, self.DEVICE)
updated_macs_for_device = (
fdb_extension.fdb_tracker.device_to_macs.get(self.DEVICE))
self.assertIn(updated_mac, updated_macs_for_device)
self.assertNotIn(mac, updated_macs_for_device)
@mock.patch('neutron.agent.common.utils.execute')
def test_unpermitted_device_owner(self, mock_execute):
fdb_extension = self._get_fdb_extension(mock_execute, '')
mock_execute.reset_mock()
def test_unpermitted_device_owner(self):
fdb_extension = self._get_fdb_extension()
details = copy.deepcopy(self.UPDATE_MSG)
details['device_owner'] = constants.DEVICE_OWNER_LOADBALANCER
fdb_extension.handle_port(context=None, details=details)
self.assertFalse(mock_execute.called)
updated_macs_for_device = (
fdb_extension.fdb_tracker.device_to_macs.get(self.DEVICE))
mac = self.UPDATE_MSG['mac_address']
self.assertNotIn(mac, updated_macs_for_device)
@mock.patch('neutron.agent.common.utils.execute')
def test_catch_init_exception(self, mock_execute):
mock_execute.side_effect = RuntimeError
fdb_extension = self._get_fdb_extension(mock_execute, '')
def test_catch_init_exception(self):
self.mock_add.side_effect = netlink_exceptions.NetlinkError
fdb_extension = self._get_fdb_extension()
updated_macs_for_device = (
fdb_extension.fdb_tracker.device_to_macs.get(self.DEVICE))
self.assertIsNone(updated_macs_for_device)
self.assertEqual([], updated_macs_for_device)
@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
def test_catch_update_port_exception(self):
fdb_extension = self._get_fdb_extension()
self.mock_add.return_value = False
fdb_extension.handle_port(context=None, details=self.UPDATE_MSG)
updated_macs_for_device = (
fdb_extension.fdb_tracker.device_to_macs.get(self.DEVICE))
mac = self.UPDATE_MSG['mac_address']
self.assertNotIn(mac, updated_macs_for_device)
@mock.patch.object(ip_lib.IpNetnsCommand, 'execute')
def test_catch_delete_port_exception(self, mock_execute):
fdb_extension = self._get_fdb_extension(mock_execute, '')
def test_catch_delete_port_exception(self):
fdb_extension = self._get_fdb_extension()
self.mock_add.return_value = True
fdb_extension.handle_port(context=None, details=self.UPDATE_MSG)
mock_execute.side_effect = RuntimeError
self.mock_delete.return_value = False
fdb_extension.delete_port(context=None, details=self.DELETE_MSG)
updated_macs_for_device = (
fdb_extension.fdb_tracker.device_to_macs.get(self.DEVICE))
mac = self.UPDATE_MSG['mac_address']
self.assertIn(mac, updated_macs_for_device)
self.assertIn(self.UPDATE_MSG['mac_address'], updated_macs_for_device)
@mock.patch.object(ip_lib.IpNetnsCommand, 'execute')
def test_delete_port(self, mock_execute):
fdb_extension = self._get_fdb_extension(mock_execute, '')
def test_delete_port(self):
fdb_extension = self._get_fdb_extension()
self.mock_add.return_value = True
fdb_extension.handle_port(context=None, details=self.UPDATE_MSG)
mock_execute.reset_mock()
self.mock_delete.return_value = False
fdb_extension.delete_port(context=None, details=self.DELETE_MSG)
cmd = ['bridge', 'fdb', 'delete', self.UPDATE_MSG['mac_address'],
'dev', self.DEVICE]
mock_execute.assert_called_once_with(cmd, run_as_root=True)
self.mock_delete.assert_called_once_with(
self.UPDATE_MSG['mac_address'], self.DEVICE)
@mock.patch.object(ip_lib.IpNetnsCommand, 'execute')
def test_multiple_devices(self, mock_execute):
def test_multiple_devices(self):
cfg.CONF.set_override('shared_physical_device_mappings',
['physnet1:p1p1', 'physnet1:p2p2'], 'FDB')
fdb_extension = self._get_fdb_extension(mock_execute, '')
fdb_extension = self._get_fdb_extension()
fdb_extension.handle_port(context=None, details=self.UPDATE_MSG)
mac = self.UPDATE_MSG['mac_address']
calls = []
cmd = ['bridge', 'fdb', 'add', mac, 'dev', 'p1p1']
calls.append(mock.call(cmd, run_as_root=True))
cmd = ['bridge', 'fdb', 'add', mac, 'dev', 'p2p2']
calls.append(mock.call(cmd, run_as_root=True))
mock_execute.assert_has_calls(calls, any_order=True)
calls = [mock.call(self.UPDATE_MSG['mac_address'], 'p1p1'),
mock.call(self.UPDATE_MSG['mac_address'], 'p2p2')]
self.mock_add.assert_has_calls(calls)

View File

@ -847,60 +847,33 @@ class TestLinuxBridgeManager(base.BaseTestCase):
vxlan_mcast_supported=False)
def _check_vxlan_ucast_supported(
self, expected, l2_population, iproute_arg_supported, fdb_append):
self, expected, l2_population, fdb_append):
cfg.CONF.set_override('l2_population', l2_population, 'VXLAN')
with mock.patch.object(ip_lib, 'device_exists', return_value=False),\
mock.patch.object(ip_lib, 'vxlan_in_use', return_value=False),\
mock.patch.object(self.lbm,
'delete_interface',
return_value=None),\
mock.patch.object(self.lbm,
'ensure_vxlan',
return_value=None),\
mock.patch.object(
ip_lib.IpNetnsCommand,
'execute',
side_effect=None if fdb_append else RuntimeError()),\
mock.patch.object(ip_lib,
'iproute_arg_supported',
return_value=iproute_arg_supported):
mock.patch.object(self.lbm, 'delete_interface'),\
mock.patch.object(self.lbm, 'ensure_vxlan',
return_value=None), \
mock.patch.object(bridge_lib.FdbInterface, 'append',
return_value=fdb_append):
self.assertEqual(expected, self.lbm.vxlan_ucast_supported())
def test_vxlan_ucast_supported(self):
self._check_vxlan_ucast_supported(
expected=False,
l2_population=False, iproute_arg_supported=True, fdb_append=True)
expected=False, l2_population=False, fdb_append=mock.ANY)
self._check_vxlan_ucast_supported(
expected=False,
l2_population=True, iproute_arg_supported=False, fdb_append=True)
expected=False, l2_population=True, fdb_append=False)
self._check_vxlan_ucast_supported(
expected=False,
l2_population=True, iproute_arg_supported=True, fdb_append=False)
self._check_vxlan_ucast_supported(
expected=True,
l2_population=True, iproute_arg_supported=True, fdb_append=True)
expected=True, l2_population=True, fdb_append=True)
def _check_vxlan_mcast_supported(
self, expected, vxlan_group, iproute_arg_supported):
def _check_vxlan_mcast_supported(self, expected, vxlan_group):
cfg.CONF.set_override('vxlan_group', vxlan_group, 'VXLAN')
with mock.patch.object(
ip_lib, 'iproute_arg_supported',
return_value=iproute_arg_supported):
self.assertEqual(expected, self.lbm.vxlan_mcast_supported())
self.assertEqual(expected, self.lbm.vxlan_mcast_supported())
def test_vxlan_mcast_supported(self):
self._check_vxlan_mcast_supported(
expected=False,
vxlan_group='',
iproute_arg_supported=True)
self._check_vxlan_mcast_supported(
expected=False,
vxlan_group='224.0.0.1',
iproute_arg_supported=False)
self._check_vxlan_mcast_supported(
expected=True,
vxlan_group='224.0.0.1',
iproute_arg_supported=True)
self._check_vxlan_mcast_supported(expected=False, vxlan_group='')
self._check_vxlan_mcast_supported(expected=True,
vxlan_group='224.0.0.1')
def _test_ensure_port_admin_state(self, admin_state):
port_id = 'fake_id'
@ -975,6 +948,16 @@ class TestLinuxBridgeRpcCallbacks(base.BaseTestCase):
segment.segmentation_id = 1
self.lb_rpc.network_map['net_id'] = segment
cfg.CONF.set_default('host', 'host')
self.mock_add = mock.patch.object(
bridge_lib.FdbInterface, 'add').start()
self.mock_append = mock.patch.object(
bridge_lib.FdbInterface, 'append').start()
self.mock_replace = mock.patch.object(
bridge_lib.FdbInterface, 'replace').start()
self.mock_delete = mock.patch.object(
bridge_lib.FdbInterface, 'delete').start()
self.mock_show = mock.patch.object(
bridge_lib.FdbInterface, 'show').start()
def test_network_delete_mapped_net(self):
mock_net = mock.Mock()
@ -1070,26 +1053,14 @@ class TestLinuxBridgeRpcCallbacks(base.BaseTestCase):
'network_type': 'vxlan',
'segment_id': 1}}
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:
with mock.patch.object(ip_lib, 'add_neigh_entry',
return_value='') as add_fn:
self.lb_rpc.fdb_add(None, fdb_entries)
expected = [
mock.call(['bridge', 'fdb', 'show', 'dev', 'vxlan-1'],
run_as_root=True),
mock.call(['bridge', 'fdb', 'add',
constants.FLOODING_ENTRY[0],
'dev', 'vxlan-1', 'dst', 'agent_ip'],
run_as_root=True,
check_exit_code=False),
mock.call(['bridge', 'fdb', 'replace', 'port_mac', 'dev',
'vxlan-1', 'dst', 'agent_ip'],
run_as_root=True,
check_exit_code=False),
]
execute_fn.assert_has_calls(expected)
self.mock_show.assert_called_once_with(dev='vxlan-1')
self.mock_add.assert_called_once_with(
constants.FLOODING_ENTRY[0], 'vxlan-1', dst_ip='agent_ip')
self.mock_replace.assert_called_once_with(
'port_mac', 'vxlan-1', dst_ip='agent_ip')
if proxy_enabled:
add_fn.assert_called_with('port_ip', 'port_mac', 'vxlan-1')
else:
@ -1139,24 +1110,13 @@ class TestLinuxBridgeRpcCallbacks(base.BaseTestCase):
'network_type': 'vxlan',
'segment_id': 1}}
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:
with mock.patch.object(ip_lib, 'delete_neigh_entry',
return_value='') as del_fn:
self.lb_rpc.fdb_remove(None, fdb_entries)
expected = [
mock.call(['bridge', 'fdb', 'delete',
constants.FLOODING_ENTRY[0],
'dev', 'vxlan-1', 'dst', 'agent_ip'],
run_as_root=True,
check_exit_code=False),
mock.call(['bridge', 'fdb', 'delete', 'port_mac',
'dev', 'vxlan-1', 'dst', 'agent_ip'],
run_as_root=True,
check_exit_code=False),
]
execute_fn.assert_has_calls(expected)
calls = [mock.call(constants.FLOODING_ENTRY[0], 'vxlan-1',
dst_ip='agent_ip'),
mock.call('port_mac', 'vxlan-1', dst_ip='agent_ip')]
self.mock_delete.assert_has_calls(calls)
if proxy_enabled:
del_fn.assert_called_with('port_ip', 'port_mac', 'vxlan-1')
else: