6daf22ce9c
This change is to update OvsDpdkSocketMemory computation in DPDK derive parameters for MTU Round off to the bytes and buffer value is 512MB instead of 500MB. Change-Id: Ib8970986b9ea66bb3c4b9b6824fbd733cba9c2bf Closes-Bug: #1737709
458 lines
18 KiB
Python
458 lines
18 KiB
Python
# Copyright 2017 Red Hat, Inc.
|
|
# All Rights Reserved.
|
|
#
|
|
# 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 logging
|
|
import math
|
|
import re
|
|
|
|
from mistral_lib import actions
|
|
|
|
from tripleo_common.actions import base
|
|
from tripleo_common import exception
|
|
|
|
LOG = logging.getLogger(__name__)
|
|
|
|
|
|
class GetDpdkNicsNumaInfoAction(base.TripleOAction):
|
|
"""Gets the DPDK NICs with MTU for NUMA nodes.
|
|
|
|
Find the DPDK interface names from the network config and
|
|
translate it to phsical interface names using the introspection
|
|
data. And then find the NUMA node associated with the DPDK
|
|
interface and the MTU value.
|
|
|
|
:param network_configs: network config list
|
|
:param inspect_data: introspection data
|
|
:param mtu_default: mtu default value for NICs
|
|
|
|
:return: DPDK NICs NUMA nodes info
|
|
"""
|
|
|
|
def __init__(self, network_configs, inspect_data, mtu_default=1500):
|
|
super(GetDpdkNicsNumaInfoAction, self).__init__()
|
|
self.network_configs = network_configs
|
|
self.inspect_data = inspect_data
|
|
self.mtu_default = mtu_default
|
|
|
|
# TODO(jpalanis): Expose this utility from os-net-config to sort
|
|
# active nics
|
|
def _natural_sort_key(self, s):
|
|
nsre = re.compile('([0-9]+)')
|
|
return [int(text) if text.isdigit() else text
|
|
for text in re.split(nsre, s)]
|
|
|
|
# TODO(jpalanis): Expose this utility from os-net-config to sort
|
|
# active nics
|
|
def _is_embedded_nic(self, nic):
|
|
if (nic.startswith('em') or nic.startswith('eth') or
|
|
nic.startswith('eno')):
|
|
return True
|
|
return False
|
|
|
|
# TODO(jpalanis): Expose this utility from os-net-config to sort
|
|
# active nics
|
|
def _ordered_nics(self, interfaces):
|
|
embedded_nics = []
|
|
nics = []
|
|
for iface in interfaces:
|
|
nic = iface.get('name', '')
|
|
if self._is_embedded_nic(nic):
|
|
embedded_nics.append(nic)
|
|
else:
|
|
nics.append(nic)
|
|
active_nics = (sorted(
|
|
embedded_nics, key=self._natural_sort_key) +
|
|
sorted(nics, key=self._natural_sort_key))
|
|
return active_nics
|
|
|
|
# Gets numa node id for physical NIC name
|
|
def find_numa_node_id(self, numa_nics, nic_name):
|
|
for nic_info in numa_nics:
|
|
if nic_info.get('name', '') == nic_name:
|
|
return nic_info.get('numa_node', None)
|
|
return None
|
|
|
|
# Get physical interface name for NIC name
|
|
def get_physical_iface_name(self, ordered_nics, nic_name):
|
|
if nic_name.startswith('nic'):
|
|
# Nic numbering, find the actual interface name
|
|
nic_number = int(nic_name.replace('nic', ''))
|
|
if nic_number > 0:
|
|
iface_name = ordered_nics[nic_number - 1]
|
|
return iface_name
|
|
return nic_name
|
|
|
|
# Gets dpdk interfaces and mtu info for dpdk config
|
|
# Default mtu(recommended 1500) is used if no MTU is set for DPDK NIC
|
|
def get_dpdk_interfaces(self, dpdk_objs):
|
|
mtu = self.mtu_default
|
|
dpdk_ifaces = []
|
|
for dpdk_obj in dpdk_objs:
|
|
obj_type = dpdk_obj.get('type')
|
|
mtu = dpdk_obj.get('mtu', self.mtu_default)
|
|
if obj_type == 'ovs_dpdk_port':
|
|
# Member interfaces of ovs_dpdk_port
|
|
dpdk_ifaces.extend(dpdk_obj.get('members', []))
|
|
elif obj_type == 'ovs_dpdk_bond':
|
|
# ovs_dpdk_bond will have multiple ovs_dpdk_ports
|
|
for bond_member in dpdk_obj.get('members', []):
|
|
if bond_member.get('type') == 'ovs_dpdk_port':
|
|
dpdk_ifaces.extend(bond_member.get('members', []))
|
|
return (dpdk_ifaces, mtu)
|
|
|
|
def run(self, context):
|
|
interfaces = self.inspect_data.get('inventory',
|
|
{}).get('interfaces', [])
|
|
# Checks whether inventory interfaces information is not available
|
|
# in introspection data.
|
|
if not interfaces:
|
|
msg = 'Introspection data does not have inventory.interfaces'
|
|
return actions.Result(error=msg)
|
|
|
|
numa_nics = self.inspect_data.get('numa_topology',
|
|
{}).get('nics', [])
|
|
# Checks whether numa topology nics information is not available
|
|
# in introspection data.
|
|
if not numa_nics:
|
|
msg = 'Introspection data does not have numa_topology.nics'
|
|
return actions.Result(error=msg)
|
|
|
|
active_interfaces = [iface for iface in interfaces
|
|
if iface.get('has_carrier', False)]
|
|
# Checks whether active interfaces are not available
|
|
if not active_interfaces:
|
|
msg = 'Unable to determine active interfaces (has_carrier)'
|
|
return actions.Result(error=msg)
|
|
|
|
dpdk_nics_numa_info = []
|
|
ordered_nics = self._ordered_nics(active_interfaces)
|
|
# Gets DPDK network config and parses to get DPDK NICs
|
|
# with mtu and numa node id
|
|
for config in self.network_configs:
|
|
if config.get('type', '') == 'ovs_user_bridge':
|
|
members = config.get('members', [])
|
|
dpdk_ifaces, mtu = self.get_dpdk_interfaces(members)
|
|
for dpdk_iface in dpdk_ifaces:
|
|
name = dpdk_iface.get('name', '')
|
|
phy_name = self.get_physical_iface_name(
|
|
ordered_nics, name)
|
|
node = self.find_numa_node_id(numa_nics, phy_name)
|
|
if node is None:
|
|
msg = ('Unable to determine NUMA node for '
|
|
'DPDK NIC: %s' % phy_name)
|
|
return actions.Result(error=msg)
|
|
|
|
dpdk_nic_info = {'name': phy_name,
|
|
'numa_node': node,
|
|
'mtu': mtu}
|
|
dpdk_nics_numa_info.append(dpdk_nic_info)
|
|
return dpdk_nics_numa_info
|
|
|
|
|
|
class GetDpdkCoreListAction(base.TripleOAction):
|
|
"""Gets the DPDK PMD Core List.
|
|
|
|
With input as the number of physical cores for each NUMA node,
|
|
find the right logical CPUs to be allocated along with its
|
|
siblings for the PMD core list.
|
|
|
|
:param inspect_data: introspection data
|
|
:param numa_nodes_cores_count: physical cores count for each NUMA
|
|
|
|
:return: DPDK Core List
|
|
"""
|
|
|
|
def __init__(self, inspect_data, numa_nodes_cores_count):
|
|
super(GetDpdkCoreListAction, self).__init__()
|
|
self.inspect_data = inspect_data
|
|
self.numa_nodes_cores_count = numa_nodes_cores_count
|
|
|
|
def run(self, context):
|
|
dpdk_core_list = []
|
|
numa_cpus_info = self.inspect_data.get('numa_topology',
|
|
{}).get('cpus', [])
|
|
|
|
# Checks whether numa topology cpus information is not available
|
|
# in introspection data.
|
|
if not numa_cpus_info:
|
|
msg = 'Introspection data does not have numa_topology.cpus'
|
|
return actions.Result(error=msg)
|
|
|
|
# Checks whether CPU physical cores count for each NUMA nodes is
|
|
# not available
|
|
if not self.numa_nodes_cores_count:
|
|
msg = ('CPU physical cores count for each NUMA nodes '
|
|
'is not available')
|
|
return actions.Result(error=msg)
|
|
|
|
numa_nodes_threads = {}
|
|
# Creates list for all available threads in each NUMA node
|
|
for cpu in numa_cpus_info:
|
|
if not cpu['numa_node'] in numa_nodes_threads:
|
|
numa_nodes_threads[cpu['numa_node']] = []
|
|
numa_nodes_threads[cpu['numa_node']].extend(cpu['thread_siblings'])
|
|
|
|
for node, node_cores_count in enumerate(self.numa_nodes_cores_count):
|
|
# Gets least thread in NUMA node
|
|
numa_node_min = min(numa_nodes_threads[node])
|
|
cores_count = node_cores_count
|
|
for cpu in numa_cpus_info:
|
|
if cpu['numa_node'] == node:
|
|
# Adds threads from core which is not having least thread
|
|
if numa_node_min not in cpu['thread_siblings']:
|
|
dpdk_core_list.extend(cpu['thread_siblings'])
|
|
cores_count -= 1
|
|
if cores_count == 0:
|
|
break
|
|
return ','.join([str(thread) for thread in dpdk_core_list])
|
|
|
|
|
|
class GetHostCpusListAction(base.TripleOAction):
|
|
"""Gets the Host CPUs List.
|
|
|
|
CPU threads from first physical core is allocated for host processes
|
|
on each NUMA nodes.
|
|
|
|
:param inspect_data: introspection data
|
|
|
|
:return: Host CPUs List
|
|
"""
|
|
|
|
def __init__(self, inspect_data):
|
|
super(GetHostCpusListAction, self).__init__()
|
|
self.inspect_data = inspect_data
|
|
|
|
def run(self, context):
|
|
host_cpus_list = []
|
|
numa_cpus_info = self.inspect_data.get('numa_topology',
|
|
{}).get('cpus', [])
|
|
|
|
# Checks whether numa topology cpus information is not available
|
|
# in introspection data.
|
|
if not numa_cpus_info:
|
|
msg = 'Introspection data does not have numa_topology.cpus'
|
|
return actions.Result(error=msg)
|
|
|
|
numa_nodes_threads = {}
|
|
# Creates a list for all available threads in each NUMA nodes
|
|
for cpu in numa_cpus_info:
|
|
if not cpu['numa_node'] in numa_nodes_threads:
|
|
numa_nodes_threads[cpu['numa_node']] = []
|
|
numa_nodes_threads[cpu['numa_node']].extend(
|
|
cpu['thread_siblings'])
|
|
|
|
for numa_node in sorted(numa_nodes_threads.keys()):
|
|
node = int(numa_node)
|
|
# Gets least thread in NUMA node
|
|
numa_node_min = min(numa_nodes_threads[numa_node])
|
|
for cpu in numa_cpus_info:
|
|
if cpu['numa_node'] == node:
|
|
# Adds threads from core which is having least thread
|
|
if numa_node_min in cpu['thread_siblings']:
|
|
host_cpus_list.extend(cpu['thread_siblings'])
|
|
break
|
|
|
|
return ','.join([str(thread) for thread in host_cpus_list])
|
|
|
|
|
|
class GetDpdkSocketMemoryAction(base.TripleOAction):
|
|
"""Gets the DPDK Socket Memory List.
|
|
|
|
For NUMA node with DPDK nic, socket memory is calculated
|
|
based on MTU, Overhead and Packet size in buffer.
|
|
|
|
For NUMA node without DPDK nic, minimum socket memory is
|
|
assigned (recommended 1GB)
|
|
|
|
:param dpdk_nics_numa_info: DPDK nics numa info
|
|
:param numa_nodes: list of numa nodes
|
|
:param overhead: overhead value
|
|
:param packet_size_in_buffer: packet size in buffer
|
|
:param minimum_socket_memory: minimum socket memory
|
|
|
|
:return: DPDK Socket Memory List
|
|
"""
|
|
def __init__(self, dpdk_nics_numa_info, numa_nodes,
|
|
overhead, packet_size_in_buffer,
|
|
minimum_socket_memory=1024):
|
|
super(GetDpdkSocketMemoryAction, self).__init__()
|
|
self.dpdk_nics_numa_info = dpdk_nics_numa_info
|
|
self.numa_nodes = numa_nodes
|
|
self.overhead = overhead
|
|
self.packet_size_in_buffer = packet_size_in_buffer
|
|
self.minimum_socket_memory = minimum_socket_memory
|
|
|
|
# Computes round off MTU value in bytes
|
|
# example: MTU value 9000 into 9216 bytes
|
|
def roundup_mtu_bytes(self, mtu):
|
|
max_div_val = int(math.ceil(float(mtu) / float(1024)))
|
|
return (max_div_val * 1024)
|
|
|
|
# Calculates socket memory for a NUMA node
|
|
def calculate_node_socket_memory(
|
|
self, numa_node, dpdk_nics_numa_info, overhead,
|
|
packet_size_in_buffer, minimum_socket_memory):
|
|
distinct_mtu_per_node = []
|
|
socket_memory = 0
|
|
|
|
# For DPDK numa node
|
|
for nics_info in dpdk_nics_numa_info:
|
|
if (numa_node == nics_info['numa_node'] and
|
|
not nics_info['mtu'] in distinct_mtu_per_node):
|
|
distinct_mtu_per_node.append(nics_info['mtu'])
|
|
roundup_mtu = self.roundup_mtu_bytes(nics_info['mtu'])
|
|
socket_memory += (((roundup_mtu + overhead)
|
|
* packet_size_in_buffer) /
|
|
(1024 * 1024))
|
|
|
|
# For Non DPDK numa node
|
|
if socket_memory == 0:
|
|
socket_memory = minimum_socket_memory
|
|
# For DPDK numa node
|
|
else:
|
|
socket_memory += 512
|
|
|
|
socket_memory_in_gb = int(socket_memory / 1024)
|
|
if socket_memory % 1024 > 0:
|
|
socket_memory_in_gb += 1
|
|
return (socket_memory_in_gb * 1024)
|
|
|
|
def run(self, context):
|
|
dpdk_socket_memory_list = []
|
|
for node in self.numa_nodes:
|
|
socket_mem = self.calculate_node_socket_memory(
|
|
node, self.dpdk_nics_numa_info, self.overhead,
|
|
self.packet_size_in_buffer,
|
|
self.minimum_socket_memory)
|
|
dpdk_socket_memory_list.append(socket_mem)
|
|
|
|
return ','.join([str(sm) for sm in dpdk_socket_memory_list])
|
|
|
|
|
|
class ConvertNumberToRangeListAction(base.TripleOAction):
|
|
"""Converts number list into range list
|
|
|
|
:param num_list: comma delimited number list as string
|
|
|
|
:return: comma delimited range list as string
|
|
"""
|
|
|
|
def __init__(self, num_list):
|
|
super(ConvertNumberToRangeListAction, self).__init__()
|
|
self.num_list = num_list
|
|
|
|
# converts number list into range list.
|
|
# here input parameter and return value as list
|
|
# example: [12, 13, 14, 17] into ["12-14", "17"]
|
|
def convert_number_to_range_list(self, num_list):
|
|
num_list.sort()
|
|
range_list = []
|
|
range_min = num_list[0]
|
|
for num in num_list:
|
|
next_val = num + 1
|
|
if next_val not in num_list:
|
|
if range_min != num:
|
|
range_list.append(str(range_min) + '-' + str(num))
|
|
else:
|
|
range_list.append(str(range_min))
|
|
next_index = num_list.index(num) + 1
|
|
if next_index < len(num_list):
|
|
range_min = num_list[next_index]
|
|
|
|
# here, range_list is a list of strings
|
|
return range_list
|
|
|
|
def run(self, context):
|
|
try:
|
|
if not self.num_list:
|
|
err_msg = ("Input param 'num_list' is blank.")
|
|
raise exception.DeriveParamsError(err_msg)
|
|
|
|
try:
|
|
# splitting a string (comma delimited list) into
|
|
# list of numbers
|
|
# example: "12,13,14,17" string into [12,13,14,17]
|
|
num_list = [int(num.strip(' '))
|
|
for num in self.num_list.split(",")]
|
|
except ValueError as exc:
|
|
err_msg = ("Invalid number in input param "
|
|
"'num_list': %s" % exc)
|
|
raise exception.DeriveParamsError(err_msg)
|
|
|
|
range_list = self.convert_number_to_range_list(num_list)
|
|
except exception.DeriveParamsError as err:
|
|
LOG.error('Derive Params Error: %s', err)
|
|
return actions.Result(error=str(err))
|
|
|
|
# converts into comma delimited range list as string
|
|
return ','.join(range_list)
|
|
|
|
|
|
class ConvertRangeToNumberListAction(base.TripleOAction):
|
|
"""Converts range list to integer list
|
|
|
|
:param range_list: comma delimited range list as string / list
|
|
|
|
:return: comma delimited number list as string
|
|
"""
|
|
|
|
def __init__(self, range_list):
|
|
super(ConvertRangeToNumberListAction, self).__init__()
|
|
self.range_list = range_list
|
|
|
|
# converts range list into number list
|
|
# here input parameter and return value as list
|
|
# example: ["12-14", "^13", "17"] into [12, 14, 17]
|
|
def convert_range_to_number_list(self, range_list):
|
|
num_list = []
|
|
exclude_num_list = []
|
|
try:
|
|
for val in range_list:
|
|
val = val.strip(' ')
|
|
if '^' in val:
|
|
exclude_num_list.append(int(val[1:]))
|
|
elif '-' in val:
|
|
split_list = val.split("-")
|
|
range_min = int(split_list[0])
|
|
range_max = int(split_list[1])
|
|
num_list.extend(range(range_min, (range_max + 1)))
|
|
else:
|
|
num_list.append(int(val))
|
|
except ValueError as exc:
|
|
err_msg = ("Invalid number in input param "
|
|
"'range_list': %s" % exc)
|
|
raise exception.DeriveParamsError(err_msg)
|
|
|
|
# here, num_list is a list of integers
|
|
return [num for num in num_list if num not in exclude_num_list]
|
|
|
|
def run(self, context):
|
|
try:
|
|
if not self.range_list:
|
|
err_msg = ("Input param 'range_list' is blank.")
|
|
raise exception.DeriveParamsError(err_msg)
|
|
range_list = self.range_list
|
|
# converts into python list if range_list is not list type
|
|
if not isinstance(range_list, list):
|
|
range_list = self.range_list.split(",")
|
|
|
|
num_list = self.convert_range_to_number_list(range_list)
|
|
except exception.DeriveParamsError as err:
|
|
LOG.error('Derive Params Error: %s', err)
|
|
return actions.Result(error=str(err))
|
|
|
|
# converts into comma delimited number list as string
|
|
return ','.join([str(num) for num in num_list])
|