aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--etc/os-net-config/samples/ovs_dpdk.json20
-rw-r--r--etc/os-net-config/samples/ovs_dpdk.yaml21
-rw-r--r--os_net_config/__init__.py20
-rw-r--r--os_net_config/impl_ifcfg.py54
-rw-r--r--os_net_config/objects.py122
-rw-r--r--os_net_config/tests/test_cli.py17
-rw-r--r--os_net_config/tests/test_impl_ifcfg.py30
-rw-r--r--os_net_config/utils.py42
8 files changed, 325 insertions, 1 deletions
diff --git a/etc/os-net-config/samples/ovs_dpdk.json b/etc/os-net-config/samples/ovs_dpdk.json
new file mode 100644
index 0000000..dc6706d
--- /dev/null
+++ b/etc/os-net-config/samples/ovs_dpdk.json
@@ -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",
+ }
+ ]
+ }
+ ]
+ }
+ ]
+}
diff --git a/etc/os-net-config/samples/ovs_dpdk.yaml b/etc/os-net-config/samples/ovs_dpdk.yaml
new file mode 100644
index 0000000..18fee31
--- /dev/null
+++ b/etc/os-net-config/samples/ovs_dpdk.yaml
@@ -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
diff --git a/os_net_config/__init__.py b/os_net_config/__init__.py
index 2aafc23..d307980 100644
--- a/os_net_config/__init__.py
+++ b/os_net_config/__init__.py
@@ -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.
diff --git a/os_net_config/impl_ifcfg.py b/os_net_config/impl_ifcfg.py
index d49b57b..c3d45bf 100644
--- a/os_net_config/impl_ifcfg.py
+++ b/os_net_config/impl_ifcfg.py
@@ -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
diff --git a/os_net_config/objects.py b/os_net_config/objects.py
index 9b5523f..725df38 100644
--- a/os_net_config/objects.py
+++ b/os_net_config/objects.py
@@ -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)
diff --git a/os_net_config/tests/test_cli.py b/os_net_config/tests/test_cli.py
index e598dcb..939b12e 100644
--- a/os_net_config/tests/test_cli.py
+++ b/os_net_config/tests/test_cli.py
@@ -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)
diff --git a/os_net_config/tests/test_impl_ifcfg.py b/os_net_config/tests/test_impl_ifcfg.py
index 9b24717..ad27d83 100644
--- a/os_net_config/tests/test_impl_ifcfg.py
+++ b/os_net_config/tests/test_impl_ifcfg.py
@@ -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):
diff --git a/os_net_config/utils.py b/os_net_config/utils.py
index 83aae63..7b85d91 100644
--- a/os_net_config/utils.py
+++ b/os_net_config/utils.py
@@ -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)