# Copyright 2016-2017 Intel Corporation. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. """Tools for network card manipulation """ import os import subprocess import logging import glob from conf import settings _LOGGER = logging.getLogger('tools.networkcard') _PCI_DIR = '/sys/bus/pci/devices/{}/' _SRIOV_NUMVFS = os.path.join(_PCI_DIR, 'sriov_numvfs') _SRIOV_TOTALVFS = os.path.join(_PCI_DIR, 'sriov_totalvfs') _SRIOV_VF_PREFIX = 'virtfn' _SRIOV_PF = 'physfn' _PCI_NET = 'net' _PCI_DRIVER = 'driver' def check_pci(pci_handle): """ Checks if given extended PCI handle has correct length and fixes it if possible. :param pci_handle: PCI slot identifier. It can contain vsperf specific suffix after '|' with VF indication. e.g. '0000:05:00.0|vf1' :returns: PCI handle """ pci = pci_handle.split('|') pci_len = len(pci[0]) if pci_len == 12: return pci_handle elif pci_len == 7: pci[0] = '0000:' + pci[0][-7:] _LOGGER.debug('Adding domain part to PCI slot %s', pci[0]) return '|'.join(pci) elif pci_len > 12: pci[0] = pci[0][-12:] _LOGGER.warning('PCI slot is too long, it will be shortened to %s', pci[0]) return '|'.join(pci) else: # pci_handle has a strange length, but let us try to use it _LOGGER.error('Unknown format of PCI slot %s', pci_handle) return pci_handle def is_sriov_supported(pci_handle): """ Checks if sriov is supported by given NIC :param pci_handle: PCI slot identifier with domain part. :returns: True on success, False otherwise """ return os.path.isfile(_SRIOV_TOTALVFS.format(pci_handle)) def is_sriov_nic(pci_handle): """ Checks if given extended PCI ID refers to the VF :param pci_handle: PCI slot identifier with domain part. It can contain vsperf specific suffix after '|' with VF indication. e.g. '0000:05:00.0|vf1' :returns: True on success, False otherwise """ for item in pci_handle.split('|'): if item.lower().startswith('vf'): return True return False def set_sriov_numvfs(pci_handle, numvfs): """ Checks if sriov is supported and configures given number of VFs :param pci_handle: PCI slot identifier with domain part. :param numvfs: Number of VFs to be configured at given NIC. :returns: True on success, False otherwise """ if not is_sriov_supported(pci_handle): return False if get_sriov_numvfs(pci_handle) == numvfs: return True if numvfs and get_sriov_numvfs(pci_handle) != 0: if not set_sriov_numvfs(pci_handle, 0): return False try: subprocess.call('sudo bash -c "echo {} > {}"'.format(numvfs, _SRIOV_NUMVFS.format(pci_handle)), shell=True) return get_sriov_numvfs(pci_handle) == numvfs except OSError: _LOGGER.debug('Number of VFs cant be changed to %s for PF %s', numvfs, pci_handle) return False def get_sriov_numvfs(pci_handle): """ Returns the number of configured VFs :param pci_handle: PCI slot identifier with domain part :returns: the number of configured VFs """ if is_sriov_supported(pci_handle): with open(_SRIOV_NUMVFS.format(pci_handle), 'r') as numvfs: return int(numvfs.readline().rstrip('\n')) return None def get_sriov_totalvfs(pci_handle): """ Checks if sriov is supported and returns max number of supported VFs :param pci_handle: PCI slot identifier with domain part :returns: the max number of supported VFs by given NIC """ if is_sriov_supported(pci_handle): with open(_SRIOV_TOTALVFS.format(pci_handle), 'r') as total: return int(total.readline().rstrip('\n')) return None def get_sriov_vfs_list(pf_pci_handle): """ Returns list of PCI handles of VFs configured at given NIC/PF :param pf_pci_handle: PCI slot identifier of PF with domain part. :returns: list """ vfs = [] if is_sriov_supported(pf_pci_handle): for vf_name in glob.glob(os.path.join(_PCI_DIR, _SRIOV_VF_PREFIX + '*').format(pf_pci_handle)): vfs.append(os.path.basename(os.path.realpath(vf_name))) return vfs def get_sriov_pf(vf_pci_handle): """ Get PCI handle of PF which belongs to given VF :param vf_pci_handle: PCI slot identifier of VF with domain part. :returns: PCI handle of parent PF """ pf_path = os.path.join(_PCI_DIR, _SRIOV_PF).format(vf_pci_handle) if os.path.isdir(pf_path): return os.path.basename(os.path.realpath(pf_path)) return None def get_driver(pci_handle): """ Returns name of kernel driver assigned to given NIC :param pci_handle: PCI slot identifier with domain part. :returns: string with assigned kernel driver, None otherwise """ driver_path = os.path.join(_PCI_DIR, _PCI_DRIVER).format(pci_handle) if os.path.isdir(driver_path): return os.path.basename(os.path.realpath(driver_path)) return None def get_device_name(pci_handle): """ Returns name of network card device name :param pci_handle: PCI slot identifier with domain part. :returns: string with assigned NIC device name, None otherwise """ net_path = os.path.join(_PCI_DIR, _PCI_NET).format(pci_handle) try: return os.listdir(net_path)[0] except FileNotFoundError: return None except IndexError: return None return None def get_mac(pci_handle): """ Returns MAC address of given NIC :param pci_handle: PCI slot identifier with domain part. :returns: string with assigned MAC address, None otherwise """ mac_path = glob.glob(os.path.join(_PCI_DIR, _PCI_NET, '*', 'address').format(pci_handle)) # kernel driver is loaded and MAC can be read if len(mac_path) and os.path.isfile(mac_path[0]): with open(mac_path[0], 'r') as _file: return _file.readline().rstrip('\n') # MAC address is unknown, e.g. NIC is assigned to DPDK return None def get_nic_info(full_pci_handle): """ Parse gi