aboutsummaryrefslogtreecommitdiffstats
path: root/os_net_config/utils.py
diff options
context:
space:
mode:
Diffstat (limited to 'os_net_config/utils.py')
-rw-r--r--os_net_config/utils.py239
1 files changed, 234 insertions, 5 deletions
diff --git a/os_net_config/utils.py b/os_net_config/utils.py
index 84e719e..a406a13 100644
--- a/os_net_config/utils.py
+++ b/os_net_config/utils.py
@@ -19,6 +19,7 @@ import logging
import os
import re
import yaml
+import json
from oslo_concurrency import processutils
@@ -35,11 +36,21 @@ _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'
+
+HIERADATA_FILE = '/etc/puppet/hieradata/common.json'
+
class OvsDpdkBindException(ValueError):
pass
+class VppException(ValueError):
+ pass
+
+
def write_config(filename, data):
with open(filename, 'w') as f:
f.write(str(data))
@@ -50,6 +61,10 @@ def write_yaml_config(filepath, data):
with open(filepath, 'w') as f:
yaml.dump(data, f, default_flow_style=False)
+def write_json_config(filepath, data):
+ ensure_directory_presence(filepath)
+ with open(filepath, 'w') as f:
+ json.dump(data, f, indent=2)
def ensure_directory_presence(filepath):
dir_path = os.path.dirname(filepath)
@@ -124,7 +139,11 @@ def _is_available_nic(interface_name, check_active=True):
# ignored.
vf_path_check = _SYS_CLASS_NET + '/%s/device/physfn' % interface_name
is_sriov_vf = os.path.isdir(vf_path_check)
- if is_sriov_vf:
+
+ if (has_device_dir and operstate == 'up' and address and
+ not is_sriov_vf):
+ return True
+ else:
return False
# nic is available
@@ -205,7 +224,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.
@@ -236,7 +255,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:
@@ -262,8 +281,7 @@ def _get_pci_address(ifname, noop):
# is stored persistently in a 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
@@ -281,9 +299,220 @@ 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 write_hiera(filename, data):
+ """Writes a dictionary as hiera variables to a file.
+
+ If file already exists, the data will be appended to the file.
+ :param filename: file to write the hiera data to
+ :param data: dictionary of key,value pairs to write as hiera variables
+ :return: None
+ """
+ if not isinstance(data, dict):
+ raise TypeError('data type must be dictionary')
+
+ current_content = get_file_data(filename) or "{}"
+ current_data = json.loads(current_content)
+ current_data.update(data)
+ write_json_config(HIERADATA_FILE, current_data)
+
+
+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.
+
+ :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', 'interface')
+ 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 dpk section exists in the config
+ if not re.search(r'^\s*dpdk\s*\{', data, re.MULTILINE):
+ data += "\ndpdk {\n}\n"
+
+ 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:
+ 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'(^\s*dpdk\s*\{)',
+ r'\1\n uio-driver %s'
+ % vpp_interface.uio_driver,
+ data,
+ flags=re.MULTILINE)
+ 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)
+
+ 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)
+ write_hiera(HIERADATA_FILE, {vpp_int.name: vpp_name})
+
+ # 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)