a5050c48fa
As part of Debian migration, the sysinv procedure to check DPDK compatibility for each host interface was also updated in order to make it customizable in case one would like to use other virtual switch than the delivered OVS with DPDK support [1]. For other virtual switches, that might or not rely on DPDK, the ELF target that sysinv uses to verify interfaces compatibility must be customizable and the query_pci_id script is already able to use custom values [2]. This change adds the required logic on sysinv ovs puppet module such that it is able to customize the hiera data with OVS-DPDK correct ELF file. This is not strictly necessary, since it is configuring sysinv with query_pci_id default ELF value, but would be an example for anyone that wants to use a different virtual switch in the future and needs to update the sysinv configuration likewise. [1] https://review.opendev.org/c/starlingx/config/+/872979 [2] https://opendev.org/starlingx/config/src/branch/master/sysinv/sysinv/sysinv/scripts/query_pci_id#L34 Test Plan: PASS - Build sysinv packages PASS - Build a custom stx ISO with the new packages PASS - Bootstrap AIO-SX virtual system (vswitch_type=none) and ensure the hiera data was not modified neither sysinv.conf was updated PASS - Bootstrap AIO-SX virtual system (vswitch_type=ovs-dpdk)* and ensure the hiera data was modified correctly and sysinv.conf was updated accordingly * A successful complete installation with ovs-dpdk is still blocked by a bug that will be solved soon: https://bugs.launchpad.net/starlingx/+bug/2008124 Story: 2010317 Task: 46389 Depends-On: https://review.opendev.org/c/starlingx/stx-puppet/+/887102 Signed-off-by: Thales Elero Cervi <thaleselero.cervi@windriver.com> Change-Id: I4e33fb86199b3a0de7015aab44a66ef84138fca3
415 lines
14 KiB
Python
415 lines
14 KiB
Python
#
|
|
# Copyright (c) 2018-2023 Wind River Systems, Inc.
|
|
#
|
|
# SPDX-License-Identifier: Apache-2.0
|
|
#
|
|
|
|
from oslo_log import log
|
|
from sysinv.common import constants
|
|
from sysinv.common import utils
|
|
|
|
from sysinv.puppet import base
|
|
from sysinv.puppet import interface
|
|
|
|
LOG = log.getLogger(__name__)
|
|
|
|
OVS_DPDK_ELF = '/usr/sbin/ovs-vswitchd'
|
|
|
|
|
|
class OVSPuppet(base.BasePuppet):
|
|
"""Class to encapsulate puppet operations for vswitch configuration"""
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
super(OVSPuppet, self).__init__(*args, **kwargs)
|
|
|
|
def get_system_config(self):
|
|
config = {}
|
|
|
|
if self._vswitch_type() == constants.VSWITCH_TYPE_OVS_DPDK:
|
|
config.update({
|
|
'sysinv::agent::dpdk_elf_file': OVS_DPDK_ELF,
|
|
})
|
|
|
|
return config
|
|
|
|
def get_host_config(self, host):
|
|
config = {}
|
|
if constants.WORKER in utils.get_personalities(host):
|
|
config.update(self._get_vswitch_enabled_config(host))
|
|
|
|
if self._vswitch_type() == constants.VSWITCH_TYPE_OVS_DPDK:
|
|
config.update(self._get_cpu_config(host))
|
|
config.update(self._get_memory_config(host))
|
|
config.update(self._get_port_config(host))
|
|
config.update(self._get_virtual_config(host))
|
|
config.update(self._get_lldp_config(host))
|
|
return config
|
|
|
|
def _get_port_config(self, host):
|
|
ovs_devices = {}
|
|
ovs_bridges = {}
|
|
ovs_ports = {}
|
|
ovs_addresses = {}
|
|
ovs_flows = {}
|
|
|
|
index = 0
|
|
for iface in sorted(self.context['interfaces'].values(),
|
|
key=interface.interface_sort_key):
|
|
if interface.is_data_network_type(iface):
|
|
# create a separate bridge for every configured data interface
|
|
brname = 'br-phy%d' % index
|
|
ovs_bridges[brname] = {}
|
|
|
|
# save the associated bridge for provider network mapping
|
|
iface['_ovs_bridge'] = brname
|
|
|
|
if iface['iftype'] == constants.INTERFACE_TYPE_ETHERNET:
|
|
port, devices = self._get_ethernet_port(
|
|
host, iface, brname, index)
|
|
elif iface['iftype'] == constants.INTERFACE_TYPE_AE:
|
|
port, devices = self._get_bond_port(
|
|
host, iface, brname, index)
|
|
elif iface['iftype'] == constants.INTERFACE_TYPE_VLAN:
|
|
port, devices = self._get_vlan_port(
|
|
host, iface, brname, index)
|
|
else:
|
|
raise Exception("unsupported interface type: %s" %
|
|
iface['iftype'])
|
|
|
|
ovs_ports.update({port['name']: port})
|
|
ovs_devices.update({d['pci_addr']: d for d in devices})
|
|
|
|
if iface['iftype'] == constants.INTERFACE_TYPE_ETHERNET:
|
|
ovs_ifname = port['interfaces'][0]['name']
|
|
lldp_port = self._get_lldp_port(
|
|
iface, brname, ovs_ifname=ovs_ifname)
|
|
ovs_ports.update({lldp_port['name']: lldp_port})
|
|
flow = self._get_lldp_flow(
|
|
brname, ovs_ifname, lldp_port['name'])
|
|
ovs_flows.update({port['name']: flow})
|
|
|
|
if iface['iftype'] == constants.INTERFACE_TYPE_AE:
|
|
slaves = interface.get_interface_slaves(
|
|
self.context, iface)
|
|
for member, slave in enumerate(slaves):
|
|
ovs_ifname = port['interfaces'][member]['name']
|
|
|
|
lldp_port = self._get_lldp_port(
|
|
slave, brname, ovs_ifname=ovs_ifname)
|
|
ovs_ports.update({lldp_port['name']: lldp_port})
|
|
flow = self._get_lldp_flow(
|
|
brname, ovs_ifname, lldp_port['name'])
|
|
ovs_flows.update({flow['name']: flow})
|
|
flow = self._get_lldp_flow(
|
|
brname, lldp_port['name'], ovs_ifname)
|
|
ovs_flows.update({flow['name']: flow})
|
|
|
|
index += 1
|
|
|
|
datanets = interface.get_interface_datanets(self.context, iface)
|
|
|
|
# setup tunnel address if assigned provider network is vxlan
|
|
if datanets and self._is_vxlan_datanet(datanets[0]):
|
|
address = interface.get_interface_primary_address(
|
|
self.context, iface)
|
|
if address:
|
|
ovs_addresses[brname] = {
|
|
'ifname': brname,
|
|
'address': address['address'],
|
|
'prefixlen': address['prefix'],
|
|
}
|
|
|
|
ovs_dict = {
|
|
'platform::vswitch::ovs::devices': ovs_devices,
|
|
'platform::vswitch::ovs::bridges': ovs_bridges,
|
|
'platform::vswitch::ovs::ports': ovs_ports,
|
|
'platform::vswitch::ovs::addresses': ovs_addresses,
|
|
'platform::vswitch::ovs::flows': ovs_flows,
|
|
}
|
|
|
|
LOG.debug("_get_port_config=%s" % ovs_dict)
|
|
|
|
return ovs_dict
|
|
|
|
def _get_ethernet_device(self, iface):
|
|
if interface.is_a_mellanox_device(self.context, iface):
|
|
# Mellanox devices are not bound to the DPDK driver
|
|
return None
|
|
|
|
port = interface.get_interface_port(self.context, iface)
|
|
|
|
pci_addr = self.quoted_str(port.pciaddr)
|
|
|
|
return {
|
|
'pci_addr': pci_addr
|
|
}
|
|
|
|
def _get_ethernet_interface(self, host, iface, ifname):
|
|
|
|
port = interface.get_interface_port(self.context, iface)
|
|
|
|
if interface.is_a_mellanox_device(self.context, iface):
|
|
# Mellanox devices use an ibverbs enumerated device name, therefore
|
|
# use the MAC address to identify the device.
|
|
device_name = "class=eth,mac=%s" % iface['imac']
|
|
else:
|
|
device_name = str(port.pciaddr)
|
|
|
|
rxq_count = len(self.context["_ovs_cpus"])
|
|
|
|
attributes = [
|
|
"options:dpdk-devargs=%s" % device_name,
|
|
"options:n_rxq=%d" % rxq_count,
|
|
"mtu_request=%d" % iface['imtu']
|
|
]
|
|
|
|
# TODO(mpeters): set other_config:pmd-rxq-affinity to pin receive
|
|
# queues to specific PMD cores
|
|
|
|
iftype = 'dpdk'
|
|
|
|
return {
|
|
'name': ifname,
|
|
'type': iftype,
|
|
'attributes': attributes,
|
|
}
|
|
|
|
def _get_ethernet_port(self, host, iface, bridge, index):
|
|
devices = []
|
|
interfaces = []
|
|
|
|
ifname = 'eth%d' % index
|
|
|
|
device = self._get_ethernet_device(iface)
|
|
if device:
|
|
devices.append(device)
|
|
interfaces.append(self._get_ethernet_interface(host, iface, ifname))
|
|
|
|
port = {
|
|
'name': ifname,
|
|
'bridge': bridge,
|
|
'interfaces': interfaces,
|
|
}
|
|
|
|
return port, devices
|
|
|
|
def _get_lldp_interface(self, ifname, peer_ifname):
|
|
attributes = []
|
|
|
|
iftype = 'internal'
|
|
|
|
attributes.append("other_config:lldp_phy_peer=%s" % peer_ifname)
|
|
|
|
return {
|
|
'name': ifname,
|
|
'type': iftype,
|
|
'attributes': attributes,
|
|
}
|
|
|
|
def _get_lldp_port(self, iface, lldp_brname, ovs_ifname=None):
|
|
interfaces = []
|
|
|
|
port = interface.get_interface_port(self.context, iface)
|
|
|
|
# Limit port name length to the maximum supported by ovs-ofctl to
|
|
# reference a port with a name rather than ofport number
|
|
# when creating flows.
|
|
|
|
port_name_len = constants.LLDP_OVS_PORT_NAME_LEN
|
|
uuid_len = port_name_len - len(constants.LLDP_OVS_PORT_PREFIX)
|
|
|
|
port_name = '{}{}'.format(constants.LLDP_OVS_PORT_PREFIX,
|
|
port.uuid[:uuid_len])
|
|
|
|
if ovs_ifname:
|
|
interfaces.append(self._get_lldp_interface(port_name, ovs_ifname))
|
|
else:
|
|
interfaces.append(self._get_lldp_interface(port_name, iface['name']))
|
|
|
|
port = {
|
|
'name': port_name,
|
|
'bridge': lldp_brname,
|
|
'interfaces': interfaces,
|
|
}
|
|
|
|
return port
|
|
|
|
def _get_lldp_flow(self, bridge, in_port, out_port):
|
|
actions = []
|
|
|
|
attributes = {
|
|
'idle_timeout': 0,
|
|
'hard_timeout': 0,
|
|
'in_port': in_port,
|
|
'dl_dst': constants.LLDP_MULTICAST_ADDRESS,
|
|
'dl_type': constants.LLDP_ETHER_TYPE
|
|
}
|
|
|
|
action = {
|
|
'type': 'output',
|
|
'value': out_port
|
|
}
|
|
|
|
actions.append(action)
|
|
|
|
flow = {
|
|
'name': '{}-{}-{}'.format(bridge, in_port, out_port),
|
|
'bridge': bridge,
|
|
'attributes': attributes,
|
|
'actions': actions
|
|
}
|
|
|
|
return flow
|
|
|
|
def _get_bond_port(self, host, iface, bridge, index):
|
|
devices = []
|
|
interfaces = []
|
|
attributes = []
|
|
|
|
ifname = 'bond%d' % index
|
|
|
|
# TODO(mpeters): OVS can support balance-tcp if interface txhashpolicy
|
|
# is set to layer3+4 (currently restricted at API for data interfaces)
|
|
ae_mode = iface['aemode']
|
|
if ae_mode in interface.ACTIVE_STANDBY_AE_MODES:
|
|
attributes.append("bond_mode=active-backup")
|
|
if ae_mode in interface.BALANCED_AE_MODES:
|
|
attributes.append("bond_mode=balance-slb")
|
|
elif ae_mode in interface.LACP_AE_MODES:
|
|
attributes.append("lacp=active")
|
|
attributes.append("bond_mode=balance-slb")
|
|
attributes.append("other_config:lacp-time=fast")
|
|
|
|
for member, lower_ifname in enumerate(iface['uses']):
|
|
lower_iface = self.context['interfaces'][lower_ifname]
|
|
member_ifname = '%s.%d' % (ifname, member)
|
|
|
|
device = self._get_ethernet_device(lower_iface)
|
|
if device:
|
|
devices.append(device)
|
|
|
|
interfaces.append(self._get_ethernet_interface(
|
|
host, lower_iface, member_ifname))
|
|
|
|
port = {
|
|
'type': 'bond',
|
|
'name': ifname,
|
|
'bridge': bridge,
|
|
'attributes': attributes,
|
|
'interfaces': interfaces,
|
|
}
|
|
|
|
return port, devices
|
|
|
|
def _get_vlan_port(self, host, iface, bridge, index):
|
|
devices = []
|
|
interfaces = []
|
|
|
|
ifname = 'vlan%d' % iface['vlan_id']
|
|
attributes = [
|
|
"tag=%d" % iface['vlan_id']
|
|
]
|
|
|
|
lower_iface = interface.get_lower_interface(self.context, iface)
|
|
|
|
device = self._get_ethernet_device(lower_iface)
|
|
if device:
|
|
devices.append(device)
|
|
|
|
interfaces.append(self._get_ethernet_interface(
|
|
host, lower_iface, ifname))
|
|
|
|
port = {
|
|
'name': ifname,
|
|
'bridge': bridge,
|
|
'attributes': attributes,
|
|
'interfaces': interfaces,
|
|
}
|
|
|
|
return port, devices
|
|
|
|
def _get_cpu_config(self, host):
|
|
platform_cpus = self._get_platform_cpu_list(host)
|
|
vswitch_cpus = self._get_vswitch_cpu_list(host)
|
|
|
|
host_cpus = platform_cpus[:1] + vswitch_cpus[:]
|
|
|
|
host_core_list = self.quoted_str(
|
|
','.join([str(c.cpu) for c in host_cpus]))
|
|
pmd_core_list = self.quoted_str(
|
|
','.join([str(c.cpu) for c in vswitch_cpus]))
|
|
|
|
# save the assigned CPUs for port assignment
|
|
self.context["_ovs_cpus"] = [c.cpu for c in vswitch_cpus]
|
|
|
|
return {
|
|
'vswitch::dpdk::host_core_list': host_core_list,
|
|
'vswitch::dpdk::pmd_core_list': pmd_core_list,
|
|
}
|
|
|
|
def _get_memory_config(self, host):
|
|
vswitch_memory = []
|
|
config = {}
|
|
vswitch_size = 0
|
|
|
|
host_memory = self.dbapi.imemory_get_by_ihost(host.id)
|
|
for memory in host_memory:
|
|
vswitch_size = memory.vswitch_hugepages_size_mib
|
|
vswitch_pages = memory.vswitch_hugepages_reqd \
|
|
if memory.vswitch_hugepages_reqd is not None \
|
|
else memory.vswitch_hugepages_nr
|
|
|
|
if vswitch_pages == 0:
|
|
vswitch_pages = memory.vswitch_hugepages_nr
|
|
|
|
vswitch_memory.append(str(vswitch_size * vswitch_pages))
|
|
|
|
dpdk_socket_mem = self.quoted_str(','.join(vswitch_memory))
|
|
|
|
config.update({
|
|
'vswitch::dpdk::socket_mem': dpdk_socket_mem
|
|
})
|
|
|
|
if vswitch_size == constants.MIB_2M:
|
|
config.update({
|
|
'platform::vswitch::params::hugepage_dir': '/mnt/huge-2048kB'
|
|
})
|
|
|
|
return config
|
|
|
|
def _get_virtual_config(self, host):
|
|
config = {}
|
|
if utils.is_virtual() or utils.is_virtual_worker(host):
|
|
config.update({
|
|
'platform::vswitch::params::iommu_enabled': False,
|
|
'platform::vswitch::params::hugepage_dir': '/mnt/huge-2048kB',
|
|
|
|
'openstack::neutron::params::tunnel_csum': True,
|
|
})
|
|
return config
|
|
|
|
def _get_vswitch_enabled_config(self, host):
|
|
host_labels = self.dbapi.label_get_by_host(host.id)
|
|
return {
|
|
'platform::vswitch::params::enabled':
|
|
utils.has_vswitch_enabled(host_labels, self.dbapi)
|
|
}
|
|
|
|
def _is_vxlan_datanet(self, datanet):
|
|
return datanet.get('network_type') == constants.DATANETWORK_TYPE_VXLAN
|
|
|
|
def _get_lldp_config(self, host):
|
|
driver_list = self.context['_lldp_drivers']
|
|
driver_list.append('ovs')
|
|
|
|
lldpd_options = []
|
|
|
|
# Disable broadcasting the kernel version
|
|
lldpd_kernel_option = {"option": "-k"}
|
|
lldpd_options.append(lldpd_kernel_option)
|
|
|
|
return {
|
|
'sysinv::agent::lldp_drivers': driver_list,
|
|
'platform::lldp::params::options': lldpd_options
|
|
}
|