Add dpdk-bond-mappings configuration option

The current charm does not support creating and managing bonded network
interfaces. They are managed externaly. This is not possible when DPDK
is enabled. In this case OVS exposes the DPDK bond PMD which enslaves
the corresponding attached bond interfaces.

The new dpdk-bond-mappings configuration option allows such configuration
where mac:bond is specified. When the data-port configuration is processed
dpdk-bond-mappings are consulted to identify if the port belongs to a bond.
If this is true - then the bond is created with the mac designated interface
and the bond is added to the bridge. Subsequently more interfaces can be
added to the same bond.

Change-Id: I0224caaa1c2431c793c4f64caa7fc9e95b972fd7
This commit is contained in:
Nikolay Nikolaev 2018-06-12 14:21:48 +03:00
parent 862c362296
commit 8225b4dca9
5 changed files with 194 additions and 28 deletions

View File

@ -37,6 +37,15 @@ options:
Port can also be a linuxbridge bridge. In this case a veth pair will be
created, the ovs bridge and the linuxbridge bridge will be connected. It
can be useful to connect the ovs bridge to juju bridge.
dpdk-bond-mappings:
type: string
default:
description: |
Space-delimited list of bond:port mappings. The DPDK assigned ports will
be added to their corresponding bond, which in turn will be put into the
bridge as specified in data-port.
.
This option is supported only when enable-dpdk is true.
disable-security-groups:
type: boolean
default: false

View File

