From c5e11bbfcda78f021b38bf9177f0c661b8f59b20 Mon Sep 17 00:00:00 2001 From: Feng Pan Date: Mon, 23 Jan 2017 22:29:27 -0500 Subject: Add support 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 interfacees are not visible to kernel networking stack and therefore require different configuration steps in os-net-config. VPP interface will be used in Openstack by either Neutron ML2 driver networking-vpp as an alternative to OVS, or by Opendaylight SDN controller with Honeycomb agent. This patch adds interface configuration support to os-net-config. The kernel nic specified to be VPP interface type will be bound to VPP with a DPDK poll-mode driver. Note that os-net-config will only configure those settings that affect interface binding, all other configurations for VPP will be configured throught TripleO Heat Templates. Implements: blueprint fdio-integration-tripleo Change-Id: Iebb40b7c5b252c51e86b6f44bcf36ed206101390 Signed-off-by: Feng Pan --- etc/os-net-config/samples/vpp_interface.json | 14 ++ etc/os-net-config/samples/vpp_interface.yaml | 18 +++ os_net_config/__init__.py | 9 ++ os_net_config/impl_ifcfg.py | 43 +++++ os_net_config/objects.py | 56 +++++++ os_net_config/tests/test_objects.py | 16 ++ os_net_config/tests/test_utils.py | 130 ++++++++++++++- os_net_config/utils.py | 227 ++++++++++++++++++++++++++- 8 files changed, 503 insertions(+), 10 deletions(-) create mode 100644 etc/os-net-config/samples/vpp_interface.json create mode 100644 etc/os-net-config/samples/vpp_interface.yaml 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 cf3b257..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 = {} @@ -626,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.""" @@ -690,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 @@ -698,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, '') @@ -906,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: @@ -932,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) @@ -1009,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 944aecd..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): @@ -1131,3 +1133,57 @@ class OvsDpdkBond(_BaseOpts): defroute=defroute, dhclient_args=dhclient_args, 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_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 {} + uio-driver + ... + } + + :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 ...' + # 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) -- cgit 1.2.3-korg