os-net-config/os_net_config/impl_nmstate.py

1622 lines
68 KiB
Python

# -*- coding: utf-8 -*-
# Copyright 2014-2015 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 copy
from libnmstate import netapplier
from libnmstate import netinfo
from libnmstate.schema import Bond
from libnmstate.schema import BondMode
from libnmstate.schema import DNS
from libnmstate.schema import Ethernet
from libnmstate.schema import Ethtool
from libnmstate.schema import InfiniBand
from libnmstate.schema import Interface
from libnmstate.schema import InterfaceIPv4
from libnmstate.schema import InterfaceIPv6
from libnmstate.schema import InterfaceState
from libnmstate.schema import InterfaceType
from libnmstate.schema import OVSBridge
from libnmstate.schema import OvsDB
from libnmstate.schema import OVSInterface
from libnmstate.schema import Route as NMRoute
from libnmstate.schema import RouteRule as NMRouteRule
from libnmstate.schema import VLAN
import logging
import netaddr
import re
import yaml
import os_net_config
from os_net_config import common
from os_net_config import objects
from os_net_config import utils
logger = logging.getLogger(__name__)
# Import the raw NetConfig object so we can call its methods
netconfig = os_net_config.NetConfig()
_OS_NET_CONFIG_MANAGED = "# os-net-config managed table"
_ROUTE_TABLE_DEFAULT = """# reserved values
#
255\tlocal
254\tmain
253\tdefault
0\tunspec
#
# local
#
#1\tinr.ruhep\n"""
IPV4_DEFAULT_GATEWAY_DESTINATION = "0.0.0.0/0"
IPV6_DEFAULT_GATEWAY_DESTINATION = "::/0"
def route_table_config_path():
return "/etc/iproute2/rt_tables"
def _get_type_value(str_val):
if isinstance(str_val, str):
if str_val.isdigit():
return int(str_val)
if str_val.lower() in ['true', 'yes', 'on']:
return True
if str_val.lower() in ['false', 'no', 'off']:
return False
return str_val
def get_route_options(route_options, key):
items = route_options.split(' ')
iter_list = iter(items)
for item in iter_list:
if key in item:
return _get_type_value(next(iter_list))
return
def is_dict_subset(superset, subset):
"""Check to see if one dict is a subset of another dict."""
if superset == subset:
return True
if superset and subset:
for key, value in subset.items():
if key not in superset:
return False
if isinstance(value, dict):
if not is_dict_subset(superset[key], value):
return False
elif isinstance(value, int):
if value != superset[key]:
return False
elif isinstance(value, str):
if value != superset[key]:
return False
elif isinstance(value, list):
try:
if not set(value) <= set(superset[key]):
return False
except TypeError:
for item in value:
if item not in superset[key]:
return False
elif isinstance(value, set):
if not value <= superset[key]:
return False
else:
if not value == superset[key]:
return False
return True
return False
def _add_sub_tree(data, subtree):
config = data
if subtree:
for cfg in subtree:
if cfg not in config:
config[cfg] = {}
config = config[cfg]
return config
def parse_bonding_options(bond_options_str):
bond_options_dict = {}
if bond_options_str:
options = re.findall(r'(.+?)=(.+?)($|\s)', bond_options_str)
for option in options:
bond_options_dict[option[0]] = _get_type_value(option[1])
return bond_options_dict
def set_linux_bonding_options(bond_options, primary_iface=None):
linux_bond_options = ["updelay", "miimon", "lacp_rate"]
bond_data = {Bond.MODE: BondMode.ACTIVE_BACKUP,
Bond.OPTIONS_SUBTREE: {},
Bond.PORT: []}
bond_options_data = {}
if 'mode' in bond_options:
bond_data[Bond.MODE] = bond_options['mode']
for options in linux_bond_options:
if options in bond_options:
bond_options_data[options] = bond_options[options]
bond_data[Bond.OPTIONS_SUBTREE] = bond_options_data
if primary_iface and bond_data[Bond.MODE] == BondMode.ACTIVE_BACKUP:
bond_options_data['primary'] = primary_iface
if len(bond_data[Bond.OPTIONS_SUBTREE]) == 0:
del bond_data[Bond.OPTIONS_SUBTREE]
return bond_data
def set_ovs_bonding_options(bond_options):
# Duplicate entries for other-config are added so as to avoid
# the confusion around other-config vs other_config in ovs
ovs_other_config = ["other_config:lacp-fallback-ab",
"other_config:lacp-time",
"other_config:bond-detect-mode",
"other_config:bond-miimon-interval",
"other_config:bond-rebalance-interval",
"other_config:bond-primary",
"other-config:lacp-fallback-ab",
"other-config:lacp-time",
"other-config:bond-detect-mode",
"other-config:bond-miimon-interval",
"other-config:bond-rebalance-interval",
"other-config:bond-primary"]
other_config = {}
bond_data = {OVSBridge.Port.LinkAggregation.MODE: 'active-backup',
OVSBridge.PORT_SUBTREE:
[{OVSBridge.Port.LinkAggregation.PORT_SUBTREE: []}],
OvsDB.KEY: {OvsDB.OTHER_CONFIG: other_config}}
if 'bond_mode' in bond_options:
bond_data[OVSBridge.Port.LinkAggregation.MODE
] = bond_options['bond_mode']
elif 'lacp' in bond_options and bond_options['lacp'] == 'active':
bond_data[OVSBridge.Port.LinkAggregation.MODE
] = OVSBridge.Port.LinkAggregation.Mode.LACP
if 'bond_updelay' in bond_options:
bond_data[OVSBridge.Port.LinkAggregation.Options.UP_DELAY
] = bond_options['bond_updelay']
for options in ovs_other_config:
if options in bond_options:
other_config[options[len("other_config:"):]
] = bond_options[options]
return bond_data
def _is_any_ip_addr(address):
if address.lower() == 'any' or address.lower() == 'all':
return True
return False
class NmstateNetConfig(os_net_config.NetConfig):
"""Configure network interfaces using NetworkManager via nmstate API."""
def __init__(self, noop=False, root_dir=''):
super(NmstateNetConfig, self).__init__(noop, root_dir)
self.interface_data = {}
self.vlan_data = {}
self.route_data = {}
self.rules_data = []
self.dns_data = {'server': [], 'domain': []}
self.bridge_data = {}
self.linuxbond_data = {}
self.ovs_port_data = {}
self.member_names = {}
self.renamed_interfaces = {}
self.bond_primary_ifaces = {}
self.route_table_data = {}
self.sriov_pf_data = {}
self.sriov_vf_data = {}
logger.info('nmstate net config provider created.')
def __dump_config(self, config, msg="Applying config"):
cfg_dump = yaml.dump(config, default_flow_style=False,
allow_unicode=True, encoding=None)
logger.debug("----------------------------")
logger.debug(f"{msg}\n{cfg_dump}")
def get_vf_config(self, sriov_vf):
"""Identify the nmstate schema for the given VF
:param sriov_vf: The SriovVF object to add
"""
vf_config = {}
vf_config[Ethernet.SRIOV.VFS.ID] = sriov_vf.vfid
if sriov_vf.macaddr:
vf_config[Ethernet.SRIOV.VFS.MAC_ADDRESS] = sriov_vf.macaddr
if sriov_vf.spoofcheck:
if sriov_vf.spoofcheck == 'on':
vf_config[Ethernet.SRIOV.VFS.SPOOF_CHECK] = True
else:
vf_config[Ethernet.SRIOV.VFS.SPOOF_CHECK] = False
if sriov_vf.trust:
if sriov_vf.trust == 'on':
vf_config[Ethernet.SRIOV.VFS.TRUST] = True
else:
vf_config[Ethernet.SRIOV.VFS.TRUST] = False
if sriov_vf.min_tx_rate:
vf_config[Ethernet.SRIOV.VFS.MIN_TX_RATE] = sriov_vf.min_tx_rate
if sriov_vf.max_tx_rate:
vf_config[Ethernet.SRIOV.VFS.MAX_TX_RATE] = sriov_vf.max_tx_rate
if sriov_vf.vlan_id:
vf_config[Ethernet.SRIOV.VFS.VLAN_ID] = sriov_vf.vlan_id
if sriov_vf.qos:
vf_config[Ethernet.SRIOV.VFS.QOS] = sriov_vf.qos
return vf_config
def prepare_sriov_vf_config(self):
iface_schema = []
for pf in self.sriov_vf_data.keys():
required_vfs = []
if pf in self.sriov_pf_data:
pf_state = self.sriov_pf_data[pf]
else:
msg = f"{pf} not found"
raise os_net_config.ConfigurationError(msg)
for vf in self.sriov_vf_data[pf]:
if vf:
required_vfs.append(vf)
pf_state[
Ethernet.CONFIG_SUBTREE][
Ethernet.SRIOV_SUBTREE][
Ethernet.SRIOV.VFS_SUBTREE] = required_vfs
iface_schema.append(pf_state)
return iface_schema
def get_route_tables(self):
"""Generate configuration content for routing tables.
This method first extracts the existing route table definitions. If
any non-default tables exist, they will be kept unless they conflict
with new tables defined in the route_tables dict.
:param route_tables: A dict of RouteTable objects
"""
rt_tables = {}
rt_config = common.get_file_data(route_table_config_path()).split('\n')
for line in rt_config:
# ignore comments and black lines
line = line.strip()
if not line or line.startswith('#'):
pass
else:
id_name = line.split()
if len(id_name) > 1 and id_name[0].isdigit():
rt_tables[id_name[1]] = int(id_name[0])
self.__dump_config(rt_tables,
msg='Contents of /etc/iproute2/rt_tables')
return rt_tables
def generate_route_table_config(self, route_tables):
"""Generate configuration content for routing tables.
This method first extracts the existing route table definitions. If
any non-default tables exist, they will be kept unless they conflict
with new tables defined in the route_tables dict.
:param route_tables: A dict of RouteTable objects
"""
custom_tables = {}
res_ids = ['0', '253', '254', '255']
res_names = ['unspec', 'default', 'main', 'local']
rt_config = common.get_file_data(route_table_config_path()).split('\n')
rt_defaults = _ROUTE_TABLE_DEFAULT.split("\n")
data = _ROUTE_TABLE_DEFAULT
for line in rt_config:
line = line.strip()
if line in rt_defaults:
continue
# Leave non-standard comments intact in file
if line.startswith('#'):
data += f"{line}\n"
# Ignore old managed entries, will be added back if in new config.
elif line.find(_OS_NET_CONFIG_MANAGED) == -1:
id_name = line.split()
# Keep custom tables if there is no conflict with new tables.
if len(id_name) > 1 and id_name[0].isdigit():
if not id_name[0] in res_ids:
if not id_name[1] in res_names:
if not int(id_name[0]) in route_tables:
if not id_name[1] in route_tables.values():
# Replicate line with any comments appended
custom_tables[id_name[0]] = id_name[1]
data += f"{line}\n"
if custom_tables:
logger.debug(f"Existing route tables: {custom_tables}")
for id in sorted(route_tables):
if str(id) in res_ids:
message = f"Table {route_tables[id]}({id}) conflicts with " \
f"reserved table "\
f"{res_names[res_ids.index(str(id))]}({id})"
raise os_net_config.ConfigurationError(message)
elif route_tables[id] in res_names:
message = f"Table {route_tables[id]}({id}) conflicts with "\
f"reserved table {route_tables[id]}" \
f"({res_ids[res_names.index(route_tables[id])]})"
raise os_net_config.ConfigurationError(message)
else:
data += f"{id}\t{route_tables[id]} "\
f"{_OS_NET_CONFIG_MANAGED}\n"
return data
def iface_state(self, name=''):
"""Return the current interface state according to nmstate.
Return the current state of all interfaces, or the named interface.
:param name: name of the interface to return state, otherwise all.
:returns: list state of all interfaces when name is not specified, or
the state of the specific interface when name is specified
"""
ifaces = netinfo.show_running_config()[Interface.KEY]
if name != '':
for iface in ifaces:
if iface[Interface.NAME] != name:
continue
self.__dump_config(iface, msg=f"Running config for {name}")
return iface
else:
self.__dump_config(ifaces,
msg=f"Running config for all interfaces")
return ifaces
def cleanup_all_ifaces(self, exclude_nics=[]):
exclude_nics.extend(['lo'])
ifaces = netinfo.show_running_config()[Interface.KEY]
for iface in ifaces:
if Interface.NAME in iface and \
iface[Interface.NAME] not in exclude_nics:
iface[Interface.STATE] = InterfaceState.DOWN
state = {Interface.KEY: [iface]}
self.__dump_config(state,
msg=f"Cleaning up {iface[Interface.NAME]}")
if not self.noop:
netapplier.apply(state, verify_change=True)
def route_state(self, name=''):
"""Return the current routes set according to nmstate.
Return the current routes for all interfaces, or the named interface.
:param name: name of the interface to return state, otherwise all.
:returns: list of all interfaces, or those matching name if specified
"""
routes = netinfo.show_running_config()[
NMRoute.KEY][NMRoute.CONFIG]
if name != "":
route = list(x for x in routes if x[
NMRoute.NEXT_HOP_INTERFACE] == name)
self.__dump_config(route,
msg=f'Running route config for {name}')
return route
else:
self.__dump_config(routes, msg=f'Running routes config')
return routes
def rule_state(self):
"""Return the current rules set according to nmstate.
Return the current ip rules for all interfaces, or the named interface.
:param name: name of the interface to return state, otherwise all.
:returns: list of all interfaces, or those matching name if specified
"""
rules = netinfo.show_running_config()[
NMRouteRule.KEY][NMRouteRule.CONFIG]
self.__dump_config(rules, msg=f'List of IP rules running config')
return rules
def set_ifaces(self, iface_data):
"""Apply the desired state using nmstate.
:param iface_data: interface config json
:param verify: boolean that determines if config will be verified
"""
state = {Interface.KEY: iface_data}
self.__dump_config(state, msg=f"Overall interface config")
return state
def set_dns(self):
"""Apply the desired DNS using nmstate.
:param dns_data: config json
:param verify: boolean that determines if config will be verified
"""
state = {DNS.KEY: {DNS.CONFIG: {DNS.SERVER: self.dns_data['server'],
DNS.SEARCH: self.dns_data['domain']}}}
self.__dump_config(state, msg=f"Overall DNS")
return state
def set_routes(self, route_data):
"""Apply the desired routes using nmstate.
:param route_data: list of routes
:param verify: boolean that determines if config will be verified
"""
state = {NMRoute.KEY: {NMRoute.CONFIG: route_data}}
self.__dump_config(state, msg=f'Overall routes')
return state
def set_rules(self, rule_data,):
"""Apply the desired rules using nmstate.
:param rule_data: list of rules
:param verify: boolean that determines if config will be verified
"""
state = {NMRouteRule.KEY: {NMRouteRule.CONFIG: rule_data}}
self.__dump_config(state, msg=f'Overall rules')
return state
def nmstate_apply(self, new_state, verify=True):
self.__dump_config(new_state, msg=f'Applying the config with nmstate')
if not self.noop:
netapplier.apply(new_state, verify_change=verify)
def generate_routes(self, interface_name):
"""Generate the route configurations required. Add/Remove routes
: param interface_name: interface name for which routes are required
"""
reqd_route = self.route_data.get(interface_name, [])
curr_routes = self.route_state(interface_name)
routes = []
self.__dump_config(curr_routes,
msg=f'Running route config for {interface_name}')
self.__dump_config(reqd_route,
msg=f'Required route changes for {interface_name}')
for c_route in curr_routes:
no_metric = copy.deepcopy(c_route)
if NMRoute.METRIC in no_metric:
del no_metric[NMRoute.METRIC]
if c_route not in reqd_route and no_metric not in reqd_route:
c_route[NMRoute.STATE] = NMRoute.STATE_ABSENT
routes.append(c_route)
logger.info(f'Removing route {c_route}')
routes.extend(reqd_route)
return routes
def generate_rules(self):
"""Generate the rule configurations required. Add/Remove rules
"""
reqd_rule = self.rules_data
curr_rules = self.rule_state()
rules = []
self.__dump_config(curr_rules,
msg=f'Running set of ip rules')
self.__dump_config(reqd_rule,
msg=f'Required ip rules')
for c_rule in curr_rules:
if c_rule not in reqd_rule:
c_rule[NMRouteRule.STATE] = NMRouteRule.STATE_ABSENT
rules.append(c_rule)
logger.info(f'Removing rule {c_rule}')
rules.extend(reqd_rule)
return rules
def interface_mac(self, iface):
iface_data = self.iface_state(iface)
if iface_data and Interface.MAC in iface_data:
return iface_data[Interface.MAC]
def get_ovs_ports(self, members):
bps = []
for member in members:
if member.startswith('vlan'):
vlan_id = int(member.strip('vlan'))
port = {
OVSBridge.Port.NAME: member,
OVSBridge.Port.VLAN_SUBTREE: {
OVSBridge.Port.Vlan.MODE: 'access',
OVSBridge.Port.Vlan.TAG: vlan_id
}
}
bps.append(port)
# if the member is of type interface but a vlan
# like eth0.605
elif re.match(r'\w+\.\d+$', member):
vlan_id = int(member.split('.')[1])
port = {
OVSBridge.Port.NAME: member,
OVSBridge.Port.VLAN_SUBTREE: {
OVSBridge.Port.Vlan.MODE: 'access',
OVSBridge.Port.Vlan.TAG: vlan_id
}
}
bps.append(port)
else:
port = {'name': member}
bps.append(port)
logger.debug(f"Adding ovs ports {bps}")
return bps
def add_ethtool_subtree(self, data, sub_config, command):
config = _add_sub_tree(data, sub_config['sub-tree'])
ethtool_map = sub_config['map']
# skip first 2 entries as they are already validated
for index in range(2, len(command) - 1, 2):
value = _get_type_value(command[index + 1])
if command[index] in ethtool_map.keys():
config[ethtool_map[command[index]]] = value
elif (sub_config['sub-options'] == 'copy'):
config[command[index]] = value
else:
msg = f'Unhandled ethtool option {command[index]} for '\
f'command {command}.'
raise os_net_config.ConfigurationError(msg)
def add_ethtool_config(self, iface_name, data, ethtool_options):
ethtool_generic_options = {'sub-tree': [Ethernet.CONFIG_SUBTREE],
'sub-options': None,
'map': {
'speed': Ethernet.SPEED,
'autoneg': Ethernet.AUTO_NEGOTIATION,
'duplex': Ethernet.DUPLEX}
}
ethtool_set_ring = {'sub-tree': [Ethtool.CONFIG_SUBTREE,
Ethtool.Ring.CONFIG_SUBTREE],
'sub-options': None,
'map': {
'rx': Ethtool.Ring.RX,
'tx': Ethtool.Ring.TX,
'rx-jumbo': Ethtool.Ring.RX_JUMBO,
'rx-mini': Ethtool.Ring.RX_MINI}
}
ethtool_set_pause = {'sub-tree': [Ethtool.CONFIG_SUBTREE,
Ethtool.Pause.CONFIG_SUBTREE],
'sub-options': None,
'map': {
'autoneg': Ethtool.Pause.AUTO_NEGOTIATION,
'tx': Ethtool.Pause.TX,
'rx': Ethtool.Pause.RX}
}
coalesce_map = {'adaptive-rx': Ethtool.Coalesce.ADAPTIVE_RX,
'adaptive-tx': Ethtool.Coalesce.ADAPTIVE_TX,
'rx-usecs': Ethtool.Coalesce.RX_USECS,
'rx-frames': Ethtool.Coalesce.RX_FRAMES,
'rx-usecs-irq': Ethtool.Coalesce.RX_USECS_IRQ,
'rx-frames-irq': Ethtool.Coalesce.RX_FRAMES_IRQ,
'tx-usecs': Ethtool.Coalesce.TX_USECS,
'tx-frames': Ethtool.Coalesce.TX_FRAMES,
'tx-usecs-irq': Ethtool.Coalesce.TX_USECS_IRQ,
'tx-frames-irq': Ethtool.Coalesce.TX_FRAMES_IRQ,
'stats-block-usecs':
Ethtool.Coalesce.STATS_BLOCK_USECS,
'pkt-rate-low': Ethtool.Coalesce.PKT_RATE_LOW,
'rx-usecs-low': Ethtool.Coalesce.RX_USECS_LOW,
'rx-frames-low': Ethtool.Coalesce.RX_FRAMES_LOW,
'tx-usecs-low': Ethtool.Coalesce.TX_USECS_LOW,
'tx-frames-low': Ethtool.Coalesce.TX_FRAMES_LOW,
'pkt-rate-high': Ethtool.Coalesce.PKT_RATE_HIGH,
'rx-usecs-high': Ethtool.Coalesce.RX_USECS_HIGH,
'rx-frames-high': Ethtool.Coalesce.RX_FRAMES_HIGH,
'tx-usecs-high': Ethtool.Coalesce.TX_USECS_HIGH,
'tx-frames-high': Ethtool.Coalesce.TX_FRAMES_HIGH,
'sample-interval': Ethtool.Coalesce.SAMPLE_INTERVAL}
ethtool_set_coalesce = {'sub-tree': [Ethtool.CONFIG_SUBTREE,
Ethtool.Coalesce.CONFIG_SUBTREE],
'sub-options': None,
'map': coalesce_map
}
ethtool_set_features = {'sub-tree': [Ethtool.CONFIG_SUBTREE,
Ethtool.Feature.CONFIG_SUBTREE],
'sub-options': 'copy',
'map': {}}
ethtool_map = {'-G': ethtool_set_ring,
'--set-ring': ethtool_set_ring,
'-A': ethtool_set_pause,
'--pause': ethtool_set_pause,
'-C': ethtool_set_coalesce,
'--coalesce': ethtool_set_coalesce,
'-K': ethtool_set_features,
'--features': ethtool_set_features,
'--offload': ethtool_set_features,
'-s': ethtool_generic_options,
'--change': ethtool_generic_options}
if Ethernet.CONFIG_SUBTREE not in data:
data[Ethernet.CONFIG_SUBTREE] = {}
if Ethtool.CONFIG_SUBTREE not in data:
data[Ethtool.CONFIG_SUBTREE] = {}
for ethtool_opts in ethtool_options.split(';'):
ethtool_opts = ethtool_opts.strip()
if re.match(r'^(-[\S-]+[ ]+[\S]+)([ ]+[\S-]+[ ]+[\S]+)+',
ethtool_opts):
# The regex pattern is strict and hence a minimum of 4 items
# are present in ethtool_opts.
command = ethtool_opts.split()
if len(command) < 4:
msg = f"Ethtool options {command} is incomplete"
raise os_net_config.ConfigurationError(msg)
option = command[0]
accepted_dev_names = ['${DEVICE}', '$DEVICE', iface_name]
if command[1] not in accepted_dev_names:
msg = f'Skipping {ethtool_opts} due to incorrect device '\
f'name present for interface {iface_name}'
raise os_net_config.ConfigurationError(msg)
if option in ethtool_map.keys():
self.add_ethtool_subtree(data, ethtool_map[option],
command)
else:
msg = f'Unhandled ethtool_opts {ethtool_opts} for device'\
f' {iface_name}. Option {option} is not supported.'
raise os_net_config.ConfigurationError(msg)
else:
command_str = '-s ${DEVICE} ' + ethtool_opts
command = command_str.split()
option = command[0]
self.add_ethtool_subtree(data, ethtool_map[option], command)
def _add_common(self, base_opt):
data = {Interface.IPV4: {InterfaceIPv4.ENABLED: False},
Interface.IPV6: {InterfaceIPv6.ENABLED: False},
Interface.NAME: base_opt.name}
if base_opt.use_dhcp:
data[Interface.IPV4][InterfaceIPv4.ENABLED] = True
data[Interface.IPV4][InterfaceIPv4.DHCP] = True
data[Interface.IPV4][InterfaceIPv4.AUTO_DNS] = True
data[Interface.IPV4][InterfaceIPv4.AUTO_ROUTES] = True
data[Interface.IPV4][InterfaceIPv4.AUTO_GATEWAY] = True
else:
data[Interface.IPV4][InterfaceIPv4.DHCP] = False
if base_opt.dns_servers:
data[Interface.IPV4][InterfaceIPv4.AUTO_DNS] = False
if base_opt.use_dhcpv6:
data[Interface.IPV6][InterfaceIPv6.ENABLED] = True
data[Interface.IPV6][InterfaceIPv6.DHCP] = True
data[Interface.IPV6][InterfaceIPv6.AUTO_DNS] = True
data[Interface.IPV6][InterfaceIPv6.AUTOCONF] = True
data[Interface.IPV6][InterfaceIPv6.AUTO_DNS] = True
data[Interface.IPV6][InterfaceIPv6.AUTO_ROUTES] = True
data[Interface.IPV6][InterfaceIPv6.AUTO_GATEWAY] = True
else:
data[Interface.IPV6][InterfaceIPv6.DHCP] = False
data[Interface.IPV6][InterfaceIPv6.AUTOCONF] = False
if base_opt.dns_servers:
data[Interface.IPV6][InterfaceIPv6.AUTO_DNS] = False
if not base_opt.defroute:
data[Interface.IPV4][InterfaceIPv4.AUTO_GATEWAY] = False
data[Interface.IPV6][InterfaceIPv6.AUTO_GATEWAY] = False
# NetworkManager always starts on boot, so set enabled state instead
if base_opt.onboot:
data[Interface.STATE] = InterfaceState.UP
else:
data[Interface.STATE] = InterfaceState.DOWN
if not base_opt.nm_controlled:
logger.info('Using NetworkManager, nm_controlled is always true.'
'Deprecating it from next release')
if isinstance(base_opt, objects.Interface):
if not base_opt.hotplug:
logger.info('Using NetworkManager, hotplug is always set to'
'true. Deprecating it from next release')
if base_opt.mtu:
data[Interface.MTU] = base_opt.mtu
if base_opt.addresses:
v4_addresses = base_opt.v4_addresses()
if v4_addresses:
for address in v4_addresses:
netmask_ip = netaddr.IPAddress(address.netmask)
ip_netmask = {'ip': address.ip,
'prefix-length': netmask_ip.netmask_bits()}
if InterfaceIPv4.ADDRESS not in data[Interface.IPV4]:
data[Interface.IPV4][InterfaceIPv4.ADDRESS] = []
data[Interface.IPV4][InterfaceIPv4.ENABLED] = True
data[Interface.IPV4][InterfaceIPv4.ADDRESS].append(
ip_netmask)
v6_addresses = base_opt.v6_addresses()
if v6_addresses:
for v6_address in v6_addresses:
netmask_ip = netaddr.IPAddress(v6_address.netmask)
v6ip_netmask = {'ip': v6_address.ip,
'prefix-length':
netmask_ip.netmask_bits()}
if InterfaceIPv6.ADDRESS not in data[Interface.IPV6]:
data[Interface.IPV6][InterfaceIPv6.ADDRESS] = []
data[Interface.IPV6][InterfaceIPv6.ENABLED] = True
data[Interface.IPV6][InterfaceIPv6.ADDRESS].append(
v6ip_netmask)
if base_opt.dhclient_args:
msg = "DHCP Client args not supported in impl_nmstate, ignoring"
logger.error(msg)
if base_opt.dns_servers:
self._add_dns_servers(base_opt.dns_servers)
if base_opt.domain:
self._add_dns_domain(base_opt.domain)
if base_opt.routes:
self._add_routes(base_opt.name, base_opt.routes)
if base_opt.rules:
self._add_rules(base_opt.name, base_opt.rules)
return data
def _add_routes(self, interface_name, routes=[]):
routes_data = []
logger.info(f'adding custom route for interface: {interface_name}')
for route in routes:
route_data = {}
if route.route_options:
value = get_route_options(route.route_options, 'metric')
if value:
route.metric = value
value = get_route_options(route.route_options, 'table')
if value:
route.route_table = value
if route.metric:
route_data[NMRoute.METRIC] = route.metric
if route.ip_netmask:
route_data[NMRoute.DESTINATION] = route.ip_netmask
if route.next_hop:
route_data[NMRoute.NEXT_HOP_ADDRESS] = route.next_hop
route_data[NMRoute.NEXT_HOP_INTERFACE] = interface_name
if route.default:
if ":" in route.next_hop:
route_data[NMRoute.DESTINATION] = \
IPV6_DEFAULT_GATEWAY_DESTINATION
else:
route_data[NMRoute.DESTINATION] = \
IPV4_DEFAULT_GATEWAY_DESTINATION
rt_tables = self.get_route_tables()
if route.route_table:
if str(route.route_table).isdigit():
route_data[NMRoute.TABLE_ID] = route.route_table
elif route.route_table in rt_tables:
route_data[NMRoute.TABLE_ID] = \
rt_tables[route.route_table]
else:
logger.error(f'Unidentified mapping for route_table '
'{route.route_table}')
routes_data.append(route_data)
self.route_data[interface_name] = routes_data
logger.debug(f'route data: {self.route_data[interface_name]}')
def add_route_table(self, route_table):
"""Add a RouteTable object to the net config object.
:param route_table: the RouteTable object to add.
"""
logger.info(f'adding route table: {route_table.table_id} '
f'{route_table.name}')
self.route_table_data[int(route_table.table_id)] = route_table.name
location = route_table_config_path()
data = self.generate_route_table_config(self.route_table_data)
self.write_config(location, data)
def _parse_ip_rules(self, rule):
nm_rule_map = {
'blackhole': {'nm_key': NMRouteRule.ACTION,
'nm_value': NMRouteRule.ACTION_BLACKHOLE},
'unreachable': {'nm_key': NMRouteRule.ACTION,
'nm_value': NMRouteRule.ACTION_UNREACHABLE},
'prohibit': {'nm_key': NMRouteRule.ACTION,
'nm_value': NMRouteRule.ACTION_PROHIBIT},
'fwmark': {'nm_key': NMRouteRule.FWMARK, 'nm_value': None},
'fwmask': {'nm_key': NMRouteRule.FWMASK, 'nm_value': None},
'iif': {'nm_key': NMRouteRule.IIF, 'nm_value': None},
'from': {'nm_key': NMRouteRule.IP_FROM, 'nm_value': None},
'to': {'nm_key': NMRouteRule.IP_TO, 'nm_value': None},
'priority': {'nm_key': NMRouteRule.PRIORITY, 'nm_value': None},
'table': {'nm_key': NMRouteRule.ROUTE_TABLE, 'nm_value': None}}
logger.debug(f"Parse Rule {rule}")
items = rule.split()
keyword = items[0]
parse_start_index = 1
rule_config = {}
if keyword == 'del':
rule_config[NMRouteRule.STATE] = NMRouteRule.STATE_ABSENT
elif keyword in nm_rule_map.keys():
parse_start_index = 0
elif keyword != 'add':
msg = f"unhandled ip rule command {rule}"
raise os_net_config.ConfigurationError(msg)
items_iter = iter(items[parse_start_index:])
parse_complete = True
while True:
try:
parse_complete = True
item = next(items_iter)
logger.debug(f"parse item {item}")
if item in nm_rule_map.keys():
value = _get_type_value(nm_rule_map[item]['nm_value'])
if not value:
parse_complete = False
value = _get_type_value(next(items_iter))
rule_config[nm_rule_map[item]['nm_key']] = value
else:
msg = f"unhandled ip rule command {rule}"
raise os_net_config.ConfigurationError(msg)
except StopIteration:
if not parse_complete:
msg = f"incomplete ip rule command {rule}"
raise os_net_config.ConfigurationError(msg)
break
# Just remove the from/to address when its all/any
# the address defaults to all/any.
if NMRouteRule.IP_FROM in rule_config:
if _is_any_ip_addr(rule_config[NMRouteRule.IP_FROM]):
del rule_config[NMRouteRule.IP_FROM]
if NMRouteRule.IP_TO in rule_config:
if _is_any_ip_addr(rule_config[NMRouteRule.IP_TO]):
del rule_config[NMRouteRule.IP_TO]
# TODO(Karthik) Add support for ipv6 rules as well
# When neither IP_FROM nor IP_TO is set, specify the IP family
if (NMRouteRule.IP_FROM not in rule_config.keys() and
NMRouteRule.IP_TO not in rule_config.keys()):
rule_config[NMRouteRule.FAMILY] = NMRouteRule.FAMILY_IPV4
if NMRouteRule.PRIORITY not in rule_config.keys():
logger.warning(f"The ip rule {rule} doesn't have the priority set."
"Its advisable to configure the priorities in "
"order to have a deterministic behaviour")
return rule_config
def _add_rules(self, interface_name, rules=[]):
for rule in rules:
rule_nm = self._parse_ip_rules(rule.rule)
self.rules_data.append(rule_nm)
logger.debug(f'rule data: {self.rules_data}')
def _add_dns_servers(self, dns_servers):
for dns_server in dns_servers:
if dns_server not in self.dns_data['server']:
logger.debug(f"Adding DNS server {dns_server}")
self.dns_data['server'].append(dns_server)
def _add_dns_domain(self, dns_domain):
if isinstance(dns_domain, str):
logger.debug(f"Adding DNS domain {dns_domain}")
self.dns_data['domain'].extend([dns_domain])
return
for domain in dns_domain:
if domain not in self.dns_data['domain']:
logger.debug(f"Adding DNS domain {domain}")
self.dns_data['domain'].append(domain)
def add_interface(self, interface):
"""Add an Interface object to the net config object.
:param interface: The Interface object to add.
"""
if re.match(r'\w+\.\d+$', interface.name):
vlan_id = int(interface.name.split('.')[1])
device = interface.name.split('.')[0]
vlan_port = objects.Vlan(
device, vlan_id,
use_dhcp=interface.use_dhcp, use_dhcpv6=interface.use_dhcpv6,
addresses=interface.addresses, routes=interface.routes,
rules=interface.rules, mtu=interface.mtu,
primary=interface.primary, nic_mapping=None,
persist_mapping=None, defroute=interface.defroute,
dhclient_args=interface.dhclient_args,
dns_servers=interface.dns_servers, nm_controlled=True,
onboot=interface.onboot, domain=interface.domain)
vlan_port.name = interface.name
self.add_vlan(vlan_port)
return
logger.info(f'adding interface: {interface.name}')
data = self._add_common(interface)
if isinstance(interface, objects.Interface):
data[Interface.TYPE] = InterfaceType.ETHERNET
data[Ethernet.CONFIG_SUBTREE] = {}
if interface.ethtool_opts:
self.add_ethtool_config(interface.name, data,
interface.ethtool_opts)
if interface.renamed:
logger.info(f"Interface {interface.hwname} being renamed to"
f"{interface.name}")
self.renamed_interfaces[interface.hwname] = interface.name
if interface.hwaddr:
data[Interface.MAC] = interface.hwaddr
logger.debug(f'interface data: {data}')
self.interface_data[interface.name] = data
def add_vlan(self, vlan):
"""Add a Vlan object to the net config object.
:param vlan: The vlan object to add.
"""
logger.info(f'adding vlan: {vlan.name}')
data = self._add_common(vlan)
if vlan.device:
base_iface = vlan.device
elif vlan.linux_bond_name:
base_iface = vlan.linux_bond_name
if vlan.bridge_name:
# Handle the VLANs for ovs bridges
# vlans on OVS bridges are internal ports (no device, etc)
data[Interface.TYPE] = InterfaceType.OVS_INTERFACE
else:
data[Interface.TYPE] = InterfaceType.VLAN
data[VLAN.CONFIG_SUBTREE] = {}
data[VLAN.CONFIG_SUBTREE][VLAN.ID] = vlan.vlan_id
data[VLAN.CONFIG_SUBTREE][VLAN.BASE_IFACE] = base_iface
logger.debug(f'vlan data: {data}')
self.vlan_data[vlan.name] = data
def _ovs_extra_cfg_eq_val(self, ovs_extra, cmd_map, data):
index = 0
logger.info(f'Current ovs_extra {ovs_extra}')
for a, b in zip(ovs_extra, cmd_map['command']):
if not re.match(b, a, re.IGNORECASE):
return False
index = index + 1
for idx in range(index, len(ovs_extra)):
value = None
for cfg in cmd_map['action']:
if re.match(cfg['config'], ovs_extra[idx], re.IGNORECASE):
value = None
if 'value' in cfg:
value = cfg['value']
elif 'value_pattern' in cfg:
m = re.search(cfg['value_pattern'], ovs_extra[idx])
if m:
value = _get_type_value(m.group(1))
if value is None:
msg = "Invalid ovs_extra format detected. "\
f"{' '.join(ovs_extra)}"
raise os_net_config.ConfigurationError(msg)
config = _add_sub_tree(data, cfg['sub_tree'])
if cfg['nm_config']:
config[cfg['nm_config']] = value
elif cfg['nm_config_regex']:
logger.info(f'Regex pattern seen for {ovs_extra}')
m = re.search(cfg['nm_config_regex'], ovs_extra[idx])
if m:
config[m.group(1)] = value
else:
msg = "Invalid ovs_extra format detected. "\
f"{' '.join(ovs_extra)}"
raise os_net_config.ConfigurationError(msg)
else:
msg = 'NM config not found'
raise os_net_config.ConfigurationError(msg)
logger.info(f"Adding ovs_extra {config} in "
f"{cfg['sub_tree']}")
def _ovs_extra_cfg_val(self, ovs_extra, cmd_map, data):
index = 0
for a, b in zip(ovs_extra, cmd_map['command']):
if not re.match(b, a, re.IGNORECASE):
return False
index = index + 1
if len(ovs_extra) > (index + 1):
value = None
for cfg in cmd_map['action']:
if re.match(cfg['config'], ovs_extra[index], re.IGNORECASE):
value = None
if 'value' in cfg:
value = cfg['value']
elif 'value_pattern' in cfg:
m = re.search(cfg['value_pattern'],
ovs_extra[index + 1])
if m:
value = _get_type_value(m.group(1))
if value is None:
msg = f"Invalid ovs_extra format detected."\
f"{' '.join(ovs_extra)}"
raise os_net_config.ConfigurationError(msg)
config = _add_sub_tree(data, cfg['sub_tree'])
if cfg['nm_config']:
config[cfg['nm_config']] = value
elif cfg['nm_config_regex']:
m = re.search(cfg['nm_config_regex'], ovs_extra[index])
if m:
config[m.group(1)] = value
else:
msg = f"Invalid ovs_extra format detected."\
f"{' '.join(ovs_extra)}"
raise os_net_config.ConfigurationError(msg)
else:
msg = 'NM config not found'
raise os_net_config.ConfigurationError(msg)
logger.info(f"Adding ovs_extra {config} in "
f"{cfg['sub_tree']}")
def parse_ovs_extra(self, ovs_extras, name, data):
bridge_cfg = [{'config': r'^fail_mode=[\w+]',
'sub_tree': [OVSBridge.CONFIG_SUBTREE,
OVSBridge.OPTIONS_SUBTREE],
'nm_config': OVSBridge.Options.FAIL_MODE,
'value_pattern': r'^fail_mode=(.+?)$'},
{'config': r'^mcast_snooping_enable=[\w+]',
'sub_tree': [OVSBridge.CONFIG_SUBTREE,
OVSBridge.OPTIONS_SUBTREE],
'nm_config': OVSBridge.Options.MCAST_SNOOPING_ENABLED,
'value_pattern': r'^mcast_snooping_enable=(.+?)$'},
{'config': r'^rstp_enable=[\w+]',
'sub_tree': [OVSBridge.CONFIG_SUBTREE,
OVSBridge.OPTIONS_SUBTREE],
'nm_config': OVSBridge.Options.RSTP,
'value_pattern': r'^rstp_enable=(.+?)$'},
{'config': r'^stp_enable=[\w+]',
'sub_tree': [OVSBridge.CONFIG_SUBTREE,
OVSBridge.OPTIONS_SUBTREE],
'nm_config': OVSBridge.Options.STP,
'value_pattern': r'^stp_enable=(.+?)$'},
{'config': r'^other_config:[\w+]',
'sub_tree': [OvsDB.KEY, OvsDB.OTHER_CONFIG],
'nm_config': None,
'nm_config_regex': r'^other_config:(.+?)=',
'value_pattern': r'^other_config:.*=(.+?)$'},
{'config': r'^other-config:[\w+]',
'sub_tree': [OvsDB.KEY, OvsDB.OTHER_CONFIG],
'nm_config': None,
'nm_config_regex': r'^other-config:(.+?)=',
'value_pattern': r'^other-config:.*=(.+?)$'}]
iface_cfg = [{'config': r'^other_config:[\w+]',
'sub_tree': [OvsDB.KEY, OvsDB.OTHER_CONFIG],
'nm_config': None,
'nm_config_regex': r'^other_config:(.+?)=',
'value_pattern': r'^other_config:.*=(.+?)$'},
{'config': r'^other-config:[\w+]',
'sub_tree': [OvsDB.KEY, OvsDB.OTHER_CONFIG],
'nm_config': None,
'nm_config_regex': r'^other-config:(.+?)=',
'value_pattern': r'^other-config:.*=(.+?)$'}]
external_id_cfg = [{'sub_tree': [OvsDB.KEY, OvsDB.EXTERNAL_IDS],
'config': r'.*',
'nm_config': None,
'nm_config_regex': r'^(.+?)$',
'value_pattern': r'^(.+?)$'}]
cfg_eq_val_pair = [{'command': ['set', 'bridge', '({name}|%s)' % name],
'action': bridge_cfg},
{'command': ['set', 'interface',
'({name}|%s)' % name],
'action': iface_cfg}]
cfg_val_pair = [{'command': ['br-set-external-id',
'({name}|%s)' % name],
'action': external_id_cfg}]
# ovs-vsctl set Bridge $name <config>=<value>
# ovs-vsctl set Interface $name <config>=<value>
# ovs-vsctl br-set-external-id $name key [value]
for ovs_extra in ovs_extras:
ovs_extra_cmd = ovs_extra.split(' ')
for cmd_map in cfg_eq_val_pair:
self._ovs_extra_cfg_eq_val(ovs_extra_cmd, cmd_map, data)
for cmd_map in cfg_val_pair:
self._ovs_extra_cfg_val(ovs_extra_cmd, cmd_map, data)
def parse_ovs_extra_for_ports(self, ovs_extras, bridge_name, data):
port_vlan_cfg = [{'config': r'^tag=[\w+]',
'sub_tree': [OVSBridge.Port.VLAN_SUBTREE],
'nm_config': OVSBridge.Port.Vlan.TAG,
'value_pattern': r'^tag=(.+?)$'},
{'config': r'^tag=[\w+]',
'sub_tree': [OVSBridge.Port.VLAN_SUBTREE],
'nm_config': OVSBridge.Port.Vlan.MODE,
'value': 'access'}]
cfg_eq_val_pair = [{'command': ['set', 'port',
'({name}|%s)' % bridge_name],
'action': port_vlan_cfg}]
for ovs_extra in ovs_extras:
ovs_extra_cmd = ovs_extra.split(' ')
for cmd_map in cfg_eq_val_pair:
self._ovs_extra_cfg_eq_val(ovs_extra_cmd, cmd_map, data)
def add_bridge(self, bridge, dpdk=False):
"""Add an OvsBridge object to the net config object.
:param bridge: The OvsBridge object to add.
"""
# Create the internal ovs interface. Some of the settings of the
# bridge like MTU, ip address are to be applied on this interface
ovs_port_name = f"{bridge.name}-p"
ovs_interface_port = objects.OvsInterface(
ovs_port_name, use_dhcp=bridge.use_dhcp,
use_dhcpv6=bridge.use_dhcpv6,
addresses=bridge.addresses, routes=bridge.routes,
rules=bridge.rules, mtu=bridge.mtu, primary=False,
nic_mapping=None, persist_mapping=None,
defroute=bridge.defroute, dhclient_args=bridge.dhclient_args,
dns_servers=bridge.dns_servers,
nm_controlled=None, onboot=bridge.onboot,
domain=bridge.domain)
self.add_ovs_interface(ovs_interface_port)
ovs_int_port = {'name': ovs_interface_port.name}
if bridge.ovs_extra:
logger.info(f"Parsing ovs_extra for ports: {bridge.ovs_extra}")
self.parse_ovs_extra_for_ports(bridge.ovs_extra,
bridge.name, ovs_int_port)
logger.info(f'adding bridge: {bridge.name}')
# Clear the settings from the bridge, since these will be applied
# on the interface
if bridge.routes:
bridge.routes.clear()
bridge.defroute = False
if bridge.dns_servers:
bridge.dns_servers.clear()
if bridge.domain:
bridge.domain.clear()
if bridge.mtu:
bridge.mtu = None
data = self._add_common(bridge)
data[Interface.TYPE] = OVSBridge.TYPE
# address bits can't be on the ovs-bridge
del data[Interface.IPV4]
del data[Interface.IPV6]
ovs_bridge_options = {OVSBridge.Options.FAIL_MODE:
objects.DEFAULT_OVS_BRIDGE_FAIL_MODE,
OVSBridge.Options.MCAST_SNOOPING_ENABLED: False,
OVSBridge.Options.RSTP: False,
OVSBridge.Options.STP: False}
data[OVSBridge.CONFIG_SUBTREE] = {
OVSBridge.OPTIONS_SUBTREE: ovs_bridge_options,
OVSBridge.PORT_SUBTREE: [],
}
data[OvsDB.KEY] = {OvsDB.EXTERNAL_IDS: {},
OvsDB.OTHER_CONFIG: {}}
if bridge.primary_interface_name:
mac = self.interface_mac(bridge.primary_interface_name)
bridge.ovs_extra.append("set bridge %s other_config:hwaddr=%s" %
(bridge.name, mac))
if bridge.ovs_extra:
logger.info(f"Parsing ovs_extra : {bridge.ovs_extra}")
self.parse_ovs_extra(bridge.ovs_extra, bridge.name, data)
if dpdk:
ovs_bridge_options[OVSBridge.Options.DATAPATH] = 'netdev'
if bridge.members:
members = []
ovs_bond = False
ovs_port = False
for member in bridge.members:
if (isinstance(member, objects.OvsBond) or
isinstance(member, objects.OvsDpdkBond)):
if ovs_port:
msg = "Ovs Bond and ovs port can't be members to "\
"the ovs bridge"
raise os_net_config.ConfigurationError(msg)
if member.primary_interface_name:
add_bond_setting = "other_config:bond-primary="\
f"{member.primary_interface_name}"
if member.ovs_options:
member.ovs_options = member.ovs_options + " " +\
add_bond_setting
else:
member.ovs_options = add_bond_setting
logger.info(f"OVS Options are {member.ovs_options}")
bond_options = parse_bonding_options(member.ovs_options)
bond_data = set_ovs_bonding_options(bond_options)
bond_port = [{
OVSBridge.Port.LINK_AGGREGATION_SUBTREE: bond_data,
OVSBridge.Port.NAME: member.name},
ovs_int_port]
data[OVSBridge.CONFIG_SUBTREE
][OVSBridge.PORT_SUBTREE] = bond_port
ovs_bond = True
logger.debug("OVS Bond members %s added" % members)
if member.members:
members = [m.name for m in member.members]
elif ovs_bond:
msg = "Ovs Bond and ovs port can't be members to "\
"the ovs bridge"
raise os_net_config.ConfigurationError(msg)
else:
ovs_port = True
logger.debug("Adding member ovs port %s" % member.name)
members.append(member.name)
if members:
logger.debug("Add ovs ports and vlans to ovs bridge")
bps = self.get_ovs_ports(members)
else:
msg = "No members added for ovs bridge"
raise os_net_config.ConfigurationError(msg)
self.member_names[bridge.name] = members
if ovs_port:
# Add the internal ovs interface
bps.append(ovs_int_port)
data[OVSBridge.CONFIG_SUBTREE][OVSBridge.PORT_SUBTREE] = bps
elif ovs_bond:
bond_data[OVSBridge.Port.LinkAggregation.PORT_SUBTREE] = bps
if bridge.primary_interface_name:
mac = self.interface_mac(bridge.primary_interface_name)
data[Interface.MAC] = mac
self.bridge_data[bridge.name] = data
logger.debug('bridge data: %s' % data)
def add_ovs_user_bridge(self, bridge):
"""Add an OvsUserBridge object to the net config object.
:param bridge: The OvsUserBridge object to add.
"""
logger.info('adding ovs user bridge: %s' % bridge.name)
self.add_bridge(bridge, dpdk=True)
def add_ovs_interface(self, ovs_interface):
"""Add a OvsDpdkPort object to the net config object.
:param ovs_dpdk_port: The OvsDpdkPort object to add.
"""
logger.info('adding ovs dpdk port: %s' % ovs_interface.name)
data = self._add_common(ovs_interface)
data[Interface.TYPE] = OVSInterface.TYPE
data[Interface.STATE] = InterfaceState.UP
logger.debug(f'add ovs_interface data: {data}')
self.interface_data[ovs_interface.name] = data
def add_ovs_dpdk_port(self, ovs_dpdk_port):
"""Add a OvsDpdkPort object to the net config object.
:param ovs_dpdk_port: The OvsDpdkPort object to add.
"""
logger.info('adding ovs dpdk port: %s' % ovs_dpdk_port.name)
# DPDK Port will have only one member of type Interface, validation
# checks are added at the object creation stage.
ifname = ovs_dpdk_port.members[0].name
# Bind the dpdk interface
utils.bind_dpdk_interfaces(ifname, ovs_dpdk_port.driver, self.noop)
data = self._add_common(ovs_dpdk_port)
data[Interface.TYPE] = OVSInterface.TYPE
data[Interface.STATE] = InterfaceState.UP
pci_address = utils.get_dpdk_devargs(ifname, noop=False)
data[OVSInterface.DPDK_CONFIG_SUBTREE
] = {OVSInterface.Dpdk.DEVARGS: pci_address}
if ovs_dpdk_port.rx_queue:
data[OVSInterface.DPDK_CONFIG_SUBTREE
][OVSInterface.Dpdk.RX_QUEUE] = ovs_dpdk_port.rx_queue
if ovs_dpdk_port.rx_queue_size:
data[OVSInterface.DPDK_CONFIG_SUBTREE
][OVSInterface.Dpdk.N_RXQ_DESC] = ovs_dpdk_port.rx_queue_size
if ovs_dpdk_port.tx_queue_size:
data[OVSInterface.DPDK_CONFIG_SUBTREE
][OVSInterface.Dpdk.N_TXQ_DESC] = ovs_dpdk_port.tx_queue_size
data[OvsDB.KEY] = {OvsDB.EXTERNAL_IDS: {},
OvsDB.OTHER_CONFIG: {}}
if ovs_dpdk_port.ovs_extra:
logger.info(f"Parsing ovs_extra : {ovs_dpdk_port.ovs_extra}")
self.parse_ovs_extra(ovs_dpdk_port.ovs_extra,
ovs_dpdk_port.name, data)
logger.debug(f'ovs dpdk port data: {data}')
self.interface_data[ovs_dpdk_port.name] = data
def add_linux_bridge(self, bridge):
"""Add a LinuxBridge object to the net config object.
:param bridge: The LinuxBridge object to add.
"""
logger.info(f'adding linux bridge: {bridge.name}')
data = self._add_common(bridge)
logger.debug('bridge data: %s' % data)
self.linuxbridge_data[bridge.name] = data
def add_bond(self, bond):
"""Add an OvsBond object to the net config object.
:param bond: The OvsBond object to add.
"""
# The ovs bond is already added in add_bridge()x
logger.info('adding bond: %s' % bond.name)
return
def add_ovs_dpdk_bond(self, bond):
"""Add an OvsDpdkBond object to the net config object.
:param bond: The OvsBond object to add.
"""
logger.info('adding Ovs DPDK Bond: %s' % bond.name)
for member in bond.members:
if bond.mtu:
member.mtu = bond.mtu
if bond.rx_queue:
member.rx_queue = bond.rx_queue
self.add_ovs_dpdk_port(member)
return
def add_linux_bond(self, bond):
"""Add a LinuxBond object to the net config object.
:param bond: The LinuxBond object to add.
"""
logger.info('adding linux bond: %s' % bond.name)
data = self._add_common(bond)
data[Interface.TYPE] = InterfaceType.BOND
data[Interface.STATE] = InterfaceState.UP
bond_options = {}
if bond.bonding_options:
bond_options = parse_bonding_options(bond.bonding_options)
bond_data = set_linux_bonding_options(
bond_options, primary_iface=bond.primary_interface_name)
if bond_data:
data[Bond.CONFIG_SUBTREE] = bond_data
if bond.members:
members = [member.name for member in bond.members]
self.member_names[bond.name] = members
data[Bond.CONFIG_SUBTREE][Bond.PORT] = members
logger.debug('bond data: %s' % data)
self.linuxbond_data[bond.name] = data
def add_sriov_pf(self, sriov_pf):
"""Add a SriovPF object to the net config object
:param sriov_pf: The SriovPF object to add
"""
logger.info(f'adding sriov pf: {sriov_pf.name}')
data = self._add_common(sriov_pf)
data[Interface.TYPE] = InterfaceType.ETHERNET
data[Ethernet.CONFIG_SUBTREE] = {}
data[Ethernet.CONFIG_SUBTREE][Ethernet.SRIOV_SUBTREE] = {
Ethernet.SRIOV.TOTAL_VFS: sriov_pf.numvfs}
if sriov_pf.promisc:
data[Interface.ACCEPT_ALL_MAC_ADDRESSES] = True
if sriov_pf.link_mode == 'switchdev':
data[Ethtool.CONFIG_SUBTREE] = {}
data[Ethtool.CONFIG_SUBTREE][Ethtool.Feature.CONFIG_SUBTREE] = {
'hw-tc-offload': True}
if sriov_pf.promisc:
data[Interface.ACCEPT_ALL_MAC_ADDRESSES] = True
if sriov_pf.ethtool_opts:
self.add_ethtool_config(sriov_pf.name, data,
sriov_pf.ethtool_opts)
logger.debug('sriov pf data: %s' % data)
self.sriov_vf_data[sriov_pf.name] = [None] * sriov_pf.numvfs
self.interface_data[sriov_pf.name] = data
self.sriov_pf_data[sriov_pf.name] = data
def add_sriov_vf(self, sriov_vf):
"""Add a SriovVF object to the net config object
:param sriov_vf: The SriovVF object to add
"""
logger.info('adding sriov vf: %s for pf: %s, vfid: %d'
% (sriov_vf.name, sriov_vf.device, sriov_vf.vfid))
data = self._add_common(sriov_vf)
data[Interface.TYPE] = InterfaceType.ETHERNET
data[Ethernet.CONFIG_SUBTREE] = {}
if sriov_vf.promisc:
data[Interface.ACCEPT_ALL_MAC_ADDRESSES] = True
logger.debug('sriov vf data: %s' % data)
self.interface_data[sriov_vf.name] = data
vf_config = self.get_vf_config(sriov_vf)
logger.debug("Adding vf config %s" % vf_config)
# sriov_vf_data is a list of vf configuration data of size numvfs.
# The vfid is used as index.
if sriov_vf.device not in self.sriov_vf_data:
msg = f"VF configuration is seen while the parent device"\
f" {sriov_vf.device} is not availavle"
raise os_net_config.ConfigurationError(msg)
self.sriov_vf_data[sriov_vf.device][sriov_vf.vfid] = vf_config
if sriov_vf.ethtool_opts:
self.add_ethtool_config(sriov_vf.name, data,
sriov_vf.ethtool_opts)
def add_ib_interface(self, ib_interface):
"""Add an InfiniBand interface object to the net config object.
:param ib_interface: The InfiniBand interface object to add.
"""
logger.info('adding ib_interface: %s' % ib_interface.name)
data = self._add_common(ib_interface)
logger.debug('ib_interface data: %s' % data)
data[Interface.TYPE] = InterfaceType.INFINIBAND
if ib_interface.ethtool_opts:
self.add_ethtool_config(ib_interface.name, data,
ib_interface.ethtool_opts)
# Default mode is set to 'datagram' since 'connected' is not
# supported in some devices
config = {}
config[InfiniBand.MODE] = InfiniBand.Mode.DATAGRAM
data[InfiniBand.CONFIG_SUBTREE] = config
self.interface_data[ib_interface.name] = data
def add_ib_child_interface(self, ib_child_interface):
"""Add an InfiniBand child interface object to the net config object.
:param ib_child_interface: The InfiniBand child
interface object to add.
"""
logger.info('adding ib_child_interface: %s' % ib_child_interface.name)
data = self._add_common(ib_child_interface)
logger.debug('ib_child_interface data: %s' % data)
data[Interface.TYPE] = InterfaceType.INFINIBAND
config = {}
config[InfiniBand.PKEY] = ib_child_interface.pkey_id
config[InfiniBand.BASE_IFACE] = ib_child_interface.parent
# Default mode is set to 'datagram' since 'connected' is not
# supported in some devices
config[InfiniBand.MODE] = InfiniBand.Mode.DATAGRAM
data[InfiniBand.CONFIG_SUBTREE] = config
self.interface_data[ib_child_interface.name] = data
def apply(self, cleanup=False, activate=True):
"""Apply the network configuration.
:param cleanup: A boolean which indicates whether any undefined
(existing but not present in the object model) interface
should be disabled and deleted.
:param activate: A boolean which indicates if the config should
be activated by stopping/starting interfaces
NOTE: if cleanup is specified we will deactivate interfaces even
if activate is false
:returns: a dict of the format: filename/data which contains info
for each file that was changed (or would be changed if in --noop
mode).
Note the noop mode is set via the constructor noop boolean
"""
logger.info('applying network configs...')
if cleanup:
logger.info('Cleaning up all network configs...')
self.cleanup_all_ifaces()
apply_routes = []
updated_interfaces = {}
logger.debug("----------------------------")
vf_config = self.prepare_sriov_vf_config()
apply_data = {}
apply_data.update(self.set_ifaces(vf_config))
for interface_name, iface_data in self.interface_data.items():
iface_state = self.iface_state(interface_name)
if not is_dict_subset(iface_state, iface_data):
updated_interfaces[interface_name] = iface_data
else:
logger.info('No changes required for interface: '
f'{interface_name}')
routes_data = self.generate_routes(interface_name)
logger.info(f'Routes_data {routes_data}')
apply_routes.extend(routes_data)
for bridge_name, bridge_data in self.bridge_data.items():
bridge_state = self.iface_state(bridge_name)
if not is_dict_subset(bridge_state, bridge_data):
updated_interfaces[bridge_name] = bridge_data
else:
logger.info('No changes required for bridge: %s' %
bridge_name)
routes_data = self.generate_routes(bridge_name)
logger.info(f'Routes_data {routes_data}')
apply_routes.extend(routes_data)
for bond_name, bond_data in self.linuxbond_data.items():
bond_state = self.iface_state(bond_name)
if not is_dict_subset(bond_state, bond_data):
updated_interfaces[bond_name] = bond_data
else:
logger.info('No changes required for bond: %s' %
bond_name)
routes_data = self.generate_routes(bond_name)
logger.info('Routes_data %s' % routes_data)
apply_routes.extend(routes_data)
for vlan_name, vlan_data in self.vlan_data.items():
vlan_state = self.iface_state(vlan_name)
if not is_dict_subset(vlan_state, vlan_data):
updated_interfaces[vlan_name] = vlan_data
else:
logger.info('No changes required for vlan interface: %s' %
vlan_name)
routes_data = self.generate_routes(vlan_name)
logger.info('Routes_data %s' % routes_data)
apply_routes.extend(routes_data)
apply_data.update(self.set_ifaces(list(updated_interfaces.values())))
apply_data.update(self.set_routes(apply_routes))
rules_data = self.generate_rules()
logger.info(f'Rules_data {rules_data}')
apply_data.update(self.set_rules(rules_data))
apply_data.update(self.set_dns())
if activate:
if not self.noop:
self.nmstate_apply(apply_data, verify=True)
if self.errors:
message = 'Failure(s) occurred when applying configuration'
logger.error(message)
for e in self.errors:
logger.error(str(e))
raise os_net_config.ConfigurationError(message)
self.interface_data = {}
self.bridge_data = {}
self.linuxbond_data = {}
self.vlan_data = {}
return updated_interfaces