@ -240,7 +240,7 @@ class L3AgentContext(OSContextGenerator):
return ctxt
def resolve_dpdk_ports():
def resolve_dpdk_bridges():
'''
Resolve local PCI devices from configured mac addresses
using the data-port configuration option
@ -269,6 +269,35 @@ def resolve_dpdk_ports():
return resolved_devices
def resolve_dpdk_bonds():
'''
Resolve local PCI devices from configured mac addresses
using the dpdk-bond-mappings configuration option
@return: OrderDict indexed by PCI device address.
'''
bonds = config('dpdk-bond-mappings')
devices = PCINetDevices()
resolved_devices = collections.OrderedDict()
db = kv()
if bonds:
# NOTE: ordered dict of format {[mac]: bond}
bondmap = parse_data_port_mappings(bonds)
for mac, bond in bondmap.items():
pcidev = devices.get_device_from_mac(mac)
if pcidev:
# NOTE: store mac->pci allocation as post binding
# to dpdk, it disappears from PCIDevices.
db.set(mac, pcidev.pci_address)
db.flush()
pci_address = db.get(mac)
if pci_address:
resolved_devices[pci_address] = bond
return resolved_devices
def parse_cpu_list(cpulist):
'''
Parses a linux cpulist for a numa node
@ -304,7 +333,7 @@ class DPDKDeviceContext(OSContextGenerator):
driver = config('dpdk-driver')
if driver is None:
return {}
return {'devices': resolve_dpdk_ports(),
return {'devices': resolve_dpdk_bridges(),
'driver': driver}
@ -340,7 +369,7 @@ class OVSDPDKDeviceContext(OSContextGenerator):
'''Formatted list of devices to whitelist for dpdk'''
_flag = '-w {device}'
whitelist = []
for device in resolve_dpdk_ports():
for device in resolve_dpdk_bridges():
whitelist.append(_flag.format(device=device))
return ' '.join(whitelist)

View File

@ -426,14 +426,25 @@ def configure_ovs():
else:
# NOTE: when in dpdk mode, add based on pci bus order
# with type 'dpdk'
bridgemaps = neutron_ovs_context.resolve_dpdk_ports()
bridgemaps = neutron_ovs_context.resolve_dpdk_bridges()
bondmaps = neutron_ovs_context.resolve_dpdk_bonds()
device_index = 0
bridge_bond_map = DPDKBridgeBondMap()
for pci_address, br in bridgemaps.items():
add_bridge(br, datapath_type)
dpdk_add_bridge_port(br, 'dpdk{}'.format(device_index),
pci_address)
portname = 'dpdk{}'.format(device_index)
if pci_address in bondmaps:
bond = bondmaps[pci_address]
bridge_bond_map.add_port(br, bond, portname, pci_address)
else:
dpdk_add_bridge_port(br, portname,
pci_address)
device_index += 1
for br, bonds in bridge_bond_map.items():
for bond, t in bonds.items():
dpdk_add_bridge_bond(br, bond, *t)
target = config('ipfix-target')
bridges = [INT_BRIDGE, EXT_BRIDGE]
if bridgemaps:
@ -606,6 +617,24 @@ def dpdk_add_bridge_port(name, port, pci_address=None):
subprocess.check_call(cmd)
def dpdk_add_bridge_bond(bridge_name, bond_name, port_list, pci_address_list):
''' Add ports to a bond attached to the named openvswitch bridge '''
if ovs_has_late_dpdk_init():
cmd = ["ovs-vsctl", "--may-exist",
"add-bond", bridge_name, bond_name]
for port in port_list:
cmd.append(port)
id = 0
for pci_address in pci_address_list:
cmd.extend(["--", "set", "Interface", port_list[id],
"type=dpdk",
"options:dpdk-devargs={}".format(pci_address)])
id += 1
else:
raise Exception("Bond's not supported for OVS pre-2.6.0")
subprocess.check_call(cmd)
def enable_nova_metadata():
return use_dvr() or enable_local_dhcp()
@ -685,3 +714,20 @@ def _pause_resume_helper(f, configs):
f(assess_status_func(configs),
services=services(),
ports=None)
class DPDKBridgeBondMap():
def __init__(self):
self.map = {}
def add_port(self, bridge, bond, portname, pci_address):
if bridge not in self.map:
self.map[bridge] = {}
if bond not in self.map[bridge]:
self.map[bridge][bond] = ([], [])
self.map[bridge][bond][0].append(portname)
self.map[bridge][bond][1].append(pci_address)
def items(self):
return list(self.map.items())

View File

@ -501,6 +501,11 @@ DPDK_DATA_PORTS = (
"br-phynet1:fe:16:41:df:23:fd "
"br-phynet2:fe:f2:d0:45:dc:66"
)
BOND_MAPPINGS = (
"bond0:fe:16:41:df:23:fe "
"bond0:fe:16:41:df:23:fd "
"bond1:fe:f2:d0:45:dc:66"
)
PCI_DEVICE_MAP = {
'fe:16:41:df:23:fd': MockPCIDevice('0000:00:1c.0'),
'fe:16:41:df:23:fe': MockPCIDevice('0000:00:1d.0'),
@ -534,20 +539,29 @@ class TestDPDKUtils(CharmTestCase):
self.glob.glob.assert_called_with('/sys/devices/system/node/node*')
_parse_cpu_list.assert_called_with(TEST_CPULIST_1)
def test_resolve_dpdk_ports(self):
def test_resolve_dpdk_bridges(self):
self.test_config.set('data-port', DPDK_DATA_PORTS)
_pci_devices = Mock()
_pci_devices.get_device_from_mac.side_effect = PCI_DEVICE_MAP.get
self.PCINetDevices.return_value = _pci_devices
self.assertEqual(context.resolve_dpdk_ports(),
self.assertEqual(context.resolve_dpdk_bridges(),
{'0000:00:1c.0': 'br-phynet1',
'0000:00:1d.0': 'br-phynet3'})
def test_resolve_dpdk_bonds(self):
self.test_config.set('dpdk-bond-mappings', BOND_MAPPINGS)
_pci_devices = Mock()
_pci_devices.get_device_from_mac.side_effect = PCI_DEVICE_MAP.get
self.PCINetDevices.return_value = _pci_devices
self.assertEqual(context.resolve_dpdk_bonds(),
{'0000:00:1c.0': 'bond0',
'0000:00:1d.0': 'bond0'})
DPDK_PATCH = [
'parse_cpu_list',
'numa_node_cores',
'resolve_dpdk_ports',
'resolve_dpdk_bridges',
'glob',
]
@ -572,7 +586,7 @@ class TestOVSDPDKDeviceContext(CharmTestCase):
def test_device_whitelist(self):
'''Test device whitelist generation'''
self.resolve_dpdk_ports.return_value = [
self.resolve_dpdk_bridges.return_value = [
'0000:00:1c.0',
'0000:00:1d.0'
]
@ -606,12 +620,12 @@ class TestOVSDPDKDeviceContext(CharmTestCase):
def test_context_no_devices(self):
'''Ensure that DPDK is disable when no devices detected'''
self.resolve_dpdk_ports.return_value = []
self.resolve_dpdk_bridges.return_value = []
self.assertEqual(self.test_context(), {})
def test_context_devices(self):
'''Ensure DPDK is enabled when devices are detected'''
self.resolve_dpdk_ports.return_value = [
self.resolve_dpdk_bridges.return_value = [
'0000:00:1c.0',
'0000:00:1d.0'
]
@ -635,7 +649,7 @@ class TestDPDKDeviceContext(CharmTestCase):
def test_context(self):
self.test_config.set('dpdk-driver', 'uio_pci_generic')
self.resolve_dpdk_ports.return_value = [
self.resolve_dpdk_bridges.return_value = [
'0000:00:1c.0',
'0000:00:1d.0'
]
@ -646,7 +660,7 @@ class TestDPDKDeviceContext(CharmTestCase):
self.config.assert_called_with('dpdk-driver')
def test_context_none_driver(self):
self.resolve_dpdk_ports.return_value = [
self.resolve_dpdk_bridges.return_value = [
'0000:00:1c.0',
'0000:00:1d.0'
]

View File

@ -34,6 +34,7 @@ TO_PATCH = [
'add_ovsbridge_linuxbridge',
'is_linuxbridge_interface',
'dpdk_add_bridge_port',
'dpdk_add_bridge_bond',
'apt_install',
'apt_update',
'config',
@ -473,12 +474,21 @@ class TestNeutronOVSUtils(CharmTestCase):
self.add_bridge_port.assert_called_with('br-ex', 'eth0')
def _run_configure_ovs_dpdk(self, mock_config, _use_dvr,
_resolve_dpdk_ports, _late_init):
_resolve_dpdk_ports.return_value = OrderedDict([
_resolve_dpdk_bridges, _resolve_dpdk_bonds,
_late_init, _test_bonds):
_resolve_dpdk_bridges.return_value = OrderedDict([
('0000:001c.01', 'br-phynet1'),
('0000:001c.02', 'br-phynet2'),
('0000:001c.03', 'br-phynet3'),
])
if _test_bonds:
_resolve_dpdk_bonds.return_value = OrderedDict([
('0000:001c.01', 'bond0'),
('0000:001c.02', 'bond1'),
('0000:001c.03', 'bond2'),
])
else:
_resolve_dpdk_bonds.return_value = OrderedDict()
_use_dvr.return_value = True
self.use_dpdk.return_value = True
self.ovs_has_late_dpdk_init.return_value = _late_init
@ -494,28 +504,59 @@ class TestNeutronOVSUtils(CharmTestCase):
call('br-phynet3', 'netdev')],
any_order=True
)
self.dpdk_add_bridge_port.assert_has_calls([
call('br-phynet1', 'dpdk0', '0000:001c.01'),
call('br-phynet2', 'dpdk1', '0000:001c.02'),
call('br-phynet3', 'dpdk2', '0000:001c.03')],
any_order=True
)
if _test_bonds:
self.dpdk_add_bridge_bond.assert_has_calls([
call('br-phynet1', 'bond0', ['dpdk0'], ['0000:001c.01']),
call('br-phynet2', 'bond1', ['dpdk1'], ['0000:001c.02']),
call('br-phynet3', 'bond2', ['dpdk2'], ['0000:001c.03'])],
any_order=True
)
else:
self.dpdk_add_bridge_port.assert_has_calls([
call('br-phynet1', 'dpdk0', '0000:001c.01'),
call('br-phynet2', 'dpdk1', '0000:001c.02'),
call('br-phynet3', 'dpdk2', '0000:001c.03')],
any_order=True
)
@patch.object(neutron_ovs_context, 'resolve_dpdk_ports')
@patch.object(neutron_ovs_context, 'resolve_dpdk_bonds')
@patch.object(neutron_ovs_context, 'resolve_dpdk_bridges')
@patch.object(nutils, 'use_dvr')
@patch('charmhelpers.contrib.openstack.context.config')
def test_configure_ovs_dpdk(self, mock_config, _use_dvr,
_resolve_dpdk_ports):
_resolve_dpdk_bridges,
_resolve_dpdk_bonds):
return self._run_configure_ovs_dpdk(mock_config, _use_dvr,
_resolve_dpdk_ports, False)
_resolve_dpdk_bridges,
_resolve_dpdk_bonds,
_late_init=False,
_test_bonds=False)
@patch.object(neutron_ovs_context, 'resolve_dpdk_ports')
@patch.object(neutron_ovs_context, 'resolve_dpdk_bonds')
@patch.object(neutron_ovs_context, 'resolve_dpdk_bridges')
@patch.object(nutils, 'use_dvr')
@patch('charmhelpers.contrib.openstack.context.config')
def test_configure_ovs_dpdk_late_init(self, mock_config, _use_dvr,
_resolve_dpdk_ports):
_resolve_dpdk_bridges,
_resolve_dpdk_bonds):
return self._run_configure_ovs_dpdk(mock_config, _use_dvr,
_resolve_dpdk_ports, True)
_resolve_dpdk_bridges,
_resolve_dpdk_bonds,
_late_init=True,
_test_bonds=False)
@patch.object(neutron_ovs_context, 'resolve_dpdk_bonds')
@patch.object(neutron_ovs_context, 'resolve_dpdk_bridges')
@patch.object(nutils, 'use_dvr')
@patch('charmhelpers.contrib.openstack.context.config')
def test_configure_ovs_dpdk_late_init_bonds(self, mock_config, _use_dvr,
_resolve_dpdk_bridges,
_resolve_dpdk_bonds):
return self._run_configure_ovs_dpdk(mock_config, _use_dvr,
_resolve_dpdk_bridges,
_resolve_dpdk_bonds,
_late_init=True,
_test_bonds=True)
@patch.object(nutils, 'use_dvr')
@patch('charmhelpers.contrib.openstack.context.config')
@ -694,3 +735,30 @@ class TestNeutronOVSUtils(CharmTestCase):
self.mock_sriov_device2.set_sriov_numvfs.assert_not_called()
self.assertTrue(self.remote_restart.called)
class TestDPDKBridgeBondMap(CharmTestCase):
def setUp(self):
super(TestDPDKBridgeBondMap, self).setUp(nutils,
TO_PATCH)
self.config.side_effect = self.test_config.get
def test_add_port(self):
ctx = nutils.DPDKBridgeBondMap()
ctx.add_port("br1", "bond1", "port1", "00:00:00:00:00:01")
ctx.add_port("br1", "bond1", "port2", "00:00:00:00:00:02")
ctx.add_port("br1", "bond2", "port3", "00:00:00:00:00:03")
ctx.add_port("br1", "bond2", "port4", "00:00:00:00:00:04")
expected = [('br1',
{'bond1':
(['port1', 'port2'],
['00:00:00:00:00:01', '00:00:00:00:00:02']),
'bond2':
(['port3', 'port4'],
['00:00:00:00:00:03', '00:00:00:00:00:04'])
})
]
self.assertEqual(ctx.items(), expected)