charm-openvswitch-odl/hooks/lib/PCIDev.py

328 lines
12 KiB
Python

#!/usr/bin/python
import re
import os
import glob
import subprocess
from charmhelpers.core.decorators import retry_on_exception
import shlex
from charmhelpers.core.hookenv import(
log,
config,
)
def format_pci_addr(pci_addr):
domain, bus, slot_func = pci_addr.split(':')
slot, func = slot_func.split('.')
return '{}:{}:{}.{}'.format(domain.zfill(4), bus.zfill(2), slot.zfill(2),
func)
class PCINetDevice(object):
def __init__(self, pci_address):
self.pci_address = pci_address
self.update_attributes()
def update_attributes(self):
self.update_loaded_kmod()
self.update_modalias_kmod()
self.update_interface_info()
def update_loaded_kmod(self):
cmd = ['lspci', '-ks', self.pci_address]
lspci_output = subprocess.check_output(cmd)
kdrive = None
for line in lspci_output.split('\n'):
if 'Kernel driver' in line:
kdrive = line.split(':')[1].strip()
log('Loaded kmod for {} is {}'.format(self.pci_address, kdrive))
self.loaded_kmod = kdrive
def update_modalias_kmod(self):
cmd = ['lspci', '-ns', self.pci_address]
lspci_output = subprocess.check_output(cmd).split()
vendor_device = lspci_output[2]
vendor, device = vendor_device.split(':')
pci_string = 'pci:v{}d{}'.format(vendor.zfill(8), device.zfill(8))
kernel_name = self.get_kernel_name()
alias_files = '/lib/modules/{}/modules.alias'.format(kernel_name)
kmod = None
with open(alias_files, 'r') as f:
for line in f.readlines():
if pci_string in line:
kmod = line.split()[-1]
log('module.alias kmod for {} is {}'.format(self.pci_address, kmod))
self.modalias_kmod = kmod
def update_interface_info(self):
if self.loaded_kmod:
if self.loaded_kmod == 'igb_uio':
return self.update_interface_info_vpe()
else:
return self.update_interface_info_eth()
else:
self.interface_name = None
self.mac_address = None
self.state = 'unbound'
def get_kernel_name(self):
return subprocess.check_output(['uname', '-r']).strip()
def pci_rescan(self):
rescan_file = '/sys/bus/pci/rescan'
with open(rescan_file, 'w') as f:
f.write('1')
def bind(self, kmod):
bind_file = '/sys/bus/pci/drivers/{}/bind'.format(kmod)
log('Binding {} to {}'.format(self.pci_address, bind_file))
with open(bind_file, 'w') as f:
f.write(self.pci_address)
self.pci_rescan()
self.update_attributes()
def unbind(self):
if not self.loaded_kmod:
return
unbind_file = '/sys/bus/pci/drivers/{}/unbind'.format(self.loaded_kmod)
log('Unbinding {} from {}'.format(self.pci_address, unbind_file))
with open(unbind_file, 'w') as f:
f.write(self.pci_address)
self.pci_rescan()
self.update_attributes()
def update_interface_info_vpe(self):
vpe_devices = self.get_vpe_interfaces_and_macs()
device_info = {}
for interface in vpe_devices:
if self.pci_address == interface['pci_address']:
device_info['interface'] = interface['interface']
device_info['macAddress'] = interface['macAddress']
if device_info:
self.interface_name = device_info['interface']
self.mac_address = device_info['macAddress']
self.state = 'vpebound'
else:
self.interface_name = None
self.mac_address = None
self.state = None
@retry_on_exception(5, base_delay=10,
exc_type=subprocess.CalledProcessError)
def get_vpe_cli_out(self):
echo_cmd = [
'echo', '-e', 'show interfaces-state interface phys-address\nexit']
cli_cmd = ['/opt/cisco/vpe/bin/confd_cli', '-N', '-C', '-u', 'system']
echo = subprocess.Popen(echo_cmd, stdout=subprocess.PIPE)
cli_output = subprocess.check_output(cli_cmd, stdin=echo.stdout)
echo.wait()
echo.terminate
log('confd_cli: ' + cli_output)
return cli_output
def get_vpe_interfaces_and_macs(self):
cli_output = self.get_vpe_cli_out()
vpe_devs = []
if 'local0' not in cli_output:
log('local0 missing from confd_cli output, assuming things went '
'wrong')
raise subprocess.CalledProcessError
for line in cli_output.split('\n'):
if re.search(r'([0-9A-F]{2}[:-]){5}([0-9A-F]{2})', line, re.I):
interface, mac = line.split()
pci_addr = self.extract_pci_addr_from_vpe_interface(interface)
vpe_devs.append({
'interface': interface,
'macAddress': mac,
'pci_address': pci_addr,
})
return vpe_devs
def extract_pci_addr_from_vpe_interface(self, nic):
''' Convert a str from nic postfix format to padded format
eg 6/1/2 -> 0000:06:01.2'''
log('Extracting pci address from {}'.format(nic))
# addr = re.sub(r'^\D*', '', nic, re.IGNORECASE)
addr = re.sub(r'^.*Ethernet', '', nic, re.IGNORECASE)
bus, slot, func = addr.split('/')
domain = '0000'
pci_addr = format_pci_addr(
'{}:{}:{}.{}'.format(domain, bus, slot, func))
log('pci address for {} is {}'.format(nic, pci_addr))
return pci_addr
def update_interface_info_eth(self):
net_devices = self.get_sysnet_interfaces_and_macs()
for interface in net_devices:
if self.pci_address == interface['pci_address']:
self.interface_name = interface['interface']
self.mac_address = interface['macAddress']
self.state = interface['state']
def get_sysnet_interfaces_and_macs(self):
net_devs = []
for sdir in glob.glob('/sys/class/net/*'):
sym_link = sdir + "/device"
if os.path.islink(sym_link):
fq_path = os.path.realpath(sym_link)
path = fq_path.split('/')
if 'virtio' in path[-1]:
pci_address = path[-2]
else:
pci_address = path[-1]
net_devs.append({
'interface': self.get_sysnet_interface(sdir),
'macAddress': self.get_sysnet_mac(sdir),
'pci_address': pci_address,
'state': self.get_sysnet_device_state(sdir),
})
return net_devs
def get_sysnet_mac(self, sysdir):
mac_addr_file = sysdir + '/address'
with open(mac_addr_file, 'r') as f:
read_data = f.read()
mac = read_data.strip()
log('mac from {} is {}'.format(mac_addr_file, mac))
return mac
def get_sysnet_device_state(self, sysdir):
state_file = sysdir + '/operstate'
with open(state_file, 'r') as f:
read_data = f.read()
state = read_data.strip()
log('state from {} is {}'.format(state_file, state))
return state
def get_sysnet_interface(self, sysdir):
return sysdir.split('/')[-1]
class PCINetDevices(object):
def __init__(self):
pci_addresses = self.get_pci_ethernet_addresses()
self.pci_devices = [PCINetDevice(dev) for dev in pci_addresses]
def get_pci_ethernet_addresses(self):
cmd = ['lspci', '-m', '-D']
lspci_output = subprocess.check_output(cmd)
pci_addresses = []
for line in lspci_output.split('\n'):
columns = shlex.split(line)
if len(columns) > 1 and columns[1] == 'Ethernet controller':
pci_address = columns[0]
pci_addresses.append(format_pci_addr(pci_address))
return pci_addresses
def update_devices(self):
for pcidev in self.pci_devices:
pcidev.update_attributes()
def get_macs(self):
macs = []
for pcidev in self.pci_devices:
if pcidev.mac_address:
macs.append(pcidev.mac_address)
return macs
def get_device_from_mac(self, mac):
for pcidev in self.pci_devices:
if pcidev.mac_address == mac:
return pcidev
def get_device_from_pci_address(self, pci_addr):
for pcidev in self.pci_devices:
if pcidev.pci_address == pci_addr:
return pcidev
def rebind_orphans(self):
self.unbind_orphans()
self.bind_orphans()
def unbind_orphans(self):
for orphan in self.get_orphans():
orphan.unbind()
self.update_devices()
def bind_orphans(self):
for orphan in self.get_orphans():
orphan.bind(orphan.modalias_kmod)
self.update_devices()
def get_orphans(self):
orphans = []
for pcidev in self.pci_devices:
if not pcidev.loaded_kmod or pcidev.loaded_kmod == 'igb_uio':
if not pcidev.interface_name and not pcidev.mac_address:
orphans.append(pcidev)
return orphans
class PCIInfo(dict):
def __init__(self):
''' Generate pci info '''
if not self.is_ready():
log('PCIInfo not ready')
return
self.user_requested_config = self.get_user_requested_config()
net_devices = PCINetDevices()
self['local_macs'] = net_devices.get_macs()
pci_addresses = []
self['local_config'] = {}
for mac in self.user_requested_config.keys():
log('Checking if {} is on this host'.format(mac))
if mac in self['local_macs']:
log('{} is on this host'.format(mac))
device = net_devices.get_device_from_mac(mac)
log('{} is {} and is currently {}'.format(mac,
device.pci_address, device.interface_name))
if device.state == 'up':
log('Refusing to add {} to device list as it is {}'.format(
device.pci_address, device.state))
else:
pci_addresses.append(device.pci_address)
self['local_config'][mac] = []
for conf in self.user_requested_config[mac]:
self['local_config'][mac].append({
'net': conf.get('net'),
'interface': device.interface_name,
})
if pci_addresses:
self['pci_devs'] = 'dev ' + ' dev '.join(pci_addresses)
else:
self['pci_devs'] = 'no-pci'
log('pci_devs {}'.format(self['pci_devs']))
def is_ready(self):
'''Override this for SDN specific integrations'''
return True
def get_user_requested_config(self):
''' Parse the user requested config str
mac=<mac>;net=<net>;vlan=<vlan> and return a dict'''
mac_net_config = {}
mac_map = config('mac-network-map')
if mac_map:
for line in mac_map.split():
entries = line.split(';')
tmp_dict = {}
for entry in entries:
if '=' in entry:
key, value = entry.split('=')
tmp_dict[key] = value
keys = tmp_dict.keys()
if 'mac' in keys:
if tmp_dict['mac'] in mac_net_config:
mac_net_config[tmp_dict['mac']].append({
'net': tmp_dict.get('net'),
})
else:
mac_net_config[tmp_dict['mac']] = [{
'net': tmp_dict.get('net'),
}]
return mac_net_config