Merge "Add support for OVS DPDK Bridge and Port"

This commit is contained in:
Jenkins 2016-08-26 09:53:51 +00:00 committed by Gerrit Code Review
commit 6d046bca3c
8 changed files with 325 additions and 1 deletions

View File

@ -0,0 +1,20 @@
{ "network_config": [
{
"type": "ovs_user_bridge",
"name": "br-link",
"members": [
{
"type": "ovs_dpdk_port",
"name": "dpdk0",
"driver": "igb_uio",
"members": [
{
"type": "interface",
"name": "eth1",
}
]
}
]
}
]
}

View File

@ -0,0 +1,21 @@
# 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'.
network_config:
-
type: ovs_user_bridge
name: br-link
members:
-
type: ovs_dpdk_port
# dpdk0 name is generated by dpdk drivers after dpdk_nic_bind
name: dpdk0
# driver is optional argument, default driver is 'vfio-pci'
driver: igb_uio
members:
- type: interface
# nic style number does not work as of now. real interface name has
# to be provided here.
name: eth1

View File

@ -57,6 +57,10 @@ class NetConfig(object):
self.add_bridge(obj)
for member in obj.members:
self.add_object(member)
elif isinstance(obj, objects.OvsUserBridge):
self.add_ovs_user_bridge(obj)
for member in obj.members:
self.add_object(member)
elif isinstance(obj, objects.LinuxBridge):
self.add_linux_bridge(obj)
for member in obj.members:
@ -87,6 +91,8 @@ class NetConfig(object):
self.add_ovs_patch_port(obj)
elif isinstance(obj, objects.IbInterface):
self.add_ib_interface(obj)
elif isinstance(obj, objects.OvsDpdkPort):
self.add_ovs_dpdk_port(obj)
def add_interface(self, interface):
"""Add an Interface object to the net config object.
@ -109,6 +115,13 @@ class NetConfig(object):
"""
raise NotImplemented("add_bridge is not implemented.")
def add_ovs_user_bridge(self, bridge):
"""Add an OvsUserBridge object to the net config object.
:param bridge: The OvsUserBridge object to add.
"""
raise NotImplemented("add_ovs_user_bridge is not implemented.")
def add_linux_bridge(self, bridge):
"""Add a LinuxBridge object to the net config object.
@ -172,6 +185,13 @@ class NetConfig(object):
"""
raise NotImplemented("add_ib_interface is not implemented.")
def add_ovs_dpdk_port(self, ovs_dpdk_port):
"""Add a OvsDpdkPort object to the net config object.
:param ovs_dpdk_port: The OvsDpdkPort object to add.
"""
raise NotImplemented("add_ovs_dpdk_port is not implemented.")
def apply(self, cleanup=False):
"""Apply the network configuration.

View File

@ -158,6 +158,19 @@ class IfcfgNetConfig(os_net_config.NetConfig):
if base_opt.ovs_options:
data += "OVS_OPTIONS=\"%s\"\n" % base_opt.ovs_options
ovs_extra.extend(base_opt.ovs_extra)
elif isinstance(base_opt, objects.OvsUserBridge):
data += "DEVICETYPE=ovs\n"
data += "TYPE=OVSUserBridge\n"
if base_opt.use_dhcp:
data += "OVSBOOTPROTO=dhcp\n"
if base_opt.members:
members = [member.name for member in base_opt.members]
self.member_names[base_opt.name] = members
if base_opt.use_dhcp:
data += ("OVSDHCPINTERFACES=\"%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)
elif isinstance(base_opt, objects.OvsBond):
if base_opt.primary_interface_name:
primary_name = base_opt.primary_interface_name
@ -224,6 +237,11 @@ class IfcfgNetConfig(os_net_config.NetConfig):
data += "TYPE=OVSPatchPort\n"
data += "OVS_BRIDGE=%s\n" % base_opt.bridge_name
data += "OVS_PATCH_PEER=%s\n" % base_opt.peer
elif isinstance(base_opt, objects.OvsDpdkPort):
ovs_extra.extend(base_opt.ovs_extra)
data += "DEVICETYPE=ovs\n"
data += "TYPE=OVSDPDKPort\n"
data += "OVS_BRIDGE=%s\n" % base_opt.bridge_name
else:
if base_opt.use_dhcp:
data += "BOOTPROTO=dhcp\n"
@ -369,6 +387,18 @@ class IfcfgNetConfig(os_net_config.NetConfig):
if bridge.routes:
self._add_routes(bridge.name, bridge.routes)
def add_ovs_user_bridge(self, bridge):
"""Add an OvsUserBridge object to the net config object.
:param bridge: The OvsUserBridge object to add.
"""
logger.info('adding ovs user bridge: %s' % bridge.name)
data = self._add_common(bridge)
logger.debug('ovs user bridge data: %s' % data)
self.bridge_data[bridge.name] = data
if bridge.routes:
self._add_routes(bridge.name, bridge.routes)
def add_linux_bridge(self, bridge):
"""Add a LinuxBridge object to the net config object.
@ -476,6 +506,24 @@ class IfcfgNetConfig(os_net_config.NetConfig):
% (ib_interface.hwname, ib_interface.name))
self.renamed_interfaces[ib_interface.hwname] = ib_interface.name
def add_ovs_dpdk_port(self, ovs_dpdk_port):
"""Add a OvsDpdkPort object to the net config object.
:param ovs_dpdk_port: The OvsDpdkPort object to add.
"""
logger.info('adding ovs dpdk port: %s' % ovs_dpdk_port.name)
# DPDK Port will have only one member of type Interface, validation
# checks are added at the object creation stage.
ifname = ovs_dpdk_port.members[0].name
# Bind the dpdk interface
utils.bind_dpdk_interfaces(ifname, ovs_dpdk_port.driver, self.noop)
data = self._add_common(ovs_dpdk_port)
logger.debug('ovs dpdk port data: %s' % data)
self.interface_data[ovs_dpdk_port.name] = data
def generate_ivs_config(self, ivs_uplinks, ivs_interfaces):
"""Generate configuration content for ivs."""
@ -648,7 +696,11 @@ class IfcfgNetConfig(os_net_config.NetConfig):
utils.diff(br_route_path, route_data) or
utils.diff(br_route6_path, route6_data)):
restart_bridges.append(bridge_name)
restart_interfaces.extend(self.child_members(bridge_name))
# Avoid duplicate interface being added to the restart list
children = self.child_members(bridge_name)
for child in children:
if child not in restart_interfaces:
restart_interfaces.append(child)
update_files[bridge_path] = bridge_data
update_files[br_route_path] = route_data
update_files[br_route6_path] = route6_data

