394 lines
13 KiB
Python
394 lines
13 KiB
Python
#
|
|
# Copyright (c) 2018 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__)
|
|
|
|
|
|
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_host_config(self, host):
|
|
config = {}
|
|
if (constants.WORKER in utils.get_personalities(host) and
|
|
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 _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
|
|
}
|