aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--etc/os-net-config/samples/linux_bond_networkmanager.yaml18
-rw-r--r--etc/os-net-config/samples/vpp_interface.json14
-rw-r--r--etc/os-net-config/samples/vpp_interface.yaml18
-rw-r--r--os_net_config/__init__.py9
-rw-r--r--os_net_config/impl_ifcfg.py48
-rw-r--r--os_net_config/objects.py198
-rw-r--r--os_net_config/tests/test_impl_ifcfg.py36
-rw-r--r--os_net_config/tests/test_objects.py16
-rw-r--r--os_net_config/tests/test_utils.py130
-rw-r--r--os_net_config/utils.py227
-rw-r--r--requirements.txt23
-rw-r--r--[-rwxr-xr-x]setup.py11
-rw-r--r--test-requirements.txt25
-rw-r--r--tox.ini5
14 files changed, 683 insertions, 95 deletions
diff --git a/etc/os-net-config/samples/linux_bond_networkmanager.yaml b/etc/os-net-config/samples/linux_bond_networkmanager.yaml
new file mode 100644
index 0000000..c7036c0
--- /dev/null
+++ b/etc/os-net-config/samples/linux_bond_networkmanager.yaml
@@ -0,0 +1,18 @@
+# To use NetworkManager to manage an interface, set nm_controlled=true.
+# Note that some interface types may not be supported by NetworkManager.
+network_config:
+ -
+ type: linux_bond
+ name: bond1
+ nm_controlled: true
+ use_dhcp: true
+ bonding_options: "mode=active-backup"
+ members:
+ -
+ type: interface
+ name: em1
+ nm_controlled: true
+ -
+ type: interface
+ name: em2
+ nm_controlled: true
diff --git a/etc/os-net-config/samples/vpp_interface.json b/etc/os-net-config/samples/vpp_interface.json
new file mode 100644
index 0000000..5d2f82a
--- /dev/null
+++ b/etc/os-net-config/samples/vpp_interface.json
@@ -0,0 +1,14 @@
+{ "network_config": [
+ {
+ "type": "vpp_interface",
+ "name": "nic2",
+ "addresses": [
+ {
+ "ip_netmask": "192.0.2.1/24"
+ }
+ ],
+ "uio_driver": "uio_pci_generic",
+ "options": "vlan-strip-offload off"
+ }
+ ]
+}
diff --git a/etc/os-net-config/samples/vpp_interface.yaml b/etc/os-net-config/samples/vpp_interface.yaml
new file mode 100644
index 0000000..de790d5
--- /dev/null
+++ b/etc/os-net-config/samples/vpp_interface.yaml
@@ -0,0 +1,18 @@
+network_config:
+ -
+ type: vpp_interface
+ name: nic2
+ addresses:
+ -
+ ip_netmask: 192.0.2.1/24
+ # DPDK poll-mode driver name. Defaults to 'vfio-pci', other possible value
+ # is 'uio_pci_generic'. It is also possible to specify other driver names
+ # such as 'igb_uio', however, it is assumed that any required kernel
+ # modules for those drivers are already loaded when os-net-config is
+ # invoked.
+ uio_driver: uio_pci_generic
+ # Interface options such as vlan stripping and tx/rx transmit queues
+ # specification. Reference for those configurations can
+ # be found at https://wiki.fd.io/view/VPP/Command-line_Arguments
+ # Example: 'vlan-strip-offload on num-rx-queues 3'
+ #options: "vlan-strip-offload off"
diff --git a/os_net_config/__init__.py b/os_net_config/__init__.py
index faa5e92..cb56059 100644
--- a/os_net_config/__init__.py
+++ b/os_net_config/__init__.py
@@ -95,6 +95,8 @@ class NetConfig(object):
self.add_ovs_dpdk_port(obj)
elif isinstance(obj, objects.OvsDpdkBond):
self.add_ovs_dpdk_bond(obj)
+ elif isinstance(obj, objects.VppInterface):
+ self.add_vpp_interface(obj)
def add_interface(self, interface):
"""Add an Interface object to the net config object.
@@ -201,6 +203,13 @@ class NetConfig(object):
"""
raise NotImplementedError("add_ovs_dpdk_bond is not implemented.")
+ def add_vpp_interface(self, vpp_interface):
+ """Add a VppInterface object to the net config object.
+
+ :param vpp_interface: The VppInterface object to add.
+ """
+ raise NotImplementedError("add_vpp_interface 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 84a5139..8284ad5 100644
--- a/os_net_config/impl_ifcfg.py
+++ b/os_net_config/impl_ifcfg.py
@@ -54,6 +54,10 @@ def nfvswitch_config_path():
return "/etc/sysconfig/nfvswitch"
+def vpp_config_path():
+ return "/etc/vpp/startup.conf"
+
+
def route_config_path(name):
return "/etc/sysconfig/network-scripts/route-%s" % name
@@ -116,6 +120,7 @@ class IfcfgNetConfig(os_net_config.NetConfig):
self.linuxbond_data = {}
self.ib_interface_data = {}
self.linuxteam_data = {}
+ self.vpp_interface_data = {}
self.member_names = {}
self.renamed_interfaces = {}
self.bond_primary_ifaces = {}
@@ -142,7 +147,10 @@ class IfcfgNetConfig(os_net_config.NetConfig):
data += "HOTPLUG=yes\n"
else:
data += "HOTPLUG=no\n"
- data += "NM_CONTROLLED=no\n"
+ if base_opt.nm_controlled:
+ data += "NM_CONTROLLED=yes\n"
+ else:
+ data += "NM_CONTROLLED=no\n"
if not base_opt.dns_servers and not base_opt.use_dhcp:
data += "PEERDNS=no\n"
if isinstance(base_opt, objects.Vlan):
@@ -623,6 +631,21 @@ class IfcfgNetConfig(os_net_config.NetConfig):
if ovs_dpdk_bond.routes:
self._add_routes(ovs_dpdk_bond.name, ovs_dpdk_bond.routes)
+ def add_vpp_interface(self, vpp_interface):
+ """Add a VppInterface object to the net config object
+
+ :param vpp_interface: The VppInterface object to add
+ """
+ vpp_interface.pci_dev = utils.get_pci_address(vpp_interface.name,
+ False)
+ vpp_interface.hwaddr = utils.interface_mac(vpp_interface.name)
+ if not self.noop:
+ self.ifdown(vpp_interface.name)
+ remove_ifcfg_config(vpp_interface.name)
+ logger.info('adding vpp interface: %s %s'
+ % (vpp_interface.name, vpp_interface.pci_dev))
+ self.vpp_interface_data[vpp_interface.name] = vpp_interface
+
def generate_ivs_config(self, ivs_uplinks, ivs_interfaces):
"""Generate configuration content for ivs."""
@@ -687,6 +710,7 @@ class IfcfgNetConfig(os_net_config.NetConfig):
restart_bridges = []
restart_linux_bonds = []
restart_linux_teams = []
+ restart_vpp = False
update_files = {}
all_file_names = []
ivs_uplinks = [] # ivs physical uplinks
@@ -695,6 +719,7 @@ class IfcfgNetConfig(os_net_config.NetConfig):
nfvswitch_internal_ifaces = [] # nfvswitch internal/management ports
stop_dhclient_interfaces = []
ovs_needs_restart = False
+ vpp_interfaces = self.vpp_interface_data.values()
for interface_name, iface_data in self.interface_data.items():
route_data = self.route_data.get(interface_name, '')
@@ -903,6 +928,15 @@ class IfcfgNetConfig(os_net_config.NetConfig):
logger.info('No changes required for InfiniBand iface: %s' %
interface_name)
+ if self.vpp_interface_data:
+ vpp_path = self.root_dir + vpp_config_path()
+ vpp_config = utils.generate_vpp_config(vpp_path, vpp_interfaces)
+ if utils.diff(vpp_path, vpp_config):
+ restart_vpp = True
+ update_files[vpp_path] = vpp_config
+ else:
+ logger.info('No changes required for VPP')
+
if cleanup:
for ifcfg_file in glob.iglob(cleanup_pattern()):
if ifcfg_file not in all_file_names:
@@ -929,6 +963,9 @@ class IfcfgNetConfig(os_net_config.NetConfig):
for bridge in restart_bridges:
self.ifdown(bridge, iftype='bridge')
+ for vpp_interface in vpp_interfaces:
+ self.ifdown(vpp_interface.name)
+
for oldname, newname in self.renamed_interfaces.items():
self.ifrename(oldname, newname)
@@ -1006,4 +1043,13 @@ class IfcfgNetConfig(os_net_config.NetConfig):
for vlan in restart_vlans:
self.ifup(vlan)
+ if not self.noop:
+ if restart_vpp:
+ logger.info('Restarting VPP')
+ utils.restart_vpp(vpp_interfaces)
+
+ if self.vpp_interface_data:
+ logger.info('Updating VPP mapping')
+ utils.update_vpp_mapping(vpp_interfaces)
+
return update_files
diff --git a/os_net_config/objects.py b/os_net_config/objects.py
index 3136be0..ec915da 100644
--- a/os_net_config/objects.py
+++ b/os_net_config/objects.py
@@ -68,6 +68,8 @@ def object_from_json(json):
return OvsDpdkPort.from_json(json)
elif obj_type == "ovs_dpdk_bond":
return OvsDpdkBond.from_json(json)
+ elif obj_type == "vpp_interface":
+ return VppInterface.from_json(json)
def _get_required_field(json, name, object_name):
@@ -181,7 +183,7 @@ class _BaseOpts(object):
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):
+ dns_servers=None, nm_controlled=False):
addresses = addresses or []
routes = routes or []
dns_servers = dns_servers or []
@@ -209,6 +211,7 @@ class _BaseOpts(object):
self.defroute = defroute
self.dhclient_args = dhclient_args
self.dns_servers = dns_servers
+ self.nm_controlled = nm_controlled
self.bridge_name = None # internal
self.linux_bridge_name = None # internal
self.ivs_bridge_name = None # internal
@@ -244,6 +247,7 @@ class _BaseOpts(object):
mtu = json.get('mtu', None)
dhclient_args = json.get('dhclient_args')
dns_servers = json.get('dns_servers')
+ nm_controlled = json.get('nm_controlled')
primary = strutils.bool_from_string(str(json.get('primary', False)))
addresses = []
routes = []
@@ -274,11 +278,11 @@ class _BaseOpts(object):
if include_primary:
return (use_dhcp, use_dhcpv6, addresses, routes, mtu, primary,
nic_mapping, persist_mapping, defroute, dhclient_args,
- dns_servers)
+ dns_servers, nm_controlled)
else:
return (use_dhcp, use_dhcpv6, addresses, routes, mtu,
nic_mapping, persist_mapping, defroute, dhclient_args,
- dns_servers)
+ dns_servers, nm_controlled)
class Interface(_BaseOpts):
@@ -287,14 +291,16 @@ class Interface(_BaseOpts):
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, ethtool_opts=None, hotplug=False):
+ dns_servers=None, nm_controlled=False, ethtool_opts=None,
+ hotplug=False):
addresses = addresses or []
routes = routes or []
dns_servers = dns_servers or []
super(Interface, self).__init__(name, use_dhcp, use_dhcpv6, addresses,
routes, mtu, primary, nic_mapping,
persist_mapping, defroute,
- dhclient_args, dns_servers)
+ dhclient_args, dns_servers,
+ nm_controlled)
self.ethtool_opts = ethtool_opts
self.hotplug = hotplug
@@ -318,7 +324,7 @@ class Vlan(_BaseOpts):
def __init__(self, device, vlan_id, 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):
+ dhclient_args=None, dns_servers=None, nm_controlled=False):
addresses = addresses or []
routes = routes or []
dns_servers = dns_servers or []
@@ -326,7 +332,7 @@ class Vlan(_BaseOpts):
super(Vlan, self).__init__(name, use_dhcp, use_dhcpv6, addresses,
routes, mtu, primary, nic_mapping,
persist_mapping, defroute, dhclient_args,
- dns_servers)
+ dns_servers, nm_controlled)
self.vlan_id = int(vlan_id)
mapped_nic_names = _mapped_nics(nic_mapping)
@@ -350,7 +356,7 @@ class IvsInterface(_BaseOpts):
def __init__(self, vlan_id, name='ivs', use_dhcp=False, use_dhcpv6=False,
addresses=None, routes=None, mtu=1500, primary=False,
nic_mapping=None, persist_mapping=False, defroute=True,
- dhclient_args=None, dns_servers=None):
+ dhclient_args=None, dns_servers=None, nm_controlled=False):
addresses = addresses or []
routes = routes or []
dns_servers = dns_servers or []
@@ -359,7 +365,7 @@ class IvsInterface(_BaseOpts):
addresses, routes, mtu, primary,
nic_mapping, persist_mapping,
defroute, dhclient_args,
- dns_servers)
+ dns_servers, nm_controlled)
self.vlan_id = int(vlan_id)
@staticmethod
@@ -376,7 +382,8 @@ class NfvswitchInternal(_BaseOpts):
def __init__(self, vlan_id, name='nfvswitch', use_dhcp=False,
use_dhcpv6=False, addresses=None, routes=None, mtu=1500,
primary=False, nic_mapping=None, persist_mapping=False,
- defroute=True, dhclient_args=None, dns_servers=None):
+ defroute=True, dhclient_args=None, dns_servers=None,
+ nm_controlled=False):
addresses = addresses or []
routes = routes or []
dns_servers = dns_servers or []
@@ -385,7 +392,8 @@ class NfvswitchInternal(_BaseOpts):
use_dhcpv6, addresses, routes,
mtu, primary, nic_mapping,
persist_mapping, defroute,
- dhclient_args, dns_servers)
+ dhclient_args, dns_servers,
+ nm_controlled)
self.vlan_id = int(vlan_id)
@staticmethod
@@ -403,7 +411,7 @@ class OvsBridge(_BaseOpts):
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,
- fail_mode=None):
+ nm_controlled=False, fail_mode=None):
addresses = addresses or []
routes = routes or []
members = members or []
@@ -411,7 +419,8 @@ class OvsBridge(_BaseOpts):
super(OvsBridge, self).__init__(name, use_dhcp, use_dhcpv6, addresses,
routes, mtu, False, nic_mapping,
persist_mapping, defroute,
- dhclient_args, dns_servers)
+ dhclient_args, dns_servers,
+ nm_controlled)
self.members = members
self.ovs_options = ovs_options
ovs_extra = ovs_extra or []
@@ -436,7 +445,8 @@ class OvsBridge(_BaseOpts):
name = _get_required_field(json, 'name', 'OvsBridge')
(use_dhcp, use_dhcpv6, addresses, routes, mtu, nic_mapping,
persist_mapping, defroute,
- dhclient_args, dns_servers) = _BaseOpts.base_opts_from_json(
+ dhclient_args, dns_servers,
+ nm_controlled) = _BaseOpts.base_opts_from_json(
json, include_primary=False)
ovs_options = json.get('ovs_options')
ovs_extra = json.get('ovs_extra', [])
@@ -461,7 +471,7 @@ class OvsBridge(_BaseOpts):
ovs_extra=ovs_extra, nic_mapping=nic_mapping,
persist_mapping=persist_mapping, defroute=defroute,
dhclient_args=dhclient_args, dns_servers=dns_servers,
- fail_mode=fail_mode)
+ nm_controlled=nm_controlled, fail_mode=fail_mode)
class OvsUserBridge(_BaseOpts):
@@ -471,12 +481,12 @@ class OvsUserBridge(_BaseOpts):
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,
- fail_mode=None):
+ nm_controlled=False, fail_mode=None):
super(OvsUserBridge, self).__init__(name, use_dhcp, use_dhcpv6,
addresses, routes, mtu, False,
nic_mapping, persist_mapping,
defroute, dhclient_args,
- dns_servers)
+ dns_servers, nm_controlled)
self.members = members or []
self.ovs_options = ovs_options
ovs_extra = ovs_extra or []
@@ -503,7 +513,8 @@ class OvsUserBridge(_BaseOpts):
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(
+ dhclient_args, dns_servers,
+ nm_controlled) = _BaseOpts.base_opts_from_json(
json, include_primary=False)
ovs_options = json.get('ovs_options')
ovs_extra = json.get('ovs_extra', [])
@@ -528,7 +539,8 @@ class OvsUserBridge(_BaseOpts):
ovs_extra=ovs_extra, nic_mapping=nic_mapping,
persist_mapping=persist_mapping,
defroute=defroute, dhclient_args=dhclient_args,
- dns_servers=dns_servers, fail_mode=fail_mode)
+ dns_servers=dns_servers,
+ nm_controlled=nm_controlled, fail_mode=fail_mode)
class LinuxBridge(_BaseOpts):
@@ -537,7 +549,7 @@ class LinuxBridge(_BaseOpts):
def __init__(self, name, use_dhcp=False, use_dhcpv6=False, addresses=None,
routes=None, mtu=None, members=None, nic_mapping=None,
persist_mapping=False, defroute=True, dhclient_args=None,
- dns_servers=None):
+ dns_servers=None, nm_controlled=False):
addresses = addresses or []
routes = routes or []
members = members or []
@@ -545,7 +557,8 @@ class LinuxBridge(_BaseOpts):
super(LinuxBridge, self).__init__(name, use_dhcp, use_dhcpv6,
addresses, routes, mtu, False,
nic_mapping, persist_mapping,
- defroute, dhclient_args, dns_servers)
+ defroute, dhclient_args, dns_servers,
+ nm_controlled)
self.members = members
for member in self.members:
member.linux_bridge_name = name
@@ -564,7 +577,7 @@ class LinuxBridge(_BaseOpts):
name = _get_required_field(json, 'name', 'LinuxBridge')
(use_dhcp, use_dhcpv6, addresses, routes, mtu, nic_mapping,
persist_mapping, defroute, dhclient_args,
- dns_servers) = _BaseOpts.base_opts_from_json(
+ dns_servers, nm_controlled) = _BaseOpts.base_opts_from_json(
json, include_primary=False)
members = []
@@ -583,7 +596,8 @@ class LinuxBridge(_BaseOpts):
members=members, nic_mapping=nic_mapping,
persist_mapping=persist_mapping, defroute=defroute,
dhclient_args=dhclient_args,
- dns_servers=dns_servers)
+ dns_servers=dns_servers,
+ nm_controlled=nm_controlled)
class IvsBridge(_BaseOpts):
@@ -602,7 +616,7 @@ class IvsBridge(_BaseOpts):
def __init__(self, name='ivs', use_dhcp=False, use_dhcpv6=False,
addresses=None, routes=None, mtu=1500, members=None,
nic_mapping=None, persist_mapping=False, defroute=True,
- dhclient_args=None, dns_servers=None):
+ dhclient_args=None, dns_servers=None, nm_controlled=False):
addresses = addresses or []
routes = routes or []
members = members or []
@@ -610,7 +624,8 @@ class IvsBridge(_BaseOpts):
super(IvsBridge, self).__init__(name, use_dhcp, use_dhcpv6,
addresses, routes, mtu, False,
nic_mapping, persist_mapping,
- defroute, dhclient_args, dns_servers)
+ defroute, dhclient_args, dns_servers,
+ nm_controlled)
self.members = members
for member in self.members:
if isinstance(member, OvsBond) or isinstance(member, LinuxBond):
@@ -625,7 +640,7 @@ class IvsBridge(_BaseOpts):
name = 'ivs'
(use_dhcp, use_dhcpv6, addresses, routes, mtu, nic_mapping,
persist_mapping, defroute, dhclient_args,
- dns_servers) = _BaseOpts.base_opts_from_json(
+ dns_servers, nm_controlled) = _BaseOpts.base_opts_from_json(
json, include_primary=False)
members = []
@@ -644,7 +659,7 @@ class IvsBridge(_BaseOpts):
members=members, nic_mapping=nic_mapping,
persist_mapping=persist_mapping, defroute=defroute,
dhclient_args=dhclient_args,
- dns_servers=dns_servers)
+ dns_servers=dns_servers, nm_controlled=nm_controlled)
class NfvswitchBridge(_BaseOpts):
@@ -658,7 +673,8 @@ class NfvswitchBridge(_BaseOpts):
def __init__(self, name='nfvswitch', use_dhcp=False, use_dhcpv6=False,
addresses=None, routes=None, mtu=1500, members=None,
nic_mapping=None, persist_mapping=False, defroute=True,
- dhclient_args=None, dns_servers=None, options=""):
+ dhclient_args=None, dns_servers=None, nm_controlled=False,
+ options=""):
addresses = addresses or []
routes = routes or []
members = members or []
@@ -667,7 +683,7 @@ class NfvswitchBridge(_BaseOpts):
addresses, routes, mtu, False,
nic_mapping, persist_mapping,
defroute, dhclient_args,
- dns_servers)
+ dns_servers, nm_controlled)
self.options = options
self.members = members
for member in self.members:
@@ -683,7 +699,7 @@ class NfvswitchBridge(_BaseOpts):
name = 'nfvswitch'
(use_dhcp, use_dhcpv6, addresses, routes, mtu, nic_mapping,
persist_mapping, defroute, dhclient_args,
- dns_servers) = _BaseOpts.base_opts_from_json(
+ dns_servers, nm_controlled) = _BaseOpts.base_opts_from_json(
json, include_primary=False)
# members
@@ -707,7 +723,8 @@ class NfvswitchBridge(_BaseOpts):
members=members, nic_mapping=nic_mapping,
persist_mapping=persist_mapping,
defroute=defroute, dhclient_args=dhclient_args,
- dns_servers=dns_servers, options=options)
+ dns_servers=dns_servers,
+ nm_controlled=nm_controlled, options=options)
class LinuxTeam(_BaseOpts):
@@ -716,7 +733,8 @@ class LinuxTeam(_BaseOpts):
def __init__(self, name, use_dhcp=False, use_dhcpv6=False, addresses=None,
routes=None, mtu=None, primary=False, members=None,
bonding_options=None, nic_mapping=None, persist_mapping=False,
- defroute=True, dhclient_args=None, dns_servers=None):
+ defroute=True, dhclient_args=None, dns_servers=None,
+ nm_controlled=False):
addresses = addresses or []
routes = routes or []
members = members or []
@@ -724,7 +742,8 @@ class LinuxTeam(_BaseOpts):
super(LinuxTeam, self).__init__(name, use_dhcp, use_dhcpv6, addresses,
routes, mtu, primary, nic_mapping,
persist_mapping, defroute,
- dhclient_args, dns_servers)
+ dhclient_args, dns_servers,
+ nm_controlled)
self.members = members
self.bonding_options = bonding_options
for member in self.members:
@@ -743,7 +762,7 @@ class LinuxTeam(_BaseOpts):
name = _get_required_field(json, 'name', 'LinuxTeam')
(use_dhcp, use_dhcpv6, addresses, routes, mtu, nic_mapping,
persist_mapping, defroute, dhclient_args,
- dns_servers) = _BaseOpts.base_opts_from_json(
+ dns_servers, nm_controlled) = _BaseOpts.base_opts_from_json(
json, include_primary=False)
bonding_options = json.get('bonding_options')
members = []
@@ -763,7 +782,8 @@ class LinuxTeam(_BaseOpts):
members=members, bonding_options=bonding_options,
nic_mapping=nic_mapping,
persist_mapping=persist_mapping, defroute=defroute,
- dhclient_args=dhclient_args, dns_servers=dns_servers)
+ dhclient_args=dhclient_args, dns_servers=dns_servers,
+ nm_controlled=nm_controlled)
class LinuxBond(_BaseOpts):
@@ -772,7 +792,8 @@ class LinuxBond(_BaseOpts):
def __init__(self, name, use_dhcp=False, use_dhcpv6=False, addresses=None,
routes=None, mtu=None, primary=False, members=None,
bonding_options=None, nic_mapping=None, persist_mapping=False,
- defroute=True, dhclient_args=None, dns_servers=None):
+ defroute=True, dhclient_args=None, dns_servers=None,
+ nm_controlled=False):
addresses = addresses or []
routes = routes or []
members = members or []
@@ -780,7 +801,8 @@ class LinuxBond(_BaseOpts):
super(LinuxBond, self).__init__(name, use_dhcp, use_dhcpv6, addresses,
routes, mtu, primary, nic_mapping,
persist_mapping, defroute,
- dhclient_args, dns_servers)
+ dhclient_args, dns_servers,
+ nm_controlled)
self.members = members
self.bonding_options = bonding_options
for member in self.members:
@@ -799,7 +821,7 @@ class LinuxBond(_BaseOpts):
name = _get_required_field(json, 'name', 'LinuxBond')
(use_dhcp, use_dhcpv6, addresses, routes, mtu, nic_mapping,
persist_mapping, defroute, dhclient_args,
- dns_servers) = _BaseOpts.base_opts_from_json(
+ dns_servers, nm_controlled) = _BaseOpts.base_opts_from_json(
json, include_primary=False)
bonding_options = json.get('bonding_options')
members = []
@@ -819,7 +841,8 @@ class LinuxBond(_BaseOpts):
members=members, bonding_options=bonding_options,
nic_mapping=nic_mapping,
persist_mapping=persist_mapping, defroute=defroute,
- dhclient_args=dhclient_args, dns_servers=dns_servers)
+ dhclient_args=dhclient_args, dns_servers=dns_servers,
+ nm_controlled=nm_controlled)
class OvsBond(_BaseOpts):
@@ -829,7 +852,7 @@ class OvsBond(_BaseOpts):
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):
+ dns_servers=None, nm_controlled=False):
addresses = addresses or []
routes = routes or []
members = members or []
@@ -837,7 +860,7 @@ class OvsBond(_BaseOpts):
super(OvsBond, self).__init__(name, use_dhcp, use_dhcpv6, addresses,
routes, mtu, primary, nic_mapping,
persist_mapping, defroute, dhclient_args,
- dns_servers)
+ dns_servers, nm_controlled)
self.members = members
self.ovs_options = ovs_options
self.ovs_extra = format_ovs_extra(self, ovs_extra)
@@ -860,7 +883,7 @@ class OvsBond(_BaseOpts):
name = _get_required_field(json, 'name', 'OvsBond')
(use_dhcp, use_dhcpv6, addresses, routes, mtu, nic_mapping,
persist_mapping, defroute, dhclient_args,
- dns_servers) = _BaseOpts.base_opts_from_json(
+ dns_servers, nm_controlled) = _BaseOpts.base_opts_from_json(
json, include_primary=False)
ovs_options = json.get('ovs_options')
ovs_extra = json.get('ovs_extra', [])
@@ -883,7 +906,8 @@ class OvsBond(_BaseOpts):
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)
+ dhclient_args=dhclient_args, dns_servers=dns_servers,
+ nm_controlled=nm_controlled)
class OvsTunnel(_BaseOpts):
@@ -892,15 +916,16 @@ class OvsTunnel(_BaseOpts):
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, tunnel_type=None, ovs_options=None,
- ovs_extra=None):
+ dns_servers=None, nm_controlled=False, tunnel_type=None,
+ ovs_options=None, ovs_extra=None):
addresses = addresses or []
routes = routes or []
dns_servers = dns_servers or []
super(OvsTunnel, self).__init__(name, use_dhcp, use_dhcpv6, addresses,
routes, mtu, primary, nic_mapping,
persist_mapping, defroute,
- dhclient_args, dns_servers)
+ dhclient_args, dns_servers,
+ nm_controlled)
self.tunnel_type = tunnel_type
self.ovs_options = ovs_options or []
self.ovs_extra = format_ovs_extra(self, ovs_extra)
@@ -925,8 +950,8 @@ class OvsPatchPort(_BaseOpts):
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, bridge_name=None, peer=None,
- ovs_options=None, ovs_extra=None):
+ dns_servers=None, nm_controlled=False, bridge_name=None,
+ peer=None, ovs_options=None, ovs_extra=None):
addresses = addresses or []
routes = routes or []
dns_servers = dns_servers or []
@@ -934,7 +959,7 @@ class OvsPatchPort(_BaseOpts):
addresses, routes, mtu, primary,
nic_mapping, persist_mapping,
defroute, dhclient_args,
- dns_servers)
+ dns_servers, nm_controlled)
self.bridge_name = bridge_name
self.peer = peer
self.ovs_options = ovs_options or []
@@ -961,14 +986,15 @@ class IbInterface(_BaseOpts):
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, ethtool_opts=None):
+ dns_servers=None, nm_controlled=False, ethtool_opts=None):
addresses = addresses or []
routes = routes or []
dns_servers = dns_servers or []
super(IbInterface, self).__init__(name, use_dhcp, use_dhcpv6,
addresses, routes, mtu, primary,
nic_mapping, persist_mapping,
- defroute, dhclient_args, dns_servers)
+ defroute, dhclient_args, dns_servers,
+ nm_controlled)
self.ethtool_opts = ethtool_opts
@staticmethod
@@ -985,14 +1011,14 @@ class OvsDpdkPort(_BaseOpts):
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):
+ dns_servers=None, nm_controlled=False, 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)
+ dns_servers, nm_controlled)
self.members = members or []
self.ovs_options = ovs_options or []
self.ovs_extra = format_ovs_extra(self, ovs_extra)
@@ -1047,11 +1073,12 @@ class OvsDpdkBond(_BaseOpts):
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):
+ dns_servers=None, nm_controlled=False):
super(OvsDpdkBond, self).__init__(name, use_dhcp, use_dhcpv6,
addresses, routes, mtu, primary,
nic_mapping, persist_mapping,
- defroute, dhclient_args, dns_servers)
+ defroute, dhclient_args, dns_servers,
+ nm_controlled)
self.members = members or []
self.ovs_options = ovs_options
self.ovs_extra = format_ovs_extra(self, ovs_extra)
@@ -1075,7 +1102,7 @@ class OvsDpdkBond(_BaseOpts):
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(
+ dns_servers, nm_controlled) = _BaseOpts.base_opts_from_json(
json, include_primary=False)
ovs_options = json.get('ovs_options')
ovs_extra = json.get('ovs_extra', [])
@@ -1104,4 +1131,59 @@ class OvsDpdkBond(_BaseOpts):
ovs_extra=ovs_extra, nic_mapping=nic_mapping,
persist_mapping=persist_mapping,
defroute=defroute, dhclient_args=dhclient_args,
- dns_servers=dns_servers)
+ dns_servers=dns_servers,
+ nm_controlled=nm_controlled)
+
+
+class VppInterface(_BaseOpts):
+ """Base class for VPP Interface.
+
+ Vector Packet Processing (VPP) is a high performance packet processing
+ stack that runs in user space in Linux. VPP is used as an alternative to
+ kernel networking stack for accelerated network data path. VPP uses DPDK
+ poll-mode drivers to bind system interfaces rather than kernel drivers.
+ VPP bound interfaces are not visible to kernel networking stack, so we
+ must handle them separately.
+
+ The following parameters can be specified in addition to base Interface:
+ - uio_driver: DPDK poll-mode driver name. Defaults to 'vfio-pci', valid
+ values are 'uio_pci_generic' and 'vfio-pci'.
+ - options: Interface options such as vlan stripping and tx/rx transmit
+ queues specification. Defaults to None. Reference for those
+ configurations can be found at
+ https://wiki.fd.io/view/VPP/Command-line_Arguments
+ Example: 'vlan-strip-offload on num-rx-queues 3'
+
+ Note that 'name' attribute is used to indicate the kernel nic that should
+ be bound to VPP. Once VPP binds the interface, a mapping file will be
+ updated with the interface's information, and this file will be used in
+ subsequent runs of os-net-config.
+ """
+ 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, nm_controlled=False, uio_driver='vfio-pci',
+ options=None):
+ addresses = addresses or []
+
+ super(VppInterface, self).__init__(name, use_dhcp, use_dhcpv6,
+ addresses, routes, mtu, primary,
+ nic_mapping, persist_mapping,
+ defroute, dhclient_args,
+ dns_servers, nm_controlled)
+ self.uio_driver = uio_driver
+ self.options = options
+ # pci_dev contains pci address for the interface, it will be populated
+ # when interface is added to config object. It will be determined
+ # either through ethtool or by looking up the dpdk mapping file.
+ self.pci_dev = None
+
+ @staticmethod
+ def from_json(json):
+ name = _get_required_field(json, 'name', 'VppInterface')
+ uio_driver = json.get('uio_driver', 'vfio-pci')
+ options = json.get('options', '')
+
+ opts = _BaseOpts.base_opts_from_json(json)
+ return VppInterface(name, *opts, uio_driver=uio_driver,
+ options=options)
diff --git a/os_net_config/tests/test_impl_ifcfg.py b/os_net_config/tests/test_impl_ifcfg.py
index 366c408..d770415 100644
--- a/os_net_config/tests/test_impl_ifcfg.py
+++ b/os_net_config/tests/test_impl_ifcfg.py
@@ -34,6 +34,14 @@ NM_CONTROLLED=no
PEERDNS=no
"""
+_BASE_IFCFG_NETWORKMANAGER = """# This file is autogenerated by os-net-config
+DEVICE=em1
+ONBOOT=yes
+HOTPLUG=no
+NM_CONTROLLED=yes
+PEERDNS=no
+"""
+
_HOTPLUG = """# This file is autogenerated by os-net-config
DEVICE=em1
ONBOOT=yes
@@ -153,6 +161,19 @@ OVSBOOTPROTO=dhcp
OVSDHCPINTERFACES="em1"
"""
+_NM_CONTROLLED_INTERFACE = _BASE_IFCFG_NETWORKMANAGER + """MASTER=bond1
+SLAVE=yes
+BOOTPROTO=none
+"""
+
+_NM_CONTROLLED_BOND = """# This file is autogenerated by os-net-config
+DEVICE=bond1
+ONBOOT=yes
+HOTPLUG=no
+NM_CONTROLLED=yes
+PEERDNS=no
+"""
+
_OVS_BRIDGE_DHCP_STANDALONE = _OVS_BRIDGE_DHCP + \
"OVS_EXTRA=\"set bridge br-ctlplane fail_mode=standalone\"\n"
@@ -845,6 +866,20 @@ DNS2=5.6.7.8
"""
self.assertEqual(em1_config, self.get_interface_config('em1'))
+ def test_nm_controlled(self):
+ interface1 = objects.Interface('em1', nm_controlled=True)
+ interface2 = objects.Interface('em2', nm_controlled=True)
+ bond = objects.LinuxBond('bond1', nm_controlled=True,
+ members=[interface1, interface2])
+ self.provider.add_linux_bond(bond)
+ self.provider.add_interface(interface1)
+ self.provider.add_interface(interface2)
+
+ ifcfg_data = self.get_interface_config('em1')
+ self.assertEqual(_NM_CONTROLLED_INTERFACE, ifcfg_data)
+ bond_data = self.get_linux_bond_config('bond1')
+ self.assertEqual(_NM_CONTROLLED_BOND, bond_data)
+
def test_network_ovs_dpdk_bridge_and_port(self):
nic_mapping = {'nic1': 'eth0', 'nic2': 'eth1', 'nic3': 'eth2'}
self.stubbed_mapped_nics = nic_mapping
@@ -924,6 +959,7 @@ class TestIfcfgNetConfigApply(base.TestCase):
def setUp(self):
super(TestIfcfgNetConfigApply, self).setUp()
self.temp_ifcfg_file = tempfile.NamedTemporaryFile()
+ self.temp_bond_file = tempfile.NamedTemporaryFile()
self.temp_route_file = tempfile.NamedTemporaryFile()
self.temp_route6_file = tempfile.NamedTemporaryFile()
self.temp_bridge_file = tempfile.NamedTemporaryFile()
diff --git a/os_net_config/tests/test_objects.py b/os_net_config/tests/test_objects.py
index f5daf31..4ff87c8 100644
--- a/os_net_config/tests/test_objects.py
+++ b/os_net_config/tests/test_objects.py
@@ -988,3 +988,19 @@ class TestOvsDpdkBond(base.TestCase):
self.assertEqual("vfio-pci", dpdk_port1.driver)
iface2 = dpdk_port1.members[0]
self.assertEqual("eth2", iface2.name)
+
+
+class TestVppInterface(base.TestCase):
+ def test_vpp_interface_from_json(self):
+ data = """{
+"type": "vpp_interface",
+"name": "em1",
+"uio_driver": "uio_pci_generic",
+"options": "vlan-strip-offload off"
+}
+"""
+
+ vpp_interface = objects.object_from_json(json.loads(data))
+ self.assertEqual("em1", vpp_interface.name)
+ self.assertEqual("uio_pci_generic", vpp_interface.uio_driver)
+ self.assertEqual("vlan-strip-offload off", vpp_interface.options)
diff --git a/os_net_config/tests/test_utils.py b/os_net_config/tests/test_utils.py
index 1885cbb..5a8b6af 100644
--- a/os_net_config/tests/test_utils.py
+++ b/os_net_config/tests/test_utils.py
@@ -21,6 +21,7 @@ import shutil
import tempfile
import yaml
+from os_net_config import objects
from os_net_config.tests import base
from os_net_config import utils
@@ -38,6 +39,33 @@ supports-register-dump: yes
supports-priv-flags: no
'''
+_VPPCTL_OUTPUT = '''
+ Name Idx State Counter Count
+GigabitEthernet0/9/0 1 down
+local0 0 down
+
+'''
+
+_INITIAL_VPP_CONFIG = '''
+unix {
+ nodaemon
+ log /tmp/vpp.log
+ full-coredump
+}
+
+
+api-trace {
+ on
+}
+
+api-segment {
+ gid vpp
+}
+
+dpdk {
+}
+'''
+
class TestUtils(base.TestCase):
@@ -83,7 +111,7 @@ class TestUtils(base.TestCase):
out = _PCI_OUTPUT
return out, None
self.stubs.Set(processutils, 'execute', test_execute)
- pci = utils._get_pci_address('nic2', False)
+ pci = utils.get_pci_address('nic2', False)
self.assertEqual('0000:00:19.0', pci)
def test_get_pci_address_exception(self):
@@ -91,7 +119,7 @@ class TestUtils(base.TestCase):
if 'ethtool' in name:
raise processutils.ProcessExecutionError
self.stubs.Set(processutils, 'execute', test_execute)
- pci = utils._get_pci_address('nic2', False)
+ pci = utils.get_pci_address('nic2', False)
self.assertEqual(None, pci)
def test_get_pci_address_error(self):
@@ -99,7 +127,7 @@ class TestUtils(base.TestCase):
if 'ethtool' in name:
return None, 'Error'
self.stubs.Set(processutils, 'execute', test_execute)
- pci = utils._get_pci_address('nic2', False)
+ pci = utils.get_pci_address('nic2', False)
self.assertEqual(None, pci)
def test_bind_dpdk_interfaces(self):
@@ -241,3 +269,99 @@ class TestUtils(base.TestCase):
self.assertEqual(utils._is_active_nic('enp129s2'), False)
shutil.rmtree(tmpdir)
+
+ def test_get_vpp_interface_name(self):
+ def test_execute(name, dummy1, dummy2=None, dummy3=None):
+ if 'systemctl' in name:
+ return None, None
+ if 'vppctl' in name:
+ return _VPPCTL_OUTPUT, None
+
+ self.stubs.Set(processutils, 'execute', test_execute)
+
+ self.assertEqual('GigabitEthernet0/9/0',
+ utils._get_vpp_interface_name('0000:00:09.0'))
+ self.assertIsNone(utils._get_vpp_interface_name(None))
+ self.assertIsNone(utils._get_vpp_interface_name('0000:01:09.0'))
+ self.assertRaises(utils.VppException,
+ utils._get_vpp_interface_name, '0000:09.0')
+
+ def test_generate_vpp_config(self):
+ tmpdir = tempfile.mkdtemp()
+ config_path = os.path.join(tmpdir, 'startup.conf')
+ with open(config_path, 'w') as f:
+ f.write(_INITIAL_VPP_CONFIG)
+ vpp_exec_path = os.path.join(tmpdir, 'vpp-exec')
+ utils._VPP_EXEC_FILE = vpp_exec_path
+
+ int1 = objects.VppInterface('em1', options="vlan-strip-offload off")
+ int1.pci_dev = '0000:00:09.0'
+ int2 = objects.VppInterface('em2')
+ int2.pci_dev = '0000:00:09.1'
+ interfaces = [int1, int2]
+ expected_config = '''
+unix {
+ exec %s
+ nodaemon
+ log /tmp/vpp.log
+ full-coredump
+}
+
+
+api-trace {
+ on
+}
+
+api-segment {
+ gid vpp
+}
+
+dpdk {
+ dev 0000:00:09.1
+ uio-driver vfio-pci
+ dev 0000:00:09.0 {vlan-strip-offload off}
+
+}
+''' % vpp_exec_path
+ self.assertEqual(expected_config,
+ utils.generate_vpp_config(config_path, interfaces))
+
+ def test_update_vpp_mapping(self):
+ def test_get_dpdk_map():
+ return [{'name': 'eth1', 'pci_address': '0000:00:09.0',
+ 'mac_address': '01:02:03:04:05:06',
+ 'driver': 'vfio-pci'}]
+
+ self.stubs.Set(utils, '_get_dpdk_map', test_get_dpdk_map)
+
+ def test_execute(name, dummy1, dummy2=None, dummy3=None):
+ return None, None
+ self.stubs.Set(processutils, 'execute', test_execute)
+
+ def test_get_vpp_interface_name(pci_dev):
+ return 'GigabitEthernet0/9/0'
+
+ self.stubs.Set(utils, '_get_vpp_interface_name',
+ test_get_vpp_interface_name)
+
+ int1 = objects.VppInterface('eth1', options="vlan-strip-offload off")
+ int1.pci_dev = '0000:00:09.0'
+ int1.hwaddr = '01:02:03:04:05:06'
+ int2 = objects.VppInterface('eth2')
+ int2.pci_dev = '0000:00:09.1'
+ int2.hwaddr = '01:02:03:04:05:07'
+ interfaces = [int1, int2]
+
+ utils.update_vpp_mapping(interfaces)
+
+ contents = utils.get_file_data(utils._DPDK_MAPPING_FILE)
+
+ dpdk_test = [{'name': 'eth1', 'pci_address': '0000:00:09.0',
+ 'mac_address': '01:02:03:04:05:06',
+ 'driver': 'vfio-pci'},
+ {'name': 'eth2', 'pci_address': '0000:00:09.1',
+ 'mac_address': '01:02:03:04:05:07',
+ 'driver': 'vfio-pci'}]
+ dpdk_map = yaml.load(contents) if contents else []
+ self.assertEqual(2, len(dpdk_map))
+ self.assertListEqual(dpdk_test, dpdk_map)
diff --git a/os_net_config/utils.py b/os_net_config/utils.py
index 98bfe99..a4dfa4b 100644
--- a/os_net_config/utils.py
+++ b/os_net_config/utils.py
@@ -35,11 +35,19 @@ _SYS_CLASS_NET = '/sys/class/net'
# driver: vfio-pci
_DPDK_MAPPING_FILE = '/var/lib/os-net-config/dpdk_mapping.yaml'
+# VPP startup operational configuration file. The content of this file will
+# be executed when VPP starts as if typed from CLI.
+_VPP_EXEC_FILE = '/etc/vpp/vpp-exec'
+
class OvsDpdkBindException(ValueError):
pass
+class VppException(ValueError):
+ pass
+
+
def write_config(filename, data):
with open(filename, 'w') as f:
f.write(str(data))
@@ -82,7 +90,7 @@ def interface_mac(name):
return f.read().rstrip()
except IOError:
# If the interface is bound to a DPDK driver, get the mac address from
- # the dpdk mapping file as /sys files will be removed after binding.
+ # the DPDK mapping file as /sys files will be removed after binding.
dpdk_mac_address = _get_dpdk_mac_address(name)
if dpdk_mac_address:
return dpdk_mac_address
@@ -187,7 +195,7 @@ def diff(filename, data):
def bind_dpdk_interfaces(ifname, driver, noop):
- pci_address = _get_pci_address(ifname, noop)
+ pci_address = get_pci_address(ifname, noop)
if not noop:
if pci_address:
# modbprobe of the driver has to be done before binding.
@@ -218,7 +226,7 @@ def bind_dpdk_interfaces(ifname, driver, noop):
{'name': ifname, 'driver': driver})
-def _get_pci_address(ifname, noop):
+def get_pci_address(ifname, noop):
# TODO(skramaja): Validate if the given interface supports dpdk
if not noop:
try:
@@ -241,11 +249,10 @@ def _get_pci_address(ifname, noop):
# Once the interface is bound to a DPDK driver, all the references to the
# interface including '/sys' and '/proc', will be removed. And there is no
# way to identify the nic name after it is bound. So, the DPDK bound nic info
-# is stored persistently in a file and is used to for nic numbering on
-# subsequent runs of os-net-config.
+# is stored persistently in _DPDK_MAPPING_FILE and is used to for nic numbering
+# on subsequent runs of os-net-config.
def _update_dpdk_map(ifname, pci_address, mac_address, driver):
- contents = get_file_data(_DPDK_MAPPING_FILE)
- dpdk_map = yaml.load(contents) if contents else []
+ dpdk_map = _get_dpdk_map()
for item in dpdk_map:
if item['pci_address'] == pci_address:
item['name'] = ifname
@@ -263,9 +270,215 @@ def _update_dpdk_map(ifname, pci_address, mac_address, driver):
write_yaml_config(_DPDK_MAPPING_FILE, dpdk_map)
+def _get_dpdk_map():
+ contents = get_file_data(_DPDK_MAPPING_FILE)
+ dpdk_map = yaml.load(contents) if contents else []
+ return dpdk_map
+
+
def _get_dpdk_mac_address(name):
contents = get_file_data(_DPDK_MAPPING_FILE)
dpdk_map = yaml.load(contents) if contents else []
for item in dpdk_map:
if item['name'] == name:
return item['mac_address']
+
+
+def restart_vpp(vpp_interfaces):
+ for vpp_int in vpp_interfaces:
+ if 'vfio-pci' in vpp_int.uio_driver:
+ processutils.execute('modprobe', 'vfio-pci')
+ logger.info('Restarting VPP')
+ processutils.execute('systemctl', 'restart', 'vpp')
+
+
+def _get_vpp_interface_name(pci_addr):
+ """Get VPP interface name from a given PCI address
+
+ From a running VPP instance, attempt to find the interface name from
+ a given PCI address of a NIC.
+
+ VppException will be raised if pci_addr is not formatted correctly.
+ ProcessExecutionError will be raised if VPP interface mapped to pci_addr
+ is not found.
+
+ :param pci_addr: PCI address to lookup, in the form of DDDD:BB:SS.F, where
+ - DDDD = Domain
+ - BB = Bus Number
+ - SS = Slot number
+ - F = Function
+ :return: VPP interface name. None if an interface is not found.
+ """
+ if not pci_addr:
+ return None
+
+ try:
+ processutils.execute('systemctl', 'is-active', 'vpp')
+ out, err = processutils.execute('vppctl', 'show', 'interfaces')
+ m = re.search(r':([0-9a-fA-F]{2}):([0-9a-fA-F]{2}).([0-9a-fA-F])',
+ pci_addr)
+ if m:
+ formatted_pci = "%x/%x/%x" % (int(m.group(1), 16),
+ int(m.group(2), 16),
+ int(m.group(3), 16))
+ else:
+ raise VppException('Invalid PCI address format: %s' % pci_addr)
+
+ m = re.search(r'^(\w+%s)\s+' % formatted_pci, out, re.MULTILINE)
+ if m:
+ logger.debug('VPP interface found: %s' % m.group(1))
+ return m.group(1)
+ else:
+ logger.debug('Interface with pci address %s not bound to VPP'
+ % pci_addr)
+ return None
+ except processutils.ProcessExecutionError:
+ logger.debug('Interface with pci address %s not bound to vpp' %
+ pci_addr)
+
+
+def generate_vpp_config(vpp_config_path, vpp_interfaces):
+ """Generate configuration content for VPP
+
+ Generate interface related configuration content for VPP. Current
+ configuration will be preserved, with interface related configurations
+ updated or inserted. The config only affects 'dpdk' section of VPP config
+ file, and only those lines affecting interfaces, specifically, lines
+ containing the following:
+ dpdk {
+ ...
+ dev <pci_dev> {<options>}
+ uio-driver <uio_driver_name>
+ ...
+ }
+
+ :param vpp_config_path: VPP Configuration file path
+ :param vpp_interfaces: List of VPP interface objects
+ :return: updated VPP config content.
+ """
+
+ data = get_file_data(vpp_config_path)
+
+ # Add interface config to 'dpdk' section
+ for vpp_interface in vpp_interfaces:
+ if vpp_interface.pci_dev:
+ logger.info('vpp interface %s pci dev: %s'
+ % (vpp_interface.name, vpp_interface.pci_dev))
+
+ if vpp_interface.options:
+ int_cfg = '%s {%s}' % (vpp_interface.pci_dev,
+ vpp_interface.options)
+ else:
+ int_cfg = vpp_interface.pci_dev
+
+ # Make sure 'dpdk' section exists in the config
+ if not re.search(r'^\s*dpdk\s*\{', data, re.MULTILINE):
+ data += "\ndpdk {\n}\n"
+
+ # Find existing config line for the device we are trying to
+ # configure, the line should look like 'dev <pci_dev> ...'
+ # If such config line is found, we will replace the line with
+ # appropriate configuration, otherwise, add a new config line
+ # in 'dpdk' section of the config.
+ m = re.search(r'^\s*dev\s+%s\s*(\{[^}]*\})?\s*'
+ % vpp_interface.pci_dev, data,
+ re.IGNORECASE | re.MULTILINE)
+ if m:
+ data = re.sub(m.group(0), ' dev %s\n' % int_cfg, data)
+ else:
+ data = re.sub(r'(^\s*dpdk\s*\{)',
+ r'\1\n dev %s\n' % int_cfg,
+ data,
+ flags=re.MULTILINE)
+
+ if vpp_interface.uio_driver:
+ # Check if there is existing uio-driver configuration, if
+ # found, the line will be replaced with the appropriate
+ # configuration, otherwise, add a new line in 'dpdk' section.
+ m = re.search(r'^\s*uio-driver.*$', data, re.MULTILINE)
+ if m:
+ data = re.sub(m.group(0), r' uio-driver %s'
+ % vpp_interface.uio_driver, data)
+ else:
+ data = re.sub(r'(dpdk\s*\{)',
+ r'\1\n uio-driver %s'
+ % vpp_interface.uio_driver,
+ data)
+ else:
+ logger.debug('pci address not found for interface %s, may have'
+ 'already been bound to vpp' % vpp_interface.name)
+
+ # Add start up script for VPP to config. This script will be executed by
+ # VPP on service start.
+ if not re.search(r'^\s*unix\s*\{', data, re.MULTILINE):
+ data += "\nunix {\n}\n"
+
+ m = re.search(r'^\s*(exec|startup-config).*$',
+ data,
+ re.IGNORECASE | re.MULTILINE)
+ if m:
+ data = re.sub(m.group(0), ' exec %s' % _VPP_EXEC_FILE, data)
+ else:
+ data = re.sub(r'(^\s*unix\s*\{)',
+ r'\1\n exec %s' % _VPP_EXEC_FILE,
+ data,
+ flags=re.MULTILINE)
+ # Make sure startup script exists to avoid VPP startup failure.
+ open(_VPP_EXEC_FILE, 'a').close()
+
+ return data
+
+
+def update_vpp_mapping(vpp_interfaces):
+ """Verify VPP interface binding and update mapping file
+
+ VppException will be raised if interfaces are not properly bound.
+
+ :param vpp_interfaces: List of VPP interface objects
+ """
+ vpp_start_cli = ""
+
+ for vpp_int in vpp_interfaces:
+ if not vpp_int.pci_dev:
+ dpdk_map = _get_dpdk_map()
+ for dpdk_int in dpdk_map:
+ if dpdk_int['name'] == vpp_int.name:
+ vpp_int.pci_dev = dpdk_int['pci_address']
+ break
+ else:
+ raise VppException('Interface %s has no PCI address and is not'
+ ' found in mapping file' % vpp_int.name)
+
+ # Try to get VPP interface name. In case VPP service is down
+ # for some reason, we will restart VPP and try again. Currently
+ # only trying one more time, can turn into a retry_counter if needed
+ # in the future.
+ for i in range(2):
+ vpp_name = _get_vpp_interface_name(vpp_int.pci_dev)
+ if not vpp_name:
+ restart_vpp(vpp_interfaces)
+ else:
+ break
+ else:
+ raise VppException('Interface %s with pci address %s not '
+ 'bound to vpp'
+ % (vpp_int.name, vpp_int.pci_dev))
+
+ # Generate content of startup script for VPP
+ for address in vpp_int.addresses:
+ vpp_start_cli += 'set interface state %s up\n' % vpp_name
+ vpp_start_cli += 'set interface ip address %s %s/%s\n' \
+ % (vpp_name, address.ip, address.prefixlen)
+
+ logger.info('Updating mapping for vpp interface %s:'
+ 'pci_dev: %s mac address: %s uio driver: %s'
+ % (vpp_int.name, vpp_int.pci_dev, vpp_int.hwaddr,
+ vpp_int.uio_driver))
+ _update_dpdk_map(vpp_int.name, vpp_int.pci_dev, vpp_int.hwaddr,
+ vpp_int.uio_driver)
+ # Enable VPP service to make the VPP interface configuration
+ # persistent.
+ processutils.execute('systemctl', 'enable', 'vpp')
+ if diff(_VPP_EXEC_FILE, vpp_start_cli):
+ write_config(_VPP_EXEC_FILE, vpp_start_cli)
+ restart_vpp(vpp_interfaces)
diff --git a/requirements.txt b/requirements.txt
index cc08a28..d085a12 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,10 +1,13 @@
-pbr>=0.6,!=0.7,<1.0
-anyjson>=0.3.3
-Babel>=0.9.6
-six>=1.6.0
-eventlet>=0.13.0
-iso8601>=0.1.9
-netaddr>=0.7.6
-oslo.concurrency>=1.4.1 # Apache-2.0
-oslo.utils>=1.2.0 # Apache-2.0
-PyYAML>=3.1.0
+# The order of packages is significant, because pip processes them in the order
+# of appearance. Changing the order has an impact on the overall integration
+# process, which may cause wedges in the gate later.
+pbr>=2.0.0 # Apache-2.0
+anyjson>=0.3.3 # BSD
+Babel>=2.3.4 # BSD
+six>=1.9.0 # MIT
+eventlet!=0.18.3,>=0.18.2 # MIT
+iso8601>=0.1.11 # MIT
+netaddr!=0.7.16,>=0.7.13 # BSD
+oslo.concurrency>=3.8.0 # Apache-2.0
+oslo.utils>=3.20.0 # Apache-2.0
+PyYAML>=3.10.0 # MIT
diff --git a/setup.py b/setup.py
index 70c2b3f..566d844 100755..100644
--- a/setup.py
+++ b/setup.py
@@ -1,4 +1,3 @@
-#!/usr/bin/env python
# Copyright (c) 2013 Hewlett-Packard Development Company, L.P.
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -17,6 +16,14 @@
# THIS FILE IS MANAGED BY THE GLOBAL REQUIREMENTS REPO - DO NOT EDIT
import setuptools
+# In python < 2.7.4, a lazy loading of package `pbr` will break
+# setuptools if some other modules registered functions in `atexit`.
+# solution from: http://bugs.python.org/issue15881#msg170215
+try:
+ import multiprocessing # noqa
+except ImportError:
+ pass
+
setuptools.setup(
- setup_requires=['pbr'],
+ setup_requires=['pbr>=2.0.0'],
pbr=True)
diff --git a/test-requirements.txt b/test-requirements.txt
index fe554a0..4b27709 100644
--- a/test-requirements.txt
+++ b/test-requirements.txt
@@ -1,12 +1,15 @@
-hacking>=0.10.2,<0.11 # Apache-2.0
+# The order of packages is significant, because pip processes them in the order
+# of appearance. Changing the order has an impact on the overall integration
+# process, which may cause wedges in the gate later.
+hacking<0.11,>=0.10.2 # Apache-2.0
-coverage>=3.6
-fixtures>=0.3.14
-python-subunit
-sphinx>=1.1.2
-oslosphinx
-testrepository>=0.0.17
-testscenarios>=0.4,<0.5
-testtools>=0.9.32
-mock>=1.0
-mox>=0.5.3
+coverage>=4.0 # Apache-2.0
+fixtures>=3.0.0 # Apache-2.0/BSD
+python-subunit>=0.0.18 # Apache-2.0/BSD
+sphinx>=1.5.1 # BSD
+oslosphinx>=4.7.0 # Apache-2.0
+testrepository>=0.0.18 # Apache-2.0/BSD
+testscenarios>=0.4 # Apache-2.0/BSD
+testtools>=1.4.0 # MIT
+mock>=2.0 # BSD
+mox>=0.5.3 # Apache-2.0
diff --git a/tox.ini b/tox.ini
index efe01f4..a1bd79c 100644
--- a/tox.ini
+++ b/tox.ini
@@ -19,16 +19,15 @@ commands = flake8
commands = {posargs}
[testenv:cover]
-commands = python setup.py testr --coverage --testr-args='{posargs}'
+commands = python setup.py testr --coverage --coverage-package-name=os_net_config --testr-args='{posargs}'
[testenv:docs]
commands = python setup.py build_sphinx
[flake8]
-# H803 skipped on purpose per list discussion.
# E123, E125 skipped as they are invalid PEP-8.
show-source = True
-ignore = E123,E125,H803
+ignore = E123,E125
builtins = _
exclude=.venv,.git,.tox,dist,doc,*openstack/common*,*lib/python*,*egg,build