View File

@ -38,6 +38,8 @@ def object_from_json(json):
return Vlan.from_json(json)
elif obj_type == "ovs_bridge":
return OvsBridge.from_json(json)
elif obj_type == "ovs_user_bridge":
return OvsUserBridge.from_json(json)
elif obj_type == "ovs_bond":
return OvsBond.from_json(json)
elif obj_type == "linux_bond":
@ -60,6 +62,8 @@ def object_from_json(json):
return OvsPatchPort.from_json(json)
elif obj_type == "ib_interface":
return IbInterface.from_json(json)
elif obj_type == "ovs_dpdk_port":
return OvsDpdkPort.from_json(json)
def _get_required_field(json, name, object_name):
@ -435,6 +439,65 @@ class OvsBridge(_BaseOpts):
dhclient_args=dhclient_args, dns_servers=dns_servers)
class OvsUserBridge(_BaseOpts):
"""Base class for OVS User bridges."""
def __init__(self, name, use_dhcp=False, use_dhcpv6=False, addresses=None,
routes=None, mtu=None, members=None, ovs_options=None,
ovs_extra=None, nic_mapping=None, persist_mapping=False,
defroute=True, dhclient_args=None, dns_servers=None):
super(OvsUserBridge, self).__init__(name, use_dhcp, use_dhcpv6,
addresses, routes, mtu, False,
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:
member.bridge_name = name
if not isinstance(member, OvsTunnel) and \
not isinstance(member, OvsDpdkPort):
member.ovs_port = True
if member.primary:
if self.primary_interface_name:
msg = 'Only one primary interface allowed per bridge.'
raise InvalidConfigException(msg)
if member.primary_interface_name:
self.primary_interface_name = member.primary_interface_name
else:
self.primary_interface_name = member.name
@staticmethod
def from_json(json):
name = _get_required_field(json, 'name', 'OvsUserBridge')
(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:
members.append(object_from_json(member))
else:
msg = 'Members must be a list.'
raise InvalidConfigException(msg)
return OvsUserBridge(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)
class LinuxBridge(_BaseOpts):
"""Base class for Linux bridges."""
@ -883,3 +946,62 @@ class IbInterface(_BaseOpts):
name = _get_required_field(json, 'name', 'IbInterface')
opts = _BaseOpts.base_opts_from_json(json)
return IbInterface(name, *opts)
class OvsDpdkPort(_BaseOpts):
"""Base class for OVS Dpdk Ports."""
def __init__(self, name, use_dhcp=False, use_dhcpv6=False, addresses=None,
routes=None, mtu=None, primary=False, nic_mapping=None,
persist_mapping=False, defroute=True, dhclient_args=None,
dns_servers=None, members=None, driver='vfio-pci',
ovs_options=None, ovs_extra=None):
super(OvsDpdkPort, 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 or []
self.ovs_extra = ovs_extra or []
self.driver = driver
@staticmethod
def from_json(json):
name = _get_required_field(json, 'name', 'OvsDpdkPort')
# driver name by default will be 'vfio-pci' if not specified
driver = json.get('driver')
if not driver:
driver = 'vfio-pci'
# members
members = []
members_json = json.get('members')
if members_json:
if isinstance(members_json, list):
if len(members_json) == 1:
iface = object_from_json(members_json[0])
if isinstance(iface, Interface):
# TODO(skramaja): Add checks for IP and route not to
# be set in the interface part of DPDK Port
members.append(iface)
else:
msg = 'OVS DPDK Port should have only interface member'
raise InvalidConfigException(msg)
else:
msg = 'OVS DPDK Port should have only one member'
raise InvalidConfigException(msg)
else:
msg = 'Members must be a list.'
raise InvalidConfigException(msg)
else:
msg = 'DPDK Port should have one member as Interface'
raise InvalidConfigException(msg)
ovs_options = json.get('ovs_options', [])
ovs_options = ['options:%s' % opt for opt in ovs_options]
ovs_extra = json.get('ovs_extra', [])
opts = _BaseOpts.base_opts_from_json(json)
return OvsDpdkPort(name, *opts, members=members, driver=driver,
ovs_options=ovs_options, ovs_extra=ovs_extra)

View File

@ -164,3 +164,20 @@ class TestCli(base.TestCase):
for dev in sanity_devices:
self.assertIn(dev, stdout_yaml)
self.assertEqual(stdout_yaml, stdout_json)
def test_ovs_dpdk_noop_output(self):
ivs_yaml = os.path.join(SAMPLE_BASE, 'ovs_dpdk.yaml')
ivs_json = os.path.join(SAMPLE_BASE, 'ovs_dpdk.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=dpdk0',
'TYPE=OVSDPDKPort']
for dev in sanity_devices:
self.assertIn(dev, stdout_yaml)
self.assertEqual(stdout_yaml, stdout_json)

View File

@ -758,6 +758,36 @@ DNS2=5.6.7.8
"""
self.assertEqual(em1_config, self.get_interface_config('em1'))
def test_network_ovs_dpdk_bridge_and_port(self):
interface = objects.Interface(name='eth1')
dpdk_port = objects.OvsDpdkPort(name='dpdk0', members=[interface])
bridge = objects.OvsUserBridge('br-link', members=[dpdk_port])
self.provider.add_interface(interface)
self.provider.add_interface(dpdk_port)
self.provider.add_bridge(bridge)
br_link_config = """# This file is autogenerated by os-net-config
DEVICE=br-link
ONBOOT=yes
HOTPLUG=no
NM_CONTROLLED=no
PEERDNS=no
DEVICETYPE=ovs
TYPE=OVSUserBridge
"""
dpdk0_config = """# This file is autogenerated by os-net-config
DEVICE=dpdk0
ONBOOT=yes
HOTPLUG=no
NM_CONTROLLED=no
PEERDNS=no
DEVICETYPE=ovs
TYPE=OVSDPDKPort
OVS_BRIDGE=br-link
"""
self.assertEqual(br_link_config,
self.provider.bridge_data['br-link'])
self.assertEqual(dpdk0_config, self.get_interface_config('dpdk0'))
class TestIfcfgNetConfigApply(base.TestCase):

View File

@ -19,11 +19,17 @@ import logging
import os
import re
from oslo_concurrency import processutils
logger = logging.getLogger(__name__)
_SYS_CLASS_NET = '/sys/class/net'
class OvsDpdkBindException(ValueError):
pass
def write_config(filename, data):
with open(filename, 'w') as f:
f.write(str(data))
@ -118,3 +124,39 @@ def diff(filename, data):
logger.debug("Diff data:\n%s" % data)
# convert to string as JSON may have unicode in it
return not file_data == data
def bind_dpdk_interfaces(ifname, driver, noop):
pci_addres = _get_pci_address(ifname, noop)
if not noop:
if pci_addres:
# modbprobe of the driver has to be done before binding.
# for reboots, puppet will add the modprobe to /etc/rc.modules
processutils.execute('modprobe', 'vfio-pci')
out, err = processutils.execute('driverctl', 'set-override',
pci_addres, driver)
if err:
msg = "Failed to bind interface %s with dpdk" % ifname
raise OvsDpdkBindException(msg)
else:
processutils.execute('driverctl', 'load-override', pci_addres)
else:
logger.info('Interface %(name)s bound to DPDK driver %(driver)s '
'using driverctl command' %
{'name': ifname, 'driver': driver})
def _get_pci_address(ifname, noop):
# TODO(skramaja): Validate if the given interface supports dpdk
if not noop:
# If ifname is already bound, then ethtool will not be able to list the
# device, in which case, binding is already done, proceed with scripts
out, err = processutils.execute('ethtool', '-i', ifname)
if not err:
for item in out.split('\n'):
if 'bus-info' in item:
return item.split(' ')[1]
else:
logger.info('Fetch the PCI address of the interface %s using '
'ethtool' % ifname)