config/sysinv/sysinv/sysinv/sysinv/puppet/interface.py

1188 lines
42 KiB
Python

#
# Copyright (c) 2017-2019 Wind River Systems, Inc.
#
# SPDX-License-Identifier: Apache-2.0
#
import collections
import copy
import six
from netaddr import IPAddress
from netaddr import IPNetwork
from sysinv.common import constants
from sysinv.common import exception
from sysinv.common import interface
from sysinv.common import utils
from sysinv.conductor import openstack
from sysinv.openstack.common import log
from sysinv.puppet import base
LOG = log.getLogger(__name__)
PLATFORM_NETWORK_TYPES = [constants.NETWORK_TYPE_PXEBOOT,
constants.NETWORK_TYPE_MGMT,
constants.NETWORK_TYPE_INFRA,
constants.NETWORK_TYPE_CLUSTER_HOST,
constants.NETWORK_TYPE_OAM]
DATA_NETWORK_TYPES = [constants.NETWORK_TYPE_DATA]
DATA_INTERFACE_CLASSES = [constants.INTERFACE_CLASS_DATA]
PCI_NETWORK_TYPES = [constants.NETWORK_TYPE_PCI_SRIOV,
constants.NETWORK_TYPE_PCI_PASSTHROUGH]
PCI_INTERFACE_CLASSES = [constants.INTERFACE_CLASS_PCI_PASSTHROUGH,
constants.INTERFACE_CLASS_PCI_SRIOV]
ACTIVE_STANDBY_AE_MODES = ['active_backup', 'active-backup', 'active_standby']
BALANCED_AE_MODES = ['balanced', 'balanced-xor']
LACP_AE_MODES = ['802.3ad']
DRIVER_MLX_CX3 = 'mlx4_core'
DRIVER_MLX_CX4 = 'mlx5_core'
MELLANOX_DRIVERS = [DRIVER_MLX_CX3,
DRIVER_MLX_CX4]
LOOPBACK_IFNAME = 'lo'
LOOPBACK_METHOD = 'loopback'
STATIC_METHOD = 'static'
MANUAL_METHOD = 'manual'
DHCP_METHOD = 'dhcp'
NETWORK_CONFIG_RESOURCE = 'platform::interfaces::network_config'
ROUTE_CONFIG_RESOURCE = 'platform::interfaces::route_config'
ADDRESS_CONFIG_RESOURCE = 'platform::addresses::address_config'
class InterfacePuppet(base.BasePuppet):
"""Class to encapsulate puppet operations for interface configuration"""
def __init__(self, *args, **kwargs):
super(InterfacePuppet, self).__init__(*args, **kwargs)
self._openstack = None
@property
def openstack(self):
if not self._openstack:
self._openstack = openstack.OpenStackOperator(self.dbapi)
return self._openstack
def get_host_config(self, host):
"""
Generate the hiera data for the puppet network config and route config
resources for the host.
"""
# Normalize some of the host info into formats that are easier to
# use when parsing the interface list.
context = self._create_interface_context(host)
# interface configuration is organized into sets of network_config,
# route_config and address_config resource hashes (dict)
config = {
NETWORK_CONFIG_RESOURCE: {},
ROUTE_CONFIG_RESOURCE: {},
ADDRESS_CONFIG_RESOURCE: {},
}
system = self._get_system()
# For AIO-SX subcloud, mgmt n/w will be on a separate
# physical interface instead of the loopback interface.
if system.system_mode != constants.SYSTEM_MODE_SIMPLEX or \
self._distributed_cloud_role() == \
constants.DISTRIBUTED_CLOUD_ROLE_SUBCLOUD:
# Setup the loopback interface first
generate_loopback_config(config)
# Generate the actual interface config resources
generate_interface_configs(context, config)
# Generate the actual interface config resources
generate_address_configs(context, config)
# Generate driver specific configuration
generate_driver_config(context, config)
# Generate the dhcp client configuration
generate_dhcp_config(context, config)
# Update the global context with generated interface context
self.context.update(context)
return config
def _create_interface_context(self, host):
context = {
'hostname': host.hostname,
'personality': host.personality,
'subfunctions': host.subfunctions,
'system_uuid': host.isystem_uuid,
'system_mode': self._get_system().system_mode,
'ports': self._get_port_interface_id_index(host),
'interfaces': self._get_interface_name_index(host),
'interfaces_datanets': self._get_interface_name_datanets(host),
'devices': self._get_port_pciaddr_index(host),
'addresses': self._get_address_interface_name_index(host),
'routes': self._get_routes_interface_name_index(host),
'networks': self._get_network_type_index(),
'gateways': self._get_gateway_index(),
'floatingips': self._get_floating_ip_index(),
'datanets': self._get_datanetworks(host),
}
return context
def _find_host_interface(self, host, networktype):
"""
Search the host interface list looking for an interface with a given
primary network type.
"""
for iface in self.dbapi.iinterface_get_by_ihost(host.id):
for ni in self.dbapi.interface_network_get_by_interface(
iface['id']):
network = self.dbapi.network_get(ni.id)
if network.type == networktype:
return iface
def _get_port_interface_id_index(self, host):
"""
Builds a dictionary of ports indexed by interface id.
"""
return interface._get_port_interface_id_index(self.dbapi, host)
def _get_interface_name_index(self, host):
"""
Builds a dictionary of interfaces indexed by interface name.
"""
return interface._get_interface_name_index(self.dbapi, host)
def _get_interface_name_datanets(self, host):
"""
Builds a dictionary of datanets indexed by interface name.
"""
return interface._get_interface_name_datanets(self.dbapi, host)
def _get_port_pciaddr_index(self, host):
"""
Builds a dictionary of port lists indexed by PCI address.
"""
devices = collections.defaultdict(list)
for port in self.dbapi.ethernet_port_get_by_host(host.id):
devices[port.pciaddr].append(port)
return devices
def _get_address_interface_name_index(self, host):
"""
Builds a dictionary of address lists indexed by interface name.
"""
return interface._get_address_interface_name_index(self.dbapi, host)
def _get_routes_interface_name_index(self, host):
"""
Builds a dictionary of route lists indexed by interface name.
"""
routes = collections.defaultdict(list)
for route in self.dbapi.routes_get_by_host(host.id):
routes[route.ifname].append(route)
results = collections.defaultdict(list)
for ifname, entries in six.iteritems(routes):
entries = sorted(entries, key=lambda r: r['prefix'], reverse=True)
results[ifname] = entries
return results
def _get_network_type_index(self):
networks = {}
for network in self.dbapi.networks_get_all():
networks[network['type']] = network
return networks
def _get_gateway_index(self):
"""
Builds a dictionary of gateway IP addresses indexed by network type.
"""
gateways = {}
try:
mgmt_address = self._get_address_by_name(
constants.CONTROLLER_GATEWAY, constants.NETWORK_TYPE_MGMT)
gateways.update({
constants.NETWORK_TYPE_MGMT: mgmt_address.address})
except exception.AddressNotFoundByName:
pass
try:
oam_address = self._get_address_by_name(
constants.CONTROLLER_GATEWAY, constants.NETWORK_TYPE_OAM)
gateways.update({
constants.NETWORK_TYPE_OAM: oam_address.address})
except exception.AddressNotFoundByName:
pass
return gateways
def _get_floating_ip_index(self):
"""
Builds a dictionary of floating ip addresses indexed by network type.
"""
mgmt_address = self._get_address_by_name(
constants.CONTROLLER_HOSTNAME, constants.NETWORK_TYPE_MGMT)
mgmt_floating_ip = (str(mgmt_address.address) + '/' +
str(mgmt_address.prefix))
floating_ips = {
constants.NETWORK_TYPE_MGMT: mgmt_floating_ip
}
try:
pxeboot_address = self._get_address_by_name(
constants.CONTROLLER_HOSTNAME, constants.NETWORK_TYPE_PXEBOOT)
pxeboot_floating_ip = (str(pxeboot_address.address) + '/' +
str(pxeboot_address.prefix))
floating_ips.update({
constants.NETWORK_TYPE_PXEBOOT: pxeboot_floating_ip,
})
except exception.AddressNotFoundByName:
pass
system = self._get_system()
if system.system_mode != constants.SYSTEM_MODE_SIMPLEX:
oam_address = self._get_address_by_name(
constants.CONTROLLER_HOSTNAME, constants.NETWORK_TYPE_OAM)
oam_floating_ip = (str(oam_address.address) + '/' +
str(oam_address.prefix))
floating_ips.update({
constants.NETWORK_TYPE_OAM: oam_floating_ip
})
try:
cluster_address = self._get_address_by_name(
constants.CONTROLLER_HOSTNAME,
constants.NETWORK_TYPE_CLUSTER_HOST)
if cluster_address:
cluster_floating_ip = (str(cluster_address.address) + '/' +
str(cluster_address.prefix))
floating_ips.update({
constants.NETWORK_TYPE_CLUSTER_HOST: cluster_floating_ip
})
except exception.AddressNotFoundByName:
pass
return floating_ips
def _get_datanetworks(self, host):
dnets = {}
if constants.WORKER in utils.get_personalities(host):
dnets = self.dbapi.datanetworks_get_all()
return dnets
def is_platform_network_type(iface):
return bool(iface['ifclass'] == constants.INTERFACE_CLASS_PLATFORM)
def is_data_network_type(iface):
return bool(iface['ifclass'] == constants.INTERFACE_CLASS_DATA)
def is_controller(context):
"""
Determine we are creating a manifest for a controller node; regardless of
whether it has a worker subfunction or not.
"""
return bool(context['personality'] == constants.CONTROLLER)
def is_worker_subfunction(context):
"""
Determine if we are creating a manifest for a worker node or a worker
subfunction.
"""
if context['personality'] == constants.WORKER:
return True
if constants.WORKER in context['subfunctions']:
return True
return False
def is_pci_interface(iface):
"""
Determine if the interface is one of the PCI device types.
"""
return bool(iface['ifclass'] in PCI_INTERFACE_CLASSES)
def is_platform_interface(context, iface):
"""
Determine whether the interface needs to be configured in the linux kernel
as opposed to interfaces that exist purely in the vswitch. This includes
interfaces that are themselves platform interfaces or interfaces that have
platform interfaces above them. Both of these groups of interfaces require
a linux interface that will be used for platform purposes (i.e., pxeboot,
mgmt, infra, oam).
"""
if '_kernel' in iface: # check cached result
return iface['_kernel']
else:
kernel = False
if is_platform_network_type(iface):
kernel = True
else:
upper_ifnames = iface['used_by'] or []
for upper_ifname in upper_ifnames:
upper_iface = context['interfaces'][upper_ifname]
if is_platform_interface(context, upper_iface):
kernel = True
break
iface['_kernel'] = kernel # cache the result
return iface['_kernel']
def is_data_interface(context, iface):
"""
Determine whether the interface needs to be configured in the vswitch.
This includes interfaces that are themselves data interfaces or interfaces
that have data interfaces above them. Both of these groups of interfaces
require vswitch configuration data.
"""
if '_data' in iface: # check cached result
return iface['_data']
else:
data = False
if is_data_network_type(iface):
data = True
else:
upper_ifnames = iface['used_by'] or []
for upper_ifname in upper_ifnames:
upper_iface = context['interfaces'][upper_ifname]
if is_data_interface(context, upper_iface):
data = True
break
iface['_data'] = data # cache the result
return iface['_data']
def is_dpdk_compatible(context, iface):
"""
Determine whether an interface can be supported in vswitch as a native DPDK
interface. Since whether an interface is supported or not by the DPDK
means whether the DPDK has a hardware device driver for the underlying
physical device this also implies that all non-hardware related interfaces
are automatically supported in the DPDK. For this reason we report True
for VLAN and AE interfaces but check the DPDK support status for any
ethernet interfaces.
"""
if '_dpdksupport' in iface: # check the cached result
return iface['_dpdksupport']
elif iface['iftype'] == constants.INTERFACE_TYPE_ETHERNET:
port = get_interface_port(context, iface)
dpdksupport = port.get('dpdksupport', False)
else:
dpdksupport = True
iface['_dpdksupport'] = dpdksupport # cache the result
return iface['_dpdksupport']
def is_a_mellanox_device(context, iface):
"""
Determine if the underlying device is a Mellanox device.
"""
if iface['iftype'] != constants.INTERFACE_TYPE_ETHERNET:
# We only care about configuring specific settings for related ethernet
# devices.
return False
port = get_interface_port(context, iface)
if port['driver'] in MELLANOX_DRIVERS:
return True
return False
def is_a_mellanox_cx3_device(context, iface):
"""
Determine if the underlying device is a Mellanox CX3 device.
"""
if iface['iftype'] != constants.INTERFACE_TYPE_ETHERNET:
# We only care about configuring specific settings for related ethernet
# devices.
return False
port = get_interface_port(context, iface)
if port['driver'] == DRIVER_MLX_CX3:
return True
return False
def get_master_interface(context, iface):
"""
Get the interface name of the given interface's master (if any). The
master interface is the AE interface for any Ethernet interfaces.
"""
if '_master' not in iface: # check the cached result
master = None
if iface['iftype'] == constants.INTERFACE_TYPE_ETHERNET:
upper_ifnames = iface['used_by'] or []
for upper_ifname in upper_ifnames:
upper_iface = context['interfaces'][upper_ifname]
if upper_iface['iftype'] == constants.INTERFACE_TYPE_AE:
master = upper_iface['ifname']
break
iface['_master'] = master # cache the result
return iface['_master']
def is_slave_interface(context, iface):
"""
Determine if this interface is a slave interface. A slave interface is an
interface that is part of an AE interface.
"""
if '_slave' not in iface: # check the cached result
master = get_master_interface(context, iface)
iface['_slave'] = bool(master) # cache the result
return iface['_slave']
def get_interface_mtu(context, iface):
"""
Determine the MTU value to use for a given interface. We trust that sysinv
has selected the correct value.
"""
return iface['imtu']
def get_interface_datanets(context, iface):
"""
Return the list of data networks of the supplied interface
"""
return interface.get_interface_datanets(context, iface)
def _get_datanetwork_names(context, iface):
"""
Return the CSV list of data networks of the supplied interface
"""
return interface._get_datanetwork_names(context, iface)
def get_interface_port(context, iface):
"""
Determine the port of the underlying device.
"""
return interface.get_interface_port(context, iface)
def get_interface_port_name(context, iface):
"""
Determine the port name of the underlying device.
"""
assert iface['iftype'] == constants.INTERFACE_TYPE_ETHERNET
port = get_interface_port(context, iface)
if port:
return port['name']
def get_lower_interface(context, iface):
"""
Return the interface object that is used to implement a VLAN interface.
"""
assert iface['iftype'] == constants.INTERFACE_TYPE_VLAN
lower_ifname = iface['uses'][0]
return context['interfaces'][lower_ifname]
def get_lower_interface_os_ifname(context, iface):
"""
Return the kernel interface name of the lower interface used to implement a
VLAN interface.
"""
lower_iface = get_lower_interface(context, iface)
return get_interface_os_ifname(context, lower_iface)
def get_interface_os_ifname(context, iface):
"""
Determine the interface name used in the linux kernel for the given
interface. Ethernet interfaces uses the original linux device name while
AE devices can use the user-defined named. VLAN interface must derive
their names based on their lower interface name.
"""
if '_os_ifname' in iface: # check cached result
return iface['_os_ifname']
else:
os_ifname = iface['ifname']
if iface['iftype'] == constants.INTERFACE_TYPE_ETHERNET:
os_ifname = get_interface_port_name(context, iface)
elif iface['iftype'] == constants.INTERFACE_TYPE_VLAN:
lower_os_ifname = get_lower_interface_os_ifname(context, iface)
os_ifname = lower_os_ifname + "." + str(iface['vlan_id'])
elif iface['iftype'] == constants.INTERFACE_TYPE_AE:
os_ifname = iface['ifname']
iface['_os_ifname'] = os_ifname # cache the result
return iface['_os_ifname']
def get_interface_routes(context, iface):
"""
Determine the list of routes that are applicable to a given interface (if
any).
"""
return context['routes'][iface['ifname']]
def _set_address_netmask(address):
"""
The netmask is not supplied by sysinv but is required by the puppet
resource class.
"""
network = IPNetwork(address['address'] + '/' + str(address['prefix']))
if network.version == 6:
address['netmask'] = str(network.prefixlen)
else:
address['netmask'] = str(network.netmask)
return address
def get_interface_primary_address(context, iface, network_id=None):
"""
Determine the primary IP address on an interface (if any). If multiple
addresses exist then the first address is returned.
"""
addresses = context['addresses'].get(iface['ifname'], [])
if len(addresses) > 0 and network_id is None:
return _set_address_netmask(addresses[0])
elif network_id:
for address in addresses:
net = find_network_by_pool_uuid(context,
address.pool_uuid)
if net and network_id == net.id:
return _set_address_netmask(address)
def get_interface_address_family(context, iface, network_id=None):
"""
Determine the IP family/version of the interface primary address. If there
is no address then the IPv4 family identifier is returned so that an
appropriate default is always present in interface configurations.
"""
address = get_interface_primary_address(context, iface, network_id)
if not address:
return 'inet' # default to ipv4
elif IPAddress(address['address']).version == 4:
return 'inet'
else:
return 'inet6'
def get_interface_gateway_address(context, networktype):
"""
Determine if the interface has a default gateway.
"""
return context['gateways'].get(networktype, None)
def get_interface_address_method(context, iface, network_id=None):
"""
Determine what type of interface to configure for each network type.
"""
networktype = find_networktype_by_network_id(context, network_id)
if not iface.ifclass or iface.ifclass == constants.INTERFACE_CLASS_NONE \
or not networktype:
# Interfaces that are configured purely as a dependency from other
# interfaces (i.e., vlan lower interface, bridge member, bond slave)
# should be left as manual config
return MANUAL_METHOD
elif iface.ifclass == constants.INTERFACE_CLASS_DATA:
# All data interfaces configured in the kernel because they are not
# natively supported in vswitch or need to be shared with the kernel
# because of a platform VLAN should be left as manual config
return MANUAL_METHOD
elif iface.ifclass in PCI_INTERFACE_CLASSES:
return MANUAL_METHOD
else:
if is_controller(context):
# All other interface types that exist on a controller are setup
# statically since the controller themselves run the DHCP server.
return STATIC_METHOD
elif networktype == constants.NETWORK_TYPE_CLUSTER_HOST:
return STATIC_METHOD
elif networktype == constants.NETWORK_TYPE_PXEBOOT:
# All pxeboot interfaces that exist on non-controller nodes are set
# to manual as they are not needed/used once the install is done.
# They exist only in support of the vlan mgmt interface above it.
return MANUAL_METHOD
else:
# All other types get their addresses from the controller
return DHCP_METHOD
def get_interface_traffic_classifier(context, iface, network_id=None):
"""
Get the interface traffic classifier command line (if any)
"""
networktype = find_networktype_by_network_id(context, network_id)
if (networktype and
networktype in [constants.NETWORK_TYPE_MGMT,
constants.NETWORK_TYPE_INFRA]):
networkspeed = constants.LINK_SPEED_10G
ifname = get_interface_os_ifname(context, iface)
return '/usr/local/bin/cgcs_tc_setup.sh %s %s %s > /dev/null' \
% (ifname,
networktype,
networkspeed)
return None
def get_bridge_interface_name(context, iface):
"""
If the given interface is a bridge member then retrieve the bridge
interface name otherwise return None.
"""
if '_bridge' in iface: # check the cached result
return iface['_bridge']
else:
bridge = None
if (iface['iftype'] == constants.INTERFACE_TYPE_ETHERNET and
is_data_interface(context, iface) and
not is_dpdk_compatible(context, iface)):
bridge = 'br-' + get_interface_os_ifname(context, iface)
iface['_bridge'] = bridge # cache the result
return iface['_bridge']
def is_bridged_interface(context, iface):
"""
Determine if this interface is a member of a bridge. A interface is a
member of a bridge if the interface is a data interface that is not
accelerated (i.e., a slow data interface).
"""
if '_bridged' in iface: # check the cached result
return iface['_bridged']
else:
bridge = get_bridge_interface_name(context, iface)
iface['_bridged'] = bool(bridge) # cache the result
return iface['_bridged']
def needs_interface_config(context, iface):
"""
Determine whether an interface needs to be configured in the linux kernel.
This is true if the interface is a platform interface, is required by a
platform interface (i.e., an AE member, a VLAN lower interface), or is an
unaccelerated data interface.
"""
if is_platform_interface(context, iface):
return True
elif not is_worker_subfunction(context):
return False
elif is_data_interface(context, iface):
if not is_dpdk_compatible(context, iface):
# vswitch interfaces for devices that are not natively supported by
# the DPDK are created as regular Linux devices and then bridged in
# to vswitch in order for it to be able to use it indirectly.
return True
if is_a_mellanox_device(context, iface):
# Check for Mellanox data interfaces. We must set the MTU sizes of
# Mellanox data interfaces in case it is not the default. Normally
# data interfaces are owned by DPDK, they are not managed through
# Linux but in the Mellanox case, the interfaces are still visible
# in Linux so in case one needs to set jumbo frames, it has to be
# set in Linux as well. We only do this for combined nodes or
# non-controller nodes.
return True
elif is_pci_interface(iface):
return True
return False
def get_basic_network_config(ifname, ensure='present',
method='manual', onboot='true',
hotplug='false', family='inet',
mtu=None):
"""
Builds a basic network config dictionary with all of the fields required to
format a basic network_config puppet resource.
"""
config = {'ifname': ifname,
'ensure': ensure,
'family': family,
'method': method,
'hotplug': hotplug,
'onboot': onboot,
'options': {}}
if mtu:
config['mtu'] = str(mtu)
return config
def get_bridge_network_config(context, iface):
"""
Builds a network config dictionary for bridge interface resource.
"""
os_ifname = get_interface_os_ifname(context, iface)
os_ifname = 'br-' + os_ifname
method = get_interface_address_method(context, iface)
family = get_interface_address_family(context, iface)
config = get_basic_network_config(
os_ifname, method=method, family=family)
config['options']['TYPE'] = 'Bridge'
return config
def get_vlan_network_config(context, iface, config):
"""
Augments a basic config dictionary with the attributes specific to a VLAN
interface.
"""
options = {'VLAN': 'yes',
'pre_up': '/sbin/modprobe -q 8021q'}
config['options'].update(options)
return config
def get_bond_interface_options(iface, primary_iface):
"""
Get the interface config attribute for bonding options
"""
ae_mode = iface['aemode']
tx_hash_policy = iface['txhashpolicy']
options = None
if ae_mode in ACTIVE_STANDBY_AE_MODES:
# Requires the active device in an active_standby LAG
# configuration to be determined based on the lowest MAC address
options = 'mode=active-backup miimon=100 primary={}'.format(primary_iface['ifname'])
else:
options = 'xmit_hash_policy=%s miimon=100' % tx_hash_policy
if ae_mode in BALANCED_AE_MODES:
options = 'mode=balance-xor ' + options
elif ae_mode in LACP_AE_MODES:
options = 'mode=802.3ad lacp_rate=fast ' + options
return options
def get_bond_network_config(context, iface, config):
"""
Augments a basic config dictionary with the attributes specific to a bond
interface.
"""
options = {'MACADDR': iface['imac'].rstrip()}
primary_iface = get_primary_bond_interface(context, iface)
bonding_options = get_bond_interface_options(iface, primary_iface)
if bonding_options:
options['BONDING_OPTS'] = bonding_options
options['up'] = 'sleep 10'
config['options'].update(options)
return config
def get_primary_bond_interface(context, iface):
"""
Return the slave interface with the lowest MAC address
"""
slaves = get_bond_interface_slaves(context, iface)
sorted_slaves = sorted(slaves, key=slave_sort_key)
primary_iface = sorted_slaves[0]
return primary_iface
def get_bond_interface_slaves(context, iface):
"""
Return the slave interface objects for the corresponding
bond interface
"""
slaves = iface['uses']
ifaces = []
for ifname, iface in six.iteritems(context['interfaces']):
if ifname in slaves:
ifaces.append(iface)
return ifaces
def slave_sort_key(iface):
"""
Sort interfaces by lowest MAC address
"""
return int(iface['imac'].replace(':', ''), 16)
def get_ethernet_network_config(context, iface, config):
"""
Augments a basic config dictionary with the attributes specific to an
ethernet interface.
"""
interface_class = iface['ifclass']
options = {}
# Increased to accommodate devices that require more time to
# complete link auto-negotiation
options['LINKDELAY'] = '20'
if is_bridged_interface(context, iface):
options['BRIDGE'] = get_bridge_interface_name(context, iface)
elif is_slave_interface(context, iface):
if not is_data_interface(context, iface):
# Data interfaces that require a network configuration are not
# candidates for bonding. They exist because their DPDK drivers
# rely on the Linux device driver to setup some or all functions
# on the device (e.g., the Mellanox DPDK driver relies on the
# Linux driver to set the proper MTU value).
options['SLAVE'] = 'yes'
options['MASTER'] = get_master_interface(context, iface)
options['PROMISC'] = 'yes'
elif interface_class == constants.INTERFACE_CLASS_PCI_SRIOV:
if not is_a_mellanox_cx3_device(context, iface):
# CX3 device can only use kernel module options to enable vfs
# others share the same pci-sriov sysfs enabling mechanism
sriovfs_path = ("/sys/class/net/%s/device/sriov_numvfs" %
get_interface_port_name(context, iface))
options['pre_up'] = "echo 0 > %s; echo %s > %s" % (
sriovfs_path, iface['sriov_numvfs'], sriovfs_path)
elif interface_class == constants.INTERFACE_CLASS_PCI_PASSTHROUGH:
sriovfs_path = ("/sys/class/net/%s/device/sriov_numvfs" %
get_interface_port_name(context, iface))
options['pre_up'] = "if [ -f %s ]; then echo 0 > %s; fi" % (
sriovfs_path, sriovfs_path)
config['options'].update(options)
return config
def get_route_config(route, ifname):
"""
Builds a basic route config dictionary with all of the fields required to
format a basic network_route puppet resource.
"""
if route['prefix']:
name = '%s/%s' % (route['network'], route['prefix'])
else:
name = 'default'
netmask = IPNetwork(route['network'] + "/" + str(route['prefix'])).netmask
config = {
'name': name,
'ensure': 'present',
'gateway': route['gateway'],
'interface': ifname,
'netmask': str(netmask) if route['prefix'] else '0.0.0.0',
'network': route['network'] if route['prefix'] else 'default',
'options': 'metric ' + str(route['metric'])
}
return config
def get_common_network_config(context, iface, config, network_id=None):
"""
Augments a basic config dictionary with the attributes specific to an upper
layer interface (i.e., an interface that is used to terminate IP traffic).
"""
LOG.debug("get_common_network_config %s %s network_id=%s" %
(iface.ifname, iface.networks, network_id))
traffic_classifier = get_interface_traffic_classifier(context, iface,
network_id)
if traffic_classifier:
config['options']['post_up'] = traffic_classifier
method = get_interface_address_method(context, iface, network_id)
if method == STATIC_METHOD:
address = get_interface_primary_address(context, iface, network_id)
if address:
config['ipaddress'] = address['address']
config['netmask'] = address['netmask']
else:
LOG.info("Interface %s has no primary address" % iface['ifname'])
networktype = find_networktype_by_network_id(context, network_id)
gateway = get_interface_gateway_address(context, networktype)
if gateway:
config['gateway'] = gateway
return config
def get_interface_network_config(context, iface, network_id=None):
"""
Builds a network_config resource dictionary for a given interface
"""
# Create a basic network config resource
os_ifname = get_interface_os_ifname(context, iface)
method = get_interface_address_method(context, iface, network_id)
family = get_interface_address_family(context, iface, network_id)
# setup an alias interface if there are multiple addresses assigned
# NOTE: DHCP will only operate over a non-alias interface
if len(iface.networks) > 1 and network_id and method != DHCP_METHOD:
ifname = "%s:%d" % (os_ifname, network_id)
else:
ifname = os_ifname
mtu = get_interface_mtu(context, iface)
config = get_basic_network_config(
ifname, method=method, family=family, mtu=mtu)
# Add options common to all top level interfaces
config = get_common_network_config(context, iface, config, network_id)
# ensure addresses have host scope when configured against the loopback
if os_ifname == LOOPBACK_IFNAME:
options = {'SCOPE': 'scope host'}
config['options'].update(options)
# Add type specific options
if iface['iftype'] == constants.INTERFACE_TYPE_VLAN:
config = get_vlan_network_config(context, iface, config)
elif iface['iftype'] == constants.INTERFACE_TYPE_AE:
config = get_bond_network_config(context, iface, config)
else:
config = get_ethernet_network_config(context, iface, config)
return config
def generate_network_config(context, config, iface):
"""
Produce the puppet network config resources necessary to configure the
given interface. In some cases this will emit a single network_config
resource, while in other cases it will emit multiple resources to create a
bridge, or to add additional route resources.
"""
ifname = get_interface_os_ifname(context, iface)
# Setup the default device configuration for the interface. This will be
# overridden if there is a specific network type configuration, otherwise
# it will act as the parent device for the aliases
net_config = get_interface_network_config(context, iface)
config[NETWORK_CONFIG_RESOURCE].update({
net_config['ifname']: format_network_config(net_config)
})
for net_id in iface.networks:
net_config = get_interface_network_config(context, iface, int(net_id))
config[NETWORK_CONFIG_RESOURCE].update({
net_config['ifname']: format_network_config(net_config)
})
# Add complementary puppet resource definitions (if needed)
for route in get_interface_routes(context, iface):
route_config = get_route_config(route, ifname)
config[ROUTE_CONFIG_RESOURCE].update({
route_config['name']: route_config
})
def find_network_by_pool_uuid(context, pool_uuid):
for networktype, network in six.iteritems(context['networks']):
if network.pool_uuid == pool_uuid:
return network
return None
def find_network_id_by_networktype(context, networktype):
for net_type, network in six.iteritems(context['networks']):
if networktype == net_type:
return network.id
def find_networktype_by_network_id(context, network_id):
for networktype, network in six.iteritems(context['networks']):
if network.id == network_id:
return networktype
def find_interface_by_type(context, networktype):
"""
Lookup an interface based on networktype. This is only intended for
platform interfaces that have only 1 such interface per node (i.e., oam,
mgmt, infra, pxeboot, bmc).
"""
for ifname, iface in six.iteritems(context['interfaces']):
for net_id in iface.networks:
net_type = find_networktype_by_network_id(context, int(net_id))
if networktype == net_type:
return iface
def find_address_by_type(context, networktype):
"""
Lookup an address based on networktype. This is only intended for for
types that only have 1 such address per node. For example, for SDN we
only expect/support a single data IP address per node because the SDN
controller cannot support more than 1.
"""
for ifname, addresses in six.iteritems(context['addresses']):
for address in addresses:
if address['networktype'] == networktype:
return address['address'], address['prefix']
return None, None
def find_sriov_interfaces_by_driver(context, driver):
"""
Lookup all interfaces based on port driver.
To be noted that this is only used for IFTYPE_ETHERNET
"""
ifaces = []
for ifname, iface in six.iteritems(context['interfaces']):
if iface['iftype'] != constants.INTERFACE_TYPE_ETHERNET:
continue
port = get_interface_port(context, iface)
if (port['driver'] == driver and
iface['ifclass'] == constants.INTERFACE_CLASS_PCI_SRIOV):
ifaces.append(iface)
return ifaces
def interface_sort_key(iface):
"""
Sort interfaces by interface type placing ethernet interfaces ahead of
aggregated ethernet interfaces, and vlan interfaces last.
"""
if iface['iftype'] == constants.INTERFACE_TYPE_ETHERNET:
return 0, iface['ifname']
elif iface['iftype'] == constants.INTERFACE_TYPE_AE:
return 1, iface['ifname']
else: # if iface['iftype'] == constants.INTERFACE_TYPE_VLAN:
return 2, iface['ifname']
def generate_interface_configs(context, config):
"""
Generate the puppet resource for each of the interface and route config
resources.
"""
for iface in sorted(context['interfaces'].values(),
key=interface_sort_key):
if needs_interface_config(context, iface):
generate_network_config(context, config, iface)
def get_address_config(context, iface, address):
ifname = get_interface_os_ifname(context, iface)
return {
'ifname': ifname,
'address': address,
}
def generate_address_configs(context, config):
"""
Generate the puppet resource for each of the floating IP addresses
"""
for networktype, address in six.iteritems(context['floatingips']):
iface = find_interface_by_type(context, networktype)
if iface:
address_config = get_address_config(context, iface, address)
config[ADDRESS_CONFIG_RESOURCE].update({
networktype: address_config
})
elif networktype == constants.NETWORK_TYPE_PXEBOOT:
# Fallback PXE boot address against management interface
iface = find_interface_by_type(context,
constants.NETWORK_TYPE_MGMT)
if iface:
address_config = get_address_config(context, iface, address)
config[ADDRESS_CONFIG_RESOURCE].update({
networktype: address_config
})
elif networktype == constants.NETWORK_TYPE_CLUSTER_HOST:
# Fallback cluster host address against management interface
iface = find_interface_by_type(context,
constants.NETWORK_TYPE_CLUSTER_HOST)
if iface:
address_config = get_address_config(context, iface, address)
config[ADDRESS_CONFIG_RESOURCE].update({
networktype: address_config
})
def build_mlx4_num_vfs_options(context):
"""
Generate the manifest fragment that will create mlx4_core
modprobe conf file in which VF is set and reload the mlx4_core
kernel module
"""
ifaces = find_sriov_interfaces_by_driver(context, DRIVER_MLX_CX3)
if not ifaces:
return ""
num_vfs_options = ""
for iface in ifaces:
port = get_interface_port(context, iface)
# For CX3 SR-IOV configuration, we only configure VFs on the 1st port
# Since two ports share the same PCI address, if the first port has
# been configured, we need to skip the second port
if port['pciaddr'] in num_vfs_options:
continue
if not num_vfs_options:
num_vfs_options = "%s-%d;0;0" % (port['pciaddr'],
iface['sriov_numvfs'])
else:
num_vfs_options += ",%s-%d;0;0" % (port['pciaddr'],
iface['sriov_numvfs'])
return num_vfs_options
def generate_mlx4_core_options(context, config):
"""
Generate the config options that will create mlx4_core modprobe
conf file in which VF is set and execute mlx4_core_conf.sh in which
/var/run/.mlx4_cx3_reboot_required is created to indicate a reboot
is needed for goenable and /etc/modprobe.d/mlx4_sriov.conf is injected
into initramfs, this way mlx4_core options can be applied after reboot
"""
num_vfs_options = build_mlx4_num_vfs_options(context)
if not num_vfs_options:
return
mlx4_core_options = "port_type_array=2,2 num_vfs=%s" % num_vfs_options
config['platform::networking::mlx4_core_options'] = mlx4_core_options
def generate_driver_config(context, config):
"""
Generate custom configuration for driver specific parameters.
"""
if is_worker_subfunction(context):
generate_mlx4_core_options(context, config)
def generate_loopback_config(config):
"""
Generate the loopback network config resource so that the loopback
interface is automatically enabled on reboots.
"""
network_config = get_basic_network_config(LOOPBACK_IFNAME,
method=LOOPBACK_METHOD)
config[NETWORK_CONFIG_RESOURCE].update({
LOOPBACK_IFNAME: format_network_config(network_config)
})
def format_network_config(config):
"""
Converts a network_config resource dictionary to the equivalent puppet
resource definition parameters.
"""
network_config = copy.copy(config)
del network_config['ifname']
return network_config
def generate_dhcp_config(context, config):
"""
Generate the DHCP client configuration.
"""
if not is_controller(context):
infra_interface = find_interface_by_type(
context, constants.NETWORK_TYPE_INFRA)
if infra_interface:
infra_cid = utils.get_dhcp_cid(context['hostname'],
constants.NETWORK_TYPE_INFRA,
infra_interface['imac'])
config['platform::dhclient::params::infra_client_id'] = infra_cid