Merge "Add support for OVS DPDK Bridge and Port"
This commit is contained in:
commit
6d046bca3c
20
etc/os-net-config/samples/ovs_dpdk.json
Normal file
20
etc/os-net-config/samples/ovs_dpdk.json
Normal 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",
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
21
etc/os-net-config/samples/ovs_dpdk.yaml
Normal file
21
etc/os-net-config/samples/ovs_dpdk.yaml
Normal 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
|
@ -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.
|
||||
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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):
|
||||
|
||||
|
@ -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)
|
||||
|
Loading…
Reference in New Issue
Block a user