tripleo-heat-templates/deployment/neutron/derive_pci_passthrough_whit...

319 lines
11 KiB
Python

#!/usr/bin/env python
#
# Copyright 2019 Red Hat Inc.
#
# 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.
import json
import os
import re
import yaml
from oslo_concurrency import processutils
_PASSTHROUGH_WHITELIST_KEY = 'nova::compute::pci::passthrough'
_PCI_DEVICES_PATH = '/sys/bus/pci/devices'
_SYS_CLASS_NET_PATH = '/sys/class/net'
def get_sriov_configs():
configs = []
try:
with open('/var/lib/os-net-config/sriov_config.yaml') as sriov_config:
configs = yaml.safe_load(sriov_config)
except IOError:
configs = []
return configs
def get_sriov_nic_partition_pfs(configs):
pfs = []
for config in configs:
device_type = config.get('device_type', None)
if device_type and 'vf' in device_type:
device = config.get('device', None)
if device:
dev_name = device.get('name', None)
if dev_name and dev_name not in pfs:
pfs.append(dev_name)
return pfs
def get_sriov_non_nic_partition_pfs(configs):
all_pfs = []
non_nicp_pfs = []
nicp_pfs = get_sriov_nic_partition_pfs(configs)
for config in configs:
device_type = config.get('device_type', None)
if device_type and 'pf' in device_type:
name = config.get('name', None)
if name and name not in all_pfs:
all_pfs.append(name)
non_nicp_pfs = [x for x in all_pfs if x not in nicp_pfs]
return non_nicp_pfs
def get_pci_device_info_by_ifname(pci_dir, sub_dir):
if not os.path.isdir(os.path.join(pci_dir, sub_dir)):
return None
try:
# ids located in files inside PCI devices
# directory are stored in hex format (0x1234 for example)
with open(os.path.join(pci_dir, sub_dir,
'vendor')) as vendor_file:
vendor = vendor_file.read().strip()
with open(os.path.join(pci_dir, sub_dir,
'device')) as product_file:
product = product_file.read().strip()
return (vendor, product)
except IOError:
return None
def get_pci_addresses_by_ifname(pfs, allocated_pci):
pci_addresses = {}
device_info = {}
pci_dir = _PCI_DEVICES_PATH
if os.path.isdir(pci_dir):
for sub_dir in os.listdir(pci_dir):
if sub_dir in allocated_pci:
continue
pci_phyfn_dir = os.path.join(pci_dir, sub_dir, 'physfn/net')
if os.path.isdir(pci_phyfn_dir):
phyfn_dirs = os.listdir(pci_phyfn_dir)
for phyfn in phyfn_dirs:
if phyfn in pfs:
if phyfn not in pci_addresses:
pci_addresses[phyfn] = [sub_dir]
else:
pci_addresses[phyfn].append(sub_dir)
if phyfn not in device_info:
dev_info = get_pci_device_info_by_ifname(pci_dir,
sub_dir)
if dev_info:
device_info[phyfn] = dev_info
return (pci_addresses, device_info)
def get_allocated_pci_addresses(configs):
alloc_pci_info = []
for config in configs:
pci = config.get('pci_address', None)
if pci:
alloc_pci_info.append(pci)
return alloc_pci_info
def get_pci_passthrough_whitelist(user_config, pf, pci_addresses,
device_info):
pci_passthrough_list = []
for pci in pci_addresses:
pci_passthrough = dict(user_config)
address = {}
pci_params = re.split('[:.]+', pci)
address['domain'] = '.*'
address['bus'] = pci_params[1]
address['slot'] = pci_params[2]
address['function'] = pci_params[3]
pci_passthrough['address'] = address
# devname and address fields can't co exist
if 'devname' in pci_passthrough:
del pci_passthrough['devname']
pci_passthrough_list.append(pci_passthrough)
return pci_passthrough_list
def user_passthrough_config():
try:
out, err = processutils.execute(
'hiera', '-f', 'json', '-c', '/etc/puppet/hiera.yaml',
_PASSTHROUGH_WHITELIST_KEY
)
if not err:
return json.loads(out)
except processutils.ProcessExecutionError:
raise
def get_regex_pattern(config_regex, size):
if config_regex == ".*":
regex_pattern = "[0-9a-fA-F]{%d}" % size
else:
regex_pattern = config_regex
return regex_pattern
def get_passthrough_config(user_config, pf, allocated_pci):
sel_addr = []
if 'address' in user_config:
addr_dict = user_config['address']
user_address_pattern = "%s:%s:%s.%s" % (
get_regex_pattern(addr_dict['domain'], 4),
get_regex_pattern(addr_dict['bus'], 2),
get_regex_pattern(addr_dict['slot'], 2),
addr_dict['function'])
else:
user_address_pattern = ("[0-9a-fA-F]{4}:[0-9a-fA-F]{2}:"
"[0-9a-fA-F]{2}.[0-7]")
pci_addresses, dev_info = get_pci_addresses_by_ifname(pf, allocated_pci)
for pci_addr in pci_addresses[pf]:
user_address_regex = re.compile(user_address_pattern)
if user_address_regex.match(pci_addr):
sel_addr.append(pci_addr)
pci_passthrough = get_pci_passthrough_whitelist(
user_config, pf, sel_addr, dev_info)
return pci_passthrough
def get_passthrough_config_by_address(user_config,
system_configs,
allocated_pci):
nic_part_config = []
non_nic_part_config = []
nic_partition_pfs = get_sriov_nic_partition_pfs(system_configs)
non_nic_partition_pfs = get_sriov_non_nic_partition_pfs(system_configs)
for pf in nic_partition_pfs:
passthrough_tmp = get_passthrough_config(
user_config, pf, allocated_pci)
nic_part_config.extend(passthrough_tmp)
if len(nic_part_config) == 0:
return []
for pf in non_nic_partition_pfs:
passthrough_tmp = get_passthrough_config(
user_config, pf, allocated_pci)
non_nic_part_config.extend(passthrough_tmp)
return nic_part_config + non_nic_part_config
def get_passthrough_config_by_product(user_config,
system_configs,
allocated_pci):
nic_part_config = []
non_nic_part_config = []
nic_partition_pfs = get_sriov_nic_partition_pfs(system_configs)
non_nic_partition_pfs = get_sriov_non_nic_partition_pfs(system_configs)
for pf in nic_partition_pfs:
pf_path = _SYS_CLASS_NET_PATH + "/%s/device" % pf
vendor, product = get_pci_device_info_by_ifname(pf_path, 'virtfn0')
if (user_config['product_id'][-4:] == product[-4:] and
user_config['vendor_id'][-4:] == vendor[-4:]):
passthrough_tmp = get_passthrough_config(
user_config, pf, allocated_pci)
nic_part_config.extend(passthrough_tmp)
if len(nic_part_config) == 0:
return []
for pf in non_nic_partition_pfs:
pf_path = _SYS_CLASS_NET_PATH + "/%s/device" % pf
vendor, product = get_pci_device_info_by_ifname(pf_path, 'virtfn0')
if (user_config['product_id'][-4:] == product[-4:] and
user_config['vendor_id'][-4:] == vendor[-4:]):
passthrough_tmp = get_passthrough_config(
user_config, pf, allocated_pci)
non_nic_part_config.extend(passthrough_tmp)
return nic_part_config + non_nic_part_config
def get_pf_name_from_phy_network(physical_network):
try:
out, err = processutils.execute(
'hiera', '-c', '/etc/puppet/hiera.yaml',
'neutron::agents::ml2::sriov::physical_device_mappings')
if not err:
phys_dev_mappings = json.loads(out)
for phy_dev_map in phys_dev_mappings:
net_name, nic_name = phy_dev_map.split(':')
if net_name == physical_network:
return nic_name
return None
except processutils.ProcessExecutionError:
raise
def generate_combined_configuration(user_configs, system_configs):
"""Derived configuration = user_config - system_configs
Identify the user_defined configuration that overlaps with the
NIC Partitioned VFs and remove those VFs from the derived configuration
In case of no overlap, the user defined configuration shall be used
as it is.
:param user_configs: THT param NovaPCIPassthrough
:param system_configs: Derived from sriov-mapping.yaml
"""
non_nic_part_config = []
nic_part_config = []
allocated_pci = get_allocated_pci_addresses(system_configs)
nic_partition_pfs = get_sriov_nic_partition_pfs(system_configs)
for user_config in user_configs:
if ('devname' in user_config and
(user_config['devname'] in nic_partition_pfs)):
passthru_tmp = get_passthrough_config(
user_config, user_config['devname'], allocated_pci)
nic_part_config.extend(passthru_tmp)
elif 'physical_network' in user_config:
pf = get_pf_name_from_phy_network(user_config['physical_network'])
if pf in nic_partition_pfs:
passthru_tmp = get_passthrough_config(
user_config, pf, allocated_pci)
nic_part_config.extend(passthru_tmp)
else:
non_nic_part_config.append(user_config)
elif 'address' in user_config:
passthrough_tmp = get_passthrough_config_by_address(
user_config, system_configs, allocated_pci)
if len(passthrough_tmp) == 0:
non_nic_part_config.append(user_config)
else:
nic_part_config.extend(passthrough_tmp)
elif ('product_id' in user_config and 'vendor_id' in user_config):
passthrough_tmp = get_passthrough_config_by_product(
user_config, system_configs, allocated_pci)
if len(passthrough_tmp) == 0:
non_nic_part_config.append(user_config)
else:
nic_part_config.extend(passthrough_tmp)
else:
non_nic_part_config.append(user_config)
return (non_nic_part_config, nic_part_config)
if __name__ == "__main__":
pci_passthrough = {}
pci_file_path = '/etc/puppet/hieradata/pci_passthrough_whitelist.json'
system_configs = get_sriov_configs()
user_configs = user_passthrough_config()
non_nic_part, nic_part = generate_combined_configuration(
user_configs, system_configs)
if len(nic_part) > 0:
pci_passthrough[_PASSTHROUGH_WHITELIST_KEY] = (non_nic_part +
nic_part)
with open(pci_file_path, 'w') as pci_file:
json.dump(pci_passthrough, pci_file)