Add support for OVS DPDK Bond
Add functionality to os-net-config to allow DPDK bonding of interfaces, and implement support for parameters to be passed by TripleO Heat Templates. Implements: blueprint tripleo-ovs-dpdk Depends-On: Id4a23ced28b92a642c180a35c55080e5f4e2e05d Change-Id: If1c91402d2d393140dc1b4a678e68a1bcdbe81e4
This commit is contained in:
parent
6d046bca3c
commit
9108fcb7bb
44
etc/os-net-config/samples/ovs_dpdk_bond.json
Normal file
44
etc/os-net-config/samples/ovs_dpdk_bond.json
Normal file
@ -0,0 +1,44 @@
|
||||
{ "network_config": [
|
||||
{
|
||||
"type": "ovs_user_bridge",
|
||||
"name": "br-link",
|
||||
"members": [
|
||||
{
|
||||
"type" : "ovs_dpdk_bond",
|
||||
"name" : "dpdkbond0",
|
||||
"members": [
|
||||
{
|
||||
"type" : "ovs_dpdk_port",
|
||||
"name" : "dpdk0",
|
||||
"members": [
|
||||
{
|
||||
"type": "interface",
|
||||
"name": "eth1"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type" : "ovs_dpdk_port",
|
||||
"name" : "dpdk1",
|
||||
"members": [
|
||||
{
|
||||
"type": "interface",
|
||||
"name": "eth2"
|
||||
}
|
||||
]
|
||||
},
|
||||
]
|
||||
},
|
||||
{
|
||||
"type" : "vlan",
|
||||
"vlan_id" : 16,
|
||||
"addresses" : [
|
||||
{
|
||||
"ip_netmask" : "192.0.2.1/24"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
36
etc/os-net-config/samples/ovs_dpdk_bond.yaml
Normal file
36
etc/os-net-config/samples/ovs_dpdk_bond.yaml
Normal file
@ -0,0 +1,36 @@
|
||||
# ovs_user_bridge type refers to the OVSUserBridge OVS ifup type, which will
|
||||
# have the datapath type set as 'netdev' for DPDK processing.
|
||||
# ovs_dpdk_port type refers to the OVSDPDKPort OVS ifup type, which will
|
||||
# add the port to the bridge with the interface type as 'dpdk'.
|
||||
# ovs_dpdk_bond type refers to the OVSDPDKBond OVS ifup type, which will
|
||||
# create the bond with dpdk interface type for dpdk ports
|
||||
|
||||
network_config:
|
||||
-
|
||||
type: ovs_user_bridge
|
||||
name: br-link
|
||||
members:
|
||||
-
|
||||
type: ovs_dpdk_bond
|
||||
name: dpdkbond0
|
||||
members:
|
||||
-
|
||||
type: ovs_dpdk_port
|
||||
name: dpdk0
|
||||
members:
|
||||
-
|
||||
type: interface
|
||||
name: eth1
|
||||
-
|
||||
type: ovs_dpdk_port
|
||||
name: dpdk1
|
||||
members:
|
||||
-
|
||||
type: interface
|
||||
name: eth2
|
||||
-
|
||||
type: vlan
|
||||
vlan_id: 16
|
||||
addresses:
|
||||
-
|
||||
ip_netmask: 192.0.2.1/24
|
@ -93,6 +93,8 @@ class NetConfig(object):
|
||||
self.add_ib_interface(obj)
|
||||
elif isinstance(obj, objects.OvsDpdkPort):
|
||||
self.add_ovs_dpdk_port(obj)
|
||||
elif isinstance(obj, objects.OvsDpdkBond):
|
||||
self.add_ovs_dpdk_bond(obj)
|
||||
|
||||
def add_interface(self, interface):
|
||||
"""Add an Interface object to the net config object.
|
||||
@ -192,6 +194,13 @@ class NetConfig(object):
|
||||
"""
|
||||
raise NotImplemented("add_ovs_dpdk_port is not implemented.")
|
||||
|
||||
def add_ovs_dpdk_bond(self, ovs_dpdk_bond):
|
||||
"""Add a OvsDpdkBond object to the net config object.
|
||||
|
||||
:param ovs_dpdk_bond: The OvsDpdkBond object to add.
|
||||
"""
|
||||
raise NotImplemented("add_ovs_dpdk_bond is not implemented.")
|
||||
|
||||
def apply(self, cleanup=False):
|
||||
"""Apply the network configuration.
|
||||
|
||||
|
@ -242,6 +242,21 @@ class IfcfgNetConfig(os_net_config.NetConfig):
|
||||
data += "DEVICETYPE=ovs\n"
|
||||
data += "TYPE=OVSDPDKPort\n"
|
||||
data += "OVS_BRIDGE=%s\n" % base_opt.bridge_name
|
||||
elif isinstance(base_opt, objects.OvsDpdkBond):
|
||||
ovs_extra.extend(base_opt.ovs_extra)
|
||||
if base_opt.primary_interface_name:
|
||||
primary_name = base_opt.primary_interface_name
|
||||
self.bond_primary_ifaces[base_opt.name] = primary_name
|
||||
data += "DEVICETYPE=ovs\n"
|
||||
data += "TYPE=OVSDPDKBond\n"
|
||||
data += "OVS_BRIDGE=%s\n" % base_opt.bridge_name
|
||||
if base_opt.members:
|
||||
members = [member.name for member in base_opt.members]
|
||||
self.member_names[base_opt.name] = members
|
||||
data += ("BOND_IFACES=\"%s\"\n" % " ".join(members))
|
||||
if base_opt.ovs_options:
|
||||
data += "OVS_OPTIONS=\"%s\"\n" % base_opt.ovs_options
|
||||
ovs_extra.extend(base_opt.ovs_extra)
|
||||
else:
|
||||
if base_opt.use_dhcp:
|
||||
data += "BOOTPROTO=dhcp\n"
|
||||
@ -524,6 +539,26 @@ class IfcfgNetConfig(os_net_config.NetConfig):
|
||||
logger.debug('ovs dpdk port data: %s' % data)
|
||||
self.interface_data[ovs_dpdk_port.name] = data
|
||||
|
||||
def add_ovs_dpdk_bond(self, ovs_dpdk_bond):
|
||||
"""Add an OvsDPDKBond object to the net config object.
|
||||
|
||||
:param ovs_dpdk_bond: The OvsBond object to add.
|
||||
"""
|
||||
logger.info('adding ovs dpdk bond: %s' % ovs_dpdk_bond.name)
|
||||
|
||||
# Bind the dpdk interface
|
||||
for dpdk_port in ovs_dpdk_bond.members:
|
||||
# DPDK Port will have only one member of type Interface, validation
|
||||
# checks are added at the object creation stage.
|
||||
ifname = dpdk_port.members[0].name
|
||||
utils.bind_dpdk_interfaces(ifname, dpdk_port.driver, self.noop)
|
||||
|
||||
data = self._add_common(ovs_dpdk_bond)
|
||||
logger.debug('ovs dpdk bond data: %s' % data)
|
||||
self.interface_data[ovs_dpdk_bond.name] = data
|
||||
if ovs_dpdk_bond.routes:
|
||||
self._add_routes(ovs_dpdk_bond.name, ovs_dpdk_bond.routes)
|
||||
|
||||
def generate_ivs_config(self, ivs_uplinks, ivs_interfaces):
|
||||
"""Generate configuration content for ivs."""
|
||||
|
||||
|
@ -64,6 +64,8 @@ def object_from_json(json):
|
||||
return IbInterface.from_json(json)
|
||||
elif obj_type == "ovs_dpdk_port":
|
||||
return OvsDpdkPort.from_json(json)
|
||||
elif obj_type == "ovs_dpdk_bond":
|
||||
return OvsDpdkBond.from_json(json)
|
||||
|
||||
|
||||
def _get_required_field(json, name, object_name):
|
||||
@ -457,7 +459,8 @@ class OvsUserBridge(_BaseOpts):
|
||||
for member in self.members:
|
||||
member.bridge_name = name
|
||||
if not isinstance(member, OvsTunnel) and \
|
||||
not isinstance(member, OvsDpdkPort):
|
||||
not isinstance(member, OvsDpdkPort) and \
|
||||
not isinstance(member, OvsDpdkBond):
|
||||
member.ovs_port = True
|
||||
if member.primary:
|
||||
if self.primary_interface_name:
|
||||
@ -1005,3 +1008,68 @@ class OvsDpdkPort(_BaseOpts):
|
||||
opts = _BaseOpts.base_opts_from_json(json)
|
||||
return OvsDpdkPort(name, *opts, members=members, driver=driver,
|
||||
ovs_options=ovs_options, ovs_extra=ovs_extra)
|
||||
|
||||
|
||||
class OvsDpdkBond(_BaseOpts):
|
||||
"""Base class for OVS DPDK bonds."""
|
||||
|
||||
def __init__(self, name, use_dhcp=False, use_dhcpv6=False, addresses=None,
|
||||
routes=None, mtu=None, primary=False, members=None,
|
||||
ovs_options=None, ovs_extra=None, nic_mapping=None,
|
||||
persist_mapping=False, defroute=True, dhclient_args=None,
|
||||
dns_servers=None):
|
||||
super(OvsDpdkBond, self).__init__(name, use_dhcp, use_dhcpv6,
|
||||
addresses, routes, mtu, primary,
|
||||
nic_mapping, persist_mapping,
|
||||
defroute, dhclient_args, dns_servers)
|
||||
self.members = members or []
|
||||
self.ovs_options = ovs_options
|
||||
self.ovs_extra = ovs_extra or []
|
||||
|
||||
for member in self.members:
|
||||
if member.primary:
|
||||
if self.primary_interface_name:
|
||||
msg = 'Only one primary interface allowed per bond (dpdk).'
|
||||
raise InvalidConfigException(msg)
|
||||
if member.primary_interface_name:
|
||||
self.primary_interface_name = member.primary_interface_name
|
||||
else:
|
||||
self.primary_interface_name = member.name
|
||||
if not self.primary_interface_name:
|
||||
bond_members = list(self.members)
|
||||
bond_members.sort(key=lambda x: x.name)
|
||||
self.primary_interface_name = bond_members[0].name
|
||||
|
||||
@staticmethod
|
||||
def from_json(json):
|
||||
name = _get_required_field(json, 'name', 'OvsDpdkBond')
|
||||
(use_dhcp, use_dhcpv6, addresses, routes, mtu, nic_mapping,
|
||||
persist_mapping, defroute, dhclient_args,
|
||||
dns_servers) = _BaseOpts.base_opts_from_json(
|
||||
json, include_primary=False)
|
||||
ovs_options = json.get('ovs_options')
|
||||
ovs_extra = json.get('ovs_extra', [])
|
||||
members = []
|
||||
|
||||
# members
|
||||
members_json = json.get('members')
|
||||
if members_json:
|
||||
if isinstance(members_json, list):
|
||||
for member in members_json:
|
||||
obj = object_from_json(member)
|
||||
if isinstance(obj, OvsDpdkPort):
|
||||
members.append(obj)
|
||||
else:
|
||||
msg = 'Membrs must be of type ovs_dpdk_port'
|
||||
raise InvalidConfigException(msg)
|
||||
else:
|
||||
msg = 'Members must be a list.'
|
||||
raise InvalidConfigException(msg)
|
||||
|
||||
return OvsDpdkBond(name, use_dhcp=use_dhcp, use_dhcpv6=use_dhcpv6,
|
||||
addresses=addresses, routes=routes, mtu=mtu,
|
||||
members=members, ovs_options=ovs_options,
|
||||
ovs_extra=ovs_extra, nic_mapping=nic_mapping,
|
||||
persist_mapping=persist_mapping,
|
||||
defroute=defroute, dhclient_args=dhclient_args,
|
||||
dns_servers=dns_servers)
|
||||
|
@ -147,6 +147,23 @@ class TestCli(base.TestCase):
|
||||
'-c %s --detailed-exit-codes'
|
||||
% interface_yaml, exitcodes=(0,))
|
||||
|
||||
def test_ovs_dpdk_bond_noop_output(self):
|
||||
ivs_yaml = os.path.join(SAMPLE_BASE, 'ovs_dpdk_bond.yaml')
|
||||
ivs_json = os.path.join(SAMPLE_BASE, 'ovs_dpdk_bond.json')
|
||||
stdout_yaml, stderr = self.run_cli('ARG0 --provider=ifcfg --noop '
|
||||
'-c %s' % ivs_yaml)
|
||||
self.assertEqual('', stderr)
|
||||
stdout_json, stderr = self.run_cli('ARG0 --provider=ifcfg --noop '
|
||||
'-c %s' % ivs_json)
|
||||
self.assertEqual('', stderr)
|
||||
sanity_devices = ['DEVICE=br-link',
|
||||
'TYPE=OVSUserBridge',
|
||||
'DEVICE=dpdkbond0',
|
||||
'TYPE=OVSDPDKBond']
|
||||
for dev in sanity_devices:
|
||||
self.assertIn(dev, stdout_yaml)
|
||||
self.assertEqual(stdout_yaml, stdout_json)
|
||||
|
||||
def test_nfvswitch_noop_output(self):
|
||||
nfvswitch_yaml = os.path.join(SAMPLE_BASE, 'nfvswitch.yaml')
|
||||
nfvswitch_json = os.path.join(SAMPLE_BASE, 'nfvswitch.json')
|
||||
|
@ -788,6 +788,30 @@ OVS_BRIDGE=br-link
|
||||
self.provider.bridge_data['br-link'])
|
||||
self.assertEqual(dpdk0_config, self.get_interface_config('dpdk0'))
|
||||
|
||||
def test_network_ovs_dpdk_bond(self):
|
||||
iface0 = objects.Interface(name='eth1')
|
||||
dpdk0 = objects.OvsDpdkPort(name='dpdk0', members=[iface0])
|
||||
iface1 = objects.Interface(name='eth2')
|
||||
dpdk1 = objects.OvsDpdkPort(name='dpdk1', members=[iface1])
|
||||
bond = objects.OvsDpdkBond('dpdkbond0', members=[dpdk0, dpdk1])
|
||||
bridge = objects.OvsUserBridge('br-link', members=[bond])
|
||||
self.provider.add_bond(bond)
|
||||
self.provider.add_bridge(bridge)
|
||||
|
||||
dpdk_bond_config = """# This file is autogenerated by os-net-config
|
||||
DEVICE=dpdkbond0
|
||||
ONBOOT=yes
|
||||
HOTPLUG=no
|
||||
NM_CONTROLLED=no
|
||||
PEERDNS=no
|
||||
DEVICETYPE=ovs
|
||||
TYPE=OVSDPDKBond
|
||||
OVS_BRIDGE=br-link
|
||||
BOND_IFACES="dpdk0 dpdk1"
|
||||
"""
|
||||
self.assertEqual(dpdk_bond_config,
|
||||
self.get_interface_config('dpdkbond0'))
|
||||
|
||||
|
||||
class TestIfcfgNetConfigApply(base.TestCase):
|
||||
|
||||
|
@ -830,3 +830,49 @@ class TestNicMapping(base.TestCase):
|
||||
expected = {}
|
||||
# This only emits a warning, so it should still work
|
||||
self.assertEqual(expected, objects._mapped_nics())
|
||||
|
||||
|
||||
class TestOvsDpdkBond(base.TestCase):
|
||||
|
||||
def test_from_json_dhcp(self):
|
||||
data = """{
|
||||
"type": "ovs_dpdk_bond",
|
||||
"name": "dpdkbond0",
|
||||
"use_dhcp": true,
|
||||
"members": [
|
||||
{
|
||||
"type": "ovs_dpdk_port",
|
||||
"name": "dpdk0",
|
||||
"members": [
|
||||
{
|
||||
"type": "interface",
|
||||
"name": "eth1"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "ovs_dpdk_port",
|
||||
"name": "dpdk1",
|
||||
"members": [
|
||||
{
|
||||
"type": "interface",
|
||||
"name": "eth2"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
"""
|
||||
bond = objects.object_from_json(json.loads(data))
|
||||
self.assertEqual("dpdkbond0", bond.name)
|
||||
self.assertTrue(bond.use_dhcp)
|
||||
dpdk_port0 = bond.members[0]
|
||||
self.assertEqual("dpdk0", dpdk_port0.name)
|
||||
self.assertEqual("vfio-pci", dpdk_port0.driver)
|
||||
iface1 = dpdk_port0.members[0]
|
||||
self.assertEqual("eth1", iface1.name)
|
||||
dpdk_port1 = bond.members[1]
|
||||
self.assertEqual("dpdk1", dpdk_port1.name)
|
||||
self.assertEqual("vfio-pci", dpdk_port1.driver)
|
||||
iface2 = dpdk_port1.members[0]
|
||||
self.assertEqual("eth2", iface2.name)
|
||||
|
Loading…
Reference in New Issue
Block a user