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:
Saravanan KR 2016-07-30 15:22:57 +05:30
parent 6d046bca3c
commit 9108fcb7bb
8 changed files with 280 additions and 1 deletions

View 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"
}
]
}
]
}
]
}

View 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

View File

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

View File

@ -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."""

View File

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

View File

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

View File

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

View File

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