1622 lines
68 KiB
Python
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
|